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 14, 2023
1 parent 97e790b commit 187eff5
Show file tree
Hide file tree
Showing 20 changed files with 509 additions and 116 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: 1 addition & 1 deletion hack/update-codegen-dockerized.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ MOCKGEN_TARGETS=(
"pkg/agent/cniserver/ipam IPAMDriver testing"
"pkg/agent/flowexporter/connections ConnTrackDumper,NetFilterConnTrack testing"
"pkg/agent/interfacestore InterfaceStore testing"
"pkg/agent/memberlist Memberlist testing"
"pkg/agent/memberlist Interface,Memberlist . mock_memberlist.go"
"pkg/agent/multicast RouteInterface testing"
"pkg/agent/types McastNetworkPolicyController testing"
"pkg/agent/nodeportlocal/portcache LocalPortOpener testing"
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.GetMemberlistCluster().AliveNodes()
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
}
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"
"sort"
"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"

"antrea.io/antrea/pkg/agent/memberlist"
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",
},
},
},
}
node2 = v1.Node{
ObjectMeta: metav1.ObjectMeta{Name: "node2"},
Status: v1.NodeStatus{
Addresses: []v1.NodeAddress{
{
Address: "172.16.0.12",
},
},
},
}
)

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)
informerFactory.Core().V1().Nodes().Informer().GetIndexer().Add(&node2)

ctrl := gomock.NewController(t)
q := queriertest.NewMockAgentQuerier(ctrl)
memberlistInterface := memberlist.NewMockInterface(ctrl)
q.EXPECT().GetNodeInformer().Return(nodeInformer)
q.EXPECT().GetMemberlistCluster().Return(memberlistInterface)
memberlistInterface.EXPECT().AliveNodes().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)

expectedResponse := []Response{
{
NodeName: "node1",
IP: "172.16.0.11",
Status: "Alive",
},
{
NodeName: "node2",
IP: "172.16.0.12",
Status: "Dead",
},
}
var received []Response
err = json.Unmarshal(recorder.Body.Bytes(), &received)
require.NoError(t, err)
sort.Slice(received, func(i, j int) bool {
return received[i].NodeName < received[j].NodeName
})
assert.Equal(t, expectedResponse, received)
}
7 changes: 3 additions & 4 deletions pkg/agent/memberlist/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import (

"antrea.io/antrea/pkg/agent/config"
"antrea.io/antrea/pkg/agent/consistenthash"
memberlisttest "antrea.io/antrea/pkg/agent/memberlist/testing"
"antrea.io/antrea/pkg/apis"
crdv1a2 "antrea.io/antrea/pkg/apis/crd/v1alpha2"
fakeversioned "antrea.io/antrea/pkg/client/clientset/versioned/fake"
Expand Down Expand Up @@ -148,7 +147,7 @@ func TestCluster_Run(t *testing.T) {
Name: localNodeName,
NodeIPv4Addr: &net.IPNet{IP: net.IPv4(127, 0, 0, 1), Mask: net.IPv4Mask(255, 255, 255, 255)},
}
mockMemberlist := memberlisttest.NewMockMemberlist(controller)
mockMemberlist := NewMockMemberlist(controller)
fakeCluster, err := newFakeCluster(nodeConfig, stopCh, mockMemberlist)
if err != nil {
t.Fatalf("New fake memberlist server error: %v", err)
Expand Down Expand Up @@ -204,7 +203,7 @@ func TestCluster_RunClusterEvents(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{Name: "fakeEgress1", UID: "fakeUID1"},
Spec: crdv1a2.EgressSpec{ExternalIPPool: fakeEIP1.Name, EgressIP: "1.1.1.2"},
}
mockMemberlist := memberlisttest.NewMockMemberlist(controller)
mockMemberlist := NewMockMemberlist(controller)
fakeCluster, err := newFakeCluster(nodeConfig, stopCh, mockMemberlist)
if err != nil {
t.Fatalf("New fake memberlist server error: %v", err)
Expand Down Expand Up @@ -662,7 +661,7 @@ func TestCluster_RejoinNodes(t *testing.T) {
defer close(stopCh)
controller := gomock.NewController(t)
defer controller.Finish()
mockMemberlist := memberlisttest.NewMockMemberlist(controller)
mockMemberlist := NewMockMemberlist(controller)
mockMemberlist.EXPECT().Join([]string{"10.0.0.2"})
mockMemberlist.EXPECT().Join([]string{"10.0.0.3"})
fakeCluster, _ := newFakeCluster(localNodeConfig, stopCh, mockMemberlist, node1, node2, node3)
Expand Down
Loading

0 comments on commit 187eff5

Please sign in to comment.