Skip to content

Commit

Permalink
Implements antctl query endpoint
Browse files Browse the repository at this point in the history
Antctl query endpoint gives users the ability to filter network policies
relevant to a certain endpoint. The user is able to query internal
controller stores using the antrea controller apiserver through antctl.

Queries are answered by iterating over AddressGroups and
AppliedToGroups. While we considered adding Indexers to their respective
stores to speed-up queries, we decided that it was not sufficient to
justify the memory overhead observed in some cases.

Note, antctl query endpoint only works when run in the controller.
We are working on getting it to work out-of-cluster.

Fixes antrea-io#974
  • Loading branch information
jakesokol1 authored and GraysonWu committed Sep 23, 2020
1 parent 71ba988 commit 114e7c0
Show file tree
Hide file tree
Showing 5 changed files with 795 additions and 1 deletion.
233 changes: 233 additions & 0 deletions pkg/apiserver/handlers/handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
// 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 handlers

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

"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/admission/v1"
"k8s.io/apimachinery/pkg/api/errors"

"github.com/vmware-tanzu/antrea/pkg/apiserver/handlers/endpoint"
"github.com/vmware-tanzu/antrea/pkg/controller/networkpolicy"
queriermock "github.com/vmware-tanzu/antrea/pkg/controller/networkpolicy/testing"
)

type TestCase struct {
// query arguments sent to handler function
handlerRequest string
expectedStatus int
// expected result written by handler function
expectedContent response

// arguments of call to mock
argsMock []string
// results of call to mock
mockQueryResponse response
}

type response struct {
response *networkpolicy.EndpointQueryResponse
error error
}

var responses = []response{
{
response: &networkpolicy.EndpointQueryResponse{Endpoints: nil},
error: errors.NewNotFound(v1.Resource("pod"), "pod"),
},
{
response: &networkpolicy.EndpointQueryResponse{Endpoints: []networkpolicy.Endpoint{
{
Policies: []networkpolicy.Policy{
{
PolicyRef: networkpolicy.PolicyRef{Name: "policy1"},
},
},
},
},
},
error: nil,
},
{
response: &networkpolicy.EndpointQueryResponse{Endpoints: []networkpolicy.Endpoint{
{
Policies: []networkpolicy.Policy{
{
PolicyRef: networkpolicy.PolicyRef{Name: "policy1"},
},
{
PolicyRef: networkpolicy.PolicyRef{Name: "policy2"},
},
},
},
},
},
error: nil,
},
}

// TestIncompleteArguments tests how the handler function responds when the user passes in a query command
// with incomplete arguments (for now, missing pod or namespace)
func TestIncompleteArguments(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
// sample selector arguments (right now, only supports podname and namespace)
namespace := "namespace"
// outline test cases with expected behavior
testCases := map[string]TestCase{
"Responds with error given no name and no namespace": {
handlerRequest: "",
expectedStatus: http.StatusBadRequest,
argsMock: []string{"", ""},
},
"Responds with error given no name": {
handlerRequest: "?namespace=namespace",
expectedStatus: http.StatusBadRequest,
argsMock: []string{namespace, ""},
},
}

evaluateTestCases(testCases, mockCtrl, t)

}

// TestInvalidArguments tests how the handler function responds when the user passes in a selector which does not select
// any existing endpoint
func TestInvalidArguments(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
// sample selector arguments (right now, only supports podname and namespace)
pod, namespace := "pod", "namespace"
// outline test cases with expected behavior
testCases := map[string]TestCase{
"Responds with error given no invalid selection": {
handlerRequest: "?namespace=namespace&pod=pod",
expectedStatus: http.StatusNotFound,
argsMock: []string{namespace, pod},
mockQueryResponse: response{
response: nil,
error: nil,
},
},
}

evaluateTestCases(testCases, mockCtrl, t)

}

// TestSinglePolicyResponse tests how the handler function responds when the user passes in a endpoint with a
// single policy response
func TestSinglePolicyResponse(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
// sample selector arguments (right now, only supports podName and namespace)
pod, namespace := "pod", "namespace"
// outline test cases with expected behavior
testCases := map[string]TestCase{
"Responds with list of single element": {
handlerRequest: "?namespace=namespace&pod=pod",
expectedStatus: http.StatusOK,
expectedContent: responses[1],
argsMock: []string{namespace, pod},
mockQueryResponse: response{
response: &networkpolicy.EndpointQueryResponse{Endpoints: []networkpolicy.Endpoint{
{
Policies: []networkpolicy.Policy{
{
PolicyRef: networkpolicy.PolicyRef{Name: "policy1"},
},
},
},
},
},
error: nil,
},
},
}

evaluateTestCases(testCases, mockCtrl, t)

}

// TestMultiPolicyResponse tests how the handler function responds when the user passes in a endpoint with
// multiple policy responses
func TestMultiPolicyResponse(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
// sample selector arguments (right now, only supports podName and namespace)
pod, namespace := "pod", "namespace"
// outline test cases with expected behavior
testCases := map[string]TestCase{
"Responds with list of single element": {
handlerRequest: "?namespace=namespace&pod=pod",
expectedStatus: http.StatusOK,
expectedContent: responses[2],
argsMock: []string{namespace, pod},
mockQueryResponse: response{
response: &networkpolicy.EndpointQueryResponse{Endpoints: []networkpolicy.Endpoint{
{
Policies: []networkpolicy.Policy{
{
PolicyRef: networkpolicy.PolicyRef{Name: "policy1"},
},
{
PolicyRef: networkpolicy.PolicyRef{Name: "policy2"},
},
},
},
},
},
error: nil,
},
},
}

evaluateTestCases(testCases, mockCtrl, t)

}

func evaluateTestCases(testCases map[string]TestCase, mockCtrl *gomock.Controller, t *testing.T) {
for _, tc := range testCases {
// create mock querier with expected behavior outlined in testCase
mockQuerier := queriermock.NewMockEndpointQuerier(mockCtrl)
if tc.expectedStatus != http.StatusBadRequest {
mockQuerier.EXPECT().QueryNetworkPolicies(tc.argsMock[0], tc.argsMock[1]).Return(tc.mockQueryResponse.response, tc.mockQueryResponse.error)
}
// initialize handler with mockQuerier
handler := endpoint.HandleFunc(mockQuerier)
// create http using handlerArgs and serve the http request
req, err := http.NewRequest(http.MethodGet, tc.handlerRequest, nil)
assert.Nil(t, err)
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, req)
assert.Equal(t, tc.expectedStatus, recorder.Code)
if tc.expectedStatus != http.StatusOK {
return
}
// check response is expected
var received networkpolicy.EndpointQueryResponse
err = json.Unmarshal(recorder.Body.Bytes(), &received)
assert.Nil(t, err)
for i, policy := range tc.expectedContent.response.Endpoints[0].Policies {
assert.Equal(t, policy.Name, received.Endpoints[0].Policies[i].Name)
}
}
}
Loading

0 comments on commit 114e7c0

Please sign in to comment.