Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

apiserver: add validating admission tests #55228

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
45 changes: 36 additions & 9 deletions staging/src/k8s.io/apiserver/pkg/admission/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,22 +39,23 @@ func (h *FakeHandler) Admit(a Attributes) (err error) {
}

func (h *FakeHandler) Validate(a Attributes) (err error) {
h.admitCalled = true
if h.admit {
h.validateCalled = true
if h.validate {
return nil
}
return fmt.Errorf("Don't admit")
return fmt.Errorf("Don't validate")
}

func makeHandler(name string, admit bool, ops ...Operation) Interface {
func makeHandler(name string, accept bool, ops ...Operation) Interface {
return &FakeHandler{
name: name,
admit: admit,
Handler: NewHandler(ops...),
name: name,
admit: accept,
validate: accept,
Handler: NewHandler(ops...),
}
}

func TestAdmit(t *testing.T) {
func TestAdmitAndValidate(t *testing.T) {
tests := []struct {
name string
operation Operation
Expand Down Expand Up @@ -108,6 +109,7 @@ func TestAdmit(t *testing.T) {
},
}
for _, test := range tests {
// call admit and check that validate was not called at all
err := test.chain.Admit(NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, "", "", schema.GroupVersionResource{}, "", test.operation, nil))
accepted := (err == nil)
if accepted != test.accept {
Expand All @@ -117,9 +119,34 @@ func TestAdmit(t *testing.T) {
fake := h.(*FakeHandler)
_, shouldBeCalled := test.calls[fake.name]
if shouldBeCalled != fake.admitCalled {
t.Errorf("%s: handler %s not called as expected: %v", test.name, fake.name, fake.admitCalled)
t.Errorf("%s: admit handler %s not called as expected: %v", test.name, fake.name, fake.admitCalled)
continue
}
if fake.validateCalled {
t.Errorf("%s: validate handler %s called during admit", test.name, fake.name)
}

// reset value for validation test
fake.admitCalled = false
}

// call validate and check that admit was not called at all
err = test.chain.Validate(NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, "", "", schema.GroupVersionResource{}, "", test.operation, nil))
accepted = (err == nil)
if accepted != test.accept {
t.Errorf("%s: unexpected result of validate call: %v\n", test.name, accepted)
}
for _, h := range test.chain {
fake := h.(*FakeHandler)
_, shouldBeCalled := test.calls[fake.name]
if shouldBeCalled != fake.validateCalled {
t.Errorf("%s: validate handler %s not called as expected: %v", test.name, fake.name, fake.validateCalled)
continue
}

if fake.admitCalled {
t.Errorf("%s: admit handler %s called during admit", test.name, fake.name)
}
}
}
}
Expand Down
215 changes: 121 additions & 94 deletions staging/src/k8s.io/apiserver/pkg/endpoints/apiserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,23 @@ func (alwaysAdmit) Handles(operation admission.Operation) bool {
return true
}

type alwaysDeny struct{}
type alwaysMutatingDeny struct{}

func (alwaysDeny) Admit(a admission.Attributes) (err error) {
return admission.NewForbidden(a, errors.New("Admission control is denying all modifications"))
func (alwaysMutatingDeny) Admit(a admission.Attributes) (err error) {
return admission.NewForbidden(a, errors.New("Mutating admission control is denying all modifications"))
}

func (alwaysDeny) Handles(operation admission.Operation) bool {
func (alwaysMutatingDeny) Handles(operation admission.Operation) bool {
return true
}

type alwaysValidatingDeny struct{}

func (alwaysValidatingDeny) Validate(a admission.Attributes) (err error) {
return admission.NewForbidden(a, errors.New("Validating admission control is denying all modifications"))
}

func (alwaysValidatingDeny) Handles(operation admission.Operation) bool {
return true
}

Expand Down Expand Up @@ -258,11 +268,6 @@ func handle(storage map[string]rest.Storage) http.Handler {
return handleInternal(storage, admissionControl, selfLinker, nil)
}

// tests with a deny admission controller
func handleDeny(storage map[string]rest.Storage) http.Handler {
return handleInternal(storage, alwaysDeny{}, selfLinker, nil)
}

// tests using the new namespace scope mechanism
func handleNamespaced(storage map[string]rest.Storage) http.Handler {
return handleInternal(storage, admissionControl, selfLinker, nil)
Expand Down Expand Up @@ -529,6 +534,9 @@ func (storage *SimpleRESTStorage) Create(ctx request.Context, obj runtime.Object
if storage.injectedFunction != nil {
obj, err = storage.injectedFunction(obj)
}
if err := createValidation(obj); err != nil {
return nil, err
}
return obj, err
}

Expand All @@ -545,6 +553,9 @@ func (storage *SimpleRESTStorage) Update(ctx request.Context, name string, objIn
if storage.injectedFunction != nil {
obj, err = storage.injectedFunction(obj)
}
if err := updateValidation(&storage.item, obj); err != nil {
return nil, false, err
}
return obj, false, err
}

Expand Down Expand Up @@ -725,6 +736,9 @@ func (storage *NamedCreaterRESTStorage) Create(ctx request.Context, name string,
if storage.injectedFunction != nil {
obj, err = storage.injectedFunction(obj)
}
if err := createValidation(obj); err != nil {
return nil, err
}
return obj, err
}

Expand Down Expand Up @@ -2813,22 +2827,27 @@ func TestLegacyDeleteIgnoresOptions(t *testing.T) {
}

func TestDeleteInvokesAdmissionControl(t *testing.T) {
storage := map[string]rest.Storage{}
simpleStorage := SimpleRESTStorage{}
ID := "id"
storage["simple"] = &simpleStorage
handler := handleDeny(storage)
server := httptest.NewServer(handler)
defer server.Close()
// TODO: remove mutating deny when we removed it from the endpoint implementation and ported all plugins
for _, admit := range []admission.Interface{alwaysMutatingDeny{}, alwaysValidatingDeny{}} {
t.Logf("Testing %T", admit)

client := http.Client{}
request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, nil)
response, err := client.Do(request)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if response.StatusCode != http.StatusForbidden {
t.Errorf("Unexpected response %#v", response)
storage := map[string]rest.Storage{}
simpleStorage := SimpleRESTStorage{}
ID := "id"
storage["simple"] = &simpleStorage
handler := handleInternal(storage, admit, selfLinker, nil)
server := httptest.NewServer(handler)
defer server.Close()

client := http.Client{}
request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, nil)
response, err := client.Do(request)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if response.StatusCode != http.StatusForbidden {
t.Errorf("Unexpected response %#v", response)
}
}
}

Expand Down Expand Up @@ -2971,38 +2990,42 @@ func TestUpdate(t *testing.T) {
}

func TestUpdateInvokesAdmissionControl(t *testing.T) {
storage := map[string]rest.Storage{}
simpleStorage := SimpleRESTStorage{}
ID := "id"
storage["simple"] = &simpleStorage
handler := handleDeny(storage)
server := httptest.NewServer(handler)
defer server.Close()
for _, admit := range []admission.Interface{alwaysMutatingDeny{}, alwaysValidatingDeny{}} {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure these are checking what we want. We need to make sure that generic store calls admission. This is ensuring that our mock calls it. It's good that our mock calls it, but this is just checking the mock, not the actual code, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not on this level. The generic store has its own tests which are more e2e.

t.Logf("Testing %T", admit)

item := &genericapitesting.Simple{
ObjectMeta: metav1.ObjectMeta{
Name: ID,
Namespace: metav1.NamespaceDefault,
},
Other: "bar",
}
body, err := runtime.Encode(testCodec, item)
if err != nil {
// The following cases will fail, so die now
t.Fatalf("unexpected error: %v", err)
}
storage := map[string]rest.Storage{}
simpleStorage := SimpleRESTStorage{}
ID := "id"
storage["simple"] = &simpleStorage
handler := handleInternal(storage, admit, selfLinker, nil)
server := httptest.NewServer(handler)
defer server.Close()

client := http.Client{}
request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body))
response, err := client.Do(request)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
dump, _ := httputil.DumpResponse(response, true)
t.Log(string(dump))
item := &genericapitesting.Simple{
ObjectMeta: metav1.ObjectMeta{
Name: ID,
Namespace: metav1.NamespaceDefault,
},
Other: "bar",
}
body, err := runtime.Encode(testCodec, item)
if err != nil {
// The following cases will fail, so die now
t.Fatalf("unexpected error: %v", err)
}

if response.StatusCode != http.StatusForbidden {
t.Errorf("Unexpected response %#v", response)
client := http.Client{}
request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body))
response, err := client.Do(request)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
dump, _ := httputil.DumpResponse(response, true)
t.Log(string(dump))

if response.StatusCode != http.StatusForbidden {
t.Errorf("Unexpected response %#v", response)
}
}
}

Expand Down Expand Up @@ -3602,49 +3625,53 @@ func TestCreateInNamespace(t *testing.T) {
}
}

