diff --git a/pkg/api/resource/quantity.go b/pkg/api/resource/quantity.go index 5b4a99dbc4b..e9b226caefb 100644 --- a/pkg/api/resource/quantity.go +++ b/pkg/api/resource/quantity.go @@ -83,6 +83,11 @@ import ( // This format is intended to make it difficult to use these numbers without // writing some sort of special handling code in the hopes that that will // cause implementors to also use a fixed point implementation. +// +// +protobuf=true +// +protobuf.embed=QuantityProto +// +protobuf.options.marshal=false +// +protobuf.options.(gogoproto.goproto_stringer)=false type Quantity struct { // Amount is public, so you can manipulate it if the accessor // functions are not sufficient. diff --git a/pkg/api/resource/quantity_proto.go b/pkg/api/resource/quantity_proto.go new file mode 100644 index 00000000000..986d846083d --- /dev/null +++ b/pkg/api/resource/quantity_proto.go @@ -0,0 +1,80 @@ +// +build proto + +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 resource + +import ( + "math/big" + + "speter.net/go/exp/math/dec/inf" +) + +// QuantityProto is a struct that is equivalent to Quantity, but intended for +// protobuf marshalling/unmarshalling. It is generated into a serialization +// that matches Quantity. Do not use in Go structs. +// +// +protobuf=true +type QuantityProto struct { + // The format of the quantity + Format Format + // The scale dimension of the value + Scale int32 + // Bigint is serialized as a raw bytes array + Bigint []byte +} + +// ProtoTime returns the Time as a new ProtoTime value. +func (q *Quantity) QuantityProto() *QuantityProto { + if q == nil { + return &QuantityProto{} + } + p := &QuantityProto{ + Format: q.Format, + } + if q.Amount != nil { + p.Scale = int32(q.Amount.Scale()) + p.Bigint = q.Amount.UnscaledBig().Bytes() + } + return p +} + +// Size implements the protobuf marshalling interface. +func (q *Quantity) Size() (n int) { return q.QuantityProto().Size() } + +// Reset implements the protobuf marshalling interface. +func (q *Quantity) Unmarshal(data []byte) error { + p := QuantityProto{} + if err := p.Unmarshal(data); err != nil { + return err + } + q.Format = p.Format + b := big.NewInt(0) + b.SetBytes(p.Bigint) + q.Amount = inf.NewDecBig(b, inf.Scale(p.Scale)) + return nil +} + +// Marshal implements the protobuf marshalling interface. +func (q *Quantity) Marshal() (data []byte, err error) { + return q.QuantityProto().Marshal() +} + +// MarshalTo implements the protobuf marshalling interface. +func (q *Quantity) MarshalTo(data []byte) (int, error) { + return q.QuantityProto().MarshalTo(data) +} diff --git a/pkg/api/serialization_proto_test.go b/pkg/api/serialization_proto_test.go new file mode 100644 index 00000000000..d74c1865dd3 --- /dev/null +++ b/pkg/api/serialization_proto_test.go @@ -0,0 +1,95 @@ +// +build proto + +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 api_test + +import ( + "encoding/hex" + "math/rand" + "testing" + + "github.com/gogo/protobuf/proto" + "k8s.io/kubernetes/pkg/api" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/api/v1" + _ "k8s.io/kubernetes/pkg/apis/extensions" + _ "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/runtime/protobuf" + "k8s.io/kubernetes/pkg/util" +) + +func init() { + codecsToTest = append(codecsToTest, func(version string, item runtime.Object) (runtime.Codec, error) { + return protobuf.NewCodec(version, api.Scheme, api.Scheme, api.Scheme), nil + }) +} + +func TestProtobufRoundTrip(t *testing.T) { + obj := &v1.Pod{} + apitesting.FuzzerFor(t, "v1", rand.NewSource(benchmarkSeed)).Fuzz(obj) + data, err := obj.Marshal() + if err != nil { + t.Fatal(err) + } + out := &v1.Pod{} + if err := out.Unmarshal(data); err != nil { + t.Fatal(err) + } + if !api.Semantic.Equalities.DeepEqual(out, obj) { + t.Logf("marshal\n%s", hex.Dump(data)) + t.Fatalf("Unmarshal is unequal\n%s", util.ObjectGoPrintSideBySide(out, obj)) + } +} + +func BenchmarkEncodeProtobufGeneratedMarshal(b *testing.B) { + items := benchmarkItems() + width := len(items) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if _, err := items[i%width].Marshal(); err != nil { + b.Fatal(err) + } + } + b.StopTimer() +} + +// BenchmarkDecodeJSON provides a baseline for regular JSON decode performance +func BenchmarkDecodeIntoProtobuf(b *testing.B) { + items := benchmarkItems() + width := len(items) + encoded := make([][]byte, width) + for i := range items { + data, err := (&items[i]).Marshal() + if err != nil { + b.Fatal(err) + } + encoded[i] = data + validate := &v1.Pod{} + if err := proto.Unmarshal(data, validate); err != nil { + b.Fatalf("Failed to unmarshal %d: %v\n%#v", i, err, items[i]) + } + } + + for i := 0; i < b.N; i++ { + obj := v1.Pod{} + if err := proto.Unmarshal(encoded[i%width], &obj); err != nil { + b.Fatal(err) + } + } +} diff --git a/pkg/api/serialization_test.go b/pkg/api/serialization_test.go index e08812fcbe2..467efdce3b6 100644 --- a/pkg/api/serialization_test.go +++ b/pkg/api/serialization_test.go @@ -18,29 +18,35 @@ package api_test import ( "encoding/json" - "math/rand" "reflect" "testing" "github.com/davecgh/go-spew/spew" + flag "github.com/spf13/pflag" + "github.com/ugorji/go/codec" + "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/testapi" apitesting "k8s.io/kubernetes/pkg/api/testing" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/v1" + _ "k8s.io/kubernetes/pkg/apis/extensions" + _ "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/util" "k8s.io/kubernetes/pkg/util/sets" - - _ "k8s.io/kubernetes/pkg/apis/extensions" - _ "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" - - flag "github.com/spf13/pflag" ) var fuzzIters = flag.Int("fuzz-iters", 20, "How many fuzzing iterations to do.") +var codecsToTest = []func(version string, item runtime.Object) (runtime.Codec, error){ + func(version string, item runtime.Object) (runtime.Codec, error) { + return testapi.GetCodecForObject(item) + }, +} + func fuzzInternalObject(t *testing.T, forVersion string, item runtime.Object, seed int64) runtime.Object { apitesting.FuzzerFor(t, forVersion, rand.NewSource(seed)).Fuzz(item) @@ -92,16 +98,22 @@ func roundTripSame(t *testing.T, item runtime.Object, except ...string) { seed := rand.Int63() fuzzInternalObject(t, "", item, seed) - codec, err := testapi.GetCodecForObject(item) - if err != nil { - t.Errorf("unexpected error: %v", err) - return + version := testapi.Default.VersionUnderTest + codecs := []runtime.Codec{} + for _, fn := range codecsToTest { + codec, err := fn(version, item) + if err != nil { + t.Errorf("unable to get codec: %v", err) + return + } + codecs = append(codecs, codec) } - version := testapi.Default.Version() if !set.Has(version) { fuzzInternalObject(t, version, item, seed) - roundTrip(t, codec, item) + for _, codec := range codecs { + roundTrip(t, codec, item) + } } } @@ -163,7 +175,9 @@ func doRoundTripTest(kind string, t *testing.T) { if _, err := meta.TypeAccessor(item); err != nil { t.Fatalf("%q is not a TypeMeta and cannot be tested - add it to nonRoundTrippableTypes: %v", kind, err) } - roundTripSame(t, item, nonRoundTrippableTypesByVersion[kind]...) + if api.Scheme.Recognizes(testapi.Default.VersionUnderTest, kind) { + roundTripSame(t, item, nonRoundTrippableTypesByVersion[kind]...) + } if !nonInternalRoundTrippableTypes.Has(kind) { roundTrip(t, api.Codec, fuzzInternalObject(t, "", item, rand.Int63())) } @@ -247,54 +261,132 @@ func TestUnversionedTypes(t *testing.T) { const benchmarkSeed = 100 -func BenchmarkEncode(b *testing.B) { - pod := api.Pod{} +func benchmarkItems() []v1.Pod { apiObjectFuzzer := apitesting.FuzzerFor(nil, "", rand.NewSource(benchmarkSeed)) - apiObjectFuzzer.Fuzz(&pod) - for i := 0; i < b.N; i++ { - testapi.Default.Codec().Encode(&pod) + items := make([]v1.Pod, 2) + for i := range items { + apiObjectFuzzer.Fuzz(&items[i]) } + return items } -// BenchmarkEncodeJSON provides a baseline for regular JSON encode performance -func BenchmarkEncodeJSON(b *testing.B) { - pod := api.Pod{} - apiObjectFuzzer := apitesting.FuzzerFor(nil, "", rand.NewSource(benchmarkSeed)) - apiObjectFuzzer.Fuzz(&pod) +// BenchmarkEncodeCodec measures the cost of performing a codec encode, which includes +// reflection (to clear APIVersion and Kind) +func BenchmarkEncodeCodec(b *testing.B) { + items := benchmarkItems() + width := len(items) + b.ResetTimer() for i := 0; i < b.N; i++ { - json.Marshal(&pod) + if _, err := testapi.Default.Codec().Encode(&items[i%width]); err != nil { + b.Fatal(err) + } } + b.StopTimer() } -func BenchmarkDecode(b *testing.B) { - pod := api.Pod{} - apiObjectFuzzer := apitesting.FuzzerFor(nil, "", rand.NewSource(benchmarkSeed)) - apiObjectFuzzer.Fuzz(&pod) - data, _ := testapi.Default.Codec().Encode(&pod) +// BenchmarkEncodeJSONMarshal provides a baseline for regular JSON encode performance +func BenchmarkEncodeJSONMarshal(b *testing.B) { + items := benchmarkItems() + width := len(items) + b.ResetTimer() for i := 0; i < b.N; i++ { - testapi.Default.Codec().Decode(data) + if _, err := json.Marshal(&items[i%width]); err != nil { + b.Fatal(err) + } } + b.StopTimer() } -func BenchmarkDecodeInto(b *testing.B) { - pod := api.Pod{} - apiObjectFuzzer := apitesting.FuzzerFor(nil, "", rand.NewSource(benchmarkSeed)) - apiObjectFuzzer.Fuzz(&pod) - data, _ := testapi.Default.Codec().Encode(&pod) - for i := 0; i < b.N; i++ { - obj := api.Pod{} - testapi.Default.Codec().DecodeInto(data, &obj) +func BenchmarkDecodeCodec(b *testing.B) { + codec := testapi.Default.Codec() + items := benchmarkItems() + width := len(items) + encoded := make([][]byte, width) + for i := range items { + data, err := codec.Encode(&items[i]) + if err != nil { + b.Fatal(err) + } + encoded[i] = data } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + if _, err := codec.Decode(encoded[i%width]); err != nil { + b.Fatal(err) + } + } + b.StopTimer() +} + +func BenchmarkDecodeIntoCodec(b *testing.B) { + codec := testapi.Default.Codec() + items := benchmarkItems() + width := len(items) + encoded := make([][]byte, width) + for i := range items { + data, err := codec.Encode(&items[i]) + if err != nil { + b.Fatal(err) + } + encoded[i] = data + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + obj := v1.Pod{} + if err := codec.DecodeInto(encoded[i%width], &obj); err != nil { + b.Fatal(err) + } + } + b.StopTimer() } // BenchmarkDecodeJSON provides a baseline for regular JSON decode performance -func BenchmarkDecodeJSON(b *testing.B) { - pod := api.Pod{} - apiObjectFuzzer := apitesting.FuzzerFor(nil, "", rand.NewSource(benchmarkSeed)) - apiObjectFuzzer.Fuzz(&pod) - data, _ := testapi.Default.Codec().Encode(&pod) - for i := 0; i < b.N; i++ { - obj := api.Pod{} - json.Unmarshal(data, &obj) +func BenchmarkDecodeIntoJSON(b *testing.B) { + codec := testapi.Default.Codec() + items := benchmarkItems() + width := len(items) + encoded := make([][]byte, width) + for i := range items { + data, err := codec.Encode(&items[i]) + if err != nil { + b.Fatal(err) + } + encoded[i] = data } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + obj := v1.Pod{} + if err := json.Unmarshal(encoded[i%width], &obj); err != nil { + b.Fatal(err) + } + } + b.StopTimer() +} + +// BenchmarkDecodeJSON provides a baseline for codecgen JSON decode performance +func BenchmarkDecodeIntoJSONCodecGen(b *testing.B) { + kcodec := testapi.Default.Codec() + items := benchmarkItems() + width := len(items) + encoded := make([][]byte, width) + for i := range items { + data, err := kcodec.Encode(&items[i]) + if err != nil { + b.Fatal(err) + } + encoded[i] = data + } + handler := &codec.JsonHandle{} + + b.ResetTimer() + for i := 0; i < b.N; i++ { + obj := v1.Pod{} + if err := codec.NewDecoderBytes(encoded[i%width], handler).Decode(&obj); err != nil { + b.Fatal(err) + } + } + b.StopTimer() } diff --git a/pkg/api/unversioned/duration.go b/pkg/api/unversioned/duration.go index d9209f2c3e1..6ff634ee525 100644 --- a/pkg/api/unversioned/duration.go +++ b/pkg/api/unversioned/duration.go @@ -43,5 +43,5 @@ func (d *Duration) UnmarshalJSON(b []byte) error { // MarshalJSON implements the json.Marshaler interface. func (d Duration) MarshalJSON() ([]byte, error) { - return json.Marshal(d.String()) + return json.Marshal(d.Duration.String()) } diff --git a/pkg/api/unversioned/group_version.go b/pkg/api/unversioned/group_version.go index ccb61d0be8b..9ee8387c27d 100644 --- a/pkg/api/unversioned/group_version.go +++ b/pkg/api/unversioned/group_version.go @@ -40,6 +40,8 @@ func (gvr *GroupVersionResource) String() string { // GroupKind specifies a Group and a Kind, but does not force a version. This is useful for identifying // concepts during lookup stages without having partially valid types +// +// +protobuf.options.(gogoproto.goproto_stringer)=false type GroupKind struct { Group string Kind string @@ -55,6 +57,8 @@ func (gk *GroupKind) String() string { // GroupVersionKind unambiguously identifies a kind. It doesn't anonymously include GroupVersion // to avoid automatic coersion. It doesn't use a GroupVersion to avoid custom marshalling +// +// +protobuf.options.(gogoproto.goproto_stringer)=false type GroupVersionKind struct { Group string Version string @@ -79,6 +83,8 @@ func (gvk *GroupVersionKind) String() string { } // GroupVersion contains the "group" and the "version", which uniquely identifies the API. +// +// +protobuf.options.(gogoproto.goproto_stringer)=false type GroupVersion struct { Group string Version string diff --git a/pkg/api/unversioned/time.go b/pkg/api/unversioned/time.go index bc403e55dcc..4072833d965 100644 --- a/pkg/api/unversioned/time.go +++ b/pkg/api/unversioned/time.go @@ -26,8 +26,10 @@ import ( // Time is a wrapper around time.Time which supports correct // marshaling to YAML and JSON. Wrappers are provided for many // of the factory methods that the time package offers. +// +// +protobuf.options.marshal=false type Time struct { - time.Time + time.Time `protobuf:"Timestamp,1,req,name=time"` } // NewTime returns a wrapped instance of the provided time diff --git a/pkg/api/unversioned/time_proto.go b/pkg/api/unversioned/time_proto.go new file mode 100644 index 00000000000..6d6fe5d4e7b --- /dev/null +++ b/pkg/api/unversioned/time_proto.go @@ -0,0 +1,80 @@ +// +build proto + +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + "time" +) + +// ProtoTime is a struct that is equivalent to Time, but intended for +// protobuf marshalling/unmarshalling. It is generated into a serialization +// that matches Time. Do not use in Go structs. +type ProtoTime struct { + // Represents the time of an event. + Timestamp Timestamp `json:"timestamp"` +} + +// Timestamp is a protobuf Timestamp compatible representation of time.Time +type Timestamp struct { + // Represents seconds of UTC time since Unix epoch + // 1970-01-01T00:00:00Z. Must be from from 0001-01-01T00:00:00Z to + // 9999-12-31T23:59:59Z inclusive. + Seconds int64 `json:"seconds"` + // Non-negative fractions of a second at nanosecond resolution. Negative + // second values with fractions must still have non-negative nanos values + // that count forward in time. Must be from 0 to 999,999,999 + // inclusive. + Nanos int32 `json:"nanos"` +} + +// ProtoTime returns the Time as a new ProtoTime value. +func (m *Time) ProtoTime() *ProtoTime { + if m == nil { + return &ProtoTime{} + } + return &ProtoTime{ + Timestamp: Timestamp{ + Seconds: m.Time.Unix(), + Nanos: int32(m.Time.Nanosecond()), + }, + } +} + +// Size implements the protobuf marshalling interface. +func (m *Time) Size() (n int) { return m.ProtoTime().Size() } + +// Reset implements the protobuf marshalling interface. +func (m *Time) Unmarshal(data []byte) error { + p := ProtoTime{} + if err := p.Unmarshal(data); err != nil { + return err + } + m.Time = time.Unix(p.Timestamp.Seconds, int64(p.Timestamp.Nanos)) + return nil +} + +// Marshal implements the protobuf marshalling interface. +func (m *Time) Marshal() (data []byte, err error) { + return m.ProtoTime().Marshal() +} + +// MarshalTo implements the protobuf marshalling interface. +func (m *Time) MarshalTo(data []byte) (int, error) { + return m.ProtoTime().MarshalTo(data) +} diff --git a/pkg/api/unversioned/types.go b/pkg/api/unversioned/types.go index 596ca56ac51..33bcc35d15b 100644 --- a/pkg/api/unversioned/types.go +++ b/pkg/api/unversioned/types.go @@ -311,6 +311,8 @@ func (*APIResourceList) IsAnAPIObject() {} // APIVersions lists the versions that are available, to allow clients to // discover the API at /api, which is the root path of the legacy v1 API. +// +// +protobuf.options.(gogoproto.goproto_stringer)=false type APIVersions struct { TypeMeta `json:",inline"` // versions are the api versions that are available. diff --git a/pkg/runtime/protobuf/doc.go b/pkg/runtime/protobuf/doc.go new file mode 100644 index 00000000000..33316d0c4d8 --- /dev/null +++ b/pkg/runtime/protobuf/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 protobuf implements ProtoBuf serialization and deserialization. +package protobuf diff --git a/pkg/runtime/protobuf/protobuf.go b/pkg/runtime/protobuf/protobuf.go new file mode 100644 index 00000000000..cc050a50b23 --- /dev/null +++ b/pkg/runtime/protobuf/protobuf.go @@ -0,0 +1,158 @@ +// +build proto + +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 protobuf + +import ( + "fmt" + "io" + "net/url" + "reflect" + + "github.com/gogo/protobuf/proto" + + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/runtime" +) + +// NewCodec +func NewCodec(version string, creater runtime.ObjectCreater, typer runtime.ObjectTyper, convertor runtime.ObjectConvertor) runtime.Codec { + return &codec{ + version: version, + creater: creater, + typer: typer, + convertor: convertor, + } +} + +// codec decodes protobuf objects +type codec struct { + version string + outputVersion string + creater runtime.ObjectCreater + typer runtime.ObjectTyper + convertor runtime.ObjectConvertor +} + +var _ runtime.Codec = codec{} + +func (c codec) Decode(data []byte) (runtime.Object, error) { + unknown := &runtime.Unknown{} + if err := proto.Unmarshal(data, unknown); err != nil { + return nil, err + } + obj, err := c.creater.New(unknown.APIVersion, unknown.Kind) + if err != nil { + return nil, err + } + pobj, ok := obj.(proto.Message) + if !ok { + return nil, fmt.Errorf("runtime object is not a proto.Message: %v", reflect.TypeOf(obj)) + } + if err := proto.Unmarshal(unknown.RawJSON, pobj); err != nil { + return nil, err + } + if unknown.APIVersion != c.outputVersion { + out, err := c.convertor.ConvertToVersion(obj, c.outputVersion) + if err != nil { + return nil, err + } + obj = out + } + return obj, nil +} + +func (c codec) DecodeToVersion(data []byte, version unversioned.GroupVersion) (runtime.Object, error) { + return nil, fmt.Errorf("unimplemented") +} + +func (c codec) DecodeInto(data []byte, obj runtime.Object) error { + version, kind, err := c.typer.ObjectVersionAndKind(obj) + if err != nil { + return err + } + unknown := &runtime.Unknown{} + if err := proto.Unmarshal(data, unknown); err != nil { + return err + } + if unknown.APIVersion == version && unknown.Kind == kind { + pobj, ok := obj.(proto.Message) + if !ok { + return fmt.Errorf("runtime object is not a proto.Message: %v", reflect.TypeOf(obj)) + } + + return proto.Unmarshal(unknown.RawJSON, pobj) + } + + versioned, err := c.creater.New(unknown.APIVersion, unknown.Kind) + if err != nil { + return err + } + + pobj, ok := versioned.(proto.Message) + if !ok { + return fmt.Errorf("runtime object is not a proto.Message: %v", reflect.TypeOf(obj)) + } + + if err := proto.Unmarshal(unknown.RawJSON, pobj); err != nil { + return err + } + return c.convertor.Convert(versioned, obj) +} + +func (c codec) DecodeIntoWithSpecifiedVersionKind(data []byte, obj runtime.Object, kind unversioned.GroupVersionKind) error { + return fmt.Errorf("unimplemented") +} + +func (c codec) DecodeParametersInto(parameters url.Values, obj runtime.Object) error { + return fmt.Errorf("unimplemented") +} + +func (c codec) Encode(obj runtime.Object) (data []byte, err error) { + version, kind, err := c.typer.ObjectVersionAndKind(obj) + if err != nil { + return nil, err + } + if len(version) == 0 { + version = c.version + converted, err := c.convertor.ConvertToVersion(obj, version) + if err != nil { + return nil, err + } + obj = converted + } + m, ok := obj.(proto.Marshaler) + if !ok { + return nil, fmt.Errorf("object %v (kind: %s in version: %s) does not implement ProtoBuf marshalling", reflect.TypeOf(obj), kind, c.version) + } + b, err := m.Marshal() + if err != nil { + return nil, err + } + return (&runtime.Unknown{ + TypeMeta: runtime.TypeMeta{ + Kind: kind, + APIVersion: version, + }, + RawJSON: b, + }).Marshal() +} + +func (c codec) EncodeToStream(obj runtime.Object, stream io.Writer) error { + return fmt.Errorf("unimplemented") +} diff --git a/pkg/runtime/types.go b/pkg/runtime/types.go index 5f6b792a2cc..fcc26526e6b 100644 --- a/pkg/runtime/types.go +++ b/pkg/runtime/types.go @@ -30,6 +30,7 @@ package runtime // TypeMeta is provided here for convenience. You may use it directly from this package or define // your own with the same fields. // +// +protobuf=true type TypeMeta struct { APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` @@ -98,6 +99,8 @@ type EmbeddedObject struct { // JSON stored in RawExtension, turning it into the correct object type, and storing it // in the EmbeddedObject. (TODO: In the case where the object is of an unknown type, a // runtime.Unknown object will be created and stored.) +// +// +protobuf=true type RawExtension struct { RawJSON []byte } @@ -107,6 +110,8 @@ type RawExtension struct { // TypeMeta features-- kind, version, etc. // TODO: Make this object have easy access to field based accessors and settors for // metadata and field mutatation. +// +// +protobuf=true type Unknown struct { TypeMeta `json:",inline"` // RawJSON will hold the complete JSON of the object which couldn't be matched diff --git a/pkg/util/intstr/intstr.go b/pkg/util/intstr/intstr.go index 6a4eabd8a8c..033d1d1724e 100644 --- a/pkg/util/intstr/intstr.go +++ b/pkg/util/intstr/intstr.go @@ -29,6 +29,9 @@ import ( // inner type. This allows you to have, for example, a JSON field that can // accept a name or number. // TODO: Rename to Int32OrString +// +// +protobuf=true +// +protobuf.options.(gogoproto.goproto_stringer)=false type IntOrString struct { Type Type IntVal int32