Skip to content

Commit aeb03b0

Browse files
authored
Add "antctl get bgproutes" agent command (antrea-io#6734)
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 <kumar.atish@broadcom.com>
1 parent d318621 commit aeb03b0

File tree

11 files changed

+433
-1
lines changed

11 files changed

+433
-1
lines changed

docs/antctl.md

+28
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,34 @@ PEER ASN STATE
781781
192.168.77.201:179 65002 Active
782782
```
783783

784+
`antctl` agent command `get bgproutes` prints the advertised BGP routes on the local Node.
785+
For more information about route advertisement, please refer to [Advertisements](./bgp-policy.md#advertisements).
786+
787+
```bash
788+
# Get the list of all advertised bgp routes
789+
$ antctl get bgproutes
790+
791+
ROUTE
792+
10.96.10.10/32
793+
192.168.77.100/32
794+
fec0::10:96:10:10/128
795+
fec0::192:168:77:100/128
796+
797+
# Get the list of advertised IPv4 bgp routes
798+
$ antctl get bgproutes --ipv4-only
799+
800+
ROUTE
801+
10.96.10.10/32
802+
192.168.77.100/32
803+
804+
# Get the list of advertised IPv6 bgp routes
805+
$ antctl get bgproutes --ipv6-only
806+
807+
ROUTE
808+
fec0::10:96:10:10/128
809+
fec0::192:168:77:100/128
810+
```
811+
784812
### Upgrade existing objects of CRDs
785813

786814
antctl supports upgrading existing objects of Antrea CRDs to the storage version.

pkg/agent/apis/types.go

+17
Original file line numberDiff line numberDiff line change
@@ -229,3 +229,20 @@ func (r BGPPeerResponse) GetTableRow(_ int) []string {
229229
func (r BGPPeerResponse) SortRows() bool {
230230
return true
231231
}
232+
233+
// BGPRouteResponse describes the response struct of bgproutes command.
234+
type BGPRouteResponse struct {
235+
Route string `json:"route,omitempty"`
236+
}
237+
238+
func (r BGPRouteResponse) GetTableHeader() []string {
239+
return []string{"ROUTE"}
240+
}
241+
242+
func (r BGPRouteResponse) GetTableRow(_ int) []string {
243+
return []string{r.Route}
244+
}
245+
246+
func (r BGPRouteResponse) SortRows() bool {
247+
return true
248+
}

pkg/agent/apiserver/apiserver.go

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
"antrea.io/antrea/pkg/agent/apiserver/handlers/appliedtogroup"
3939
"antrea.io/antrea/pkg/agent/apiserver/handlers/bgppeer"
4040
"antrea.io/antrea/pkg/agent/apiserver/handlers/bgppolicy"
41+
"antrea.io/antrea/pkg/agent/apiserver/handlers/bgproute"
4142
"antrea.io/antrea/pkg/agent/apiserver/handlers/featuregates"
4243
"antrea.io/antrea/pkg/agent/apiserver/handlers/memberlist"
4344
"antrea.io/antrea/pkg/agent/apiserver/handlers/multicast"
@@ -102,6 +103,7 @@ func installHandlers(aq agentquerier.AgentQuerier, npq querier.AgentNetworkPolic
102103
s.Handler.NonGoRestfulMux.HandleFunc("/memberlist", memberlist.HandleFunc(aq))
103104
s.Handler.NonGoRestfulMux.HandleFunc("/bgppolicy", bgppolicy.HandleFunc(bgpq))
104105
s.Handler.NonGoRestfulMux.HandleFunc("/bgppeers", bgppeer.HandleFunc(bgpq))
106+
s.Handler.NonGoRestfulMux.HandleFunc("/bgproutes", bgproute.HandleFunc(bgpq))
105107
}
106108

107109
func installAPIGroup(s *genericapiserver.GenericAPIServer, aq agentquerier.AgentQuerier, npq querier.AgentNetworkPolicyInfoQuerier, v4Enabled, v6Enabled bool) error {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright 2024 Antrea Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package bgproute
16+
17+
import (
18+
"encoding/json"
19+
"errors"
20+
"net/http"
21+
"reflect"
22+
23+
"k8s.io/klog/v2"
24+
25+
"antrea.io/antrea/pkg/agent/apis"
26+
"antrea.io/antrea/pkg/agent/controller/bgp"
27+
"antrea.io/antrea/pkg/querier"
28+
)
29+
30+
// HandleFunc returns the function which can handle queries issued by the bgproutes command.
31+
func HandleFunc(bq querier.AgentBGPPolicyInfoQuerier) http.HandlerFunc {
32+
return func(w http.ResponseWriter, r *http.Request) {
33+
if bq == nil || reflect.ValueOf(bq).IsNil() {
34+
// The error message must match the "FOO is not enabled" pattern to pass antctl e2e tests.
35+
http.Error(w, "bgp is not enabled", http.StatusServiceUnavailable)
36+
return
37+
}
38+
39+
values := r.URL.Query()
40+
var ipv4Only, ipv6Only bool
41+
if values.Has("ipv4-only") {
42+
if values.Get("ipv4-only") != "" {
43+
http.Error(w, "invalid query", http.StatusBadRequest)
44+
return
45+
}
46+
ipv4Only = true
47+
}
48+
if values.Has("ipv6-only") {
49+
if values.Get("ipv6-only") != "" {
50+
http.Error(w, "invalid query", http.StatusBadRequest)
51+
return
52+
}
53+
ipv6Only = true
54+
}
55+
if ipv4Only && ipv6Only {
56+
http.Error(w, "invalid query", http.StatusBadRequest)
57+
return
58+
}
59+
60+
bgpRoutes, err := bq.GetBGPRoutes(r.Context(), !ipv6Only, !ipv4Only)
61+
if err != nil {
62+
if errors.Is(err, bgp.ErrBGPPolicyNotFound) {
63+
http.Error(w, "there is no effective bgp policy applied to the Node", http.StatusNotFound)
64+
return
65+
}
66+
http.Error(w, err.Error(), http.StatusInternalServerError)
67+
return
68+
}
69+
70+
var bgpRoutesResp []apis.BGPRouteResponse
71+
for _, bgpRoute := range bgpRoutes {
72+
bgpRoutesResp = append(bgpRoutesResp, apis.BGPRouteResponse{
73+
Route: bgpRoute,
74+
})
75+
}
76+
77+
if err := json.NewEncoder(w).Encode(bgpRoutesResp); err != nil {
78+
w.WriteHeader(http.StatusInternalServerError)
79+
klog.ErrorS(err, "Error when encoding BGPRoutesResp to json")
80+
}
81+
}
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// Copyright 2024 Antrea Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package bgproute
16+
17+
import (
18+
"context"
19+
"encoding/json"
20+
"net/http"
21+
"net/http/httptest"
22+
"testing"
23+
24+
"github.com/stretchr/testify/assert"
25+
"github.com/stretchr/testify/require"
26+
"go.uber.org/mock/gomock"
27+
28+
"antrea.io/antrea/pkg/agent/apis"
29+
"antrea.io/antrea/pkg/agent/controller/bgp"
30+
queriertest "antrea.io/antrea/pkg/querier/testing"
31+
)
32+
33+
func TestBGPRouteQuery(t *testing.T) {
34+
ctx := context.Background()
35+
tests := []struct {
36+
name string
37+
url string
38+
expectedCalls func(mockBGPServer *queriertest.MockAgentBGPPolicyInfoQuerier)
39+
expectedStatus int
40+
expectedResponse []apis.BGPRouteResponse
41+
}{
42+
{
43+
name: "bgpPolicyState does not exist",
44+
expectedCalls: func(mockBGPServer *queriertest.MockAgentBGPPolicyInfoQuerier) {
45+
mockBGPServer.EXPECT().GetBGPRoutes(context.Background(), true, true).Return(nil, bgp.ErrBGPPolicyNotFound)
46+
},
47+
expectedStatus: http.StatusNotFound,
48+
},
49+
{
50+
name: "get all advertised routes",
51+
expectedCalls: func(mockBGPServer *queriertest.MockAgentBGPPolicyInfoQuerier) {
52+
mockBGPServer.EXPECT().GetBGPRoutes(ctx, true, true).Return(
53+
[]string{"192.168.1.0/24", "192.168.2.0/24", "fec0::10:96:10:10/128"}, nil)
54+
},
55+
expectedStatus: http.StatusOK,
56+
expectedResponse: []apis.BGPRouteResponse{
57+
{
58+
Route: "192.168.1.0/24",
59+
},
60+
{
61+
Route: "192.168.2.0/24",
62+
},
63+
{
64+
Route: "fec0::10:96:10:10/128",
65+
},
66+
},
67+
},
68+
{
69+
name: "get advertised ipv4 routes only",
70+
url: "?ipv4-only",
71+
expectedCalls: func(mockBGPServer *queriertest.MockAgentBGPPolicyInfoQuerier) {
72+
mockBGPServer.EXPECT().GetBGPRoutes(ctx, true, false).Return(
73+
[]string{"192.168.1.0/24", "192.168.2.0/24"}, nil)
74+
},
75+
expectedStatus: http.StatusOK,
76+
expectedResponse: []apis.BGPRouteResponse{
77+
{
78+
Route: "192.168.1.0/24",
79+
},
80+
{
81+
Route: "192.168.2.0/24",
82+
},
83+
},
84+
},
85+
{
86+
name: "get advertised ipv6 routes only",
87+
url: "?ipv6-only=",
88+
expectedCalls: func(mockBGPServer *queriertest.MockAgentBGPPolicyInfoQuerier) {
89+
mockBGPServer.EXPECT().GetBGPRoutes(ctx, false, true).Return(
90+
[]string{"fec0::192:168:77:150/128", "fec0::10:10:0:10/128"}, nil)
91+
},
92+
expectedStatus: http.StatusOK,
93+
expectedResponse: []apis.BGPRouteResponse{
94+
{
95+
Route: "fec0::192:168:77:150/128",
96+
},
97+
{
98+
Route: "fec0::10:10:0:10/128",
99+
},
100+
},
101+
},
102+
{
103+
name: "flag with value",
104+
url: "?ipv4-only=true",
105+
expectedStatus: http.StatusBadRequest,
106+
},
107+
{
108+
name: "both flags are passed",
109+
url: "?ipv4-only&ipv6-only",
110+
expectedStatus: http.StatusBadRequest,
111+
},
112+
}
113+
114+
for _, tt := range tests {
115+
t.Run(tt.name, func(t *testing.T) {
116+
ctrl := gomock.NewController(t)
117+
q := queriertest.NewMockAgentBGPPolicyInfoQuerier(ctrl)
118+
if tt.expectedCalls != nil {
119+
tt.expectedCalls(q)
120+
}
121+
handler := HandleFunc(q)
122+
123+
req, err := http.NewRequest(http.MethodGet, tt.url, nil)
124+
require.NoError(t, err)
125+
126+
recorder := httptest.NewRecorder()
127+
handler.ServeHTTP(recorder, req)
128+
assert.Equal(t, tt.expectedStatus, recorder.Code)
129+
130+
if tt.expectedStatus == http.StatusOK {
131+
var received []apis.BGPRouteResponse
132+
err = json.Unmarshal(recorder.Body.Bytes(), &received)
133+
require.NoError(t, err)
134+
assert.Equal(t, tt.expectedResponse, received)
135+
}
136+
})
137+
}
138+
}

pkg/agent/controller/bgp/controller.go

+34
Original file line numberDiff line numberDiff line change
@@ -991,3 +991,37 @@ func (c *Controller) GetBGPPeerStatus(ctx context.Context) ([]bgp.PeerStatus, er
991991
}
992992
return peers, nil
993993
}
994+
995+
// GetBGPRoutes returns the advertised BGP routes.
996+
func (c *Controller) GetBGPRoutes(ctx context.Context, ipv4Routes, ipv6Routes bool) ([]string, error) {
997+
getBgpRoutesAdvertised := func() sets.Set[bgp.Route] {
998+
c.bgpPolicyStateMutex.RLock()
999+
defer c.bgpPolicyStateMutex.RUnlock()
1000+
if c.bgpPolicyState == nil {
1001+
return nil
1002+
}
1003+
return c.bgpPolicyState.routes
1004+
}
1005+
1006+
bgpRoutesAdvertised := getBgpRoutesAdvertised()
1007+
if bgpRoutesAdvertised == nil {
1008+
return nil, ErrBGPPolicyNotFound
1009+
}
1010+
1011+
bgpRoutes := make([]string, 0, bgpRoutesAdvertised.Len())
1012+
if ipv4Routes { // insert IPv4 advertised routes
1013+
for route := range bgpRoutesAdvertised {
1014+
if utilnet.IsIPv4CIDRString(route.Prefix) {
1015+
bgpRoutes = append(bgpRoutes, route.Prefix)
1016+
}
1017+
}
1018+
}
1019+
if ipv6Routes { // insert IPv6 advertised routes
1020+
for route := range bgpRoutesAdvertised {
1021+
if utilnet.IsIPv6CIDRString(route.Prefix) {
1022+
bgpRoutes = append(bgpRoutes, route.Prefix)
1023+
}
1024+
}
1025+
}
1026+
return bgpRoutes, nil
1027+
}

0 commit comments

Comments
 (0)