Skip to content
This repository was archived by the owner on Feb 8, 2021. It is now read-only.

Commit 8998219

Browse files
committed
Add a method for encoding directly to a io.Writer and use it for HTTPx
1 parent a518a27 commit 8998219

File tree

10 files changed

+112
-49
lines changed

10 files changed

+112
-49
lines changed

hack/benchmark-integration.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,18 @@ cleanup() {
2828
kube::log::status "Benchmark cleanup complete"
2929
}
3030

31+
ARGS="-bench-pods 3000 -bench-tasks 100 -bench-tasks 10"
32+
3133
runTests() {
3234
kube::etcd::start
3335
kube::log::status "Running benchmarks"
34-
KUBE_GOFLAGS="-tags 'benchmark no-docker' -bench . -benchtime 1s -cpu 4" \
36+
KUBE_GOFLAGS="-tags 'benchmark no-docker' -bench . -benchmem -benchtime 1s -cpu 4" \
3537
KUBE_RACE="-race" \
3638
KUBE_TEST_API_VERSIONS="v1" \
3739
KUBE_TIMEOUT="-timeout 10m" \
3840
KUBE_TEST_ETCD_PREFIXES="registry"\
3941
ETCD_CUSTOM_PREFIX="None" \
40-
KUBE_TEST_ARGS="-bench-quiet 0 -bench-pods 30 -bench-tasks 1"\
42+
KUBE_TEST_ARGS="${ARGS}" \
4143
"${KUBE_ROOT}/hack/test-go.sh" test/integration
4244
cleanup
4345
}

pkg/api/meta/restmapper_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package meta
1818

