Skip to content

Commit

Permalink
Merge pull request #45666 from ilackarms/compression
Browse files Browse the repository at this point in the history
Automatic merge from submit-queue

add gzip compression to GET and LIST requests

Fixes #44164



Enable compressed response bodies for non-watch GET and LIST requests on API Objects.

**What this PR does / why we need it**: Adds compression via Accept-Encoding header, returns Content-Encoding header on responses (only supports gzip at this time). Enabled solely for GET and LIST requests which can return very large response bodies. 

**Special notes for your reviewer**:

See #44164 for discussion.

**Release note**:

```release-note-
```
  • Loading branch information
Kubernetes Submit Queue authored Jun 6, 2017
2 parents 0538023 + fc650a5 commit cc568f6
Show file tree
Hide file tree
Showing 6 changed files with 468 additions and 0 deletions.
1 change: 1 addition & 0 deletions staging/src/k8s.io/apiserver/pkg/endpoints/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,6 @@ go_library(
"//vendor/k8s.io/apiserver/pkg/endpoints/metrics:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server/filters:go_default_library",
],
)
168 changes: 168 additions & 0 deletions staging/src/k8s.io/apiserver/pkg/endpoints/apiserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package endpoints

import (
"bytes"
"compress/gzip"
"encoding/json"
"errors"
"fmt"
Expand Down Expand Up @@ -1203,6 +1204,102 @@ func TestRequestsWithInvalidQuery(t *testing.T) {
}
}

func TestListCompresion(t *testing.T) {
testCases := []struct {
url string
namespace string
selfLink string
legacy bool
label string
field string
acceptEncoding string
}{
// list items in a namespace in the path
{
url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/simple",
namespace: "default",
selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/simple",
acceptEncoding: "",
},
{
url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/simple",
namespace: "default",
selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/simple",
acceptEncoding: "gzip",
},
}
for i, testCase := range testCases {
storage := map[string]rest.Storage{}
simpleStorage := SimpleRESTStorage{expectedResourceNamespace: testCase.namespace}
storage["simple"] = &simpleStorage
selfLinker := &setTestSelfLinker{
t: t,
namespace: testCase.namespace,
expectedSet: testCase.selfLink,
}
var handler = handleInternal(storage, admissionControl, selfLinker, nil)
server := httptest.NewServer(handler)
defer server.Close()

req, err := http.NewRequest("GET", server.URL+testCase.url, nil)
if err != nil {
t.Errorf("%d: unexpected error: %v", i, err)
continue
}
// It's necessary to manually set Accept-Encoding here
// to prevent http.DefaultClient from automatically
// decoding responses
req.Header.Set("Accept-Encoding", testCase.acceptEncoding)
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Errorf("%d: unexpected error: %v", i, err)
continue
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("%d: unexpected status: %d from url %s, Expected: %d, %#v", i, resp.StatusCode, testCase.url, http.StatusOK, resp)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Errorf("%d: unexpected error: %v", i, err)
continue
}
t.Logf("%d: body: %s", i, string(body))
continue
}
// TODO: future, restore get links
if !selfLinker.called {
t.Errorf("%d: never set self link", i)
}
if !simpleStorage.namespacePresent {
t.Errorf("%d: namespace not set", i)
} else if simpleStorage.actualNamespace != testCase.namespace {
t.Errorf("%d: %q unexpected resource namespace: %s", i, testCase.url, simpleStorage.actualNamespace)
}
if simpleStorage.requestedLabelSelector == nil || simpleStorage.requestedLabelSelector.String() != testCase.label {
t.Errorf("%d: unexpected label selector: %v", i, simpleStorage.requestedLabelSelector)
}
if simpleStorage.requestedFieldSelector == nil || simpleStorage.requestedFieldSelector.String() != testCase.field {
t.Errorf("%d: unexpected field selector: %v", i, simpleStorage.requestedFieldSelector)
}

