Skip to content

Commit

Permalink
Implemented antctl subcommand: pod-interface (antrea-io#334)
Browse files Browse the repository at this point in the history
* This command is under get group to get pod interface information.
Users can filter the result with "podName" as an arg or "namespace"
as an flag. The default namespace is "default".
* Remove duplicated flag in commandDefinition.
* Turn isSingle field in nonResourceEndpoint to outputType which is an enum,
including default, single and multiple

Co-Authored-By: Antonin Bas <[email protected]>
  • Loading branch information
lzhecheng and antoninbas authored Mar 19, 2020
1 parent 2f7683d commit 3d5e14c
Show file tree
Hide file tree
Showing 14 changed files with 662 additions and 45 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ require (
github.com/evanphx/json-patch v4.5.0+incompatible // indirect
github.com/go-openapi/spec v0.17.2
github.com/gogo/protobuf v1.2.1
github.com/golang/mock v1.3.1
github.com/golang/mock v1.4.1
github.com/golang/protobuf v1.3.2
github.com/google/uuid v1.1.1
github.com/googleapis/gnostic v0.3.1 // indirect
Expand Down
6 changes: 5 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,9 @@ github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4er
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.1 h1:ocYkMQY5RrXTYgXl7ICpV0IXwlEQGwKIsery4gyXa1U=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
Expand Down Expand Up @@ -409,6 +410,7 @@ golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
Expand Down Expand Up @@ -493,6 +495,8 @@ k8s.io/kubernetes v1.13.2/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
k8s.io/utils v0.0.0-20190221042446-c2654d5206da/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0=
k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a h1:2jUDc9gJja832Ftp+QbDV0tVhQHMISFn01els+2ZAcw=
k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/structured-merge-diff v0.0.0-20190302045857-e85c7b244fd2 h1:9r5DY45ef9LtcA6BnkhW8MPV7OKAfbf2AUwUhq3LeRk=
sigs.k8s.io/structured-merge-diff v0.0.0-20190302045857-e85c7b244fd2/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
Expand Down
1 change: 1 addition & 0 deletions hack/update-codegen-dockerized.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ $GOPATH/bin/openapi-gen \
# Generate mocks for testing with mockgen.
MOCKGEN_TARGETS=(
"pkg/agent/cniserver/ipam IPAMDriver"
"pkg/agent/interfacestore InterfaceStore"
"pkg/agent/openflow Client,FlowOperations"
"pkg/ovs/openflow Bridge,Table,Flow,Action,FlowBuilder"
"pkg/ovs/ovsconfig OVSBridgeClient"
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 @@ -28,6 +28,7 @@ import (
"github.com/vmware-tanzu/antrea/pkg/agent/apiserver/handlers/agentinfo"
"github.com/vmware-tanzu/antrea/pkg/agent/apiserver/handlers/appliedtogroup"
"github.com/vmware-tanzu/antrea/pkg/agent/apiserver/handlers/networkpolicy"
"github.com/vmware-tanzu/antrea/pkg/agent/apiserver/handlers/podinterface"
"github.com/vmware-tanzu/antrea/pkg/monitor"
antreaversion "github.com/vmware-tanzu/antrea/pkg/version"
)
Expand All @@ -52,6 +53,7 @@ func (s *agentAPIServer) Run(stopCh <-chan struct{}) error {

func installHandlers(aq monitor.AgentQuerier, npq monitor.AgentNetworkPolicyInfoQuerier, s *genericapiserver.GenericAPIServer) {
s.Handler.NonGoRestfulMux.HandleFunc("/agentinfo", agentinfo.HandleFunc(aq))
s.Handler.NonGoRestfulMux.HandleFunc("/podinterfaces", podinterface.HandleFunc(aq))
s.Handler.NonGoRestfulMux.HandleFunc("/networkpolicies", networkpolicy.HandleFunc(npq))
s.Handler.NonGoRestfulMux.HandleFunc("/appliedtogroups", appliedtogroup.HandleFunc(npq))
s.Handler.NonGoRestfulMux.HandleFunc("/addressgroups", addressgroup.HandleFunc(npq))
Expand Down
74 changes: 74 additions & 0 deletions pkg/agent/apiserver/handlers/podinterface/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2020 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 podinterface

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

"github.com/vmware-tanzu/antrea/pkg/agent/interfacestore"
"github.com/vmware-tanzu/antrea/pkg/monitor"
)

// Response describes the response struct of pod-interface command.
type Response struct {
PodName string `json:"name,omitempty" yaml:"name,omitempty" antctl:"name,The name of the pod"`
PodNamespace string `json:"podNamespace,omitempty" yaml:"podNamespace,omitempty"`
InterfaceName string `json:"interfaceName,omitempty" yaml:"interfaceName,omitempty"`
IP string `json:"ip,omitempty" yaml:"ip,omitempty"`
MAC string `json:"mac,omitempty" yaml:"mac,omitempty"`
PortUUID string `json:"portUUID,omitempty" yaml:"portUUID,omitempty"`
OFPort int32 `json:"ofPort,omitempty" yaml:"ofPort,omitempty"`
ContainerID string `json:"containerID,omitempty" yaml:"containerID,omitempty"`
}

func generateResponse(i *interfacestore.InterfaceConfig) Response {
return Response{
PodName: i.ContainerInterfaceConfig.PodName,
PodNamespace: i.ContainerInterfaceConfig.PodNamespace,
InterfaceName: i.InterfaceName,
IP: i.IP.String(),
MAC: i.MAC.String(),
PortUUID: i.OVSPortConfig.PortUUID,
OFPort: i.OVSPortConfig.OFPort,
ContainerID: i.ContainerInterfaceConfig.ContainerID,
}
}

// HandleFunc returns the function which can handle queries issued by the pod-interface command,
func HandleFunc(aq monitor.AgentQuerier) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
ns := r.URL.Query().Get("namespace")

var pods []Response
for _, v := range aq.GetInterfaceStore().GetInterfacesByType(interfacestore.ContainerInterface) {
podName := (*v.ContainerInterfaceConfig).PodName
podNS := (*v.ContainerInterfaceConfig).PodNamespace
if (len(name) == 0 || name == podName) && (len(ns) == 0 || ns == podNS) {
pods = append(pods, generateResponse(v))
}
}

if len(name) > 0 && len(pods) == 0 {
w.WriteHeader(http.StatusNotFound)
return
}
err := json.NewEncoder(w).Encode(pods)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
}
}
}
244 changes: 244 additions & 0 deletions pkg/agent/apiserver/handlers/podinterface/handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
// Copyright 2020 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 podinterface

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

"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"

"github.com/vmware-tanzu/antrea/pkg/agent/interfacestore"
interfacestoretest "github.com/vmware-tanzu/antrea/pkg/agent/interfacestore/testing"
monitortest "github.com/vmware-tanzu/antrea/pkg/monitor/testing"
)

