Skip to content

Commit

Permalink
Add 'antctl get bgppolicy' command (antrea-io#6646)
Browse files Browse the repository at this point in the history
Add `antctl get bgppolicy` agent command to get effective BGP policy
applied on the Node.

The command is implemented using a new HTTP endpoint (`/bgppolicy`),
which will return a `404 Not Found` error if no BGPPolicy has been applied
on the Node.

For antrea-io#6209

Signed-off-by: Kumar Atish <[email protected]>
  • Loading branch information
Atish-iaf authored Sep 18, 2024
1 parent d3faddc commit 84f60d2
Show file tree
Hide file tree
Showing 19 changed files with 423 additions and 62 deletions.
5 changes: 4 additions & 1 deletion cmd/antrea-agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -926,6 +927,7 @@ func run(o *Options) error {
o.config.NodePortLocal.PortRange,
memberlistCluster,
nodeInformer.Lister(),
bgpController,
)

if features.DefaultFeatureGate.Enabled(features.SupportBundleCollection) {
Expand Down Expand Up @@ -956,6 +958,7 @@ func run(o *Options) error {
networkPolicyController,
mcastController,
externalIPController,
bgpController,
secureServing,
authentication,
authorization,
Expand Down
13 changes: 13 additions & 0 deletions docs/antctl.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
<!-- /toc -->

Expand Down Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions docs/bgp-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
<!-- /toc -->

Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion hack/update-codegen-dockerized.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
20 changes: 20 additions & 0 deletions pkg/agent/apis/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
7 changes: 5 additions & 2 deletions pkg/agent/apiserver/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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())
Expand All @@ -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 {
Expand All @@ -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,
Expand All @@ -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
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/agent/apiserver/apiserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
54 changes: 54 additions & 0 deletions pkg/agent/apiserver/handlers/bgppolicy/handler.go
Original file line number Diff line number Diff line change
@@ -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")
}
}
}
76 changes: 76 additions & 0 deletions pkg/agent/apiserver/handlers/bgppolicy/handler_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}
48 changes: 38 additions & 10 deletions pkg/agent/controller/bgp/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -112,7 +114,8 @@ type Controller struct {

secretInformer cache.SharedIndexInformer

bgpPolicyState *bgpPolicyState
bgpPolicyState *bgpPolicyState
bgpPolicyStateMutex sync.RWMutex

k8sClient kubernetes.Interface
bgpPeerPasswords map[string]string
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
Loading

0 comments on commit 84f60d2

Please sign in to comment.