diff --git a/cmd/antrea-agent/agent.go b/cmd/antrea-agent/agent.go index 24740ec0f67..b497a21bf59 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) { @@ -956,6 +958,7 @@ func run(o *Options) error { networkPolicyController, mcastController, externalIPController, + bgpController, secureServing, authentication, authorization, diff --git a/docs/antctl.md b/docs/antctl.md index 5f4cba5c5a8..93eb29d5741 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) @@ -756,6 +757,18 @@ worker2 172.18.0.3 Alive worker3 172.18.0.2 Dead ``` +### BGP commands + +`antctl` agent command `get bgppolicy` prints the 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 +``` + ### 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/hack/update-codegen-dockerized.sh b/hack/update-codegen-dockerized.sh index 9b228493ac8..5b9fe4eaf59 100755 --- a/hack/update-codegen-dockerized.sh +++ b/hack/update-codegen-dockerized.sh @@ -95,7 +95,7 @@ MOCKGEN_TARGETS=( "pkg/ovs/ovsconfig OVSBridgeClient testing" "pkg/ovs/ovsctl OVSCtlClient testing" "pkg/ovs/ovsctl OVSOfctlRunner,OVSAppctlRunner ." - "pkg/querier AgentNetworkPolicyInfoQuerier,AgentMulticastInfoQuerier,EgressQuerier testing" + "pkg/querier AgentNetworkPolicyInfoQuerier,AgentMulticastInfoQuerier,EgressQuerier,AgentBGPPolicyInfoQuerier testing" "pkg/flowaggregator/querier FlowAggregatorQuerier testing" "pkg/flowaggregator/s3uploader S3UploaderAPI testing" "pkg/util/podstore Interface testing" diff --git a/pkg/agent/apis/types.go b/pkg/agent/apis/types.go index 70cd8875b88..e20abe996a5 100644 --- a/pkg/agent/apis/types.go +++ b/pkg/agent/apis/types.go @@ -190,3 +190,23 @@ 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 +} diff --git a/pkg/agent/apiserver/apiserver.go b/pkg/agent/apiserver/apiserver.go index e19c5c6acef..beaa6dbbd9e 100644 --- a/pkg/agent/apiserver/apiserver.go +++ b/pkg/agent/apiserver/apiserver.go @@ -35,6 +35,7 @@ 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/featuregates" "antrea.io/antrea/pkg/agent/apiserver/handlers/memberlist" "antrea.io/antrea/pkg/agent/apiserver/handlers/multicast" @@ -84,7 +85,7 @@ func (s *agentAPIServer) GetCertData() []byte { return cert } -func installHandlers(aq agentquerier.AgentQuerier, npq querier.AgentNetworkPolicyInfoQuerier, mq querier.AgentMulticastInfoQuerier, seipq querier.ServiceExternalIPStatusQuerier, s *genericapiserver.GenericAPIServer) { +func installHandlers(aq agentquerier.AgentQuerier, npq querier.AgentNetworkPolicyInfoQuerier, mq querier.AgentMulticastInfoQuerier, seipq querier.ServiceExternalIPStatusQuerier, s *genericapiserver.GenericAPIServer, bgpq querier.AgentBGPPolicyInfoQuerier) { s.Handler.NonGoRestfulMux.HandleFunc("/loglevel", loglevel.HandleFunc()) s.Handler.NonGoRestfulMux.HandleFunc("/podmulticaststats", multicast.HandleFunc(mq)) s.Handler.NonGoRestfulMux.HandleFunc("/featuregates", featuregates.HandleFunc()) @@ -97,6 +98,7 @@ 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(bgpq)) } func installAPIGroup(s *genericapiserver.GenericAPIServer, aq agentquerier.AgentQuerier, npq querier.AgentNetworkPolicyInfoQuerier, v4Enabled, v6Enabled bool) error { @@ -114,6 +116,7 @@ func New(aq agentquerier.AgentQuerier, npq querier.AgentNetworkPolicyInfoQuerier, mq querier.AgentMulticastInfoQuerier, seipq querier.ServiceExternalIPStatusQuerier, + bgpq querier.AgentBGPPolicyInfoQuerier, secureServing *genericoptions.SecureServingOptionsWithLoopback, authentication *genericoptions.DelegatingAuthenticationOptions, authorization *genericoptions.DelegatingAuthorizationOptions, @@ -134,7 +137,7 @@ func New(aq agentquerier.AgentQuerier, if err := installAPIGroup(s, aq, npq, v4Enabled, v6Enabled); err != nil { return nil, err } - installHandlers(aq, npq, mq, seipq, s) + installHandlers(aq, npq, mq, seipq, s, bgpq) return &agentAPIServer{GenericAPIServer: s}, nil } diff --git a/pkg/agent/apiserver/apiserver_test.go b/pkg/agent/apiserver/apiserver_test.go index 0cc37c222f4..03aa0dab1fb 100644 --- a/pkg/agent/apiserver/apiserver_test.go +++ b/pkg/agent/apiserver/apiserver_test.go @@ -79,7 +79,7 @@ current-context: cluster // InClusterLookup is skipped when testing, otherwise it would always fail as there is no real cluster. authentication.SkipInClusterLookup = true authorization := options.NewDelegatingAuthorizationOptions().WithAlwaysAllowPaths("/healthz", "/livez", "/readyz") - apiServer, err := New(agentQuerier, npQuerier, nil, nil, secureServing, authentication, authorization, true, kubeConfigPath, tokenPath, true, true) + apiServer, err := New(agentQuerier, npQuerier, nil, nil, nil, secureServing, authentication, authorization, true, kubeConfigPath, tokenPath, true, true) require.NoError(t, err) fakeAPIServer := &fakeAgentAPIServer{ agentAPIServer: apiServer, diff --git a/pkg/agent/apiserver/handlers/bgppolicy/handler.go b/pkg/agent/apiserver/handlers/bgppolicy/handler.go new file mode 100644 index 00000000000..237719aeba4 --- /dev/null +++ b/pkg/agent/apiserver/handlers/bgppolicy/handler.go @@ -0,0 +1,54 @@ +// 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/querier" +) + +// HandleFunc returns the function which can handle queries issued by the bgppolicy command. +func HandleFunc(bq querier.AgentBGPPolicyInfoQuerier) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if bq == nil || reflect.ValueOf(bq).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 := bq.GetBGPPolicyInfo() + bgpPolicyResp := apis.BGPPolicyResponse{ + BGPPolicyName: bgpPolicyName, + RouterID: routerID, + LocalASN: localASN, + ListenPort: listenPort, + } + if bgpPolicyName == "" { + http.Error(w, "there is no effective bgp policy applied to the Node", http.StatusNotFound) + return + } + + if err := json.NewEncoder(w).Encode(bgpPolicyResp); err != nil { + w.WriteHeader(http.StatusInternalServerError) + klog.ErrorS(err, "Error when encoding BGPPolicyResp to json") + } + } +} diff --git a/pkg/agent/apiserver/handlers/bgppolicy/handler_test.go b/pkg/agent/apiserver/handlers/bgppolicy/handler_test.go new file mode 100644 index 00000000000..f5d5bbb7fb5 --- /dev/null +++ b/pkg/agent/apiserver/handlers/bgppolicy/handler_test.go @@ -0,0 +1,76 @@ +// 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" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + + "antrea.io/antrea/pkg/agent/apis" + queriertest "antrea.io/antrea/pkg/querier/testing" +) + +func TestBGPPolicyQuery(t *testing.T) { + tests := []struct { + name string + expectedStatus int + expectedResponse apis.BGPPolicyResponse + }{ + { + name: "bgpPolicyState exists", + expectedStatus: http.StatusOK, + expectedResponse: apis.BGPPolicyResponse{ + BGPPolicyName: "policy-1", + RouterID: "192.168.1.2", + LocalASN: 65000, + ListenPort: 179, + }, + }, + { + name: "bgpPolicyState does not exist", + expectedStatus: http.StatusNotFound, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + q := queriertest.NewMockAgentBGPPolicyInfoQuerier(ctrl) + q.EXPECT().GetBGPPolicyInfo().Return(tt.expectedResponse.BGPPolicyName, tt.expectedResponse.RouterID, + tt.expectedResponse.LocalASN, tt.expectedResponse.ListenPort) + handler := HandleFunc(q) + + req, err := http.NewRequest(http.MethodGet, "", nil) + require.NoError(t, err) + + recorder := httptest.NewRecorder() + handler.ServeHTTP(recorder, req) + assert.Equal(t, tt.expectedStatus, recorder.Code) + + if tt.expectedStatus == http.StatusOK { + var received apis.BGPPolicyResponse + err = json.Unmarshal(recorder.Body.Bytes(), &received) + require.NoError(t, err) + assert.Equal(t, tt.expectedResponse, received) + } + }) + } +} diff --git a/pkg/agent/controller/bgp/controller.go b/pkg/agent/controller/bgp/controller.go index 51c667acd84..941d5a1ba02 100644 --- a/pkg/agent/controller/bgp/controller.go +++ b/pkg/agent/controller/bgp/controller.go @@ -76,6 +76,8 @@ const dummyKey = "dummyKey" type bgpPolicyState struct { // The local BGP server. bgpServer bgp.Interface + // name of the BGP policy. + bgpPolicyName string // The port on which the local BGP server listens. listenPort int32 // The AS number used by the local BGP server. @@ -112,7 +114,8 @@ type Controller struct { secretInformer cache.SharedIndexInformer - bgpPolicyState *bgpPolicyState + bgpPolicyState *bgpPolicyState + bgpPolicyStateMutex sync.RWMutex k8sClient kubernetes.Interface bgpPeerPasswords map[string]string @@ -306,6 +309,9 @@ func (c *Controller) syncBGPPolicy(ctx context.Context) error { // Get the oldest BGPPolicy applied to the current Node as the effective BGPPolicy. effectivePolicy := c.getEffectiveBGPPolicy() + c.bgpPolicyStateMutex.Lock() + defer c.bgpPolicyStateMutex.Unlock() + // When the effective BGPPolicy is nil, it means that there is no available BGPPolicy. if effectivePolicy == nil { // If the BGPPolicy state is nil, just return. @@ -322,17 +328,18 @@ func (c *Controller) syncBGPPolicy(ctx context.Context) error { } klog.V(2).InfoS("Syncing BGPPolicy", "BGPPolicy", klog.KObj(effectivePolicy)) - // Retrieve the listen port, local AS number and router ID from the effective BGPPolicy, and update them to the + // Retrieve the BGP policy name, listen port, local AS number and router ID from the effective BGPPolicy, and update them to the // current state. routerID, err := c.getRouterID() if err != nil { return err } + bgpPolicyName := effectivePolicy.Name listenPort := *effectivePolicy.Spec.ListenPort localASN := effectivePolicy.Spec.LocalASN // If the BGPPolicy state is nil, a new BGP server should be started, initialize the BGPPolicy state to store the - // new BGP server, listen port, local ASN, and router ID. + // new BGP server, BGP policy name, listen port, local ASN, and router ID. // If the BGPPolicy is not nil, any of the listen port, local AS number, or router ID have changed, stop the current // BGP server first and reset the BGPPolicy state to nil; then start a new BGP server and initialize the BGPPolicy // state to store the new BGP server, listen port, local ASN, and router ID. @@ -363,15 +370,19 @@ func (c *Controller) syncBGPPolicy(ctx context.Context) error { return fmt.Errorf("failed to start BGP server: %w", err) } - // Initialize the BGPPolicy state to store the new BGP server, listen port, local ASN, and router ID. + // Initialize the BGPPolicy state to store the new BGP server, BGP policy name, listen port, local ASN, and router ID. c.bgpPolicyState = &bgpPolicyState{ - bgpServer: bgpServer, - routerID: routerID, - listenPort: listenPort, - localASN: localASN, - routes: make(sets.Set[bgp.Route]), - peerConfigs: make(map[string]bgp.PeerConfig), + bgpServer: bgpServer, + bgpPolicyName: bgpPolicyName, + routerID: routerID, + listenPort: listenPort, + localASN: localASN, + routes: make(sets.Set[bgp.Route]), + peerConfigs: make(map[string]bgp.PeerConfig), } + } else if c.bgpPolicyState.bgpPolicyName != bgpPolicyName { + // It may happen that only BGP policy name has changed in effective BGP policy. + c.bgpPolicyState.bgpPolicyName = bgpPolicyName } // Reconcile BGP peers. @@ -931,3 +942,20 @@ 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 + + c.bgpPolicyStateMutex.RLock() + defer c.bgpPolicyStateMutex.RUnlock() + + if c.bgpPolicyState != nil { + name = c.bgpPolicyState.bgpPolicyName + routerID = c.bgpPolicyState.routerID + localASN = c.bgpPolicyState.localASN + listenPort = c.bgpPolicyState.listenPort + } + return name, routerID, localASN, listenPort +} diff --git a/pkg/agent/controller/bgp/controller_test.go b/pkg/agent/controller/bgp/controller_test.go index 13b7041ba8e..3a9b6026f58 100644 --- a/pkg/agent/controller/bgp/controller_test.go +++ b/pkg/agent/controller/bgp/controller_test.go @@ -263,7 +263,8 @@ func TestBGPPolicyAdd(t *testing.T) { ipv4ClusterIP1Eps, node, }, - expectedState: generateBGPPolicyState(179, + expectedState: generateBGPPolicyState(bgpPolicyName1, + 179, 65000, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], []string{ipStrToPrefix(clusterIPv4)}, @@ -294,7 +295,8 @@ func TestBGPPolicyAdd(t *testing.T) { ipv6ClusterIP1Eps, node, }, - expectedState: generateBGPPolicyState(179, + expectedState: generateBGPPolicyState(bgpPolicyName1, + 179, 65000, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], []string{ipStrToPrefix(externalIPv6)}, @@ -328,7 +330,8 @@ func TestBGPPolicyAdd(t *testing.T) { ipv6LoadBalancerEps, node, }, - expectedState: generateBGPPolicyState(179, + expectedState: generateBGPPolicyState(bgpPolicyName1, + 179, 65000, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], []string{ipStrToPrefix(loadBalancerIPv4), ipStrToPrefix(loadBalancerIPv6)}, @@ -361,7 +364,8 @@ func TestBGPPolicyAdd(t *testing.T) { ipv4Egress1, ipv4Egress2, }, - expectedState: generateBGPPolicyState(179, + expectedState: generateBGPPolicyState(bgpPolicyName1, + 179, 65000, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], []string{ipStrToPrefix(ipv4EgressIP1)}, @@ -388,7 +392,8 @@ func TestBGPPolicyAdd(t *testing.T) { true, []v1alpha1.BGPPeer{ipv6Peer1})}, objects: []runtime.Object{node}, - expectedState: generateBGPPolicyState(179, + expectedState: generateBGPPolicyState(bgpPolicyName1, + 179, 65000, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], []string{podIPv6CIDR.String()}, @@ -422,7 +427,8 @@ func TestBGPPolicyAdd(t *testing.T) { ipv6ClusterIP2Eps, node, }, - expectedState: generateBGPPolicyState(1179, + expectedState: generateBGPPolicyState(bgpPolicyName1, + 1179, 65001, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], nil, @@ -460,13 +466,15 @@ func TestBGPPolicyAdd(t *testing.T) { false, []v1alpha1.BGPPeer{ipv4Peer1})}, objects: []runtime.Object{ipv4ClusterIP1, ipv4ClusterIP1Eps, node}, - existingState: generateBGPPolicyState(179, + existingState: generateBGPPolicyState(bgpPolicyName2, + 179, 65000, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], []string{ipStrToPrefix(clusterIPv4)}, []bgp.PeerConfig{ipv4Peer1Config}, ), - expectedState: generateBGPPolicyState(179, + expectedState: generateBGPPolicyState(bgpPolicyName2, + 179, 65000, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], []string{ipStrToPrefix(clusterIPv4)}, @@ -523,7 +531,8 @@ func TestBGPPolicyUpdate(t *testing.T) { ipv6Peer1, ipv6Peer2, }) - effectivePolicyState := generateBGPPolicyState(179, + effectivePolicyState := generateBGPPolicyState(bgpPolicyName1, + 179, 65000, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], []string{ipStrToPrefix(clusterIPv4), @@ -627,7 +636,8 @@ func TestBGPPolicyUpdate(t *testing.T) { mockBGPServer.AdvertiseRoutes(gomock.Any(), []bgp.Route{{Prefix: podIPv4CIDR.String()}}) mockBGPServer.AdvertiseRoutes(gomock.Any(), []bgp.Route{{Prefix: podIPv6CIDR.String()}}) }, - expectedState: generateBGPPolicyState(1179, + expectedState: generateBGPPolicyState(bgpPolicyName2, + 1179, 65000, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], []string{ipStrToPrefix(clusterIPv4), @@ -684,7 +694,8 @@ func TestBGPPolicyUpdate(t *testing.T) { ipv6Peer1, ipv6Peer2, }), - expectedState: generateBGPPolicyState(179, + expectedState: generateBGPPolicyState(bgpPolicyName1, + 179, 65000, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], []string{ipStrToPrefix(externalIPv4), @@ -729,7 +740,8 @@ func TestBGPPolicyUpdate(t *testing.T) { ipv6Peer1, ipv6Peer2, }), - expectedState: generateBGPPolicyState(179, + expectedState: generateBGPPolicyState(bgpPolicyName1, + 179, 65001, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], []string{ipStrToPrefix(externalIPv4), @@ -773,7 +785,8 @@ func TestBGPPolicyUpdate(t *testing.T) { ipv6Peer1, ipv6Peer2, }), - expectedState: generateBGPPolicyState(1179, + expectedState: generateBGPPolicyState(bgpPolicyName1, + 1179, 65000, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], []string{ipStrToPrefix(clusterIPv4), @@ -820,7 +833,8 @@ func TestBGPPolicyUpdate(t *testing.T) { updatedIPv6Peer2, ipv4Peer3, ipv6Peer3}), - expectedState: generateBGPPolicyState(179, + expectedState: generateBGPPolicyState(bgpPolicyName1, + 179, 65000, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], []string{ipStrToPrefix(clusterIPv4), @@ -943,7 +957,8 @@ func TestBGPPolicyDelete(t *testing.T) { ipv4Peer1, ipv6Peer1, }) - policy1State := generateBGPPolicyState(179, + policy1State := generateBGPPolicyState(bgpPolicyName1, + 179, 65000, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], []string{ @@ -969,7 +984,8 @@ func TestBGPPolicyDelete(t *testing.T) { ipv4Peer2, ipv6Peer2, }) - policy2State := generateBGPPolicyState(179, + policy2State := generateBGPPolicyState(bgpPolicyName2, + 179, 65000, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], []string{ @@ -994,7 +1010,8 @@ func TestBGPPolicyDelete(t *testing.T) { ipv4Peer2, ipv6Peer2, }) - policy3State := generateBGPPolicyState(1179, + policy3State := generateBGPPolicyState(bgpPolicyName3, + 1179, 65000, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], []string{ @@ -1117,7 +1134,8 @@ func TestNodeUpdate(t *testing.T) { false, true, []v1alpha1.BGPPeer{ipv4Peer1, ipv6Peer1}) - policy1State := generateBGPPolicyState(179, + policy1State := generateBGPPolicyState(bgpPolicyName1, + 179, 65000, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], []string{podIPv4CIDR.String(), podIPv6CIDR.String()}, @@ -1133,7 +1151,8 @@ func TestNodeUpdate(t *testing.T) { false, true, []v1alpha1.BGPPeer{ipv4Peer1, ipv6Peer1}) - policy2State := generateBGPPolicyState(1179, + policy2State := generateBGPPolicyState(bgpPolicyName2, + 1179, 65000, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], []string{podIPv4CIDR.String(), podIPv6CIDR.String()}, @@ -1217,7 +1236,8 @@ func TestNodeUpdate(t *testing.T) { node: generateNode(localNodeName, nodeLabels1, nodeAnnotations1), updatedNode: generateNode(localNodeName, nodeLabels1, nodeAnnotations2), existingState: deepCopyBGPPolicyState(policy1State), - expectedState: generateBGPPolicyState(179, + expectedState: generateBGPPolicyState(bgpPolicyName1, + 179, 65000, nodeAnnotations2[types.NodeBGPRouterIDAnnotationKey], []string{podIPv4CIDR.String(), podIPv6CIDR.String()}, @@ -1238,7 +1258,8 @@ func TestNodeUpdate(t *testing.T) { node: generateNode(localNodeName, nodeLabels1, nodeAnnotations1), updatedNode: generateNode(localNodeName, nodeLabels1, nil), existingState: deepCopyBGPPolicyState(policy1State), - expectedState: generateBGPPolicyState(179, + expectedState: generateBGPPolicyState(bgpPolicyName1, + 179, 65000, nodeIPv4Addr.IP.String(), []string{podIPv4CIDR.String(), podIPv6CIDR.String()}, @@ -1257,12 +1278,14 @@ func TestNodeUpdate(t *testing.T) { ipv6Enabled: true, node: generateNode(localNodeName, nodeLabels1, nodeAnnotations1), updatedNode: generateNode(localNodeName, nodeLabels1, nodeAnnotations2), - existingState: generateBGPPolicyState(179, + existingState: generateBGPPolicyState(bgpPolicyName1, + 179, 65000, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], []string{podIPv6CIDR.String()}, []bgp.PeerConfig{ipv6Peer1Config}), - expectedState: generateBGPPolicyState(179, + expectedState: generateBGPPolicyState(bgpPolicyName1, + 179, 65000, nodeAnnotations2[types.NodeBGPRouterIDAnnotationKey], []string{podIPv6CIDR.String()}, @@ -1279,12 +1302,14 @@ func TestNodeUpdate(t *testing.T) { ipv6Enabled: true, node: generateNode(localNodeName, nodeLabels1, nodeAnnotations1), updatedNode: generateNode(localNodeName, nodeLabels1, nil), - existingState: generateBGPPolicyState(179, + existingState: generateBGPPolicyState(bgpPolicyName1, + 179, 65000, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], []string{podIPv6CIDR.String()}, []bgp.PeerConfig{ipv6Peer1Config}), - expectedState: generateBGPPolicyState(179, + expectedState: generateBGPPolicyState(bgpPolicyName1, + 179, 65000, "156.67.103.8", []string{podIPv6CIDR.String()}, @@ -1727,7 +1752,8 @@ func TestSyncBGPPolicyFailures(t *testing.T) { // Done with the dummy event. doneDummyEvent(t, c) - checkBGPPolicyState(t, generateBGPPolicyState(179, + checkBGPPolicyState(t, generateBGPPolicyState(bgpPolicyName1, + 179, 65000, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], []string{ipStrToPrefix(loadBalancerIPv4)}, @@ -1743,7 +1769,8 @@ func TestSyncBGPPolicyFailures(t *testing.T) { // Mock that failing in stopping the current BGP server. mockBGPServer.EXPECT().Stop(gomock.Any()).Return(fmt.Errorf("failed reason")) require.EqualError(t, c.syncBGPPolicy(ctx), "failed to stop current BGP server: failed reason") - checkBGPPolicyState(t, generateBGPPolicyState(179, + checkBGPPolicyState(t, generateBGPPolicyState(bgpPolicyName1, + 179, 65000, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], []string{ipStrToPrefix(loadBalancerIPv4)}, @@ -1760,7 +1787,8 @@ func TestSyncBGPPolicyFailures(t *testing.T) { mockBGPServer.EXPECT().Start(gomock.Any()) mockBGPServer.EXPECT().AddPeer(gomock.Any(), ipv4Peer1Config).Return(fmt.Errorf("failed to add BGP peer")) require.EqualError(t, c.syncBGPPolicy(ctx), "failed to add BGP peer") - checkBGPPolicyState(t, generateBGPPolicyState(1179, + checkBGPPolicyState(t, generateBGPPolicyState(bgpPolicyName2, + 1179, 65000, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], []string{}, @@ -1773,7 +1801,7 @@ func TestSyncBGPPolicyFailures(t *testing.T) { require.EqualError(t, c.syncBGPPolicy(ctx), "failed to advertise routes") // Done with the dummy event. doneDummyEvent(t, c) - checkBGPPolicyState(t, generateBGPPolicyState( + checkBGPPolicyState(t, generateBGPPolicyState(bgpPolicyName2, 1179, 65000, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], @@ -1792,7 +1820,8 @@ func TestSyncBGPPolicyFailures(t *testing.T) { require.NoError(t, c.syncBGPPolicy(ctx)) doneDummyEvent(t, c) - checkBGPPolicyState(t, generateBGPPolicyState(1179, + checkBGPPolicyState(t, generateBGPPolicyState(bgpPolicyName3, + 1179, 65000, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], []string{ipStrToPrefix(externalIPv4)}, @@ -1810,7 +1839,8 @@ func TestSyncBGPPolicyFailures(t *testing.T) { require.NoError(t, c.syncBGPPolicy(ctx)) doneDummyEvent(t, c) - checkBGPPolicyState(t, generateBGPPolicyState(1179, + checkBGPPolicyState(t, generateBGPPolicyState(bgpPolicyName4, + 1179, 65000, nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], []string{ipStrToPrefix(loadBalancerIPv4)}, @@ -1818,7 +1848,8 @@ func TestSyncBGPPolicyFailures(t *testing.T) { c.bgpPolicyState) } -func generateBGPPolicyState(listenPort int32, +func generateBGPPolicyState(bgpPolicyName string, + listenPort int32, localASN int32, routerID string, prefixes []string, @@ -1833,11 +1864,12 @@ func generateBGPPolicyState(listenPort int32, peerConfigMap[peerKey] = peerConfig } return &bgpPolicyState{ - listenPort: listenPort, - localASN: localASN, - routerID: routerID, - routes: routes, - peerConfigs: peerConfigMap, + bgpPolicyName: bgpPolicyName, + listenPort: listenPort, + localASN: localASN, + routerID: routerID, + routes: routes, + peerConfigs: peerConfigMap, } } @@ -1849,17 +1881,19 @@ func deepCopyBGPPolicyState(in *bgpPolicyState) *bgpPolicyState { } return &bgpPolicyState{ - listenPort: in.listenPort, - localASN: in.localASN, - routerID: in.routerID, - routes: in.routes.Union(nil), - peerConfigs: peerConfigMap, + bgpPolicyName: in.bgpPolicyName, + listenPort: in.listenPort, + localASN: in.localASN, + routerID: in.routerID, + routes: in.routes.Union(nil), + peerConfigs: peerConfigMap, } } func checkBGPPolicyState(t *testing.T, expected, got *bgpPolicyState) { require.Equal(t, expected != nil, got != nil) if expected != nil { + assert.Equal(t, expected.bgpPolicyName, got.bgpPolicyName) assert.Equal(t, expected.listenPort, got.listenPort) assert.Equal(t, expected.localASN, got.localASN) assert.Equal(t, expected.routerID, got.routerID) @@ -2083,3 +2117,47 @@ func waitAndGetDummyEvent(t *testing.T, c *fakeController) { func doneDummyEvent(t *testing.T, c *fakeController) { c.queue.Done(dummyKey) } + +func TestGetBGPPolicyInfo(t *testing.T) { + testCases := []struct { + name string + existingState *bgpPolicyState + expectedBgpPolicyName string + expectedASN int32 + expectedRouterID string + expectedListenPort int32 + }{ + { + name: "bgpPolicyState exists", + existingState: generateBGPPolicyState(bgpPolicyName1, + 179, + 65000, + nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], + nil, + nil, + ), + expectedBgpPolicyName: bgpPolicyName1, + expectedASN: int32(65000), + expectedRouterID: nodeAnnotations1[types.NodeBGPRouterIDAnnotationKey], + expectedListenPort: int32(179), + }, + { + name: "bgpPolicyState does not exist", + existingState: nil, + }, + } + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + c := newFakeController(t, nil, nil, true, false) + + // Fake the BGPPolicy state. + c.bgpPolicyState = tt.existingState + + actualBgpPolicyName, actualRouterID, actualASN, actualListenPort := c.GetBGPPolicyInfo() + assert.Equal(t, tt.expectedBgpPolicyName, actualBgpPolicyName) + assert.Equal(t, tt.expectedRouterID, actualRouterID) + assert.Equal(t, tt.expectedASN, actualASN) + assert.Equal(t, tt.expectedListenPort, actualListenPort) + }) + } +} 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 ddd91e6a6ab..65a412893b7 100644 --- a/pkg/antctl/antctl.go +++ b/pkg/antctl/antctl.go @@ -639,6 +639,19 @@ $ antctl get podmulticaststats pod -n namespace`, }, transformedResponse: reflect.TypeOf(agentapis.MemberlistResponse{}), }, + { + use: "bgppolicy", + 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{}), + }, }, rawCommands: []rawCommand{ { diff --git a/pkg/antctl/command_list_test.go b/pkg/antctl/command_list_test.go index 8a87fc666a3..42f88e658f7 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"}, {"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 886ec8f3ab5..c8ea3725431 100644 --- a/pkg/querier/querier.go +++ b/pkg/querier/querier.go @@ -138,3 +138,8 @@ var NamespaceScopedPolicyTypes = sets.New[string]("ANNP", "K8SNP") 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) +} diff --git a/pkg/querier/testing/mock_querier.go b/pkg/querier/testing/mock_querier.go index fe30070ba73..6442a85fab7 100644 --- a/pkg/querier/testing/mock_querier.go +++ b/pkg/querier/testing/mock_querier.go @@ -14,11 +14,11 @@ // // Code generated by MockGen. DO NOT EDIT. -// Source: antrea.io/antrea/pkg/querier (interfaces: AgentNetworkPolicyInfoQuerier,AgentMulticastInfoQuerier,EgressQuerier) +// Source: antrea.io/antrea/pkg/querier (interfaces: AgentNetworkPolicyInfoQuerier,AgentMulticastInfoQuerier,EgressQuerier,AgentBGPPolicyInfoQuerier) // // Generated by this command: // -// mockgen -copyright_file hack/boilerplate/license_header.raw.txt -destination pkg/querier/testing/mock_querier.go -package testing antrea.io/antrea/pkg/querier AgentNetworkPolicyInfoQuerier,AgentMulticastInfoQuerier,EgressQuerier +// mockgen -copyright_file hack/boilerplate/license_header.raw.txt -destination pkg/querier/testing/mock_querier.go -package testing antrea.io/antrea/pkg/querier AgentNetworkPolicyInfoQuerier,AgentMulticastInfoQuerier,EgressQuerier,AgentBGPPolicyInfoQuerier // // Package testing is a generated GoMock package. @@ -333,3 +333,43 @@ func (mr *MockEgressQuerierMockRecorder) GetEgressIPByMark(arg0 any) *gomock.Cal mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEgressIPByMark", reflect.TypeOf((*MockEgressQuerier)(nil).GetEgressIPByMark), arg0) } + +// MockAgentBGPPolicyInfoQuerier is a mock of AgentBGPPolicyInfoQuerier interface. +type MockAgentBGPPolicyInfoQuerier struct { + ctrl *gomock.Controller + recorder *MockAgentBGPPolicyInfoQuerierMockRecorder +} + +// MockAgentBGPPolicyInfoQuerierMockRecorder is the mock recorder for MockAgentBGPPolicyInfoQuerier. +type MockAgentBGPPolicyInfoQuerierMockRecorder struct { + mock *MockAgentBGPPolicyInfoQuerier +} + +// NewMockAgentBGPPolicyInfoQuerier creates a new mock instance. +func NewMockAgentBGPPolicyInfoQuerier(ctrl *gomock.Controller) *MockAgentBGPPolicyInfoQuerier { + mock := &MockAgentBGPPolicyInfoQuerier{ctrl: ctrl} + mock.recorder = &MockAgentBGPPolicyInfoQuerierMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAgentBGPPolicyInfoQuerier) EXPECT() *MockAgentBGPPolicyInfoQuerierMockRecorder { + return m.recorder +} + +// GetBGPPolicyInfo mocks base method. +func (m *MockAgentBGPPolicyInfoQuerier) GetBGPPolicyInfo() (string, string, int32, int32) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBGPPolicyInfo") + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(string) + ret2, _ := ret[2].(int32) + ret3, _ := ret[3].(int32) + return ret0, ret1, ret2, ret3 +} + +// GetBGPPolicyInfo indicates an expected call of GetBGPPolicyInfo. +func (mr *MockAgentBGPPolicyInfoQuerierMockRecorder) GetBGPPolicyInfo() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBGPPolicyInfo", reflect.TypeOf((*MockAgentBGPPolicyInfoQuerier)(nil).GetBGPPolicyInfo)) +} diff --git a/test/e2e/antctl_test.go b/test/e2e/antctl_test.go index 59aa997e976..8ef5a106af7 100644 --- a/test/e2e/antctl_test.go +++ b/test/e2e/antctl_test.go @@ -122,7 +122,7 @@ func testAntctlAgentLocalAccess(t *testing.T, data *TestData) { cmd := strings.Join(args, " ") t.Run(cmd, func(t *testing.T) { stdout, stderr, err := runAntctl(podName, args, data) - if err != nil && !strings.HasSuffix(stderr, "not enabled\n") { + if err != nil && !(strings.HasSuffix(stderr, "not enabled\n") || strings.HasSuffix(stderr, "there is no effective bgp policy applied to the Node\n")) { t.Fatalf("Error when running `antctl %s` from %s: %v\n%s", c, podName, err, antctlOutput(stdout, stderr)) } })