1919
import (
2020
"errors"
21+
"io"
2122
"testing"
2223

2324
"k8s.io/kubernetes/pkg/runtime"
@@ -29,6 +30,10 @@ func (fakeCodec) Encode(runtime.Object) ([]byte, error) {
2930
return []byte{}, nil
3031
}
3132

33+
func (fakeCodec) EncodeToStream(runtime.Object, io.Writer) error {
34+
return nil
35+
}
36+
3237
func (fakeCodec) Decode([]byte) (runtime.Object, error) {
3338
return nil, nil
3439
}

pkg/apiserver/apiserver.go

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -380,24 +380,32 @@ func isPrettyPrint(req *http.Request) bool {
380380

381381
// writeJSON renders an object as JSON to the response.
382382
func writeJSON(statusCode int, codec runtime.Codec, object runtime.Object, w http.ResponseWriter, pretty bool) {
383+
w.Header().Set("Content-Type", "application/json")
384+
// We send the status code before we encode the object, so if we error, the status code stays but there will
385+
// still be an error object. This seems ok, the alternative is to validate the object before
386+
// encoding, but this really should never happen, so it's wasted compute for every API request.
387+
w.WriteHeader(statusCode)
388+
if pretty {
389+
prettyJSON(codec, object, w)
390+
return
391+
}
392+
err := codec.EncodeToStream(object, w)
393+
if err != nil {
394+
errorJSONFatal(err, codec, w)
395+
}
396+
}
397+
398+
func prettyJSON(codec runtime.Codec, object runtime.Object, w http.ResponseWriter) {
399+
formatted := &bytes.Buffer{}
383400
output, err := codec.Encode(object)
384401
if err != nil {
385402
errorJSONFatal(err, codec, w)
386-
return
387403
}
388-
if pretty {
389-
// PR #2243: Pretty-print JSON by default.
390-
formatted := &bytes.Buffer{}
391-
err = json.Indent(formatted, output, "", " ")
392-
if err != nil {
393-
errorJSONFatal(err, codec, w)
394-
return
395-
}
396-
output = formatted.Bytes()
404+
if err := json.Indent(formatted, output, "", " "); err != nil {
405+
errorJSONFatal(err, codec, w)
406+
return
397407
}
398-
w.Header().Set("Content-Type", "application/json")
399-
w.WriteHeader(statusCode)
400-
w.Write(output)
408+
w.Write(formatted.Bytes())
401409
}
402410

403411
// errorJSON renders an error to the response. Returns the HTTP status code of the error.

pkg/apiserver/apiserver_test.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2432,9 +2432,8 @@ func expectApiStatus(t *testing.T, method, url string, data []byte, code int) *u
24322432
return nil
24332433
}
24342434
var status unversioned.Status
2435-
_, err = extractBody(response, &status)
2436-
if err != nil {
2437-
t.Fatalf("unexpected error on %s %s: %v", method, url, err)
2435+
if body, err := extractBody(response, &status); err != nil {
2436+
t.Fatalf("unexpected error on %s %s: %v\nbody:\n%s", method, url, err, body)
24382437
return nil
24392438
}
24402439
if code != response.StatusCode {
@@ -2470,7 +2469,10 @@ func TestWriteJSONDecodeError(t *testing.T) {
24702469
writeJSON(http.StatusOK, codec, &UnregisteredAPIObject{"Undecodable"}, w, false)
24712470
}))
24722471
defer server.Close()
2473-
status := expectApiStatus(t, "GET", server.URL, nil, http.StatusInternalServerError)
2472+
// We send a 200 status code before we encode the object, so we expect OK, but there will
2473+
// still be an error object. This seems ok, the alternative is to validate the object before
2474+
// encoding, but this really should never happen, so it's wasted compute for every API request.
2475+
status := expectApiStatus(t, "GET", server.URL, nil, http.StatusOK)
24742476
if status.Reason != unversioned.StatusReasonUnknown {
24752477
t.Errorf("unexpected reason %#v", status)
24762478
}

pkg/conversion/encode.go

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ limitations under the License.
1717
package conversion
1818

1919
import (
20+
"bytes"
2021
"encoding/json"
2122
"fmt"
23+
"io"
2224
"path"
2325
)
2426

@@ -51,65 +53,79 @@ import (
5153
// config files.
5254
//
5355
func (s *Scheme) EncodeToVersion(obj interface{}, destVersion string) (data []byte, err error) {
56+
buff := &bytes.Buffer{}
57+
if err := s.EncodeToVersionStream(obj, destVersion, buff); err != nil {
58+
return nil, err
59+
}
60+
return buff.Bytes(), nil
61+
}
62+
63+
func (s *Scheme) EncodeToVersionStream(obj interface{}, destVersion string, stream io.Writer) error {
5464
obj = maybeCopy(obj)
5565
v, _ := EnforcePtr(obj) // maybeCopy guarantees a pointer
5666

5767
// Don't encode an object defined in the unversioned package, unless if the
5868
// destVersion is v1, encode it to v1 for backward compatibility.
5969
pkg := path.Base(v.Type().PkgPath())
6070
if pkg == "unversioned" && destVersion != "v1" {
61-
return s.encodeUnversionedObject(obj)
71+
// TODO: convert this to streaming too
72+
data, err := s.encodeUnversionedObject(obj)
73+
if err != nil {
74+
return err
75+
}
76+
_, err = stream.Write(data)
77+
return err
6278
}
6379

6480
if _, registered := s.typeToVersion[v.Type()]; !registered {
65-
return nil, fmt.Errorf("type %v is not registered for %q and it will be impossible to Decode it, therefore Encode will refuse to encode it.", v.Type(), destVersion)
81+
return fmt.Errorf("type %v is not registered for %q and it will be impossible to Decode it, therefore Encode will refuse to encode it.", v.Type(), destVersion)
6682
}
6783

6884
objVersion, objKind, err := s.ObjectVersionAndKind(obj)
6985
if err != nil {
70-
return nil, err
86+
return err
7187
}
7288

7389
// Perform a conversion if necessary.
7490
if objVersion != destVersion {
7591
objOut, err := s.NewObject(destVersion, objKind)
7692
if err != nil {
77-
return nil, err
93+
return err
7894
}
7995
flags, meta := s.generateConvertMeta(objVersion, destVersion, obj)
8096
err = s.converter.Convert(obj, objOut, flags, meta)
8197
if err != nil {
82-
return nil, err
98+
return err
8399
}
84100
obj = objOut
85101
}
86102

87103
// ensure the output object name comes from the destination type
88104
_, objKind, err = s.ObjectVersionAndKind(obj)
89105
if err != nil {
90-
return nil, err
106+
return err
91107
}
92108

93109
// Version and Kind should be set on the wire.
94110
err = s.SetVersionAndKind(destVersion, objKind, obj)
95111
if err != nil {
96-
return nil, err
112+
return err
97113
}
98114

99115
// To add metadata, do some simple surgery on the JSON.
100-
data, err = json.Marshal(obj)
101-
if err != nil {
102-
return nil, err
116+
encoder := json.NewEncoder(stream)
117+
if err := encoder.Encode(obj); err != nil {
118+
return err
103119
}
104120

105121
// Version and Kind should be blank in memory. Reset them, since it's
106122
// possible that we modified a user object and not a copy above.
107123
err = s.SetVersionAndKind("", "", obj)
108124
if err != nil {
109-
return nil, err
125+
return err
110126
}
111127

112-
return data, nil
128+
return nil
113129
}
114130

115131
func (s *Scheme) encodeUnversionedObject(obj interface{}) (data []byte, err error) {

pkg/registry/thirdpartyresourcedata/codec.go

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"bytes"
2121
"encoding/json"
2222
"fmt"
23+
"io"
2324
"strings"
2425

2526
"k8s.io/kubernetes/pkg/api/latest"
@@ -219,40 +220,49 @@ const template = `{
219220
"items": [ %s ]
220221
}`
221222

222-
func encodeToJSON(obj *experimental.ThirdPartyResourceData) ([]byte, error) {
223+
func encodeToJSON(obj *experimental.ThirdPartyResourceData, stream io.Writer) error {
223224
var objOut interface{}
224225
if err := json.Unmarshal(obj.Data, &objOut); err != nil {
225-
return nil, err
226+
return err
226227
}
227228
objMap, ok := objOut.(map[string]interface{})
228229
if !ok {
229-
return nil, fmt.Errorf("unexpected type: %v", objOut)
230+
return fmt.Errorf("unexpected type: %v", objOut)
230231
}
231232
objMap["metadata"] = obj.ObjectMeta
232-
return json.Marshal(objMap)
233+
encoder := json.NewEncoder(stream)
234+
return encoder.Encode(objMap)
235+
}
236+
237+
func (t *thirdPartyResourceDataCodec) Encode(obj runtime.Object) ([]byte, error) {
238+
buff := &bytes.Buffer{}
239+
if err := t.EncodeToStream(obj, buff); err != nil {
240+
return nil, err
241+
}
242+
return buff.Bytes(), nil
233243
}
234244

235-
func (t *thirdPartyResourceDataCodec) Encode(obj runtime.Object) (data []byte, err error) {
245+
func (t *thirdPartyResourceDataCodec) EncodeToStream(obj runtime.Object, stream io.Writer) (err error) {
236246
switch obj := obj.(type) {
237247
case *experimental.ThirdPartyResourceData:
238-
return encodeToJSON(obj)
248+
return encodeToJSON(obj, stream)
239249
case *experimental.ThirdPartyResourceDataList:
240250
// TODO: There must be a better way to do this...
241-
buff := &bytes.Buffer{}
242251
dataStrings := make([]string, len(obj.Items))
243252
for ix := range obj.Items {
244-
data, err := encodeToJSON(&obj.Items[ix])
253+
buff := &bytes.Buffer{}
254+
err := encodeToJSON(&obj.Items[ix], buff)
245255
if err != nil {
246-
return nil, err
256+
return err
247257
}
248-
dataStrings[ix] = string(data)
258+
dataStrings[ix] = buff.String()
249259
}
250-
fmt.Fprintf(buff, template, t.kind+"List", strings.Join(dataStrings, ","))
251-
return buff.Bytes(), nil
260+
fmt.Fprintf(stream, template, t.kind+"List", strings.Join(dataStrings, ","))
261+
return nil
252262
case *unversioned.Status:
253-
return t.delegate.Encode(obj)
263+
return t.delegate.EncodeToStream(obj, stream)
254264
default:
255-
return nil, fmt.Errorf("unexpected object to encode: %#v", obj)
265+
return fmt.Errorf("unexpected object to encode: %#v", obj)
256266
}
257267
}
258268

pkg/runtime/codec.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ limitations under the License.
1717
package runtime
1818

1919
import (
20+
"io"
21+
2022
"k8s.io/kubernetes/pkg/util/yaml"
2123
)
2224

@@ -78,6 +80,10 @@ func (c *codecWrapper) Encode(obj Object) ([]byte, error) {
7880
return c.EncodeToVersion(obj, c.version)
7981
}
8082

83+
func (c *codecWrapper) EncodeToStream(obj Object, stream io.Writer) error {
84+
return c.EncodeToVersionStream(obj, c.version, stream)
85+
}
86+
8187
// TODO: Make this behaviour default when we move everyone away from
8288
// the unversioned types.
8389
//

pkg/runtime/interfaces.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ limitations under the License.
1616

1717
package runtime
1818

19+
import (
20+
"io"
21+
)
22+
1923
// ObjectScheme represents common conversions between formal external API versions
2024
// and the internal Go structs. ObjectScheme is typically used with ObjectCodec to
2125
// transform internal Go structs into serialized versions. There may be many valid
@@ -45,6 +49,7 @@ type Decoder interface {
4549
// Encoder defines methods for serializing API objects into bytes
4650
type Encoder interface {
4751
Encode(obj Object) (data []byte, err error)
52+
EncodeToStream(obj Object, stream io.Writer) error
4853
}
4954

5055
// Codec defines methods for serializing and deserializing API objects.
@@ -67,6 +72,7 @@ type ObjectEncoder interface {
6772
// to a specified output version. An error is returned if the object
6873
// cannot be converted for any reason.
6974
EncodeToVersion(obj Object, outVersion string) ([]byte, error)
75+
EncodeToVersionStream(obj Object, outVersion string, stream io.Writer) error
7076
}
7177

7278
// ObjectConvertor converts an object to a different version.

pkg/runtime/scheme.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package runtime
1919
import (
2020
"encoding/json"
2121
"fmt"
22+
"io"
2223
"net/url"
2324
"reflect"
2425

@@ -434,6 +435,10 @@ func (s *Scheme) EncodeToVersion(obj Object, destVersion string) (data []byte, e
434435
return s.raw.EncodeToVersion(obj, destVersion)
435436
}
436437

438+
func (s *Scheme) EncodeToVersionStream(obj Object, destVersion string, stream io.Writer) error {
439+
return s.raw.EncodeToVersionStream(obj, destVersion, stream)
440+
}
441+
437442
// Decode converts a YAML or JSON string back into a pointer to an api object.
438443
// Deduces the type based upon the APIVersion and Kind fields, which are set
439444
// by Encode. Only versioned objects (APIVersion != "") are accepted. The object

pkg/runtime/scheme_test.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -246,13 +246,16 @@ func TestExtensionMapping(t *testing.T) {
246246
}{
247247
{
248248
&InternalExtensionType{Extension: runtime.EmbeddedObject{Object: &ExtensionA{TestString: "foo"}}},
249-
`{"kind":"ExtensionType","apiVersion":"testExternal","extension":{"kind":"A","testString":"foo"}}`,
249+
`{"kind":"ExtensionType","apiVersion":"testExternal","extension":{"kind":"A","testString":"foo"}}
250+
`,
250251
}, {
251252
&InternalExtensionType{Extension: runtime.EmbeddedObject{Object: &ExtensionB{TestString: "bar"}}},
252-
`{"kind":"ExtensionType","apiVersion":"testExternal","extension":{"kind":"B","testString":"bar"}}`,
253+
`{"kind":"ExtensionType","apiVersion":"testExternal","extension":{"kind":"B","testString":"bar"}}
254+
`,
253255
}, {
254256
&InternalExtensionType{Extension: runtime.EmbeddedObject{Object: nil}},
255-
`{"kind":"ExtensionType","apiVersion":"testExternal","extension":null}`,
257+
`{"kind":"ExtensionType","apiVersion":"testExternal","extension":null}
258+
`,
256259
},
257260
}
258261

@@ -261,7 +264,7 @@ func TestExtensionMapping(t *testing.T) {
261264
if err != nil {
262265
t.Errorf("unexpected error '%v' (%#v)", err, item.obj)
263266
} else if e, a := item.encoded, string(gotEncoded); e != a {
264-
t.Errorf("expected %v, got %v", e, a)
267+
t.Errorf("expected\n%#v\ngot\n%#v\n", e, a)
265268
}
266269

267270
gotDecoded, err := scheme.Decode([]byte(item.encoded))

0 commit comments

Comments
 (0)