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 #4601

Signed-off-by: Kumar Atish <[email protected]>
  • Loading branch information
Atish-iaf committed Feb 9, 2023
1 parent 6d76ddf commit ebb6519
Show file tree
Hide file tree
Showing 16 changed files with 287 additions and 4 deletions.
2 changes: 2 additions & 0 deletions cmd/antrea-agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,8 @@ func run(o *Options) error {
networkPolicyController,
o.config.APIPort,
o.config.NodePortLocal.PortRange,
memberlistCluster,
nodeInformer,
)

agentMonitor := monitor.NewAgentMonitor(crdClient, agentQuerier)
Expand Down
14 changes: 14 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,16 @@ 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

NODE IP STATUS
worker1 172.18.0.4 Alive
worker2 172.18.0.3 Alive
worker3 172.18.0.2 Dead
```
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
76 changes: 76 additions & 0 deletions pkg/agent/apiserver/handlers/memberlist/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// 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"

v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"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, _ := aq.GetNodeInformer().Lister().List(labels.Everything())
aliveNodes := aq.GetAliveNodes()
for _, node := range allNodes {
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
}
85 changes: 85 additions & 0 deletions pkg/agent/apiserver/handlers/memberlist/handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// 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/informers"
"k8s.io/client-go/kubernetes/fake"

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

var (
node1 = v1.Node{
ObjectMeta: metav1.ObjectMeta{Name: "node1"},
Status: v1.NodeStatus{
Addresses: []v1.NodeAddress{
{
Address: "172.16.0.11",
},
},
},
}

expectedContent = []Response{
{
NodeName: "node1",
IP: "172.16.0.11",
Status: "Alive",
},
}
)

func TestMemberlistQuery(t *testing.T) {
clientset := fake.NewSimpleClientset()
informerFactory := informers.NewSharedInformerFactory(clientset, 0)
nodeInformer := informerFactory.Core().V1().Nodes()

stopCh := make(chan struct{})
defer close(stopCh)

informerFactory.Start(stopCh)
informerFactory.WaitForCacheSync(stopCh)
informerFactory.Core().V1().Nodes().Informer().GetIndexer().Add(&node1)

ctrl := gomock.NewController(t)
q := queriertest.NewMockAgentQuerier(ctrl)
q.EXPECT().GetNodeInformer().Return(nodeInformer)
q.EXPECT().GetAliveNodes().Return(sets.NewString("node1"))
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)
}
21 changes: 21 additions & 0 deletions pkg/agent/querier/querier.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@ package querier
import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
coreinformers "k8s.io/client-go/informers/core/v1"
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 +45,8 @@ type AgentQuerier interface {
GetOVSCtlClient() ovsctl.OVSCtlClient
GetProxier() proxy.Proxier
GetNetworkPolicyInfoQuerier() querier.AgentNetworkPolicyInfoQuerier
GetAliveNodes() sets.String
GetNodeInformer() coreinformers.NodeInformer
}

type agentQuerier struct {
Expand All @@ -55,6 +60,8 @@ type agentQuerier struct {
networkPolicyInfoQuerier querier.AgentNetworkPolicyInfoQuerier
apiPort int
nplRange string
memberlistCluster memberlist.Interface
nodeInformer coreinformers.NodeInformer
}

func NewAgentQuerier(
Expand All @@ -68,6 +75,8 @@ func NewAgentQuerier(
networkPolicyInfoQuerier querier.AgentNetworkPolicyInfoQuerier,
apiPort int,
nplRange string,
memberlistCluster memberlist.Interface,
nodeInformer coreinformers.NodeInformer,
) *agentQuerier {
return &agentQuerier{
nodeConfig: nodeConfig,
Expand All @@ -80,9 +89,21 @@ func NewAgentQuerier(
networkPolicyInfoQuerier: networkPolicyInfoQuerier,
apiPort: apiPort,
nplRange: nplRange,
memberlistCluster: memberlistCluster,
nodeInformer: nodeInformer,
}
}

// GetNodeInformer returns NodeInformer.
func (aq agentQuerier) GetNodeInformer() coreinformers.NodeInformer {
return aq.nodeInformer
}

// 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
32 changes: 31 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
}
15 changes: 15 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,20 @@ $ 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",
commandGroup: get,
agentEndpoint: &endpoint{
nonResourceEndpoint: &nonResourceEndpoint{
path: "/memberlist",
outputType: multiple,
},
},
transformedResponse: reflect.TypeOf(memberlist.Response{}),
},
},
rawCommands: []rawCommand{
{
Expand Down
Loading

0 comments on commit ebb6519

Please sign in to comment.