// There are 3 pod-interfaces:
// Two pod-interfaces have same podName: podNames[0] which are in namespaceA and namespaceB.
// Another pod-interface with podName: podNames[1] is in namespaceA.
var ipStrs = []string{
"192.168.0.0",
"192.168.0.1",
"192.168.0.2",
}

var macStrs = []string{
"00:00:00:00:00:00",
"00:00:00:00:00:01",
"00:00:00:00:00:02",
}

var macs = []net.HardwareAddr{
parseMAC(macStrs[0]),
parseMAC(macStrs[1]),
parseMAC(macStrs[2]),
}

var podNames = []string{
"pod0",
"pod1",
}

var responses = []Response{
{
PodName: podNames[0],
PodNamespace: "namespaceA",
InterfaceName: "interface0",
IP: ipStrs[0],
MAC: macStrs[0],
PortUUID: "portuuid0",
OFPort: 0,
ContainerID: "containerid0",
},
{
PodName: podNames[1],
PodNamespace: "namespaceA",
InterfaceName: "interface1",
IP: ipStrs[1],
MAC: macStrs[1],
PortUUID: "portuuid1",
OFPort: 1,
ContainerID: "containerid1",
},
{
PodName: podNames[0],
PodNamespace: "namespaceB",
InterfaceName: "interface2",
IP: ipStrs[2],
MAC: macStrs[2],
PortUUID: "portuuid2",
OFPort: 2,
ContainerID: "containerid2",
},
}

var testInterfaceConfigs = []*interfacestore.InterfaceConfig{
{
InterfaceName: "interface0",
IP: net.ParseIP(ipStrs[0]),
MAC: macs[0],
OVSPortConfig: &interfacestore.OVSPortConfig{
PortUUID: "portuuid0",
OFPort: 0,
},
ContainerInterfaceConfig: &interfacestore.ContainerInterfaceConfig{
ContainerID: "containerid0",
PodName: podNames[0],
PodNamespace: "namespaceA",
},
},
{
InterfaceName: "interface1",
IP: net.ParseIP(ipStrs[1]),
MAC: macs[1],
OVSPortConfig: &interfacestore.OVSPortConfig{
PortUUID: "portuuid1",
OFPort: 1,
},
ContainerInterfaceConfig: &interfacestore.ContainerInterfaceConfig{
ContainerID: "containerid1",
PodName: podNames[1],
PodNamespace: "namespaceA",
},
},
{
InterfaceName: "interface2",
IP: net.ParseIP(ipStrs[2]),
MAC: macs[2],
OVSPortConfig: &interfacestore.OVSPortConfig{
PortUUID: "portuuid2",
OFPort: 2,
},
ContainerInterfaceConfig: &interfacestore.ContainerInterfaceConfig{
ContainerID: "containerid2",
PodName: podNames[0],
PodNamespace: "namespaceB",
},
},
}

