Skip to content

Commit

Permalink
Add CLI command to get memberlist state
Browse files Browse the repository at this point in the history
Add antrea agent command `antctl get memberlist`
to get state of memberlist cluster of antrea agent.

Fixes antrea-io#4601

Signed-off-by: Kumar Atish <atish.iaf@gmail.com>
  • Loading branch information
Atish-iaf committed Feb 7, 2023
1 parent a63314f commit c61a892
Show file tree
Hide file tree
Showing 15 changed files with 266 additions and 4 deletions.
1 change: 1 addition & 0 deletions cmd/antrea-agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,7 @@ func run(o *Options) error {
networkPolicyController,
o.config.APIPort,
o.config.NodePortLocal.PortRange,
memberlistCluster,
)

agentMonitor := monitor.NewAgentMonitor(crdClient, agentQuerier)
Expand Down
9 changes: 9 additions & 0 deletions docs/antctl.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ running in three different modes:
- [Record metrics](#record-metrics)
- [Multi-cluster commands](#multi-cluster-commands)
- [Multicast commands](#multicast-commands)
- [Showing memberlist state](#showing-memberlist-state)
<!-- /toc -->

## Installation
Expand Down Expand Up @@ -652,3 +653,11 @@ NAMESPACE NAME INBOUND OUTBOUND
testmulticast-vw7gx5b9 test3-receiver-2 30 0
testmulticast-vw7gx5b9 test3-sender-1 0 10
```

### Showing memberlist state

`antctl` agent command `get memberlist` (or `get ml`) print the state of memberlist cluster of Antrea Agent.

```bash
antctl get memberlist
```
3 changes: 2 additions & 1 deletion docs/support-bundle-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,8 @@ CR for external Nodes".
| IP Address Info | `agent`, `outside`, `Node`, `ExternalNode` | Output of `ip address` command on Linux or `ipconfig /all` command on Windows |
| IP Route Info | `agent`, `outside`, `Node`, `ExternalNode` | Output of `ip route` on Linux or `route print` on Windows |
| IP Link Info | `agent`, `outside`, `Node`, `ExternalNode` | Output of `ip link` on Linux or `Get-NetAdapter` on Windows |
| Cluster Information | `outside` | Dump of resources in the cluster, including: 1. all Pods, Deployments, Replicasets and Daemonsets in all Namespaces with any resourceVersion. 2. all Nodes with any resourceVersion. 3. all ConfigMaps in all Namespaces with any resourceVersion and label `app=antrea`. |
| Cluster Information | `outside` | Dump of resources in the cluster, including: 1. all Pods, Deployments, Replicasets and Daemonsets in all Namespaces with any resourceVersion. 2. all Nodes with any resourceVersion. 3. all ConfigMaps in all Namespaces with any resourceVersion and label `app=antrea`. |
| Memberlist State | `agent`, `outside` | YAML output of `antctl get memberlist` |

## Limitations

Expand Down
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/agentinfo"
"antrea.io/antrea/pkg/agent/apiserver/handlers/appliedtogroup"
"antrea.io/antrea/pkg/agent/apiserver/handlers/featuregates"
"antrea.io/antrea/pkg/agent/apiserver/handlers/memberlist"
"antrea.io/antrea/pkg/agent/apiserver/handlers/multicast"
"antrea.io/antrea/pkg/agent/apiserver/handlers/networkpolicy"
"antrea.io/antrea/pkg/agent/apiserver/handlers/ovsflows"
Expand Down Expand Up @@ -85,6 +86,7 @@ func installHandlers(aq agentquerier.AgentQuerier, npq querier.AgentNetworkPolic
s.Handler.NonGoRestfulMux.HandleFunc("/ovsflows", ovsflows.HandleFunc(aq))
s.Handler.NonGoRestfulMux.HandleFunc("/ovstracing", ovstracing.HandleFunc(aq))
s.Handler.NonGoRestfulMux.HandleFunc("/serviceexternalip", serviceexternalip.HandleFunc(seipq))
s.Handler.NonGoRestfulMux.HandleFunc("/memberlist", memberlist.HandleFunc(aq))
}

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/memberlist/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright 2023 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 memberlist

import (
"context"
"encoding/json"
"net/http"

v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/klog/v2"

"antrea.io/antrea/pkg/agent/querier"
)

// Response describes the response struct of memberlist command.
type Response struct {
NodeName string `json:"nodeName,omitempty"`
IP string `json:"ip,omitempty"`
Status string `json:"status,omitempty"`
}

func generateResponse(node v1.Node, aliveNodes sets.String) Response {
status := "Dead"
if aliveNodes.Has(node.Name) {
status = "Alive"
}
return Response{
NodeName: node.Name,
Status: status,
IP: node.Status.Addresses[0].Address,
}
}

// HandleFunc returns the function which can handle queries issued by the memberlist command.
func HandleFunc(aq querier.AgentQuerier) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var memberlist []Response
allNodes, err := aq.GetK8sClient().CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{})
if err != nil {
klog.Errorf("Error when listing all Nodes: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
aliveNodes := aq.GetAliveNodes()
for _, node := range allNodes.Items {
memberlist = append(memberlist, generateResponse(node, aliveNodes))
}

err = json.NewEncoder(w).Encode(memberlist)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
klog.Errorf("Error when encoding Memberlist to json: %v", err)
}
}
}

func (r Response) GetTableHeader() []string {
return []string{"NODE", "IP", "STATUS"}
}

func (r Response) GetTableRow(_ int) []string {
return []string{r.NodeName, r.IP, r.Status}
}

func (r Response) SortRows() bool {
return true
}
107 changes: 107 additions & 0 deletions pkg/agent/apiserver/handlers/memberlist/handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright 2023 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 memberlist

import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/kubernetes/fake"

queriertest "antrea.io/antrea/pkg/agent/querier/testing"
)

var (
expectedContent = []Response{
{
NodeName: "node1",
IP: "172.16.0.11",
Status: "Alive",
},
{
NodeName: "node2",
IP: "172.16.0.12",
Status: "Alive",
},
{
NodeName: "node3",
IP: "172.16.0.13",
Status: "Dead",
},
}

node1 = v1.Node{
ObjectMeta: metav1.ObjectMeta{Name: "node1"},
Status: v1.NodeStatus{
Addresses: []v1.NodeAddress{
{
Address: "172.16.0.11",
},
},
},
}
node2 = v1.Node{
ObjectMeta: metav1.ObjectMeta{Name: "node2"},
Status: v1.NodeStatus{
Addresses: []v1.NodeAddress{
{
Address: "172.16.0.12",
},
},
},
}
node3 = v1.Node{
ObjectMeta: metav1.ObjectMeta{Name: "node3"},
Status: v1.NodeStatus{
Addresses: []v1.NodeAddress{
{
Address: "172.16.0.13",
},
},
},
}

aliveNodes = sets.NewString("node1", "node2")
)

func TestMemberlistQuery(t *testing.T) {
k8sClient := fake.NewSimpleClientset(&node1, &node2, &node3)
ctrl := gomock.NewController(t)
q := queriertest.NewMockAgentQuerier(ctrl)
q.EXPECT().GetK8sClient().Return(k8sClient)
q.EXPECT().GetAliveNodes().Return(aliveNodes)

handler := HandleFunc(q)

req, err := http.NewRequest(http.MethodGet, "", nil)
require.NoError(t, err)

recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, req)
assert.Equal(t, http.StatusOK, recorder.Code)

var received []Response
err = json.Unmarshal(recorder.Body.Bytes(), &received)
require.NoError(t, err)
assert.Equal(t, expectedContent, received)
}
11 changes: 11 additions & 0 deletions pkg/agent/querier/querier.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ package querier
import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/klog/v2"

"antrea.io/antrea/pkg/agent/config"
"antrea.io/antrea/pkg/agent/interfacestore"
"antrea.io/antrea/pkg/agent/memberlist"
"antrea.io/antrea/pkg/agent/openflow"
"antrea.io/antrea/pkg/agent/proxy"
"antrea.io/antrea/pkg/apis/crd/v1beta1"
Expand All @@ -42,6 +44,7 @@ type AgentQuerier interface {
GetOVSCtlClient() ovsctl.OVSCtlClient
GetProxier() proxy.Proxier
GetNetworkPolicyInfoQuerier() querier.AgentNetworkPolicyInfoQuerier
GetAliveNodes() sets.String
}

type agentQuerier struct {
Expand All @@ -55,6 +58,7 @@ type agentQuerier struct {
networkPolicyInfoQuerier querier.AgentNetworkPolicyInfoQuerier
apiPort int
nplRange string
memberlistCluster *memberlist.Cluster
}

func NewAgentQuerier(
Expand All @@ -68,6 +72,7 @@ func NewAgentQuerier(
networkPolicyInfoQuerier querier.AgentNetworkPolicyInfoQuerier,
apiPort int,
nplRange string,
memberlistCluster *memberlist.Cluster,
) *agentQuerier {
return &agentQuerier{
nodeConfig: nodeConfig,
Expand All @@ -80,9 +85,15 @@ func NewAgentQuerier(
networkPolicyInfoQuerier: networkPolicyInfoQuerier,
apiPort: apiPort,
nplRange: nplRange,
memberlistCluster: memberlistCluster,
}
}

// GetAliveNodes returns alive Nodes in the memberlist cluster.
func (aq agentQuerier) GetAliveNodes() sets.String {
return aq.memberlistCluster.AliveNodes()
}

// GetNodeConfig returns NodeConfig.
func (aq agentQuerier) GetNodeConfig() *config.NodeConfig {
return aq.nodeConfig
Expand Down
17 changes: 16 additions & 1 deletion pkg/agent/querier/testing/mock_querier.go

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

Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ type mockAgentDumper struct {
dumpNetworkPolicyResourcesErr error
dumpHeapPprofErr error
dumpOVSPortsErr error
dumpMemberlistErr error
}

func (d *mockAgentDumper) DumpLog(basedir string) error {
Expand Down Expand Up @@ -252,3 +253,7 @@ func (d *mockAgentDumper) DumpHeapPprof(basedir string) error {
func (d *mockAgentDumper) DumpOVSPorts(basedir string) error {
return d.dumpOVSPortsErr
}

func (d *mockAgentDumper) DumpMemberlist(basedir string) error {
return d.dumpMemberlistErr
}
17 changes: 17 additions & 0 deletions pkg/antctl/antctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"reflect"

"antrea.io/antrea/pkg/agent/apiserver/handlers/agentinfo"
"antrea.io/antrea/pkg/agent/apiserver/handlers/memberlist"
"antrea.io/antrea/pkg/agent/apiserver/handlers/multicast"
"antrea.io/antrea/pkg/agent/apiserver/handlers/ovsflows"
"antrea.io/antrea/pkg/agent/apiserver/handlers/podinterface"
Expand Down Expand Up @@ -568,6 +569,22 @@ $ antctl get podmulticaststats pod -n namespace`,
},
transformedResponse: reflect.TypeOf(serviceexternalip.Response{}),
},
{
use: "memberlist",
aliases: []string{"ml"},
short: "Print state of memberlist cluster",
long: "Print state of memberlist cluster of Antrea agent",
example: ` Get a memberlist
$ antctl get memberlist`,
commandGroup: get,
agentEndpoint: &endpoint{
nonResourceEndpoint: &nonResourceEndpoint{
path: "/memberlist",
outputType: multiple,
},
},
transformedResponse: reflect.TypeOf(memberlist.Response{}),
},
},
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"}, {"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"}, {"supportbundle"}, {"traceflow"}, {"get", "featuregates"}},
},
{
name: "Antctl running against flow-aggregator mode",
Expand Down
1 change: 1 addition & 0 deletions pkg/apiserver/registry/system/supportbundle/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ func (r *supportBundleREST) collectAgent(ctx context.Context, since string) (*sy
dumper.DumpAgentInfo,
dumper.DumpHeapPprof,
dumper.DumpOVSPorts,
dumper.DumpMemberlist,
)
}

Expand Down
Loading

0 comments on commit c61a892

Please sign in to comment.