Skip to content

Commit

Permalink
Add "antctl get bgproutes" agent command (#6734)
Browse files Browse the repository at this point in the history
Add `antctl get bgproutes` agent command to print the advertised BGP routes.

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

Signed-off-by: Kumar Atish <[email protected]>
  • Loading branch information
Atish-iaf authored Oct 23, 2024
1 parent d318621 commit aeb03b0
Show file tree
Hide file tree
Showing 11 changed files with 433 additions and 1 deletion.
28 changes: 28 additions & 0 deletions docs/antctl.md
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,34 @@ PEER ASN STATE
192.168.77.201:179 65002 Active
```

`antctl` agent command `get bgproutes` prints the advertised BGP routes on the local Node.
For more information about route advertisement, please refer to [Advertisements](./bgp-policy.md#advertisements).

```bash
# Get the list of all advertised bgp routes
$ antctl get bgproutes

ROUTE
10.96.10.10/32
192.168.77.100/32
fec0::10:96:10:10/128
fec0::192:168:77:100/128

# Get the list of advertised IPv4 bgp routes
$ antctl get bgproutes --ipv4-only

ROUTE
10.96.10.10/32
192.168.77.100/32

# Get the list of advertised IPv6 bgp routes
$ antctl get bgproutes --ipv6-only

ROUTE
fec0::10:96:10:10/128
fec0::192:168:77:100/128
```

### Upgrade existing objects of CRDs

antctl supports upgrading existing objects of Antrea CRDs to the storage version.
Expand Down
17 changes: 17 additions & 0 deletions pkg/agent/apis/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,20 @@ func (r BGPPeerResponse) GetTableRow(_ int) []string {
func (r BGPPeerResponse) SortRows() bool {
return true
}

// BGPRouteResponse describes the response struct of bgproutes command.
type BGPRouteResponse struct {
Route string `json:"route,omitempty"`
}

func (r BGPRouteResponse) GetTableHeader() []string {
return []string{"ROUTE"}
}

func (r BGPRouteResponse) GetTableRow(_ int) []string {
return []string{r.Route}
}

func (r BGPRouteResponse) SortRows() bool {
return true
}
2 changes: 2 additions & 0 deletions pkg/agent/apiserver/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"antrea.io/antrea/pkg/agent/apiserver/handlers/appliedtogroup"
"antrea.io/antrea/pkg/agent/apiserver/handlers/bgppeer"
"antrea.io/antrea/pkg/agent/apiserver/handlers/bgppolicy"
"antrea.io/antrea/pkg/agent/apiserver/handlers/bgproute"
"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 @@ -102,6 +103,7 @@ func installHandlers(aq agentquerier.AgentQuerier, npq querier.AgentNetworkPolic
s.Handler.NonGoRestfulMux.HandleFunc("/memberlist", memberlist.HandleFunc(aq))
s.Handler.NonGoRestfulMux.HandleFunc("/bgppolicy", bgppolicy.HandleFunc(bgpq))
s.Handler.NonGoRestfulMux.HandleFunc("/bgppeers", bgppeer.HandleFunc(bgpq))
s.Handler.NonGoRestfulMux.HandleFunc("/bgproutes", bgproute.HandleFunc(bgpq))
}

func installAPIGroup(s *genericapiserver.GenericAPIServer, aq agentquerier.AgentQuerier, npq querier.AgentNetworkPolicyInfoQuerier, v4Enabled, v6Enabled bool) error {
Expand Down
82 changes: 82 additions & 0 deletions pkg/agent/apiserver/handlers/bgproute/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// 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 bgproute

import (
"encoding/json"
"errors"
"net/http"
"reflect"

"k8s.io/klog/v2"

"antrea.io/antrea/pkg/agent/apis"
"antrea.io/antrea/pkg/agent/controller/bgp"
"antrea.io/antrea/pkg/querier"
)

// HandleFunc returns the function which can handle queries issued by the bgproutes 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
}

values := r.URL.Query()
var ipv4Only, ipv6Only bool
if values.Has("ipv4-only") {
if values.Get("ipv4-only") != "" {
http.Error(w, "invalid query", http.StatusBadRequest)
return
}
ipv4Only = true
}
if values.Has("ipv6-only") {
if values.Get("ipv6-only") != "" {
http.Error(w, "invalid query", http.StatusBadRequest)
return
}
ipv6Only = true
}
if ipv4Only && ipv6Only {
http.Error(w, "invalid query", http.StatusBadRequest)
return
}

bgpRoutes, err := bq.GetBGPRoutes(r.Context(), !ipv6Only, !ipv4Only)
if err != nil {
if errors.Is(err, bgp.ErrBGPPolicyNotFound) {
http.Error(w, "there is no effective bgp policy applied to the Node", http.StatusNotFound)
return
}
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

var bgpRoutesResp []apis.BGPRouteResponse
for _, bgpRoute := range bgpRoutes {
bgpRoutesResp = append(bgpRoutesResp, apis.BGPRouteResponse{
Route: bgpRoute,
})
}

if err := json.NewEncoder(w).Encode(bgpRoutesResp); err != nil {
w.WriteHeader(http.StatusInternalServerError)
klog.ErrorS(err, "Error when encoding BGPRoutesResp to json")
}
}
}
138 changes: 138 additions & 0 deletions pkg/agent/apiserver/handlers/bgproute/handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// 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 bgproute

import (
"context"
"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"
"antrea.io/antrea/pkg/agent/controller/bgp"
queriertest "antrea.io/antrea/pkg/querier/testing"
)

func TestBGPRouteQuery(t *testing.T) {
ctx := context.Background()
tests := []struct {
name string
url string
expectedCalls func(mockBGPServer *queriertest.MockAgentBGPPolicyInfoQuerier)
expectedStatus int
expectedResponse []apis.BGPRouteResponse
}{
{
name: "bgpPolicyState does not exist",
expectedCalls: func(mockBGPServer *queriertest.MockAgentBGPPolicyInfoQuerier) {
mockBGPServer.EXPECT().GetBGPRoutes(context.Background(), true, true).Return(nil, bgp.ErrBGPPolicyNotFound)
},
expectedStatus: http.StatusNotFound,
},
{
name: "get all advertised routes",
expectedCalls: func(mockBGPServer *queriertest.MockAgentBGPPolicyInfoQuerier) {
mockBGPServer.EXPECT().GetBGPRoutes(ctx, true, true).Return(
[]string{"192.168.1.0/24", "192.168.2.0/24", "fec0::10:96:10:10/128"}, nil)
},
expectedStatus: http.StatusOK,
expectedResponse: []apis.BGPRouteResponse{
{
Route: "192.168.1.0/24",
},
{
Route: "192.168.2.0/24",
},
{
Route: "fec0::10:96:10:10/128",
},
},
},
{
name: "get advertised ipv4 routes only",
url: "?ipv4-only",
expectedCalls: func(mockBGPServer *queriertest.MockAgentBGPPolicyInfoQuerier) {
mockBGPServer.EXPECT().GetBGPRoutes(ctx, true, false).Return(
[]string{"192.168.1.0/24", "192.168.2.0/24"}, nil)
},
expectedStatus: http.StatusOK,
expectedResponse: []apis.BGPRouteResponse{
{
Route: "192.168.1.0/24",
},
{
Route: "192.168.2.0/24",
},
},
},
{
name: "get advertised ipv6 routes only",
url: "?ipv6-only=",
expectedCalls: func(mockBGPServer *queriertest.MockAgentBGPPolicyInfoQuerier) {
mockBGPServer.EXPECT().GetBGPRoutes(ctx, false, true).Return(
[]string{"fec0::192:168:77:150/128", "fec0::10:10:0:10/128"}, nil)
},
expectedStatus: http.StatusOK,
expectedResponse: []apis.BGPRouteResponse{
{
Route: "fec0::192:168:77:150/128",
},
{
Route: "fec0::10:10:0:10/128",
},
},
},
{
name: "flag with value",
url: "?ipv4-only=true",
expectedStatus: http.StatusBadRequest,
},
{
name: "both flags are passed",
url: "?ipv4-only&ipv6-only",
expectedStatus: http.StatusBadRequest,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
q := queriertest.NewMockAgentBGPPolicyInfoQuerier(ctrl)
if tt.expectedCalls != nil {
tt.expectedCalls(q)
}
handler := HandleFunc(q)

req, err := http.NewRequest(http.MethodGet, tt.url, 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.BGPRouteResponse
err = json.Unmarshal(recorder.Body.Bytes(), &received)
require.NoError(t, err)
assert.Equal(t, tt.expectedResponse, received)
}
})
}
}
34 changes: 34 additions & 0 deletions pkg/agent/controller/bgp/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -991,3 +991,37 @@ func (c *Controller) GetBGPPeerStatus(ctx context.Context) ([]bgp.PeerStatus, er
}
return peers, nil
}

// GetBGPRoutes returns the advertised BGP routes.
func (c *Controller) GetBGPRoutes(ctx context.Context, ipv4Routes, ipv6Routes bool) ([]string, error) {
getBgpRoutesAdvertised := func() sets.Set[bgp.Route] {
c.bgpPolicyStateMutex.RLock()
defer c.bgpPolicyStateMutex.RUnlock()
if c.bgpPolicyState == nil {
return nil
}
return c.bgpPolicyState.routes
}

bgpRoutesAdvertised := getBgpRoutesAdvertised()
if bgpRoutesAdvertised == nil {
return nil, ErrBGPPolicyNotFound
}

bgpRoutes := make([]string, 0, bgpRoutesAdvertised.Len())
if ipv4Routes { // insert IPv4 advertised routes
for route := range bgpRoutesAdvertised {
if utilnet.IsIPv4CIDRString(route.Prefix) {
bgpRoutes = append(bgpRoutes, route.Prefix)
}
}
}
if ipv6Routes { // insert IPv6 advertised routes
for route := range bgpRoutesAdvertised {
if utilnet.IsIPv6CIDRString(route.Prefix) {
bgpRoutes = append(bgpRoutes, route.Prefix)
}
}
}
return bgpRoutes, nil
}
Loading

0 comments on commit aeb03b0

Please sign in to comment.