func TestCreateInvokesAdmissionControl(t *testing.T) {
storage := SimpleRESTStorage{
injectedFunction: func(obj runtime.Object) (runtime.Object, error) {
time.Sleep(5 * time.Millisecond)
return obj, nil
},
}
selfLinker := &setTestSelfLinker{
t: t,
name: "bar",
namespace: "other",
expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/foo/bar",
}
handler := handleInternal(map[string]rest.Storage{"foo": &storage}, alwaysDeny{}, selfLinker, nil)
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{}
func TestCreateInvokeAdmissionControl(t *testing.T) {
for _, admit := range []admission.Interface{alwaysMutatingDeny{}, alwaysValidatingDeny{}} {
t.Logf("Testing %T", admit)

simple := &genericapitesting.Simple{
Other: "bar",
}
data, err := runtime.Encode(testCodec, simple)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/other/foo", bytes.NewBuffer(data))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
storage := SimpleRESTStorage{
injectedFunction: func(obj runtime.Object) (runtime.Object, error) {
time.Sleep(5 * time.Millisecond)
return obj, nil
},
}
selfLinker := &setTestSelfLinker{
t: t,
name: "bar",
namespace: "other",
expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/foo/bar",
}
handler := handleInternal(map[string]rest.Storage{"foo": &storage}, admit, selfLinker, nil)
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{}

wg := sync.WaitGroup{}
wg.Add(1)
var response *http.Response
go func() {
response, err = client.Do(request)
wg.Done()
}()
wg.Wait()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if response.StatusCode != http.StatusForbidden {
t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusForbidden, response)
simple := &genericapitesting.Simple{
Other: "bar",
}
data, err := runtime.Encode(testCodec, simple)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/other/foo", bytes.NewBuffer(data))
if err != nil {
t.Errorf("unexpected error: %v", err)
}

wg := sync.WaitGroup{}
wg.Add(1)
var response *http.Response
go func() {
response, err = client.Do(request)
wg.Done()
}()
wg.Wait()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if response.StatusCode != http.StatusForbidden {
t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusForbidden, response)
}
}
}

Expand Down