Skip to content

Commit

Permalink
Add "antctl get bgppeers" agent command
Browse files Browse the repository at this point in the history
Signed-off-by: Kumar Atish <kumar.atish@broadcom.com>
  • Loading branch information
Atish-iaf committed Sep 24, 2024
1 parent c470048 commit a965cc5
Show file tree
Hide file tree
Showing 11 changed files with 314 additions and 1 deletion.
12 changes: 12 additions & 0 deletions docs/antctl.md
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,18 @@ NAME ROUTER-ID LOCAL-ASN LISTEN-PORT
example-bgp-policy 172.18.0.2 64512 179
```

`antctl` agent command `get bgppeers` print the current status of all BGP peers
of effective BGP policy applied on the local Node. It includes Peer address,
ASN, and State of the BGP Peers.

```bash
$ antctl get bgppeers
PEER ASN STATE
192.168.77.200 65001 Established
192.168.77.201 65002 Active
```

### Upgrade existing objects of CRDs

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

// BGPPeerResponse describes the response struct of bgppeers command.
type BGPPeerResponse struct {
Peer string `json:"peer,omitempty"`
ASN int32 `json:"asn,omitempty"`
State string `json:"state,omitempty"`
}

func (r BGPPeerResponse) GetTableHeader() []string {
return []string{"PEER", "ASN", "STATE"}
}

func (r BGPPeerResponse) GetTableRow(_ int) []string {
return []string{r.Peer, strconv.Itoa(int(r.ASN)), r.State}
}

func (r BGPPeerResponse) 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 @@ -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/bgppeer"
"antrea.io/antrea/pkg/agent/apiserver/handlers/bgppolicy"
"antrea.io/antrea/pkg/agent/apiserver/handlers/featuregates"
"antrea.io/antrea/pkg/agent/apiserver/handlers/memberlist"
Expand Down Expand Up @@ -99,6 +100,7 @@ func installHandlers(aq agentquerier.AgentQuerier, npq querier.AgentNetworkPolic
s.Handler.NonGoRestfulMux.HandleFunc("/serviceexternalip", serviceexternalip.HandleFunc(seipq))
s.Handler.NonGoRestfulMux.HandleFunc("/memberlist", memberlist.HandleFunc(aq))
s.Handler.NonGoRestfulMux.HandleFunc("/bgppolicy", bgppolicy.HandleFunc(bgpq))
s.Handler.NonGoRestfulMux.HandleFunc("/bgppeers", bgppeer.HandleFunc(bgpq))
}

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

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 bgppeers 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
}

peers, err := bq.GetBGPPeerStatus()
if err != nil {
if err.Error() == "failed to get bgp peers: there is no effective bgp policy applied to the Node" {
http.Error(w, err.Error(), http.StatusNotFound)
return
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}

var bgpPeersResp []apis.BGPPeerResponse
for _, peer := range peers {
bgpPeersResp = append(bgpPeersResp, apis.BGPPeerResponse{
Peer: peer.Address,
ASN: peer.ASN,
State: string(peer.SessionState),
})
}

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

import (
"encoding/json"
"fmt"
"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/bgp"
queriertest "antrea.io/antrea/pkg/querier/testing"
)

func TestBGPPeerQuery(t *testing.T) {
tests := []struct {
name string
fakeBGPPeerStatus []bgp.PeerStatus
expectedStatus int
expectedResponse []apis.BGPPeerResponse
fakeErr error
}{
{
name: "bgpPolicyState exists",
fakeBGPPeerStatus: []bgp.PeerStatus{
{
Address: "192.168.77.200",
ASN: 65001,
SessionState: bgp.SessionEstablished,
},
{
Address: "192.168.77.201",
ASN: 65002,
SessionState: bgp.SessionActive,
},
},
expectedStatus: http.StatusOK,
expectedResponse: []apis.BGPPeerResponse{
{
Peer: "192.168.77.200",
ASN: 65001,
State: "Established",
},
{
Peer: "192.168.77.201",
ASN: 65002,
State: "Active",
},
},
},
{
name: "bgpPolicyState does not exist",
fakeBGPPeerStatus: nil,
expectedStatus: http.StatusNotFound,
fakeErr: fmt.Errorf("failed to get bgp peers: there is no effective bgp policy applied to the Node"),
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
q := queriertest.NewMockAgentBGPPolicyInfoQuerier(ctrl)
q.EXPECT().GetBGPPeerStatus().Return(tt.fakeBGPPeerStatus, tt.fakeErr)
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.BGPPeerResponse
err = json.Unmarshal(recorder.Body.Bytes(), &received)
require.NoError(t, err)
assert.ElementsMatch(t, tt.expectedResponse, received)
}
})
}
}
15 changes: 15 additions & 0 deletions pkg/agent/controller/bgp/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -959,3 +959,18 @@ func (c *Controller) GetBGPPolicyInfo() (string, string, int32, int32) {
}
return name, routerID, localASN, listenPort
}

// GetBGPPeerStatus returns current status of all BGP Peers of effective BGP Policy applied on the Node.
func (c *Controller) GetBGPPeerStatus() ([]bgp.PeerStatus, error) {
c.bgpPolicyStateMutex.RLock()
defer c.bgpPolicyStateMutex.RUnlock()

if c.bgpPolicyState == nil {
return nil, fmt.Errorf("failed to get bgp peers: there is no effective bgp policy applied to the Node")
}
peers, err := c.bgpPolicyState.bgpServer.GetPeers(context.TODO())
if err != nil {
return nil, fmt.Errorf("failed to get bgp peers: %w", err)
}
return peers, nil
}
68 changes: 68 additions & 0 deletions pkg/agent/controller/bgp/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2161,3 +2161,71 @@ func TestGetBGPPolicyInfo(t *testing.T) {
})
}
}

func TestGetBGPPeerStatus(t *testing.T) {
testCases := []struct {
name string
existingState *bgpPolicyState
expectedCalls func(mockBGPServer *bgptest.MockInterfaceMockRecorder)
expectedBgpPeerStatus []bgp.PeerStatus
expectedErr string
}{
{
name: "bgpPolicyState exists",
existingState: &bgpPolicyState{},
expectedCalls: func(mockBGPServer *bgptest.MockInterfaceMockRecorder) {
mockBGPServer.GetPeers(context.TODO()).Return([]bgp.PeerStatus{
{
Address: "192.168.77.200",
ASN: 65001,
SessionState: bgp.SessionEstablished,
},
{
Address: "192.168.77.201",
ASN: 65002,
SessionState: bgp.SessionActive,
},
}, nil)
},
expectedBgpPeerStatus: []bgp.PeerStatus{
{
Address: "192.168.77.200",
ASN: 65001,
SessionState: bgp.SessionEstablished,
},
{
Address: "192.168.77.201",
ASN: 65002,
SessionState: bgp.SessionActive,
},
},
},
{
name: "bgpPolicyState does not exist",
existingState: nil,
expectedErr: "failed to get bgp peers: there is no effective bgp policy applied to the Node",
},
}
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
if c.bgpPolicyState != nil {
c.bgpPolicyState.bgpServer = c.mockBGPServer
}

if tt.expectedCalls != nil {
tt.expectedCalls(c.mockBGPServer.EXPECT())
}

actualBgpPeerStatus, err := c.GetBGPPeerStatus()
if tt.expectedErr != "" {
assert.EqualError(t, err, tt.expectedErr)
} else {
assert.ElementsMatch(t, actualBgpPeerStatus, tt.expectedBgpPeerStatus)
}
})
}
}
17 changes: 17 additions & 0 deletions pkg/antctl/antctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,23 @@ $ antctl get podmulticaststats pod -n namespace`,
commandGroup: get,
transformedResponse: reflect.TypeOf(agentapis.BGPPolicyResponse{}),
},
{
use: "bgppeers",
aliases: []string{"bgppeer"},
short: "Print current status of all bgp peers of effective bgppolicy",
long: "Print current status of all bgp peers of effective bgppolicy which includes peer address, asn and state",
example: ` Get the list of bgppeers with their current status
$ antctl get bgppeers
`,
agentEndpoint: &endpoint{
nonResourceEndpoint: &nonResourceEndpoint{
path: "/bgppeers",
outputType: multiple,
},
},
commandGroup: get,
transformedResponse: reflect.TypeOf(agentapis.BGPPeerResponse{}),
},
},
rawCommands: []rawCommand{
{
Expand Down
2 changes: 1 addition & 1 deletion pkg/antctl/command_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"}, {"get", "bgppolicy"}, {"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", "bgppeers"}, {"supportbundle"}, {"traceflow"}, {"get", "featuregates"}},
},
{
name: "Antctl running against flow-aggregator mode",
Expand Down
3 changes: 3 additions & 0 deletions pkg/querier/querier.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"k8s.io/apimachinery/pkg/util/sets"

"antrea.io/antrea/pkg/agent/apis"
"antrea.io/antrea/pkg/agent/bgp"
"antrea.io/antrea/pkg/agent/interfacestore"
"antrea.io/antrea/pkg/agent/multicast"
"antrea.io/antrea/pkg/agent/types"
Expand Down Expand Up @@ -142,4 +143,6 @@ type ServiceExternalIPStatusQuerier interface {
type AgentBGPPolicyInfoQuerier interface {
// GetBGPPolicyInfo returns Name, RouterID, LocalASN and ListenPort of effective BGP Policy applied on the Node.
GetBGPPolicyInfo() (string, string, int32, int32)
// GetBGPPeerStatus returns current status of all BGP Peers of effective BGP Policy applied on the Node.
GetBGPPeerStatus() ([]bgp.PeerStatus, error)
}
16 changes: 16 additions & 0 deletions pkg/querier/testing/mock_querier.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit a965cc5

Please sign in to comment.