forked from antrea-io/antrea
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
1 parent
71ba988
commit 114e7c0
Showing
5 changed files
with
795 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} |
Oops, something went wrong.