func parseMAC(mac string) net.HardwareAddr {
res, _ := net.ParseMAC(mac)
return res
}

func TestPodInterfaceQuery(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

testcases := map[string]struct {
query string
expectedStatus int
expectedContent []Response
}{
"Hit Pod interface query, namespace provided": {
query: "?name=pod1&&namespace=namespaceA",
expectedStatus: http.StatusOK,
expectedContent: []Response{responses[1]},
},
"Miss Pod interface query, namespace provided": {
query: "?name=pod1&&namespace=namespaceB",
expectedStatus: http.StatusNotFound,
expectedContent: []Response{},
},
"Hit Pod interface list query, namespace not provided": {
query: "?name=pod0",
expectedStatus: http.StatusOK,
expectedContent: []Response{responses[0], responses[2]},
},
"Miss Pod interface list query, namespace not provided": {
query: "?name=pod2",
expectedStatus: http.StatusNotFound,
expectedContent: []Response{},
},
}

for k, tc := range testcases {
i := interfacestoretest.NewMockInterfaceStore(ctrl)
i.EXPECT().GetInterfacesByType(interfacestore.ContainerInterface).Return(testInterfaceConfigs).AnyTimes()

q := monitortest.NewMockAgentQuerier(ctrl)
q.EXPECT().GetInterfaceStore().Return(i).AnyTimes()
handler := HandleFunc(q)

req, err := http.NewRequest(http.MethodGet, tc.query, nil)
assert.Nil(t, err)

recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, req)
assert.Equal(t, tc.expectedStatus, recorder.Code, k)

if tc.expectedStatus == http.StatusOK {
var received []Response
err = json.Unmarshal(recorder.Body.Bytes(), &received)
assert.Nil(t, err)
assert.Equal(t, tc.expectedContent, received)
}
}
}

func TestPodInterfaceListQuery(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

testcases := map[string]struct {
query string
expectedStatus int
expectedContent []Response
}{
"Hit pod interfaces in a namespace list query": {
query: "?name=&&namespace=namespaceA",
expectedStatus: http.StatusOK,
expectedContent: []Response{responses[0], responses[1]},
},
"Miss pod interfaces in a namespaces list query": {
query: "?name=&&namespace=namespaceC",
expectedStatus: http.StatusOK,
expectedContent: []Response(nil),
},
"Hit all pod interfaces in all namespace list query": {
query: "?name=&&namespace=",
expectedStatus: http.StatusOK,
expectedContent: []Response{responses[0], responses[1], responses[2]},
},
}

for k, tc := range testcases {
i := interfacestoretest.NewMockInterfaceStore(ctrl)
i.EXPECT().GetInterfacesByType(interfacestore.ContainerInterface).Return(testInterfaceConfigs).AnyTimes()

q := monitortest.NewMockAgentQuerier(ctrl)
q.EXPECT().GetInterfaceStore().Return(i).AnyTimes()
handler := HandleFunc(q)

req, err := http.NewRequest(http.MethodGet, tc.query, nil)
assert.Nil(t, err)

recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, req)
assert.Equal(t, tc.expectedStatus, recorder.Code, k)

if tc.expectedStatus == http.StatusOK {
var received []Response
err = json.Unmarshal(recorder.Body.Bytes(), &received)
assert.Nil(t, err)
assert.Equal(t, tc.expectedContent, received)
}
}
}
12 changes: 12 additions & 0 deletions pkg/agent/interfacestore/interface_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,18 @@ func (c *interfaceCache) GetContainerInterfaceNum() int {
return num
}

func (c *interfaceCache) GetInterfacesByType(interfaceType InterfaceType) []*InterfaceConfig {
c.RLock()
defer c.RUnlock()
var interfaces []*InterfaceConfig
for _, v := range c.cache {
if v.Type == interfaceType {
interfaces = append(interfaces, v)
}
}
return interfaces
}

func (c *interfaceCache) Len() int {
c.RLock()
defer c.RUnlock()
Expand Down
Loading

0 comments on commit 3d5e14c

Please sign in to comment.