var decoder *json.Decoder
if testCase.acceptEncoding == "gzip" {
gzipReader, err := gzip.NewReader(resp.Body)
if err != nil {
t.Fatalf("unexpected error creating gzip reader: %v", err)
}
decoder = json.NewDecoder(gzipReader)
} else {
decoder = json.NewDecoder(resp.Body)
}
var itemOut genericapitesting.SimpleList
err = decoder.Decode(&itemOut)
if err != nil {
t.Errorf("failed to read response body as SimpleList: %v", err)
}
}
}

func TestLogs(t *testing.T) {
handler := handle(map[string]rest.Storage{})
server := httptest.NewServer(handler)
Expand Down Expand Up @@ -1518,6 +1615,77 @@ func TestGet(t *testing.T) {
}
}

func TestGetCompression(t *testing.T) {
storage := map[string]rest.Storage{}
simpleStorage := SimpleRESTStorage{
item: genericapitesting.Simple{
Other: "foo",
},
}
selfLinker := &setTestSelfLinker{
t: t,
expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id",
name: "id",
namespace: "default",
}

storage["simple"] = &simpleStorage
handler := handleLinker(storage, selfLinker)
server := httptest.NewServer(handler)
defer server.Close()

tests := []struct {
acceptEncoding string
}{
{acceptEncoding: ""},
{acceptEncoding: "gzip"},
}

for _, test := range tests {
req, err := http.NewRequest("GET", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/id", nil)
if err != nil {
t.Fatalf("unexpected error cretaing request: %v", err)
}
// It's necessary to manually set Accept-Encoding here
// to prevent http.DefaultClient from automatically
// decoding responses
req.Header.Set("Accept-Encoding", test.acceptEncoding)
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("unexpected response: %#v", resp)
}
var decoder *json.Decoder
if test.acceptEncoding == "gzip" {
gzipReader, err := gzip.NewReader(resp.Body)
if err != nil {
t.Fatalf("unexpected error creating gzip reader: %v", err)
}
decoder = json.NewDecoder(gzipReader)
} else {
decoder = json.NewDecoder(resp.Body)
}
var itemOut genericapitesting.Simple
err = decoder.Decode(&itemOut)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Errorf("unexpected error reading body: %v", err)
}

if itemOut.Name != simpleStorage.item.Name {
t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body))
}
if !selfLinker.called {
t.Errorf("Never set self link")
}
}
}

func TestGetUninitialized(t *testing.T) {
storage := map[string]rest.Storage{}
simpleStorage := SimpleRESTStorage{
Expand Down
3 changes: 3 additions & 0 deletions staging/src/k8s.io/apiserver/pkg/endpoints/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import (
"k8s.io/apiserver/pkg/endpoints/metrics"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
genericfilters "k8s.io/apiserver/pkg/server/filters"
)

const (
Expand Down Expand Up @@ -587,6 +588,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
handler = restfulGetResource(getter, exporter, reqScope)
}
handler = metrics.InstrumentRouteFunc(action.Verb, resource, subresource, handler)
handler = genericfilters.RestfulWithCompression(handler, a.group.Context)
doc := "read the specified " + kind
if hasSubresource {
doc = "read " + subresource + " of the specified " + kind
Expand Down Expand Up @@ -616,6 +618,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
doc = "list " + subresource + " of objects of kind " + kind
}
handler := metrics.InstrumentRouteFunc(action.Verb, resource, subresource, restfulListResource(lister, watcher, reqScope, false, a.minRequestTimeout))
handler = genericfilters.RestfulWithCompression(handler, a.group.Context)
route := ws.GET(action.Path).To(handler).
Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
Expand Down
3 changes: 3 additions & 0 deletions staging/src/k8s.io/apiserver/pkg/server/filters/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ load(
go_test(
name = "go_default_test",
srcs = [
"compression_test.go",
"cors_test.go",
"maxinflight_test.go",
"timeout_test.go",
Expand All @@ -31,6 +32,7 @@ go_test(
go_library(
name = "go_default_library",
srcs = [
"compression.go",
"cors.go",
"doc.go",
"longrunning.go",
Expand All @@ -40,6 +42,7 @@ go_library(
],
tags = ["automanaged"],
deps = [
"//vendor/github.com/emicklei/go-restful:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
Expand Down
Loading

0 comments on commit cc568f6

Please sign in to comment.