diff --git a/.travis.yml b/.travis.yml index a88fbb53374..8ce19f688f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ install: - ./hack/build-go.sh script: - - ./hack/test-go.sh + - KUBE_TIMEOUT='-timeout 60s' ./hack/test-go.sh - PATH=$HOME/gopath/bin:./third_party/etcd/bin:$PATH ./hack/test-cmd.sh - PATH=$HOME/gopath/bin:./third_party/etcd/bin:$PATH ./hack/test-integration.sh diff --git a/cmd/integration/integration.go b/cmd/integration/integration.go index 9ec635336a3..18c2fcdb868 100644 --- a/cmd/integration/integration.go +++ b/cmd/integration/integration.go @@ -29,6 +29,7 @@ import ( "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/controller" @@ -206,7 +207,7 @@ func runAtomicPutTest(c *client.Client) { var svc api.Service err := c.Post().Path("services").Body( &api.Service{ - JSONBase: api.JSONBase{ID: "atomicservice", APIVersion: "v1beta1"}, + JSONBase: api.JSONBase{ID: "atomicservice", APIVersion: latest.Version}, Port: 12345, Labels: map[string]string{ "name": "atomicService", diff --git a/cmd/kubecfg/kubecfg.go b/cmd/kubecfg/kubecfg.go index 2c58374e15f..83b45c99d61 100644 --- a/cmd/kubecfg/kubecfg.go +++ b/cmd/kubecfg/kubecfg.go @@ -30,6 +30,7 @@ import ( "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubecfg" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" @@ -129,7 +130,7 @@ func readConfig(storage string) []byte { glog.Fatal("Need config file (-c)") } - data, err := parser.ToWireFormat(readConfigData(), storage, runtime.DefaultCodec) + data, err := parser.ToWireFormat(readConfigData(), storage, latest.Codec) if err != nil { glog.Fatalf("Error parsing %v as an object for %v: %v\n", *config, storage, err) @@ -296,7 +297,7 @@ func executeAPIRequest(method string, c *client.Client) bool { if setBody { if version != 0 { data := readConfig(storage) - obj, err := runtime.DefaultCodec.Decode(data) + obj, err := latest.Codec.Decode(data) if err != nil { glog.Fatalf("error setting resource version: %v", err) } @@ -305,7 +306,7 @@ func executeAPIRequest(method string, c *client.Client) bool { glog.Fatalf("error setting resource version: %v", err) } jsonBase.SetResourceVersion(version) - data, err = runtime.DefaultCodec.Encode(obj) + data, err = latest.Codec.Encode(obj) if err != nil { glog.Fatalf("error setting resource version: %v", err) } diff --git a/examples/examples_test.go b/examples/examples_test.go index 3abb905cf3b..1f300762d34 100644 --- a/examples/examples_test.go +++ b/examples/examples_test.go @@ -25,7 +25,7 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/golang/glog" @@ -103,7 +103,7 @@ func TestApiExamples(t *testing.T) { return } tested += 1 - if err := runtime.DefaultCodec.DecodeInto(data, expectedType); err != nil { + if err := latest.Codec.DecodeInto(data, expectedType); err != nil { t.Errorf("%s did not decode correctly: %v\n%s", path, err, string(data)) return } @@ -137,7 +137,7 @@ func TestExamples(t *testing.T) { return } tested += 1 - if err := runtime.DefaultCodec.DecodeInto(data, expectedType); err != nil { + if err := latest.Codec.DecodeInto(data, expectedType); err != nil { t.Errorf("%s did not decode correctly: %v\n%s", path, err, string(data)) return } @@ -168,14 +168,14 @@ func TestReadme(t *testing.T) { } for _, json := range match[1:] { expectedType := &api.Pod{} - if err := runtime.DefaultCodec.DecodeInto([]byte(json), expectedType); err != nil { + if err := latest.Codec.DecodeInto([]byte(json), expectedType); err != nil { t.Errorf("%s did not decode correctly: %v\n%s", path, err, string(data)) return } if errors := validateObject(expectedType); len(errors) > 0 { t.Errorf("%s did not validate correctly: %v", path, errors) } - encoded, err := runtime.DefaultCodec.Encode(expectedType) + encoded, err := latest.Codec.Encode(expectedType) if err != nil { t.Errorf("Could not encode object: %v", err) continue diff --git a/pkg/api/conversion.go b/pkg/api/conversion.go new file mode 100644 index 00000000000..36ba27675bc --- /dev/null +++ b/pkg/api/conversion.go @@ -0,0 +1,55 @@ +/* +Copyright 2014 Google Inc. 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 + +import ( + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" +) + +// Codec is the identity codec for this package - it can only convert itself +// to itself. +var Codec = runtime.CodecFor(Scheme, "") + +// EmbeddedObject implements a Codec specific version of an +// embedded object. +type EmbeddedObject struct { + runtime.Object +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (a *EmbeddedObject) UnmarshalJSON(b []byte) error { + obj, err := runtime.CodecUnmarshalJSON(Codec, b) + a.Object = obj + return err +} + +// MarshalJSON implements the json.Marshaler interface. +func (a EmbeddedObject) MarshalJSON() ([]byte, error) { + return runtime.CodecMarshalJSON(Codec, a.Object) +} + +// SetYAML implements the yaml.Setter interface. +func (a *EmbeddedObject) SetYAML(tag string, value interface{}) bool { + obj, ok := runtime.CodecSetYAML(Codec, tag, value) + a.Object = obj + return ok +} + +// GetYAML implements the yaml.Getter interface. +func (a EmbeddedObject) GetYAML() (tag string, value interface{}) { + return runtime.CodecGetYAML(Codec, a.Object) +} diff --git a/pkg/api/latest/doc.go b/pkg/api/latest/doc.go new file mode 100644 index 00000000000..bb76f61a036 --- /dev/null +++ b/pkg/api/latest/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2014 Google Inc. 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 latest defines the default output serializations that code should +// use and imports the required schemas. It also ensures all previously known +// and supported API versions are available for conversion. +package latest diff --git a/pkg/api/latest/latest.go b/pkg/api/latest/latest.go new file mode 100644 index 00000000000..2c835101b7f --- /dev/null +++ b/pkg/api/latest/latest.go @@ -0,0 +1,37 @@ +/* +Copyright 2014 Google Inc. 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 latest + +import ( + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" + _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" +) + +// Version is the string that represents the current external default version +var Version = "v1beta1" + +// Codec is the default codec for serializing output that should use +// the latest supported version. Use this Codec when writing to +// disk, a data store that is not dynamically versioned, or in tests. +// This codec can decode any object that Kubernetes is aware of. +var Codec = v1beta1.Codec + +// ResourceVersioner describes a default versioner that can handle all types +// of versioning. +// TODO: when versioning changes, make this part of each API definition. +var ResourceVersioner = runtime.NewJSONBaseResourceVersioner() diff --git a/pkg/api/latest/latest_test.go b/pkg/api/latest/latest_test.go new file mode 100644 index 00000000000..997d536bc36 --- /dev/null +++ b/pkg/api/latest/latest_test.go @@ -0,0 +1,146 @@ +/* +Copyright 2014 Google Inc. 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 latest + +import ( + "encoding/json" + "reflect" + "testing" + + internal "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" + _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" + "github.com/fsouza/go-dockerclient" + "github.com/google/gofuzz" +) + +// apiObjectFuzzer can randomly populate api objects. +var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs( + func(j *internal.JSONBase, c fuzz.Continue) { + // We have to customize the randomization of JSONBases because their + // APIVersion and Kind must remain blank in memory. + j.APIVersion = "" + j.Kind = "" + j.ID = c.RandString() + // TODO: Fix JSON/YAML packages and/or write custom encoding + // for uint64's. Somehow the LS *byte* of this is lost, but + // only when all 8 bytes are set. + j.ResourceVersion = c.RandUint64() >> 8 + j.SelfLink = c.RandString() + + var sec, nsec int64 + c.Fuzz(&sec) + c.Fuzz(&nsec) + j.CreationTimestamp = util.Unix(sec, nsec).Rfc3339Copy() + }, + func(intstr *util.IntOrString, c fuzz.Continue) { + // util.IntOrString will panic if its kind is set wrong. + if c.RandBool() { + intstr.Kind = util.IntstrInt + intstr.IntVal = int(c.RandUint64()) + intstr.StrVal = "" + } else { + intstr.Kind = util.IntstrString + intstr.IntVal = 0 + intstr.StrVal = c.RandString() + } + }, + func(u64 *uint64, c fuzz.Continue) { + // TODO: uint64's are NOT handled right. + *u64 = c.RandUint64() >> 8 + }, + func(pb map[docker.Port][]docker.PortBinding, c fuzz.Continue) { + // This is necessary because keys with nil values get omitted. + // TODO: Is this a bug? + pb[docker.Port(c.RandString())] = []docker.PortBinding{ + {c.RandString(), c.RandString()}, + {c.RandString(), c.RandString()}, + } + }, + func(pm map[string]docker.PortMapping, c fuzz.Continue) { + // This is necessary because keys with nil values get omitted. + // TODO: Is this a bug? + pm[c.RandString()] = docker.PortMapping{ + c.RandString(): c.RandString(), + } + }, +) + +func TestInternalRoundTrip(t *testing.T) { + latest := "v1beta2" + + for k, _ := range internal.Scheme.KnownTypes("") { + obj, err := internal.Scheme.New("", k) + if err != nil { + t.Errorf("%s: unexpected error: %v", k, err) + continue + } + apiObjectFuzzer.Fuzz(obj) + + newer, err := internal.Scheme.New(latest, k) + if err != nil { + t.Errorf("%s: unexpected error: %v", k, err) + continue + } + + if err := internal.Scheme.Convert(obj, newer); err != nil { + t.Errorf("unable to convert %#v to %#v: %v", obj, newer, err) + } + + actual, err := internal.Scheme.New("", k) + if err != nil { + t.Errorf("%s: unexpected error: %v", k, err) + continue + } + + if err := internal.Scheme.Convert(newer, actual); err != nil { + t.Errorf("unable to convert %#v to %#v: %v", newer, actual, err) + } + + if !reflect.DeepEqual(obj, actual) { + t.Errorf("%s: diff %s", k, runtime.ObjectDiff(obj, actual)) + } + } +} + +func TestResourceVersioner(t *testing.T) { + pod := internal.Pod{JSONBase: internal.JSONBase{ResourceVersion: 10}} + version, err := ResourceVersioner.ResourceVersion(&pod) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if version != 10 { + t.Errorf("unexpected version %d", version) + } +} + +func TestCodec(t *testing.T) { + pod := internal.Pod{} + data, err := Codec.Encode(&pod) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + other := internal.Pod{} + if err := json.Unmarshal(data, &other); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if other.APIVersion != Version || other.Kind != "Pod" { + t.Errorf("unexpected unmarshalled object %#v", other) + } +} diff --git a/pkg/api/register.go b/pkg/api/register.go index 909bb906d2f..6935cb1b538 100644 --- a/pkg/api/register.go +++ b/pkg/api/register.go @@ -20,8 +20,10 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" ) +var Scheme = runtime.NewScheme() + func init() { - runtime.DefaultScheme.AddKnownTypes("", + Scheme.AddKnownTypes("", &PodList{}, &Pod{}, &ReplicationControllerList{}, diff --git a/pkg/api/serialization_test.go b/pkg/api/serialization_test.go index d77d0c9d567..3203eccc2fe 100644 --- a/pkg/api/serialization_test.go +++ b/pkg/api/serialization_test.go @@ -17,21 +17,21 @@ limitations under the License. package api_test import ( - "encoding/json" "flag" - "fmt" "reflect" "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/fsouza/go-dockerclient" "github.com/google/gofuzz" ) -var fuzzIters = flag.Int("fuzz_iters", 50, "How many fuzzing iterations to do.") +var fuzzIters = flag.Int("fuzz_iters", 40, "How many fuzzing iterations to do.") // apiObjectFuzzer can randomly populate api objects. var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs( @@ -105,27 +105,7 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs( }, ) -func objDiff(a, b runtime.Object) string { - ab, err := json.Marshal(a) - if err != nil { - panic("a") - } - bb, err := json.Marshal(b) - if err != nil { - panic("b") - } - return util.StringDiff(string(ab), string(bb)) - - // An alternate diff attempt, in case json isn't showing you - // the difference. (reflect.DeepEqual makes a distinction between - // nil and empty slices, for example.) - return util.StringDiff( - fmt.Sprintf("%#v", a), - fmt.Sprintf("%#v", b), - ) -} - -func runTest(t *testing.T, source runtime.Object) { +func runTest(t *testing.T, codec runtime.Codec, source runtime.Object) { name := reflect.TypeOf(source).Elem().Name() apiObjectFuzzer.Fuzz(source) j, err := runtime.FindJSONBase(source) @@ -135,30 +115,30 @@ func runTest(t *testing.T, source runtime.Object) { j.SetKind("") j.SetAPIVersion("") - data, err := runtime.DefaultCodec.Encode(source) + data, err := codec.Encode(source) if err != nil { t.Errorf("%v: %v (%#v)", name, err, source) return } - obj2, err := runtime.DefaultCodec.Decode(data) + obj2, err := codec.Decode(data) if err != nil { t.Errorf("%v: %v", name, err) return } else { if !reflect.DeepEqual(source, obj2) { - t.Errorf("1: %v: diff: %v", name, objDiff(source, obj2)) + t.Errorf("1: %v: diff: %v", name, runtime.ObjectDiff(source, obj2)) return } } obj3 := reflect.New(reflect.TypeOf(source).Elem()).Interface().(runtime.Object) - err = runtime.DefaultCodec.DecodeInto(data, obj3) + err = codec.DecodeInto(data, obj3) if err != nil { t.Errorf("2: %v: %v", name, err) return } else { if !reflect.DeepEqual(source, obj3) { - t.Errorf("3: %v: diff: %v", name, objDiff(source, obj3)) + t.Errorf("3: %v: diff: %v", name, runtime.ObjectDiff(source, obj3)) return } } @@ -184,7 +164,9 @@ func TestTypes(t *testing.T) { for _, item := range table { // Try a few times, since runTest uses random values. for i := 0; i < *fuzzIters; i++ { - runTest(t, item) + runTest(t, v1beta1.Codec, item) + runTest(t, v1beta2.Codec, item) + runTest(t, api.Codec, item) } } } @@ -194,8 +176,8 @@ func TestEncode_Ptr(t *testing.T) { Labels: map[string]string{"name": "foo"}, } obj := runtime.Object(pod) - data, err := runtime.DefaultCodec.Encode(obj) - obj2, err2 := runtime.DefaultCodec.Decode(data) + data, err := latest.Codec.Encode(obj) + obj2, err2 := latest.Codec.Decode(data) if err != nil || err2 != nil { t.Fatalf("Failure: '%v' '%v'", err, err2) } @@ -209,11 +191,11 @@ func TestEncode_Ptr(t *testing.T) { func TestBadJSONRejection(t *testing.T) { badJSONMissingKind := []byte(`{ }`) - if _, err := runtime.DefaultCodec.Decode(badJSONMissingKind); err == nil { + if _, err := latest.Codec.Decode(badJSONMissingKind); err == nil { t.Errorf("Did not reject despite lack of kind field: %s", badJSONMissingKind) } badJSONUnknownType := []byte(`{"kind": "bar"}`) - if _, err1 := runtime.DefaultCodec.Decode(badJSONUnknownType); err1 == nil { + if _, err1 := latest.Codec.Decode(badJSONUnknownType); err1 == nil { t.Errorf("Did not reject despite use of unknown type: %s", badJSONUnknownType) } /*badJSONKindMismatch := []byte(`{"kind": "Pod"}`) diff --git a/pkg/api/types.go b/pkg/api/types.go index 50b3b1b85ce..ec1419b6e7a 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -17,9 +17,7 @@ limitations under the License. package api import ( - "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" - "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" "github.com/fsouza/go-dockerclient" ) @@ -623,13 +621,3 @@ type ServerOpList struct { } func (*ServerOpList) IsAnAPIObject() {} - -// WatchEvent objects are streamed from the api server in response to a watch request. -type WatchEvent struct { - // The type of the watch event; added, modified, or deleted. - Type watch.EventType - - // For added or modified objects, this is the new object; for deleted objects, - // it's the state of the object immediately prior to its deletion. - Object runtime.EmbeddedObject -} diff --git a/pkg/api/v1beta1/conversion.go b/pkg/api/v1beta1/conversion.go index d83c001c2d9..14bb3956b60 100644 --- a/pkg/api/v1beta1/conversion.go +++ b/pkg/api/v1beta1/conversion.go @@ -17,14 +17,13 @@ limitations under the License. package v1beta1 import ( - // Alias this so it can be easily changed when we cut the next version. newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/conversion" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" ) func init() { - runtime.DefaultScheme.AddConversionFuncs( + newer.Scheme.AddConversionFuncs( // EnvVar's Key is deprecated in favor of Name. func(in *newer.EnvVar, out *EnvVar, s conversion.Scope) error { out.Value = in.Value @@ -81,3 +80,33 @@ func init() { ) } + +// EmbeddedObject implements a Codec specific version of an +// embedded object. +type EmbeddedObject struct { + runtime.Object +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (a *EmbeddedObject) UnmarshalJSON(b []byte) error { + obj, err := runtime.CodecUnmarshalJSON(Codec, b) + a.Object = obj + return err +} + +// MarshalJSON implements the json.Marshaler interface. +func (a EmbeddedObject) MarshalJSON() ([]byte, error) { + return runtime.CodecMarshalJSON(Codec, a.Object) +} + +// SetYAML implements the yaml.Setter interface. +func (a *EmbeddedObject) SetYAML(tag string, value interface{}) bool { + obj, ok := runtime.CodecSetYAML(Codec, tag, value) + a.Object = obj + return ok +} + +// GetYAML implements the yaml.Getter interface. +func (a EmbeddedObject) GetYAML() (tag string, value interface{}) { + return runtime.CodecGetYAML(Codec, a.Object) +} diff --git a/pkg/api/v1beta1/conversion_test.go b/pkg/api/v1beta1/conversion_test.go index 2c151445578..18f68918eeb 100644 --- a/pkg/api/v1beta1/conversion_test.go +++ b/pkg/api/v1beta1/conversion_test.go @@ -22,10 +22,9 @@ import ( newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" - "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" ) -var Convert = runtime.DefaultScheme.Convert +var Convert = newer.Scheme.Convert func TestEnvConversion(t *testing.T) { nonCanonical := []v1beta1.EnvVar{ diff --git a/pkg/api/v1beta1/register.go b/pkg/api/v1beta1/register.go index 7dd8f83c559..980aa7fc005 100644 --- a/pkg/api/v1beta1/register.go +++ b/pkg/api/v1beta1/register.go @@ -17,11 +17,15 @@ limitations under the License. package v1beta1 import ( + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" ) +// Codec encodes internal objects to the v1beta1 scheme +var Codec = runtime.CodecFor(api.Scheme, "v1beta1") + func init() { - runtime.DefaultScheme.AddKnownTypes("v1beta1", + api.Scheme.AddKnownTypes("v1beta1", &PodList{}, &Pod{}, &ReplicationControllerList{}, diff --git a/pkg/api/v1beta1/types.go b/pkg/api/v1beta1/types.go index 1dba02b5f47..b85fec96707 100644 --- a/pkg/api/v1beta1/types.go +++ b/pkg/api/v1beta1/types.go @@ -17,9 +17,7 @@ limitations under the License. package v1beta1 import ( - "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" - "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" "github.com/fsouza/go-dockerclient" ) @@ -625,13 +623,3 @@ type ServerOpList struct { } func (*ServerOpList) IsAnAPIObject() {} - -// WatchEvent objects are streamed from the api server in response to a watch request. -type WatchEvent struct { - // The type of the watch event; added, modified, or deleted. - Type watch.EventType - - // For added or modified objects, this is the new object; for deleted objects, - // it's the state of the object immediately prior to its deletion. - Object runtime.EmbeddedObject -} diff --git a/pkg/api/v1beta2/conversion.go b/pkg/api/v1beta2/conversion.go new file mode 100644 index 00000000000..fa7c913a0e1 --- /dev/null +++ b/pkg/api/v1beta2/conversion.go @@ -0,0 +1,111 @@ +/* +Copyright 2014 Google Inc. 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 v1beta2 + +import ( + newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/conversion" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" +) + +func init() { + newer.Scheme.AddConversionFuncs( + // EnvVar's Key is deprecated in favor of Name. + func(in *newer.EnvVar, out *EnvVar, s conversion.Scope) error { + out.Value = in.Value + out.Key = in.Name + out.Name = in.Name + return nil + }, + func(in *EnvVar, out *newer.EnvVar, s conversion.Scope) error { + out.Value = in.Value + if in.Name != "" { + out.Name = in.Name + } else { + out.Name = in.Key + } + return nil + }, + + // Path & MountType are deprecated. + func(in *newer.VolumeMount, out *VolumeMount, s conversion.Scope) error { + out.Name = in.Name + out.ReadOnly = in.ReadOnly + out.MountPath = in.MountPath + out.Path = in.MountPath + out.MountType = "" // MountType is ignored. + return nil + }, + func(in *VolumeMount, out *newer.VolumeMount, s conversion.Scope) error { + out.Name = in.Name + out.ReadOnly = in.ReadOnly + if in.MountPath == "" { + out.MountPath = in.Path + } else { + out.MountPath = in.MountPath + } + return nil + }, + + // MinionList.Items had a wrong name in v1beta1 + func(in *newer.MinionList, out *MinionList, s conversion.Scope) error { + s.Convert(&in.JSONBase, &out.JSONBase, 0) + s.Convert(&in.Items, &out.Items, 0) + out.Minions = out.Items + return nil + }, + func(in *MinionList, out *newer.MinionList, s conversion.Scope) error { + s.Convert(&in.JSONBase, &out.JSONBase, 0) + if len(in.Items) == 0 { + s.Convert(&in.Minions, &out.Items, 0) + } else { + s.Convert(&in.Items, &out.Items, 0) + } + return nil + }, + ) +} + +// EmbeddedObject implements a Codec specific version of an +// embedded object. +type EmbeddedObject struct { + runtime.Object +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (a *EmbeddedObject) UnmarshalJSON(b []byte) error { + obj, err := runtime.CodecUnmarshalJSON(Codec, b) + a.Object = obj + return err +} + +// MarshalJSON implements the json.Marshaler interface. +func (a EmbeddedObject) MarshalJSON() ([]byte, error) { + return runtime.CodecMarshalJSON(Codec, a.Object) +} + +// SetYAML implements the yaml.Setter interface. +func (a *EmbeddedObject) SetYAML(tag string, value interface{}) bool { + obj, ok := runtime.CodecSetYAML(Codec, tag, value) + a.Object = obj + return ok +} + +// GetYAML implements the yaml.Getter interface. +func (a EmbeddedObject) GetYAML() (tag string, value interface{}) { + return runtime.CodecGetYAML(Codec, a.Object) +} diff --git a/pkg/api/v1beta2/conversion_test.go b/pkg/api/v1beta2/conversion_test.go new file mode 100644 index 00000000000..1ddb3d9caf9 --- /dev/null +++ b/pkg/api/v1beta2/conversion_test.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 Google Inc. 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 v1beta2_test + +import () diff --git a/pkg/api/v1beta2/doc.go b/pkg/api/v1beta2/doc.go new file mode 100644 index 00000000000..43084cdc9fb --- /dev/null +++ b/pkg/api/v1beta2/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2014 Google Inc. 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 v1beta2 is the v1beta2 version of the API. +package v1beta2 diff --git a/pkg/api/v1beta2/register.go b/pkg/api/v1beta2/register.go new file mode 100644 index 00000000000..79f545330c8 --- /dev/null +++ b/pkg/api/v1beta2/register.go @@ -0,0 +1,45 @@ +/* +Copyright 2014 Google Inc. 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 v1beta2 + +import ( + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" +) + +// Codec encodes internal objects to the v1beta2 scheme +var Codec = runtime.CodecFor(api.Scheme, "v1beta2") + +func init() { + api.Scheme.AddKnownTypes("v1beta2", + &PodList{}, + &Pod{}, + &ReplicationControllerList{}, + &ReplicationController{}, + &ServiceList{}, + &Service{}, + &MinionList{}, + &Minion{}, + &Status{}, + &ServerOpList{}, + &ServerOp{}, + &ContainerManifestList{}, + &Endpoints{}, + &EndpointsList{}, + &Binding{}, + ) +} diff --git a/pkg/api/v1beta2/types.go b/pkg/api/v1beta2/types.go new file mode 100644 index 00000000000..4816d5f590e --- /dev/null +++ b/pkg/api/v1beta2/types.go @@ -0,0 +1,635 @@ +/* +Copyright 2014 Google Inc. 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 v1beta2 + +import ( + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" + "github.com/fsouza/go-dockerclient" +) + +// Common string formats +// --------------------- +// Many fields in this API have formatting requirements. The commonly used +// formats are defined here. +// +// C_IDENTIFIER: This is a string that conforms the definition of an "identifier" +// in the C language. This is captured by the following regex: +// [A-Za-z_][A-Za-z0-9_]* +// This defines the format, but not the length restriction, which should be +// specified at the definition of any field of this type. +// +// DNS_LABEL: This is a string, no more than 63 characters long, that conforms +// to the definition of a "label" in RFCs 1035 and 1123. This is captured +// by the following regex: +// [a-z0-9]([-a-z0-9]*[a-z0-9])? +// +// DNS_SUBDOMAIN: This is a string, no more than 253 characters long, that conforms +// to the definition of a "subdomain" in RFCs 1035 and 1123. This is captured +// by the following regex: +// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* +// or more simply: +// DNS_LABEL(\.DNS_LABEL)* + +// ContainerManifest corresponds to the Container Manifest format, documented at: +// https://developers.google.com/compute/docs/containers/container_vms#container_manifest +// This is used as the representation of Kubernetes workloads. +type ContainerManifest struct { + // Required: This must be a supported version string, such as "v1beta1". + Version string `yaml:"version" json:"version"` + // Required: This must be a DNS_SUBDOMAIN. + // TODO: ID on Manifest is deprecated and will be removed in the future. + ID string `yaml:"id" json:"id"` + // TODO: UUID on Manifest is deprecated in the future once we are done + // with the API refactoring. It is required for now to determine the instance + // of a Pod. + UUID string `yaml:"uuid,omitempty" json:"uuid,omitempty"` + Volumes []Volume `yaml:"volumes" json:"volumes"` + Containers []Container `yaml:"containers" json:"containers"` + RestartPolicy RestartPolicy `json:"restartPolicy,omitempty" yaml:"restartPolicy,omitempty"` +} + +// ContainerManifestList is used to communicate container manifests to kubelet. +type ContainerManifestList struct { + JSONBase `json:",inline" yaml:",inline"` + Items []ContainerManifest `json:"items,omitempty" yaml:"items,omitempty"` +} + +func (*ContainerManifestList) IsAnAPIObject() {} + +// Volume represents a named volume in a pod that may be accessed by any containers in the pod. +type Volume struct { + // Required: This must be a DNS_LABEL. Each volume in a pod must have + // a unique name. + Name string `yaml:"name" json:"name"` + // Source represents the location and type of a volume to mount. + // This is optional for now. If not specified, the Volume is implied to be an EmptyDir. + // This implied behavior is deprecated and will be removed in a future version. + Source *VolumeSource `yaml:"source" json:"source"` +} + +type VolumeSource struct { + // Only one of the following sources may be specified + // HostDirectory represents a pre-existing directory on the host machine that is directly + // exposed to the container. This is generally used for system agents or other privileged + // things that are allowed to see the host machine. Most containers will NOT need this. + // TODO(jonesdl) We need to restrict who can use host directory mounts and + // who can/can not mount host directories as read/write. + HostDirectory *HostDirectory `yaml:"hostDir" json:"hostDir"` + // EmptyDirectory represents a temporary directory that shares a pod's lifetime. + EmptyDirectory *EmptyDirectory `yaml:"emptyDir" json:"emptyDir"` +} + +// HostDirectory represents bare host directory volume. +type HostDirectory struct { + Path string `yaml:"path" json:"path"` +} + +type EmptyDirectory struct{} + +// Port represents a network port in a single container. +type Port struct { + // Optional: If specified, this must be a DNS_LABEL. Each named port + // in a pod must have a unique name. + Name string `yaml:"name,omitempty" json:"name,omitempty"` + // Optional: If specified, this must be a valid port number, 0 < x < 65536. + HostPort int `yaml:"hostPort,omitempty" json:"hostPort,omitempty"` + // Required: This must be a valid port number, 0 < x < 65536. + ContainerPort int `yaml:"containerPort" json:"containerPort"` + // Optional: Supports "TCP" and "UDP". Defaults to "TCP". + Protocol string `yaml:"protocol,omitempty" json:"protocol,omitempty"` + // Optional: What host IP to bind the external port to. + HostIP string `yaml:"hostIP,omitempty" json:"hostIP,omitempty"` +} + +// VolumeMount describes a mounting of a Volume within a container. +type VolumeMount struct { + // Required: This must match the Name of a Volume [above]. + Name string `yaml:"name" json:"name"` + // Optional: Defaults to false (read-write). + ReadOnly bool `yaml:"readOnly,omitempty" json:"readOnly,omitempty"` + // Required. + // Exactly one of the following must be set. If both are set, prefer MountPath. + // DEPRECATED: Path will be removed in a future version of the API. + MountPath string `yaml:"mountPath,omitempty" json:"mountPath,omitempty"` + Path string `yaml:"path,omitempty" json:"path,omitempty"` + // One of: "LOCAL" (local volume) or "HOST" (external mount from the host). Default: LOCAL. + // DEPRECATED: MountType will be removed in a future version of the API. + MountType string `yaml:"mountType,omitempty" json:"mountType,omitempty"` +} + +// EnvVar represents an environment variable present in a Container. +type EnvVar struct { + // Required: This must be a C_IDENTIFIER. + // Exactly one of the following must be set. If both are set, prefer Name. + // DEPRECATED: EnvVar.Key will be removed in a future version of the API. + Name string `yaml:"name" json:"name"` + Key string `yaml:"key,omitempty" json:"key,omitempty"` + // Optional: defaults to "". + Value string `yaml:"value,omitempty" json:"value,omitempty"` +} + +// HTTPGetAction describes an action based on HTTP Get requests. +type HTTPGetAction struct { + // Optional: Path to access on the HTTP server. + Path string `yaml:"path,omitempty" json:"path,omitempty"` + // Required: Name or number of the port to access on the container. + Port util.IntOrString `yaml:"port,omitempty" json:"port,omitempty"` + // Optional: Host name to connect to, defaults to the pod IP. + Host string `yaml:"host,omitempty" json:"host,omitempty"` +} + +// TCPSocketAction describes an action based on opening a socket +type TCPSocketAction struct { + // Required: Port to connect to. + Port util.IntOrString `yaml:"port,omitempty" json:"port,omitempty"` +} + +// ExecAction describes a "run in container" action. +type ExecAction struct { + // Command is the command line to execute inside the container, the working directory for the + // command is root ('/') in the container's filesystem. The command is simply exec'd, it is + // not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + // a shell, you need to explicitly call out to that shell. + Command []string `yaml:"command,omitempty" json:"command,omitempty"` +} + +// LivenessProbe describes a liveness probe to be examined to the container. +// TODO: pass structured data to the actions, and document that data here. +type LivenessProbe struct { + // Type of liveness probe. Current legal values "http", "tcp" + Type string `yaml:"type,omitempty" json:"type,omitempty"` + // HTTPGetProbe parameters, required if Type == 'http' + HTTPGet *HTTPGetAction `yaml:"httpGet,omitempty" json:"httpGet,omitempty"` + // TCPSocketProbe parameter, required if Type == 'tcp' + TCPSocket *TCPSocketAction `yaml:"tcpSocket,omitempty" json:"tcpSocket,omitempty"` + // ExecProbe parameter, required if Type == 'exec' + Exec *ExecAction `yaml:"exec,omitempty" json:"exec,omitempty"` + // Length of time before health checking is activated. In seconds. + InitialDelaySeconds int64 `yaml:"initialDelaySeconds,omitempty" json:"initialDelaySeconds,omitempty"` +} + +// Container represents a single container that is expected to be run on the host. +type Container struct { + // Required: This must be a DNS_LABEL. Each container in a pod must + // have a unique name. + Name string `yaml:"name" json:"name"` + // Required. + Image string `yaml:"image" json:"image"` + // Optional: Defaults to whatever is defined in the image. + Command []string `yaml:"command,omitempty" json:"command,omitempty"` + // Optional: Defaults to Docker's default. + WorkingDir string `yaml:"workingDir,omitempty" json:"workingDir,omitempty"` + Ports []Port `yaml:"ports,omitempty" json:"ports,omitempty"` + Env []EnvVar `yaml:"env,omitempty" json:"env,omitempty"` + // Optional: Defaults to unlimited. + Memory int `yaml:"memory,omitempty" json:"memory,omitempty"` + // Optional: Defaults to unlimited. + CPU int `yaml:"cpu,omitempty" json:"cpu,omitempty"` + VolumeMounts []VolumeMount `yaml:"volumeMounts,omitempty" json:"volumeMounts,omitempty"` + LivenessProbe *LivenessProbe `yaml:"livenessProbe,omitempty" json:"livenessProbe,omitempty"` + Lifecycle *Lifecycle `yaml:"lifecycle,omitempty" json:"lifecycle,omitempty"` + // Optional: Default to false. + Privileged bool `json:"privileged,omitempty" yaml:"privileged,omitempty"` +} + +// Handler defines a specific action that should be taken +// TODO: pass structured data to these actions, and document that data here. +type Handler struct { + // One and only one of the following should be specified. + // Exec specifies the action to take. + Exec *ExecAction `yaml:"exec,omitempty" json:"exec,omitempty"` + // HTTPGet specifies the http request to perform. + HTTPGet *HTTPGetAction `yaml:"httpGet,omitempty" json:"httpGet,omitempty"` +} + +// Lifecycle describes actions that the management system should take in response to container lifecycle +// events. For the PostStart and PreStop lifecycle handlers, management of the container blocks +// until the action is complete, unless the container process fails, in which case the handler is aborted. +type Lifecycle struct { + // PostStart is called immediately after a container is created. If the handler fails, the container + // is terminated and restarted. + PostStart *Handler `yaml:"postStart,omitempty" json:"postStart,omitempty"` + // PreStop is called immediately before a container is terminated. The reason for termination is + // passed to the handler. Regardless of the outcome of the handler, the container is eventually terminated. + PreStop *Handler `yaml:"preStop,omitempty" json:"preStop,omitempty"` +} + +// Event is the representation of an event logged to etcd backends. +type Event struct { + Event string `json:"event,omitempty"` + Manifest *ContainerManifest `json:"manifest,omitempty"` + Container *Container `json:"container,omitempty"` + Timestamp int64 `json:"timestamp"` +} + +// The below types are used by kube_client and api_server. + +// JSONBase is shared by all objects sent to, or returned from the client. +type JSONBase struct { + Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` + ID string `json:"id,omitempty" yaml:"id,omitempty"` + CreationTimestamp util.Time `json:"creationTimestamp,omitempty" yaml:"creationTimestamp,omitempty"` + SelfLink string `json:"selfLink,omitempty" yaml:"selfLink,omitempty"` + ResourceVersion uint64 `json:"resourceVersion,omitempty" yaml:"resourceVersion,omitempty"` + APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` +} + +// PodStatus represents a status of a pod. +type PodStatus string + +// These are the valid statuses of pods. +const ( + // PodWaiting means that we're waiting for the pod to begin running. + PodWaiting PodStatus = "Waiting" + // PodRunning means that the pod is up and running. + PodRunning PodStatus = "Running" + // PodTerminated means that the pod has stopped. + PodTerminated PodStatus = "Terminated" +) + +type ContainerStateWaiting struct { + // Reason could be pulling image, + Reason string `json:"reason,omitempty" yaml:"reason,omitempty"` +} + +type ContainerStateRunning struct { +} + +type ContainerStateTerminated struct { + ExitCode int `json:"exitCode,omitempty" yaml:"exitCode,omitempty"` + Signal int `json:"signal,omitempty" yaml:"signal,omitempty"` + Reason string `json:"reason,omitempty" yaml:"reason,omitempty"` +} + +type ContainerState struct { + // Only one of the following ContainerState may be specified. + // If none of them is specified, the default one is ContainerStateWaiting. + Waiting *ContainerStateWaiting `json:"waiting,omitempty" yaml:"waiting,omitempty"` + Running *ContainerStateRunning `json:"running,omitempty" yaml:"running,omitempty"` + Termination *ContainerStateTerminated `json:"termination,omitempty" yaml:"termination,omitempty"` +} + +type ContainerStatus struct { + // TODO(dchen1107): Should we rename PodStatus to a more generic name or have a separate states + // defined for container? + State ContainerState `json:"state,omitempty" yaml:"state,omitempty"` + RestartCount int `json:"restartCount" yaml:"restartCount"` + // TODO(dchen1107): Introduce our own NetworkSettings struct here? + // TODO(dchen1107): Once we have done with integration with cadvisor, resource + // usage should be included. + // TODO(dchen1107): In long run, I think we should replace this with our own struct to remove + // the dependency on docker. + DetailInfo docker.Container `json:"detailInfo,omitempty" yaml:"detailInfo,omitempty"` +} + +// PodInfo contains one entry for every container with available info. +// TODO(dchen1107): Replace docker.Container below with ContainerStatus defined above. +type PodInfo map[string]docker.Container + +type RestartPolicyAlways struct{} + +// TODO(dchen1107): Define what kinds of failures should restart. +// TODO(dchen1107): Decide whether to support policy knobs, and, if so, which ones. +type RestartPolicyOnFailure struct{} + +type RestartPolicyNever struct{} + +type RestartPolicy struct { + // Only one of the following restart policies may be specified. + // If none of the following policies is specified, the default one + // is RestartPolicyAlways. + Always *RestartPolicyAlways `json:"always,omitempty" yaml:"always,omitempty"` + OnFailure *RestartPolicyOnFailure `json:"onFailure,omitempty" yaml:"onFailure,omitempty"` + Never *RestartPolicyNever `json:"never,omitempty" yaml:"never,omitempty"` +} + +// PodState is the state of a pod, used as either input (desired state) or output (current state). +type PodState struct { + Manifest ContainerManifest `json:"manifest,omitempty" yaml:"manifest,omitempty"` + Status PodStatus `json:"status,omitempty" yaml:"status,omitempty"` + Host string `json:"host,omitempty" yaml:"host,omitempty"` + HostIP string `json:"hostIP,omitempty" yaml:"hostIP,omitempty"` + PodIP string `json:"podIP,omitempty" yaml:"podIP,omitempty"` + + // The key of this map is the *name* of the container within the manifest; it has one + // entry per container in the manifest. The value of this map is currently the output + // of `docker inspect`. This output format is *not* final and should not be relied + // upon. + // TODO: Make real decisions about what our info should look like. Re-enable fuzz test + // when we have done this. + Info PodInfo `json:"info,omitempty" yaml:"info,omitempty"` +} + +// PodList is a list of Pods. +type PodList struct { + JSONBase `json:",inline" yaml:",inline"` + Items []Pod `json:"items" yaml:"items,omitempty"` +} + +func (*PodList) IsAnAPIObject() {} + +// Pod is a collection of containers, used as either input (create, update) or as output (list, get). +type Pod struct { + JSONBase `json:",inline" yaml:",inline"` + Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` + DesiredState PodState `json:"desiredState,omitempty" yaml:"desiredState,omitempty"` + CurrentState PodState `json:"currentState,omitempty" yaml:"currentState,omitempty"` +} + +func (*Pod) IsAnAPIObject() {} + +// ReplicationControllerState is the state of a replication controller, either input (create, update) or as output (list, get). +type ReplicationControllerState struct { + Replicas int `json:"replicas" yaml:"replicas"` + ReplicaSelector map[string]string `json:"replicaSelector,omitempty" yaml:"replicaSelector,omitempty"` + PodTemplate PodTemplate `json:"podTemplate,omitempty" yaml:"podTemplate,omitempty"` +} + +// ReplicationControllerList is a collection of replication controllers. +type ReplicationControllerList struct { + JSONBase `json:",inline" yaml:",inline"` + Items []ReplicationController `json:"items,omitempty" yaml:"items,omitempty"` +} + +func (*ReplicationControllerList) IsAnAPIObject() {} + +// ReplicationController represents the configuration of a replication controller. +type ReplicationController struct { + JSONBase `json:",inline" yaml:",inline"` + DesiredState ReplicationControllerState `json:"desiredState,omitempty" yaml:"desiredState,omitempty"` + CurrentState ReplicationControllerState `json:"currentState,omitempty" yaml:"currentState,omitempty"` + Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` +} + +func (*ReplicationController) IsAnAPIObject() {} + +// PodTemplate holds the information used for creating pods. +type PodTemplate struct { + DesiredState PodState `json:"desiredState,omitempty" yaml:"desiredState,omitempty"` + Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` +} + +// ServiceList holds a list of services. +type ServiceList struct { + JSONBase `json:",inline" yaml:",inline"` + Items []Service `json:"items" yaml:"items"` +} + +func (*ServiceList) IsAnAPIObject() {} + +// Service is a named abstraction of software service (for example, mysql) consisting of local port +// (for example 3306) that the proxy listens on, and the selector that determines which pods +// will answer requests sent through the proxy. +type Service struct { + JSONBase `json:",inline" yaml:",inline"` + + // Required. + Port int `json:"port" yaml:"port"` + // Optional: Supports "TCP" and "UDP". Defaults to "TCP". + Protocol string `yaml:"protocol,omitempty" json:"protocol,omitempty"` + + // This service's labels. + Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` + + // This service will route traffic to pods having labels matching this selector. + Selector map[string]string `json:"selector,omitempty" yaml:"selector,omitempty"` + CreateExternalLoadBalancer bool `json:"createExternalLoadBalancer,omitempty" yaml:"createExternalLoadBalancer,omitempty"` + + // ContainerPort is the name of the port on the container to direct traffic to. + // Optional, if unspecified use the first port on the container. + ContainerPort util.IntOrString `json:"containerPort,omitempty" yaml:"containerPort,omitempty"` +} + +func (*Service) IsAnAPIObject() {} + +// Endpoints is a collection of endpoints that implement the actual service, for example: +// Name: "mysql", Endpoints: ["10.10.1.1:1909", "10.10.2.2:8834"] +type Endpoints struct { + JSONBase `json:",inline" yaml:",inline"` + Endpoints []string `json:"endpoints,omitempty" yaml:"endpoints,omitempty"` +} + +func (*Endpoints) IsAnAPIObject() {} + +// EndpointsList is a list of endpoints. +type EndpointsList struct { + JSONBase `json:",inline" yaml:",inline"` + Items []Endpoints `json:"items,omitempty" yaml:"items,omitempty"` +} + +func (*EndpointsList) IsAnAPIObject() {} + +// Minion is a worker node in Kubernetenes. +// The name of the minion according to etcd is in JSONBase.ID. +type Minion struct { + JSONBase `json:",inline" yaml:",inline"` + // Queried from cloud provider, if available. + HostIP string `json:"hostIP,omitempty" yaml:"hostIP,omitempty"` +} + +func (*Minion) IsAnAPIObject() {} + +// MinionList is a list of minions. +type MinionList struct { + JSONBase `json:",inline" yaml:",inline"` + // DEPRECATED: the below Minions is due to a naming mistake and + // will be replaced with Items in the future. + Minions []Minion `json:"minions,omitempty" yaml:"minions,omitempty"` + Items []Minion `json:"items,omitempty" yaml:"items,omitempty"` +} + +func (*MinionList) IsAnAPIObject() {} + +// Binding is written by a scheduler to cause a pod to be bound to a host. +type Binding struct { + JSONBase `json:",inline" yaml:",inline"` + PodID string `json:"podID" yaml:"podID"` + Host string `json:"host" yaml:"host"` +} + +func (*Binding) IsAnAPIObject() {} + +// Status is a return value for calls that don't return other objects. +// TODO: this could go in apiserver, but I'm including it here so clients needn't +// import both. +type Status struct { + JSONBase `json:",inline" yaml:",inline"` + // One of: "success", "failure", "working" (for operations not yet completed) + Status string `json:"status,omitempty" yaml:"status,omitempty"` + // A human-readable description of the status of this operation. + Message string `json:"message,omitempty" yaml:"message,omitempty"` + // A machine-readable description of why this operation is in the + // "failure" or "working" status. If this value is empty there + // is no information available. A Reason clarifies an HTTP status + // code but does not override it. + Reason StatusReason `json:"reason,omitempty" yaml:"reason,omitempty"` + // Extended data associated with the reason. Each reason may define its + // own extended details. This field is optional and the data returned + // is not guaranteed to conform to any schema except that defined by + // the reason type. + Details *StatusDetails `json:"details,omitempty" yaml:"details,omitempty"` + // Suggested HTTP return code for this status, 0 if not set. + Code int `json:"code,omitempty" yaml:"code,omitempty"` +} + +func (*Status) IsAnAPIObject() {} + +// StatusDetails is a set of additional properties that MAY be set by the +// server to provide additional information about a response. The Reason +// field of a Status object defines what attributes will be set. Clients +// must ignore fields that do not match the defined type of each attribute, +// and should assume that any attribute may be empty, invalid, or under +// defined. +type StatusDetails struct { + // The ID attribute of the resource associated with the status StatusReason + // (when there is a single ID which can be described). + ID string `json:"id,omitempty" yaml:"id,omitempty"` + // The kind attribute of the resource associated with the status StatusReason. + // On some operations may differ from the requested resource Kind. + Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` + // The Causes array includes more details associated with the StatusReason + // failure. Not all StatusReasons may provide detailed causes. + Causes []StatusCause `json:"causes,omitempty" yaml:"causes,omitempty"` +} + +// Values of Status.Status +const ( + StatusSuccess = "success" + StatusFailure = "failure" + StatusWorking = "working" +) + +// StatusReason is an enumeration of possible failure causes. Each StatusReason +// must map to a single HTTP status code, but multiple reasons may map +// to the same HTTP status code. +// TODO: move to apiserver +type StatusReason string + +const ( + // StatusReasonUnknown means the server has declined to indicate a specific reason. + // The details field may contain other information about this error. + // Status code 500. + StatusReasonUnknown StatusReason = "" + + // StatusReasonWorking means the server is processing this request and will complete + // at a future time. + // Details (optional): + // "kind" string - the name of the resource being referenced ("operation" today) + // "id" string - the identifier of the Operation resource where updates + // will be returned + // Headers (optional): + // "Location" - HTTP header populated with a URL that can retrieved the final + // status of this operation. + // Status code 202 + StatusReasonWorking StatusReason = "working" + + // StatusReasonNotFound means one or more resources required for this operation + // could not be found. + // Details (optional): + // "kind" string - the kind attribute of the missing resource + // on some operations may differ from the requested + // resource. + // "id" string - the identifier of the missing resource + // Status code 404 + StatusReasonNotFound StatusReason = "not_found" + + // StatusReasonAlreadyExists means the resource you are creating already exists. + // Details (optional): + // "kind" string - the kind attribute of the conflicting resource + // "id" string - the identifier of the conflicting resource + // Status code 409 + StatusReasonAlreadyExists StatusReason = "already_exists" + + // StatusReasonConflict means the requested update operation cannot be completed + // due to a conflict in the operation. The client may need to alter the request. + // Each resource may define custom details that indicate the nature of the + // conflict. + // Status code 409 + StatusReasonConflict StatusReason = "conflict" + + // StatusReasonInvalid means the requested create or update operation cannot be + // completed due to invalid data provided as part of the request. The client may + // need to alter the request. When set, the client may use the StatusDetails + // message field as a summary of the issues encountered. + // Details (optional): + // "kind" string - the kind attribute of the invalid resource + // "id" string - the identifier of the invalid resource + // "causes" - one or more StatusCause entries indicating the data in the + // provided resource that was invalid. The code, message, and + // field attributes will be set. + // Status code 422 + StatusReasonInvalid StatusReason = "invalid" +) + +// StatusCause provides more information about an api.Status failure, including +// cases when multiple errors are encountered. +type StatusCause struct { + // A machine-readable description of the cause of the error. If this value is + // empty there is no information available. + Type CauseType `json:"reason,omitempty" yaml:"reason,omitempty"` + // A human-readable description of the cause of the error. This field may be + // presented as-is to a reader. + Message string `json:"message,omitempty" yaml:"message,omitempty"` + // The field of the resource that has caused this error, as named by its JSON + // serialization. May include dot and postfix notation for nested attributes. + // Arrays are zero-indexed. Fields may appear more than once in an array of + // causes due to fields having multiple errors. + // Optional. + // + // Examples: + // "name" - the field "name" on the current resource + // "items[0].name" - the field "name" on the first array entry in "items" + Field string `json:"field,omitempty" yaml:"field,omitempty"` +} + +// CauseType is a machine readable value providing more detail about what +// occured in a status response. An operation may have multiple causes for a +// status (whether failure, success, or working). +type CauseType string + +const ( + // CauseTypeFieldValueNotFound is used to report failure to find a requested value + // (e.g. looking up an ID). + CauseTypeFieldValueNotFound CauseType = "fieldValueNotFound" + // CauseTypeFieldValueInvalid is used to report required values that are not + // provided (e.g. empty strings, null values, or empty arrays). + CauseTypeFieldValueRequired CauseType = "fieldValueRequired" + // CauseTypeFieldValueDuplicate is used to report collisions of values that must be + // unique (e.g. unique IDs). + CauseTypeFieldValueDuplicate CauseType = "fieldValueDuplicate" + // CauseTypeFieldValueInvalid is used to report malformed values (e.g. failed regex + // match). + CauseTypeFieldValueInvalid CauseType = "fieldValueInvalid" + // CauseTypeFieldValueNotSupported is used to report valid (as per formatting rules) + // values that can not be handled (e.g. an enumerated string). + CauseTypeFieldValueNotSupported CauseType = "fieldValueNotSupported" +) + +// ServerOp is an operation delivered to API clients. +type ServerOp struct { + JSONBase `yaml:",inline" json:",inline"` +} + +func (*ServerOp) IsAnAPIObject() {} + +// ServerOpList is a list of operations, as delivered to API clients. +type ServerOpList struct { + JSONBase `yaml:",inline" json:",inline"` + Items []ServerOp `yaml:"items,omitempty" json:"items,omitempty"` +} + +func (*ServerOpList) IsAnAPIObject() {} diff --git a/pkg/api/v1beta3/types.go b/pkg/api/v1beta3/types.go index 1dba02b5f47..661bc855749 100644 --- a/pkg/api/v1beta3/types.go +++ b/pkg/api/v1beta3/types.go @@ -14,12 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v1beta1 +package v1beta3 import ( - "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" - "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" "github.com/fsouza/go-dockerclient" ) @@ -55,8 +53,8 @@ type ContainerManifest struct { // Required: This must be a DNS_SUBDOMAIN. // TODO: ID on Manifest is deprecated and will be removed in the future. ID string `yaml:"id" json:"id"` - // TODO: UUID on Manifext is deprecated in the future once we are done - // with the API refactory. It is required for now to determine the instance + // TODO: UUID on Manifest is deprecated in the future once we are done + // with the API refactoring. It is required for now to determine the instance // of a Pod. UUID string `yaml:"uuid,omitempty" json:"uuid,omitempty"` Volumes []Volume `yaml:"volumes" json:"volumes"` @@ -124,22 +122,13 @@ type VolumeMount struct { // Optional: Defaults to false (read-write). ReadOnly bool `yaml:"readOnly,omitempty" json:"readOnly,omitempty"` // Required. - // Exactly one of the following must be set. If both are set, prefer MountPath. - // DEPRECATED: Path will be removed in a future version of the API. MountPath string `yaml:"mountPath,omitempty" json:"mountPath,omitempty"` - Path string `yaml:"path,omitempty" json:"path,omitempty"` - // One of: "LOCAL" (local volume) or "HOST" (external mount from the host). Default: LOCAL. - // DEPRECATED: MountType will be removed in a future version of the API. - MountType string `yaml:"mountType,omitempty" json:"mountType,omitempty"` } // EnvVar represents an environment variable present in a Container. type EnvVar struct { // Required: This must be a C_IDENTIFIER. - // Exactly one of the following must be set. If both are set, prefer Name. - // DEPRECATED: EnvVar.Key will be removed in a future version of the API. Name string `yaml:"name" json:"name"` - Key string `yaml:"key,omitempty" json:"key,omitempty"` // Optional: defaults to "". Value string `yaml:"value,omitempty" json:"value,omitempty"` } @@ -166,7 +155,6 @@ type ExecAction struct { // command is root ('/') in the container's filesystem. The command is simply exec'd, it is // not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use // a shell, you need to explicitly call out to that shell. - // A return code of zero is treated as 'Healthy', non-zero is 'Unhealthy' Command []string `yaml:"command,omitempty" json:"command,omitempty"` } @@ -210,7 +198,6 @@ type Container struct { } // Handler defines a specific action that should be taken -// TODO: merge this with liveness probing? // TODO: pass structured data to these actions, and document that data here. type Handler struct { // One and only one of the following should be specified. @@ -252,8 +239,6 @@ type JSONBase struct { APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` } -func (*JSONBase) IsAnAPIObject() {} - // PodStatus represents a status of a pod. type PodStatus string @@ -303,18 +288,19 @@ type ContainerStatus struct { } // PodInfo contains one entry for every container with available info. +// TODO(dchen1107): Replace docker.Container below with ContainerStatus defined above. type PodInfo map[string]docker.Container type RestartPolicyAlways struct{} -// TODO(dchen1107): Define what kinds of failures should restart +// TODO(dchen1107): Define what kinds of failures should restart. // TODO(dchen1107): Decide whether to support policy knobs, and, if so, which ones. type RestartPolicyOnFailure struct{} type RestartPolicyNever struct{} type RestartPolicy struct { - // Only one of the following restart policy may be specified. + // Only one of the following restart policies may be specified. // If none of the following policies is specified, the default one // is RestartPolicyAlways. Always *RestartPolicyAlways `json:"always,omitempty" yaml:"always,omitempty"` @@ -333,9 +319,9 @@ type PodState struct { // The key of this map is the *name* of the container within the manifest; it has one // entry per container in the manifest. The value of this map is currently the output // of `docker inspect`. This output format is *not* final and should not be relied - // upon. To allow marshalling/unmarshalling, we copied the client's structs and added - // json/yaml tags. - // TODO: Make real decisions about what our info should look like. + // upon. + // TODO: Make real decisions about what our info should look like. Re-enable fuzz test + // when we have done this. Info PodInfo `json:"info,omitempty" yaml:"info,omitempty"` } @@ -451,10 +437,7 @@ func (*Minion) IsAnAPIObject() {} // MinionList is a list of minions. type MinionList struct { JSONBase `json:",inline" yaml:",inline"` - // DEPRECATED: the below Minions is due to a naming mistake and - // will be replaced with Items in the future. - Minions []Minion `json:"minions,omitempty" yaml:"minions,omitempty"` - Items []Minion `json:"items,omitempty" yaml:"items,omitempty"` + Items []Minion `json:"items,omitempty" yaml:"items,omitempty"` } func (*MinionList) IsAnAPIObject() {} @@ -550,14 +533,14 @@ const ( // resource. // "id" string - the identifier of the missing resource // Status code 404 - StatusReasonNotFound StatusReason = "notFound" + StatusReasonNotFound StatusReason = "not_found" // StatusReasonAlreadyExists means the resource you are creating already exists. // Details (optional): // "kind" string - the kind attribute of the conflicting resource // "id" string - the identifier of the conflicting resource // Status code 409 - StatusReasonAlreadyExists StatusReason = "alreadyExists" + StatusReasonAlreadyExists StatusReason = "already_exists" // StatusReasonConflict means the requested update operation cannot be completed // due to a conflict in the operation. The client may need to alter the request. @@ -565,6 +548,19 @@ const ( // conflict. // Status code 409 StatusReasonConflict StatusReason = "conflict" + + // StatusReasonInvalid means the requested create or update operation cannot be + // completed due to invalid data provided as part of the request. The client may + // need to alter the request. When set, the client may use the StatusDetails + // message field as a summary of the issues encountered. + // Details (optional): + // "kind" string - the kind attribute of the invalid resource + // "id" string - the identifier of the invalid resource + // "causes" - one or more StatusCause entries indicating the data in the + // provided resource that was invalid. The code, message, and + // field attributes will be set. + // Status code 422 + StatusReasonInvalid StatusReason = "invalid" ) // StatusCause provides more information about an api.Status failure, including @@ -625,13 +621,3 @@ type ServerOpList struct { } func (*ServerOpList) IsAnAPIObject() {} - -// WatchEvent objects are streamed from the api server in response to a watch request. -type WatchEvent struct { - // The type of the watch event; added, modified, or deleted. - Type watch.EventType - - // For added or modified objects, this is the new object; for deleted objects, - // it's the state of the object immediately prior to its deletion. - Object runtime.EmbeddedObject -} diff --git a/pkg/api/watch.go b/pkg/api/watch.go new file mode 100644 index 00000000000..288c4cb2458 --- /dev/null +++ b/pkg/api/watch.go @@ -0,0 +1,57 @@ +/* +Copyright 2014 Google Inc. 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 + +import ( + "encoding/json" + "fmt" + "reflect" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" + "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" +) + +// WatchEvent objects are streamed from the api server in response to a watch request. +// These are not API objects and are unversioned today. +type WatchEvent struct { + // The type of the watch event; added, modified, or deleted. + Type watch.EventType + + // For added or modified objects, this is the new object; for deleted objects, + // it's the state of the object immediately prior to its deletion. + Object EmbeddedObject +} + +// watchSerialization defines the JSON wire equivalent of watch.Event +type watchSerialization struct { + Type watch.EventType + Object json.RawMessage +} + +// NewJSONWatcHEvent returns an object that will serialize to JSON and back +// to a WatchEvent. +func NewJSONWatchEvent(codec runtime.Codec, event watch.Event) (interface{}, error) { + obj, ok := event.Object.(runtime.Object) + if !ok { + return nil, fmt.Errorf("The event object cannot be safely converted to JSON: %v", reflect.TypeOf(event.Object).Name()) + } + data, err := codec.Encode(obj) + if err != nil { + return nil, err + } + return &watchSerialization{event.Type, json.RawMessage(data)}, nil +} diff --git a/pkg/api/watch_test.go b/pkg/api/watch_test.go new file mode 100644 index 00000000000..228568da116 --- /dev/null +++ b/pkg/api/watch_test.go @@ -0,0 +1,43 @@ +/* +Copyright 2014 Google Inc. 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 + +import ( + "encoding/json" + "reflect" + "testing" +) + +func TestEmbeddedDefaultSerialization(t *testing.T) { + expected := WatchEvent{ + Type: "foo", + Object: EmbeddedObject{&Pod{}}, + } + data, err := json.Marshal(expected) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + actual := WatchEvent{} + if err := json.Unmarshal(data, &actual); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if !reflect.DeepEqual(actual, expected) { + t.Errorf("Expected %#v, Got %#v", expected, actual) + } +} diff --git a/pkg/apiserver/apiserver_test.go b/pkg/apiserver/apiserver_test.go index 59dceff11ec..f926b85b2f9 100644 --- a/pkg/apiserver/apiserver_test.go +++ b/pkg/apiserver/apiserver_test.go @@ -32,6 +32,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" apierrs "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" @@ -43,11 +44,11 @@ func convert(obj runtime.Object) (runtime.Object, error) { return obj, nil } -var codec = runtime.DefaultCodec +var codec = latest.Codec func init() { - runtime.DefaultScheme.AddKnownTypes("", &Simple{}, &SimpleList{}) - runtime.DefaultScheme.AddKnownTypes("v1beta1", &Simple{}, &SimpleList{}) + api.Scheme.AddKnownTypes("", &Simple{}, &SimpleList{}) + api.Scheme.AddKnownTypes(latest.Version, &Simple{}, &SimpleList{}) } type Simple struct { @@ -95,7 +96,7 @@ func (storage *SimpleRESTStorage) List(labels.Selector) (runtime.Object, error) } func (storage *SimpleRESTStorage) Get(id string) (runtime.Object, error) { - return runtime.DefaultScheme.CopyOrDie(&storage.item), storage.errors["get"] + return api.Scheme.CopyOrDie(&storage.item), storage.errors["get"] } func (storage *SimpleRESTStorage) Delete(id string) (<-chan runtime.Object, error) { @@ -676,7 +677,7 @@ func (*UnregisteredAPIObject) IsAnAPIObject() {} func TestWriteJSONDecodeError(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - writeJSON(http.StatusOK, runtime.DefaultCodec, &UnregisteredAPIObject{"Undecodable"}, w) + writeJSON(http.StatusOK, latest.Codec, &UnregisteredAPIObject{"Undecodable"}, w) })) status := expectApiStatus(t, "GET", server.URL, nil, http.StatusInternalServerError) if status.Reason != api.StatusReasonUnknown { diff --git a/pkg/apiserver/watch.go b/pkg/apiserver/watch.go index 38b3ace5000..29c29413657 100644 --- a/pkg/apiserver/watch.go +++ b/pkg/apiserver/watch.go @@ -74,7 +74,7 @@ func (h *WatchHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { // TODO: This is one watch per connection. We want to multiplex, so that // multiple watches of the same thing don't create two watches downstream. - watchServer := &WatchServer{watching} + watchServer := &WatchServer{watching, h.codec} if req.Header.Get("Connection") == "Upgrade" && req.Header.Get("Upgrade") == "websocket" { websocket.Handler(watchServer.HandleWS).ServeHTTP(httplog.Unlogged(w), req) } else { @@ -89,6 +89,7 @@ func (h *WatchHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { // WatchServer serves a watch.Interface over a websocket or vanilla HTTP. type WatchServer struct { watching watch.Interface + codec runtime.Codec } // HandleWS implements a websocket handler. @@ -111,15 +112,17 @@ func (w *WatchServer) HandleWS(ws *websocket.Conn) { // End of results. return } - err := websocket.JSON.Send(ws, &api.WatchEvent{ - Type: event.Type, - Object: runtime.EmbeddedObject{event.Object}, - }) + obj, err := api.NewJSONWatchEvent(w.codec, event) if err != nil { // Client disconnect. w.watching.Stop() return } + if err := websocket.JSON.Send(ws, obj); err != nil { + // Client disconnect. + w.watching.Stop() + return + } } } } @@ -158,15 +161,17 @@ func (self *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { // End of results. return } - err := encoder.Encode(&api.WatchEvent{ - Type: event.Type, - Object: runtime.EmbeddedObject{event.Object}, - }) + obj, err := api.NewJSONWatchEvent(self.codec, event) if err != nil { // Client disconnect. self.watching.Stop() return } + if err := encoder.Encode(obj); err != nil { + // Client disconnect. + self.watching.Stop() + return + } flusher.Flush() } } diff --git a/pkg/apiserver/watch_test.go b/pkg/apiserver/watch_test.go index 70027c1d44a..51d52285549 100644 --- a/pkg/apiserver/watch_test.go +++ b/pkg/apiserver/watch_test.go @@ -114,26 +114,22 @@ func TestWatchHTTP(t *testing.T) { decoder := json.NewDecoder(response.Body) - try := func(action watch.EventType, object runtime.Object) { + for i, item := range watchTestTable { // Send - simpleStorage.fakeWatch.Action(action, object) + simpleStorage.fakeWatch.Action(item.t, item.obj) // Test receive var got api.WatchEvent err := decoder.Decode(&got) if err != nil { - t.Fatalf("Unexpected error: %v", err) + t.Fatalf("%d: Unexpected error: %v", i, err) } - if got.Type != action { - t.Errorf("Unexpected type: %v", got.Type) + if got.Type != item.t { + t.Errorf("%d: Unexpected type: %v", i, got.Type) } - if e, a := object, got.Object.Object; !reflect.DeepEqual(e, a) { - t.Errorf("Expected %v, got %v", e, a) + if e, a := item.obj, got.Object.Object; !reflect.DeepEqual(e, a) { + t.Errorf("%d: Expected %v, got %v", i, e, a) } } - - for _, item := range watchTestTable { - try(item.t, item.obj) - } simpleStorage.fakeWatch.Stop() var got api.WatchEvent diff --git a/pkg/client/client.go b/pkg/client/client.go index 470983e1a8c..5147dce1d60 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -26,7 +26,7 @@ import ( "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/version" @@ -99,7 +99,7 @@ type Client struct { // to a URL will prepend the server path. Returns an error if host cannot be converted to a // valid URL. func New(host string, auth *AuthInfo) (*Client, error) { - restClient, err := NewRESTClient(host, auth, "/api/v1beta1/", runtime.DefaultCodec) + restClient, err := NewRESTClient(host, auth, "/api/v1beta1/", latest.Codec) if err != nil { return nil, err } @@ -224,7 +224,7 @@ func (c *RESTClient) doRequest(request *http.Request) ([]byte, error) { // Did the server give us a status response? isStatusResponse := false var status api.Status - if err := runtime.DefaultCodec.DecodeInto(body, &status); err == nil && status.Status != "" { + if err := latest.Codec.DecodeInto(body, &status); err == nil && status.Status != "" { isStatusResponse = true } diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go index 02da5a1dd54..1f0b2018fb6 100644 --- a/pkg/client/client_test.go +++ b/pkg/client/client_test.go @@ -26,6 +26,7 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" @@ -48,7 +49,7 @@ func TestValidatesHostParameter(t *testing.T) { "host/server": {"", "", true}, } for k, expected := range testCases { - c, err := NewRESTClient(k, nil, "/api/v1beta1/", runtime.DefaultCodec) + c, err := NewRESTClient(k, nil, "/api/v1beta1/", latest.Codec) switch { case err == nil && expected.Err: t.Errorf("expected error but was nil") @@ -309,7 +310,7 @@ func TestCreateController(t *testing.T) { func body(obj runtime.Object, raw *string) *string { if obj != nil { - bs, _ := runtime.DefaultCodec.Encode(obj) + bs, _ := latest.Codec.Encode(obj) body := string(bs) return &body } @@ -533,7 +534,7 @@ func TestDoRequest(t *testing.T) { func TestDoRequestAccepted(t *testing.T) { status := &api.Status{Status: api.StatusWorking} - expectedBody, _ := runtime.DefaultCodec.Encode(status) + expectedBody, _ := latest.Codec.Encode(status) fakeHandler := util.FakeHandler{ StatusCode: 202, ResponseBody: string(expectedBody), @@ -570,7 +571,7 @@ func TestDoRequestAccepted(t *testing.T) { func TestDoRequestAcceptedSuccess(t *testing.T) { status := &api.Status{Status: api.StatusSuccess} - expectedBody, _ := runtime.DefaultCodec.Encode(status) + expectedBody, _ := latest.Codec.Encode(status) fakeHandler := util.FakeHandler{ StatusCode: 202, ResponseBody: string(expectedBody), @@ -590,7 +591,7 @@ func TestDoRequestAcceptedSuccess(t *testing.T) { if err != nil { t.Errorf("Unexpected error %#v", err) } - statusOut, err := runtime.DefaultCodec.Decode(body) + statusOut, err := latest.Codec.Decode(body) if err != nil { t.Errorf("Unexpected error %#v", err) } diff --git a/pkg/client/fake.go b/pkg/client/fake.go index c89c7aa3bc5..b745f57091e 100644 --- a/pkg/client/fake.go +++ b/pkg/client/fake.go @@ -19,7 +19,6 @@ package client import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" - "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/version" "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" ) @@ -44,7 +43,7 @@ type Fake struct { func (c *Fake) ListPods(selector labels.Selector) (*api.PodList, error) { c.Actions = append(c.Actions, FakeAction{Action: "list-pods"}) - return runtime.DefaultScheme.CopyOrDie(&c.Pods).(*api.PodList), nil + return api.Scheme.CopyOrDie(&c.Pods).(*api.PodList), nil } func (c *Fake) GetPod(name string) (*api.Pod, error) { @@ -74,7 +73,7 @@ func (c *Fake) ListReplicationControllers(selector labels.Selector) (*api.Replic func (c *Fake) GetReplicationController(name string) (*api.ReplicationController, error) { c.Actions = append(c.Actions, FakeAction{Action: "get-controller", Value: name}) - return runtime.DefaultScheme.CopyOrDie(&c.Ctrl).(*api.ReplicationController), nil + return api.Scheme.CopyOrDie(&c.Ctrl).(*api.ReplicationController), nil } func (c *Fake) CreateReplicationController(controller *api.ReplicationController) (*api.ReplicationController, error) { @@ -129,7 +128,7 @@ func (c *Fake) WatchServices(label, field labels.Selector, resourceVersion uint6 func (c *Fake) ListEndpoints(selector labels.Selector) (*api.EndpointsList, error) { c.Actions = append(c.Actions, FakeAction{Action: "list-endpoints"}) - return runtime.DefaultScheme.CopyOrDie(&c.EndpointsList).(*api.EndpointsList), c.Err + return api.Scheme.CopyOrDie(&c.EndpointsList).(*api.EndpointsList), c.Err } func (c *Fake) WatchEndpoints(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) { diff --git a/pkg/client/request.go b/pkg/client/request.go index f6a64710824..7dd1060cafc 100644 --- a/pkg/client/request.go +++ b/pkg/client/request.go @@ -28,9 +28,9 @@ import ( "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + cwatch "github.com/GoogleCloudPlatform/kubernetes/pkg/client/watch" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" - "github.com/GoogleCloudPlatform/kubernetes/pkg/tools" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" "github.com/golang/glog" @@ -269,7 +269,7 @@ func (r *Request) Watch() (watch.Interface, error) { if response.StatusCode != http.StatusOK { return nil, fmt.Errorf("Got status: %v", response.StatusCode) } - return watch.NewStreamWatcher(tools.NewAPIEventDecoder(response.Body)), nil + return watch.NewStreamWatcher(cwatch.NewAPIEventDecoder(response.Body)), nil } // Do formats and executes the request. Returns the API object received, or an error. diff --git a/pkg/client/request_test.go b/pkg/client/request_test.go index a7dab29af13..24bebadceb7 100644 --- a/pkg/client/request_test.go +++ b/pkg/client/request_test.go @@ -29,6 +29,7 @@ import ( "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" @@ -38,7 +39,7 @@ import ( func TestDoRequestNewWay(t *testing.T) { reqBody := "request body" expectedObj := &api.Service{Port: 12345} - expectedBody, _ := runtime.DefaultCodec.Encode(expectedObj) + expectedBody, _ := latest.Codec.Encode(expectedObj) fakeHandler := util.FakeHandler{ StatusCode: 200, ResponseBody: string(expectedBody), @@ -71,9 +72,9 @@ func TestDoRequestNewWay(t *testing.T) { func TestDoRequestNewWayReader(t *testing.T) { reqObj := &api.Pod{JSONBase: api.JSONBase{ID: "foo"}} - reqBodyExpected, _ := runtime.DefaultCodec.Encode(reqObj) + reqBodyExpected, _ := latest.Codec.Encode(reqObj) expectedObj := &api.Service{Port: 12345} - expectedBody, _ := runtime.DefaultCodec.Encode(expectedObj) + expectedBody, _ := latest.Codec.Encode(expectedObj) fakeHandler := util.FakeHandler{ StatusCode: 200, ResponseBody: string(expectedBody), @@ -108,9 +109,9 @@ func TestDoRequestNewWayReader(t *testing.T) { func TestDoRequestNewWayObj(t *testing.T) { reqObj := &api.Pod{JSONBase: api.JSONBase{ID: "foo"}} - reqBodyExpected, _ := runtime.DefaultCodec.Encode(reqObj) + reqBodyExpected, _ := latest.Codec.Encode(reqObj) expectedObj := &api.Service{Port: 12345} - expectedBody, _ := runtime.DefaultCodec.Encode(expectedObj) + expectedBody, _ := latest.Codec.Encode(expectedObj) fakeHandler := util.FakeHandler{ StatusCode: 200, ResponseBody: string(expectedBody), @@ -144,7 +145,7 @@ func TestDoRequestNewWayObj(t *testing.T) { func TestDoRequestNewWayFile(t *testing.T) { reqObj := &api.Pod{JSONBase: api.JSONBase{ID: "foo"}} - reqBodyExpected, err := runtime.DefaultCodec.Encode(reqObj) + reqBodyExpected, err := latest.Codec.Encode(reqObj) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -160,7 +161,7 @@ func TestDoRequestNewWayFile(t *testing.T) { } expectedObj := &api.Service{Port: 12345} - expectedBody, _ := runtime.DefaultCodec.Encode(expectedObj) + expectedBody, _ := latest.Codec.Encode(expectedObj) fakeHandler := util.FakeHandler{ StatusCode: 200, ResponseBody: string(expectedBody), @@ -295,7 +296,7 @@ func TestPolling(t *testing.T) { callNumber := 0 testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - data, err := runtime.DefaultCodec.Encode(objects[callNumber]) + data, err := latest.Codec.Encode(objects[callNumber]) if err != nil { t.Errorf("Unexpected encode error") } @@ -401,7 +402,13 @@ func TestWatch(t *testing.T) { encoder := json.NewEncoder(w) for _, item := range table { - encoder.Encode(&api.WatchEvent{item.t, runtime.EmbeddedObject{item.obj}}) + data, err := api.NewJSONWatchEvent(latest.Codec, watch.Event{item.t, item.obj}) + if err != nil { + panic(err) + } + if err := encoder.Encode(data); err != nil { + panic(err) + } flusher.Flush() } })) diff --git a/pkg/tools/decoder.go b/pkg/client/watch/decoder.go similarity index 94% rename from pkg/tools/decoder.go rename to pkg/client/watch/decoder.go index 0c1778a23cf..2a6f91e0700 100644 --- a/pkg/tools/decoder.go +++ b/pkg/client/watch/decoder.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package tools +package watch import ( "encoding/json" @@ -28,6 +28,8 @@ import ( // APIEventDecoder implements the watch.Decoder interface for io.ReadClosers that // have contents which consist of a series of api.WatchEvent objects encoded via JSON. +// It will decode any object which is registered to convert to api.WatchEvent via +// api.Scheme type APIEventDecoder struct { stream io.ReadCloser decoder *json.Decoder diff --git a/pkg/tools/decoder_test.go b/pkg/client/watch/decoder_test.go similarity index 80% rename from pkg/tools/decoder_test.go rename to pkg/client/watch/decoder_test.go index e818ba4e049..52d57c0b198 100644 --- a/pkg/tools/decoder_test.go +++ b/pkg/client/watch/decoder_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package tools +package watch import ( "encoding/json" @@ -24,29 +24,37 @@ import ( "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" - "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" ) +type watchSerialization struct { + Type watch.EventType + Object json.RawMessage +} + func TestDecoder(t *testing.T) { out, in := io.Pipe() - encoder := json.NewEncoder(in) decoder := NewAPIEventDecoder(out) expect := &api.Pod{JSONBase: api.JSONBase{ID: "foo"}} + encoder := json.NewEncoder(in) go func() { - err := encoder.Encode(api.WatchEvent{watch.Added, runtime.EmbeddedObject{expect}}) + data, err := v1beta1.Codec.Encode(expect) if err != nil { + t.Fatalf("Unexpected error %v", err) + } + if err := encoder.Encode(&watchSerialization{watch.Added, json.RawMessage(data)}); err != nil { t.Errorf("Unexpected error %v", err) } + in.Close() }() done := make(chan struct{}) go func() { action, got, err := decoder.Decode() if err != nil { - t.Errorf("Unexpected error %v", err) + t.Fatalf("Unexpected error %v", err) } if e, a := watch.Added, action; e != a { t.Errorf("Expected %v, got %v", e, a) @@ -54,17 +62,12 @@ func TestDecoder(t *testing.T) { if e, a := expect, got; !reflect.DeepEqual(e, a) { t.Errorf("Expected %v, got %v", e, a) } + t.Logf("Exited read") close(done) }() - select { - case <-done: - break - case <-time.After(10 * time.Second): - t.Error("Timeout") - } + <-done done = make(chan struct{}) - go func() { _, _, err := decoder.Decode() if err == nil { @@ -72,15 +75,9 @@ func TestDecoder(t *testing.T) { } close(done) }() + <-done decoder.Close() - - select { - case <-done: - break - case <-time.After(10 * time.Second): - t.Error("Timeout") - } } func TestDecoder_SourceClose(t *testing.T) { diff --git a/pkg/controller/replication_controller_test.go b/pkg/controller/replication_controller_test.go index fd319b149d0..1012ed01e0c 100644 --- a/pkg/controller/replication_controller_test.go +++ b/pkg/controller/replication_controller_test.go @@ -27,6 +27,8 @@ import ( "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" @@ -109,7 +111,7 @@ func validateSyncReplication(t *testing.T, fakePodControl *FakePodControl, expec } func TestSyncReplicationControllerDoesNothing(t *testing.T) { - body, _ := runtime.DefaultCodec.Encode(newPodList(2)) + body, _ := latest.Codec.Encode(newPodList(2)) fakeHandler := util.FakeHandler{ StatusCode: 200, ResponseBody: string(body), @@ -129,7 +131,7 @@ func TestSyncReplicationControllerDoesNothing(t *testing.T) { } func TestSyncReplicationControllerDeletes(t *testing.T) { - body, _ := runtime.DefaultCodec.Encode(newPodList(2)) + body, _ := latest.Codec.Encode(newPodList(2)) fakeHandler := util.FakeHandler{ StatusCode: 200, ResponseBody: string(body), @@ -149,7 +151,7 @@ func TestSyncReplicationControllerDeletes(t *testing.T) { } func TestSyncReplicationControllerCreates(t *testing.T) { - body, _ := runtime.DefaultCodec.Encode(newPodList(0)) + body, _ := latest.Codec.Encode(newPodList(0)) fakeHandler := util.FakeHandler{ StatusCode: 200, ResponseBody: string(body), @@ -169,7 +171,7 @@ func TestSyncReplicationControllerCreates(t *testing.T) { } func TestCreateReplica(t *testing.T) { - body, _ := runtime.DefaultCodec.Encode(&api.Pod{}) + body, _ := v1beta1.Codec.Encode(&api.Pod{}) fakeHandler := util.FakeHandler{ StatusCode: 200, ResponseBody: string(body), @@ -209,7 +211,7 @@ func TestCreateReplica(t *testing.T) { expectedPod := api.Pod{ JSONBase: api.JSONBase{ Kind: "Pod", - APIVersion: "v1beta1", + APIVersion: latest.Version, }, Labels: controllerSpec.DesiredState.PodTemplate.Labels, DesiredState: controllerSpec.DesiredState.PodTemplate.DesiredState, @@ -287,12 +289,12 @@ func TestSyncronize(t *testing.T) { fakePodHandler := util.FakeHandler{ StatusCode: 200, - ResponseBody: "{\"apiVersion\": \"v1beta1\", \"kind\": \"PodList\"}", + ResponseBody: "{\"apiVersion\": \"" + latest.Version + "\", \"kind\": \"PodList\"}", T: t, } fakeControllerHandler := util.FakeHandler{ StatusCode: 200, - ResponseBody: runtime.DefaultScheme.EncodeOrDie(&api.ReplicationControllerList{ + ResponseBody: runtime.EncodeOrDie(latest.Codec, &api.ReplicationControllerList{ Items: []api.ReplicationController{ controllerSpec1, controllerSpec2, diff --git a/pkg/conversion/decode.go b/pkg/conversion/decode.go index c2c67e72092..23a4a119634 100644 --- a/pkg/conversion/decode.go +++ b/pkg/conversion/decode.go @@ -25,14 +25,14 @@ import ( // Decode converts a YAML or JSON string back into a pointer to an api object. // Deduces the type based upon the fields added by the MetaInsertionFactory // technique. The object will be converted, if necessary, into the -// s.InternalVersion type before being returned. Decode will refuse to decode -// objects without a version, because that's probably an error. +// s.InternalVersion type before being returned. Decode will not decode +// objects without version set unless InternalVersion is also "". func (s *Scheme) Decode(data []byte) (interface{}, error) { version, kind, err := s.DataVersionAndKind(data) if err != nil { return nil, err } - if version == "" { + if version == "" && s.InternalVersion != "" { return nil, fmt.Errorf("version not set in '%s'", string(data)) } obj, err := s.NewObject(version, kind) diff --git a/pkg/conversion/encode.go b/pkg/conversion/encode.go index e82ddce9236..0f17f6057ab 100644 --- a/pkg/conversion/encode.go +++ b/pkg/conversion/encode.go @@ -21,16 +21,7 @@ import ( "fmt" ) -// EncodeOrDie is a version of Encode which will panic instead of returning an error. For tests. -func (s *Scheme) EncodeOrDie(obj interface{}) string { - bytes, err := s.Encode(obj) - if err != nil { - panic(err) - } - return string(bytes) -} - -// Encode turns the given api object into an appropriate JSON string. +// EncodeToVersion turns the given api object into an appropriate JSON string. // Obj may be a pointer to a struct, or a struct. If a struct, a copy // will be made, therefore it's recommended to pass a pointer to a // struct. The type must have been registered. @@ -58,11 +49,6 @@ func (s *Scheme) EncodeOrDie(obj interface{}) string { // objects, whether they be in our storage layer (e.g., etcd), or in user's // config files. // -func (s *Scheme) Encode(obj interface{}) (data []byte, err error) { - return s.EncodeToVersion(obj, s.ExternalVersion) -} - -// EncodeToVersion is like Encode, but you may choose the version. func (s *Scheme) EncodeToVersion(obj interface{}, destVersion string) (data []byte, err error) { obj = maybeCopy(obj) v, _ := enforcePtr(obj) // maybeCopy guarantees a pointer diff --git a/pkg/conversion/scheme.go b/pkg/conversion/scheme.go index c77b80e4703..d7711265623 100644 --- a/pkg/conversion/scheme.go +++ b/pkg/conversion/scheme.go @@ -66,9 +66,6 @@ type Scheme struct { // you use "" for the internal version. InternalVersion string - // ExternalVersion is the default external version. - ExternalVersion string - // MetaInsertionFactory is used to create an object to store and retrieve // the version and kind information for all objects. The default uses the // keys "version" and "kind" respectively. @@ -83,7 +80,6 @@ func NewScheme() *Scheme { typeToKind: map[reflect.Type]string{}, converter: NewConverter(), InternalVersion: "", - ExternalVersion: "v1", MetaInsertionFactory: metaInsertion{}, } s.converter.NameFunc = s.nameFunc @@ -146,6 +142,19 @@ func (s *Scheme) AddKnownTypeWithName(version, kind string, obj interface{}) { s.typeToKind[t] = kind } +// KnownTypes returns an array of the types that are known for a particular version. +func (s *Scheme) KnownTypes(version string) map[string]reflect.Type { + all, ok := s.versionMap[version] + if !ok { + return map[string]reflect.Type{} + } + types := make(map[string]reflect.Type) + for k, v := range all { + types[k] = v + } + return types +} + // NewObject returns a new object of the given version and name, // or an error if it hasn't been registered. func (s *Scheme) NewObject(versionName, typeName string) (interface{}, error) { diff --git a/pkg/conversion/scheme_test.go b/pkg/conversion/scheme_test.go index d59f6dde450..e75708478a3 100644 --- a/pkg/conversion/scheme_test.go +++ b/pkg/conversion/scheme_test.go @@ -125,7 +125,6 @@ func GetTestScheme() *Scheme { s.AddKnownTypes("v1", &ExternalInternalSame{}) s.AddKnownTypeWithName("v1", "TestType1", &ExternalTestType1{}) s.AddKnownTypeWithName("v1", "TestType2", &ExternalTestType2{}) - s.ExternalVersion = "v1" s.InternalVersion = "" s.MetaInsertionFactory = testMetaInsertionFactory{} return s @@ -178,7 +177,7 @@ func runTest(t *testing.T, source interface{}) { TestObjectFuzzer.Fuzz(source) s := GetTestScheme() - data, err := s.Encode(source) + data, err := s.EncodeToVersion(source, "v1") if err != nil { t.Errorf("%v: %v (%#v)", name, err, source) return @@ -221,7 +220,7 @@ func TestEncode_NonPtr(t *testing.T) { s := GetTestScheme() tt := TestType1{A: "I'm not a pointer object"} obj := interface{}(tt) - data, err := s.Encode(obj) + data, err := s.EncodeToVersion(obj, "v1") obj2, err2 := s.Decode(data) if err != nil || err2 != nil { t.Fatalf("Failure: '%v' '%v'", err, err2) @@ -238,7 +237,7 @@ func TestEncode_Ptr(t *testing.T) { s := GetTestScheme() tt := &TestType1{A: "I am a pointer object"} obj := interface{}(tt) - data, err := s.Encode(obj) + data, err := s.EncodeToVersion(obj, "v1") obj2, err2 := s.Decode(data) if err != nil || err2 != nil { t.Fatalf("Failure: '%v' '%v'", err, err2) @@ -255,7 +254,6 @@ func TestBadJSONRejection(t *testing.T) { s := GetTestScheme() badJSONs := [][]byte{ []byte(`{"myVersionKey":"v1"}`), // Missing kind - []byte(`{"myKindKey":"TestType1"}`), // Missing version []byte(`{"myVersionKey":"v1","myKindKey":"bar"}`), // Unknown kind []byte(`{"myVersionKey":"bar","myKindKey":"TestType1"}`), // Unknown version } @@ -270,6 +268,23 @@ func TestBadJSONRejection(t *testing.T) { } } +func TestBadJSONRejectionForSetInternalVersion(t *testing.T) { + s := GetTestScheme() + s.InternalVersion = "v1" + badJSONs := [][]byte{ + []byte(`{"myKindKey":"TestType1"}`), // Missing version + } + for _, b := range badJSONs { + if _, err := s.Decode(b); err == nil { + t.Errorf("Did not reject bad json: %s", string(b)) + } + } + badJSONKindMismatch := []byte(`{"myVersionKey":"v1","myKindKey":"ExternalInternalSame"}`) + if err := s.DecodeInto(badJSONKindMismatch, &TestType1{}); err == nil { + t.Errorf("Kind is set but doesn't match the object type: %s", badJSONKindMismatch) + } +} + func TestMetaValues(t *testing.T) { type InternalSimple struct { Version string `json:"version,omitempty" yaml:"version,omitempty"` @@ -283,7 +298,6 @@ func TestMetaValues(t *testing.T) { } s := NewScheme() s.InternalVersion = "" - s.ExternalVersion = "externalVersion" s.AddKnownTypeWithName("", "Simple", &InternalSimple{}) s.AddKnownTypeWithName("externalVersion", "Simple", &ExternalSimple{}) @@ -373,7 +387,6 @@ func TestMetaValuesUnregisteredConvert(t *testing.T) { } s := NewScheme() s.InternalVersion = "" - s.ExternalVersion = "externalVersion" // We deliberately don't register the types. internalToExternalCalls := 0 diff --git a/pkg/kubecfg/kubecfg_test.go b/pkg/kubecfg/kubecfg_test.go index 092c3978897..120f4e3d03c 100644 --- a/pkg/kubecfg/kubecfg_test.go +++ b/pkg/kubecfg/kubecfg_test.go @@ -26,7 +26,6 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" - "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" ) func validateAction(expectedAction, actualAction client.FakeAction, t *testing.T) { @@ -94,7 +93,7 @@ func TestUpdateWithNewImage(t *testing.T) { } validateAction(client.FakeAction{Action: "get-controller", Value: "foo"}, fakeClient.Actions[0], t) - newCtrl := runtime.DefaultScheme.CopyOrDie(&fakeClient.Ctrl).(*api.ReplicationController) + newCtrl := api.Scheme.CopyOrDie(&fakeClient.Ctrl).(*api.ReplicationController) newCtrl.DesiredState.PodTemplate.DesiredState.Manifest.Containers[0].Image = "fooImage:2" validateAction(client.FakeAction{Action: "update-controller", Value: newCtrl}, fakeClient.Actions[1], t) diff --git a/pkg/kubecfg/parse_test.go b/pkg/kubecfg/parse_test.go index 5681bfd7610..25db54f26f1 100644 --- a/pkg/kubecfg/parse_test.go +++ b/pkg/kubecfg/parse_test.go @@ -21,27 +21,29 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" + _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "gopkg.in/v1/yaml" ) func TestParseBadStorage(t *testing.T) { p := NewParser(map[string]runtime.Object{}) - _, err := p.ToWireFormat([]byte("{}"), "badstorage", runtime.DefaultCodec) + _, err := p.ToWireFormat([]byte("{}"), "badstorage", latest.Codec) if err == nil { t.Errorf("Expected error, received none") } } func DoParseTest(t *testing.T, storage string, obj runtime.Object, p *Parser) { - jsonData, _ := runtime.DefaultCodec.Encode(obj) + jsonData, _ := latest.Codec.Encode(obj) var tmp map[string]interface{} json.Unmarshal(jsonData, &tmp) yamlData, _ := yaml.Marshal(tmp) t.Logf("Intermediate yaml:\n%v\n", string(yamlData)) t.Logf("Intermediate json:\n%v\n", string(jsonData)) - jsonGot, jsonErr := p.ToWireFormat(jsonData, storage, runtime.DefaultCodec) - yamlGot, yamlErr := p.ToWireFormat(yamlData, storage, runtime.DefaultCodec) + jsonGot, jsonErr := p.ToWireFormat(jsonData, storage, latest.Codec) + yamlGot, yamlErr := p.ToWireFormat(yamlData, storage, latest.Codec) if jsonErr != nil { t.Errorf("json err: %#v", jsonErr) @@ -125,8 +127,9 @@ type TestParseType struct { func (*TestParseType) IsAnAPIObject() {} func TestParseCustomType(t *testing.T) { - runtime.DefaultScheme.AddKnownTypes("", &TestParseType{}) - runtime.DefaultScheme.AddKnownTypes("v1beta1", &TestParseType{}) + api.Scheme.AddKnownTypes("", &TestParseType{}) + api.Scheme.AddKnownTypes("v1beta1", &TestParseType{}) + api.Scheme.AddKnownTypes("v1beta2", &TestParseType{}) parser := NewParser(map[string]runtime.Object{ "custom": &TestParseType{}, }) diff --git a/pkg/kubecfg/proxy_server.go b/pkg/kubecfg/proxy_server.go index 002ba6530ee..4517bbc0c21 100644 --- a/pkg/kubecfg/proxy_server.go +++ b/pkg/kubecfg/proxy_server.go @@ -21,8 +21,8 @@ import ( "net/http" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" - "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" ) // ProxyServer is a http.Handler which proxies Kubernetes APIs to remote API server. @@ -53,7 +53,7 @@ func (s *ProxyServer) Serve() error { func (s *ProxyServer) doError(w http.ResponseWriter, err error) { w.WriteHeader(http.StatusInternalServerError) w.Header().Add("Content-type", "application/json") - data, _ := runtime.DefaultCodec.Encode(&api.Status{ + data, _ := latest.Codec.Encode(&api.Status{ Status: api.StatusFailure, Message: fmt.Sprintf("internal error: %#v", err), }) diff --git a/pkg/kubecfg/resource_printer.go b/pkg/kubecfg/resource_printer.go index b3c5a54d348..cbc0d920ec2 100644 --- a/pkg/kubecfg/resource_printer.go +++ b/pkg/kubecfg/resource_printer.go @@ -26,6 +26,7 @@ import ( "text/template" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/golang/glog" @@ -50,7 +51,7 @@ func (i *IdentityPrinter) Print(data []byte, w io.Writer) error { // PrintObj is an implementation of ResourcePrinter.PrintObj which simply writes the object to the Writer. func (i *IdentityPrinter) PrintObj(obj runtime.Object, output io.Writer) error { - data, err := runtime.DefaultCodec.Encode(obj) + data, err := latest.Codec.Encode(obj) if err != nil { return err } @@ -260,7 +261,7 @@ func (h *HumanReadablePrinter) Print(data []byte, output io.Writer) error { return fmt.Errorf("unexpected object with no 'kind' field: %s", data) } - obj, err := runtime.DefaultCodec.Decode(data) + obj, err := latest.Codec.Decode(data) if err != nil { return err } @@ -292,7 +293,7 @@ type TemplatePrinter struct { // Print parses the data as JSON, and re-formats it with the Go Template. func (t *TemplatePrinter) Print(data []byte, w io.Writer) error { - obj, err := runtime.DefaultCodec.Decode(data) + obj, err := latest.Codec.Decode(data) if err != nil { return err } diff --git a/pkg/kubecfg/resource_printer_test.go b/pkg/kubecfg/resource_printer_test.go index 6e72168cc64..c8cf8b5c6f8 100644 --- a/pkg/kubecfg/resource_printer_test.go +++ b/pkg/kubecfg/resource_printer_test.go @@ -25,7 +25,7 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "gopkg.in/v1/yaml" ) @@ -96,7 +96,7 @@ func TestIdentityPrinter(t *testing.T) { } buff.Reset() printer.PrintObj(obj, buff) - objOut, err := runtime.DefaultCodec.Decode([]byte(buff.String())) + objOut, err := latest.Codec.Decode([]byte(buff.String())) if err != nil { t.Errorf("Unexpeted error: %#v", err) } diff --git a/pkg/kubelet/config/etcd.go b/pkg/kubelet/config/etcd.go index e0d0c785d73..1c4672c558d 100644 --- a/pkg/kubelet/config/etcd.go +++ b/pkg/kubelet/config/etcd.go @@ -24,9 +24,8 @@ import ( "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet" - "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/tools" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" @@ -47,8 +46,8 @@ type SourceEtcd struct { func NewSourceEtcd(key string, client tools.EtcdClient, updates chan<- interface{}) *SourceEtcd { helper := tools.EtcdHelper{ client, - runtime.DefaultCodec, - runtime.DefaultResourceVersioner, + latest.Codec, + latest.ResourceVersioner, } source := &SourceEtcd{ key: key, diff --git a/pkg/master/master.go b/pkg/master/master.go index 425862e90ab..7c949a8c4e3 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -20,7 +20,7 @@ import ( "net/http" "time" - _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" @@ -136,5 +136,5 @@ func (m *Master) API_v1beta1() (map[string]apiserver.RESTStorage, runtime.Codec) for k, v := range m.storage { storage[k] = v } - return storage, runtime.DefaultCodec + return storage, v1beta1.Codec } diff --git a/pkg/proxy/config/etcd.go b/pkg/proxy/config/etcd.go index a3a9a6a5f4e..1bb6ab15541 100644 --- a/pkg/proxy/config/etcd.go +++ b/pkg/proxy/config/etcd.go @@ -39,7 +39,7 @@ import ( "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/tools" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/coreos/go-etcd/etcd" @@ -135,7 +135,7 @@ func (s ConfigSourceEtcd) GetServices() ([]api.Service, []api.Endpoints, error) // and create a Service entry for it. for i, node := range response.Node.Nodes { var svc api.Service - err = runtime.DefaultCodec.DecodeInto([]byte(node.Value), &svc) + err = latest.Codec.DecodeInto([]byte(node.Value), &svc) if err != nil { glog.Errorf("Failed to load Service: %s (%#v)", node.Value, err) continue @@ -168,7 +168,7 @@ func (s ConfigSourceEtcd) GetEndpoints(service string) (api.Endpoints, error) { } // Parse all the endpoint specifications in this value. var e api.Endpoints - err = runtime.DefaultCodec.DecodeInto([]byte(response.Node.Value), &e) + err = latest.Codec.DecodeInto([]byte(response.Node.Value), &e) return e, err } @@ -178,7 +178,7 @@ func etcdResponseToService(response *etcd.Response) (*api.Service, error) { return nil, fmt.Errorf("invalid response from etcd: %#v", response) } var svc api.Service - err := runtime.DefaultCodec.DecodeInto([]byte(response.Node.Value), &svc) + err := latest.Codec.DecodeInto([]byte(response.Node.Value), &svc) if err != nil { return nil, err } @@ -232,7 +232,7 @@ func (s ConfigSourceEtcd) ProcessChange(response *etcd.Response) { func (s ConfigSourceEtcd) ProcessEndpointResponse(response *etcd.Response) { glog.Infof("Processing a change in endpoint configuration... %s", *response) var endpoints api.Endpoints - err := runtime.DefaultCodec.DecodeInto([]byte(response.Node.Value), &endpoints) + err := latest.Codec.DecodeInto([]byte(response.Node.Value), &endpoints) if err != nil { glog.Errorf("Failed to parse service out of etcd key: %v : %+v", response.Node.Value, err) return diff --git a/pkg/registry/binding/rest_test.go b/pkg/registry/binding/rest_test.go index 5c8cb688111..3bbc216f287 100644 --- a/pkg/registry/binding/rest_test.go +++ b/pkg/registry/binding/rest_test.go @@ -23,9 +23,8 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" - "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" ) func TestNewREST(t *testing.T) { @@ -38,12 +37,12 @@ func TestNewREST(t *testing.T) { PodID: "foo", Host: "bar", } - body, err := runtime.DefaultCodec.Encode(binding) + body, err := latest.Codec.Encode(binding) if err != nil { t.Fatalf("Unexpected encode error %v", err) } obj := b.New() - err = runtime.DefaultCodec.DecodeInto(body, obj) + err = latest.Codec.DecodeInto(body, obj) if err != nil { t.Fatalf("Unexpected error %v", err) } diff --git a/pkg/registry/controller/rest_test.go b/pkg/registry/controller/rest_test.go index d2f66810aa6..8768d7847b6 100644 --- a/pkg/registry/controller/rest_test.go +++ b/pkg/registry/controller/rest_test.go @@ -26,10 +26,10 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest" - "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" ) func TestListControllersError(t *testing.T) { @@ -113,13 +113,13 @@ func TestControllerDecode(t *testing.T) { ID: "foo", }, } - body, err := runtime.DefaultCodec.Encode(controller) + body, err := latest.Codec.Encode(controller) if err != nil { t.Errorf("unexpected error: %v", err) } controllerOut := storage.New() - if err := runtime.DefaultCodec.DecodeInto(body, controllerOut); err != nil { + if err := latest.Codec.DecodeInto(body, controllerOut); err != nil { t.Errorf("unexpected error: %v", err) } diff --git a/pkg/registry/etcd/etcd.go b/pkg/registry/etcd/etcd.go index 33a279d7272..c74b78a9fa5 100644 --- a/pkg/registry/etcd/etcd.go +++ b/pkg/registry/etcd/etcd.go @@ -21,6 +21,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" etcderr "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors/etcd" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/constraint" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" @@ -45,8 +46,8 @@ func NewRegistry(client tools.EtcdClient) *Registry { registry := &Registry{ EtcdHelper: tools.EtcdHelper{ client, - runtime.DefaultCodec, - runtime.DefaultResourceVersioner, + latest.Codec, + latest.ResourceVersioner, }, } registry.manifestFactory = &BasicManifestFactory{ diff --git a/pkg/registry/etcd/etcd_test.go b/pkg/registry/etcd/etcd_test.go index d55c0582d38..322c58826be 100644 --- a/pkg/registry/etcd/etcd_test.go +++ b/pkg/registry/etcd/etcd_test.go @@ -22,7 +22,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" - _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" @@ -41,7 +41,7 @@ func NewTestEtcdRegistry(client tools.EtcdClient) *Registry { func TestEtcdGetPod(t *testing.T) { fakeClient := tools.NewFakeEtcdClient(t) - fakeClient.Set("/registry/pods/foo", runtime.DefaultScheme.EncodeOrDie(&api.Pod{JSONBase: api.JSONBase{ID: "foo"}}), 0) + fakeClient.Set("/registry/pods/foo", runtime.EncodeOrDie(latest.Codec, &api.Pod{JSONBase: api.JSONBase{ID: "foo"}}), 0) registry := NewTestEtcdRegistry(fakeClient) pod, err := registry.GetPod("foo") if err != nil { @@ -77,7 +77,7 @@ func TestEtcdCreatePod(t *testing.T) { }, E: tools.EtcdErrorNotFound, } - fakeClient.Set("/registry/hosts/machine/kubelet", runtime.DefaultScheme.EncodeOrDie(&api.ContainerManifestList{}), 0) + fakeClient.Set("/registry/hosts/machine/kubelet", runtime.EncodeOrDie(latest.Codec, &api.ContainerManifestList{}), 0) registry := NewTestEtcdRegistry(fakeClient) err := registry.CreatePod(&api.Pod{ JSONBase: api.JSONBase{ @@ -108,7 +108,7 @@ func TestEtcdCreatePod(t *testing.T) { t.Fatalf("Unexpected error %v", err) } var pod api.Pod - err = runtime.DefaultCodec.DecodeInto([]byte(resp.Node.Value), &pod) + err = latest.Codec.DecodeInto([]byte(resp.Node.Value), &pod) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -122,7 +122,7 @@ func TestEtcdCreatePod(t *testing.T) { t.Errorf("unexpected error: %v", err) } - err = runtime.DefaultCodec.DecodeInto([]byte(resp.Node.Value), &manifests) + err = latest.Codec.DecodeInto([]byte(resp.Node.Value), &manifests) if len(manifests.Items) != 1 || manifests.Items[0].ID != "foo" { t.Errorf("Unexpected manifest list: %#v", manifests) } @@ -133,7 +133,7 @@ func TestEtcdCreatePodAlreadyExisting(t *testing.T) { fakeClient.Data["/registry/pods/foo"] = tools.EtcdResponseWithError{ R: &etcd.Response{ Node: &etcd.Node{ - Value: runtime.DefaultScheme.EncodeOrDie(&api.Pod{JSONBase: api.JSONBase{ID: "foo"}}), + Value: runtime.EncodeOrDie(latest.Codec, &api.Pod{JSONBase: api.JSONBase{ID: "foo"}}), }, }, E: nil, @@ -235,7 +235,7 @@ func TestEtcdCreatePodWithContainersNotFound(t *testing.T) { t.Fatalf("Unexpected error %v", err) } var pod api.Pod - err = runtime.DefaultCodec.DecodeInto([]byte(resp.Node.Value), &pod) + err = latest.Codec.DecodeInto([]byte(resp.Node.Value), &pod) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -249,7 +249,7 @@ func TestEtcdCreatePodWithContainersNotFound(t *testing.T) { t.Errorf("unexpected error: %v", err) } - err = runtime.DefaultCodec.DecodeInto([]byte(resp.Node.Value), &manifests) + err = latest.Codec.DecodeInto([]byte(resp.Node.Value), &manifests) if len(manifests.Items) != 1 || manifests.Items[0].ID != "foo" { t.Errorf("Unexpected manifest list: %#v", manifests) } @@ -264,7 +264,7 @@ func TestEtcdCreatePodWithExistingContainers(t *testing.T) { }, E: tools.EtcdErrorNotFound, } - fakeClient.Set("/registry/hosts/machine/kubelet", runtime.DefaultScheme.EncodeOrDie(&api.ContainerManifestList{ + fakeClient.Set("/registry/hosts/machine/kubelet", runtime.EncodeOrDie(latest.Codec, &api.ContainerManifestList{ Items: []api.ContainerManifest{ {ID: "bar"}, }, @@ -300,7 +300,7 @@ func TestEtcdCreatePodWithExistingContainers(t *testing.T) { t.Fatalf("Unexpected error %v", err) } var pod api.Pod - err = runtime.DefaultCodec.DecodeInto([]byte(resp.Node.Value), &pod) + err = latest.Codec.DecodeInto([]byte(resp.Node.Value), &pod) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -314,7 +314,7 @@ func TestEtcdCreatePodWithExistingContainers(t *testing.T) { t.Errorf("unexpected error: %v", err) } - err = runtime.DefaultCodec.DecodeInto([]byte(resp.Node.Value), &manifests) + err = latest.Codec.DecodeInto([]byte(resp.Node.Value), &manifests) if len(manifests.Items) != 2 || manifests.Items[1].ID != "foo" { t.Errorf("Unexpected manifest list: %#v", manifests) } @@ -325,11 +325,11 @@ func TestEtcdDeletePod(t *testing.T) { fakeClient.TestIndex = true key := "/registry/pods/foo" - fakeClient.Set(key, runtime.DefaultScheme.EncodeOrDie(&api.Pod{ + fakeClient.Set(key, runtime.EncodeOrDie(latest.Codec, &api.Pod{ JSONBase: api.JSONBase{ID: "foo"}, DesiredState: api.PodState{Host: "machine"}, }), 0) - fakeClient.Set("/registry/hosts/machine/kubelet", runtime.DefaultScheme.EncodeOrDie(&api.ContainerManifestList{ + fakeClient.Set("/registry/hosts/machine/kubelet", runtime.EncodeOrDie(latest.Codec, &api.ContainerManifestList{ Items: []api.ContainerManifest{ {ID: "foo"}, }, @@ -350,7 +350,7 @@ func TestEtcdDeletePod(t *testing.T) { t.Fatalf("Unexpected error %v", err) } var manifests api.ContainerManifestList - runtime.DefaultCodec.DecodeInto([]byte(response.Node.Value), &manifests) + latest.Codec.DecodeInto([]byte(response.Node.Value), &manifests) if len(manifests.Items) != 0 { t.Errorf("Unexpected container set: %s, expected empty", response.Node.Value) } @@ -361,11 +361,11 @@ func TestEtcdDeletePodMultipleContainers(t *testing.T) { fakeClient.TestIndex = true key := "/registry/pods/foo" - fakeClient.Set(key, runtime.DefaultScheme.EncodeOrDie(&api.Pod{ + fakeClient.Set(key, runtime.EncodeOrDie(latest.Codec, &api.Pod{ JSONBase: api.JSONBase{ID: "foo"}, DesiredState: api.PodState{Host: "machine"}, }), 0) - fakeClient.Set("/registry/hosts/machine/kubelet", runtime.DefaultScheme.EncodeOrDie(&api.ContainerManifestList{ + fakeClient.Set("/registry/hosts/machine/kubelet", runtime.EncodeOrDie(latest.Codec, &api.ContainerManifestList{ Items: []api.ContainerManifest{ {ID: "foo"}, {ID: "bar"}, @@ -388,7 +388,7 @@ func TestEtcdDeletePodMultipleContainers(t *testing.T) { t.Fatalf("Unexpected error %v", err) } var manifests api.ContainerManifestList - runtime.DefaultCodec.DecodeInto([]byte(response.Node.Value), &manifests) + latest.Codec.DecodeInto([]byte(response.Node.Value), &manifests) if len(manifests.Items) != 1 { t.Fatalf("Unexpected manifest set: %#v, expected empty", manifests) } @@ -445,13 +445,13 @@ func TestEtcdListPods(t *testing.T) { Node: &etcd.Node{ Nodes: []*etcd.Node{ { - Value: runtime.DefaultScheme.EncodeOrDie(&api.Pod{ + Value: runtime.EncodeOrDie(latest.Codec, &api.Pod{ JSONBase: api.JSONBase{ID: "foo"}, DesiredState: api.PodState{Host: "machine"}, }), }, { - Value: runtime.DefaultScheme.EncodeOrDie(&api.Pod{ + Value: runtime.EncodeOrDie(latest.Codec, &api.Pod{ JSONBase: api.JSONBase{ID: "bar"}, DesiredState: api.PodState{Host: "machine"}, }), @@ -520,10 +520,10 @@ func TestEtcdListControllers(t *testing.T) { Node: &etcd.Node{ Nodes: []*etcd.Node{ { - Value: runtime.DefaultScheme.EncodeOrDie(&api.ReplicationController{JSONBase: api.JSONBase{ID: "foo"}}), + Value: runtime.EncodeOrDie(latest.Codec, &api.ReplicationController{JSONBase: api.JSONBase{ID: "foo"}}), }, { - Value: runtime.DefaultScheme.EncodeOrDie(&api.ReplicationController{JSONBase: api.JSONBase{ID: "bar"}}), + Value: runtime.EncodeOrDie(latest.Codec, &api.ReplicationController{JSONBase: api.JSONBase{ID: "bar"}}), }, }, }, @@ -543,7 +543,7 @@ func TestEtcdListControllers(t *testing.T) { func TestEtcdGetController(t *testing.T) { fakeClient := tools.NewFakeEtcdClient(t) - fakeClient.Set("/registry/controllers/foo", runtime.DefaultScheme.EncodeOrDie(&api.ReplicationController{JSONBase: api.JSONBase{ID: "foo"}}), 0) + fakeClient.Set("/registry/controllers/foo", runtime.EncodeOrDie(latest.Codec, &api.ReplicationController{JSONBase: api.JSONBase{ID: "foo"}}), 0) registry := NewTestEtcdRegistry(fakeClient) ctrl, err := registry.GetController("foo") if err != nil { @@ -607,7 +607,7 @@ func TestEtcdCreateController(t *testing.T) { t.Fatalf("Unexpected error %v", err) } var ctrl api.ReplicationController - err = runtime.DefaultCodec.DecodeInto([]byte(resp.Node.Value), &ctrl) + err = latest.Codec.DecodeInto([]byte(resp.Node.Value), &ctrl) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -619,7 +619,7 @@ func TestEtcdCreateController(t *testing.T) { func TestEtcdCreateControllerAlreadyExisting(t *testing.T) { fakeClient := tools.NewFakeEtcdClient(t) - fakeClient.Set("/registry/controllers/foo", runtime.DefaultScheme.EncodeOrDie(&api.ReplicationController{JSONBase: api.JSONBase{ID: "foo"}}), 0) + fakeClient.Set("/registry/controllers/foo", runtime.EncodeOrDie(latest.Codec, &api.ReplicationController{JSONBase: api.JSONBase{ID: "foo"}}), 0) registry := NewTestEtcdRegistry(fakeClient) err := registry.CreateController(&api.ReplicationController{ @@ -636,7 +636,7 @@ func TestEtcdUpdateController(t *testing.T) { fakeClient := tools.NewFakeEtcdClient(t) fakeClient.TestIndex = true - resp, _ := fakeClient.Set("/registry/controllers/foo", runtime.DefaultScheme.EncodeOrDie(&api.ReplicationController{JSONBase: api.JSONBase{ID: "foo"}}), 0) + resp, _ := fakeClient.Set("/registry/controllers/foo", runtime.EncodeOrDie(latest.Codec, &api.ReplicationController{JSONBase: api.JSONBase{ID: "foo"}}), 0) registry := NewTestEtcdRegistry(fakeClient) err := registry.UpdateController(&api.ReplicationController{ JSONBase: api.JSONBase{ID: "foo", ResourceVersion: resp.Node.ModifiedIndex}, @@ -662,10 +662,10 @@ func TestEtcdListServices(t *testing.T) { Node: &etcd.Node{ Nodes: []*etcd.Node{ { - Value: runtime.DefaultScheme.EncodeOrDie(&api.Service{JSONBase: api.JSONBase{ID: "foo"}}), + Value: runtime.EncodeOrDie(latest.Codec, &api.Service{JSONBase: api.JSONBase{ID: "foo"}}), }, { - Value: runtime.DefaultScheme.EncodeOrDie(&api.Service{JSONBase: api.JSONBase{ID: "bar"}}), + Value: runtime.EncodeOrDie(latest.Codec, &api.Service{JSONBase: api.JSONBase{ID: "bar"}}), }, }, }, @@ -699,7 +699,7 @@ func TestEtcdCreateService(t *testing.T) { } var service api.Service - err = runtime.DefaultCodec.DecodeInto([]byte(resp.Node.Value), &service) + err = latest.Codec.DecodeInto([]byte(resp.Node.Value), &service) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -711,7 +711,7 @@ func TestEtcdCreateService(t *testing.T) { func TestEtcdCreateServiceAlreadyExisting(t *testing.T) { fakeClient := tools.NewFakeEtcdClient(t) - fakeClient.Set("/registry/services/specs/foo", runtime.DefaultScheme.EncodeOrDie(&api.Service{JSONBase: api.JSONBase{ID: "foo"}}), 0) + fakeClient.Set("/registry/services/specs/foo", runtime.EncodeOrDie(latest.Codec, &api.Service{JSONBase: api.JSONBase{ID: "foo"}}), 0) registry := NewTestEtcdRegistry(fakeClient) err := registry.CreateService(&api.Service{ JSONBase: api.JSONBase{ID: "foo"}, @@ -723,7 +723,7 @@ func TestEtcdCreateServiceAlreadyExisting(t *testing.T) { func TestEtcdGetService(t *testing.T) { fakeClient := tools.NewFakeEtcdClient(t) - fakeClient.Set("/registry/services/specs/foo", runtime.DefaultScheme.EncodeOrDie(&api.Service{JSONBase: api.JSONBase{ID: "foo"}}), 0) + fakeClient.Set("/registry/services/specs/foo", runtime.EncodeOrDie(latest.Codec, &api.Service{JSONBase: api.JSONBase{ID: "foo"}}), 0) registry := NewTestEtcdRegistry(fakeClient) service, err := registry.GetService("foo") if err != nil { @@ -775,7 +775,7 @@ func TestEtcdUpdateService(t *testing.T) { fakeClient := tools.NewFakeEtcdClient(t) fakeClient.TestIndex = true - resp, _ := fakeClient.Set("/registry/services/specs/foo", runtime.DefaultScheme.EncodeOrDie(&api.Service{JSONBase: api.JSONBase{ID: "foo"}}), 0) + resp, _ := fakeClient.Set("/registry/services/specs/foo", runtime.EncodeOrDie(latest.Codec, &api.Service{JSONBase: api.JSONBase{ID: "foo"}}), 0) registry := NewTestEtcdRegistry(fakeClient) testService := api.Service{ JSONBase: api.JSONBase{ID: "foo", ResourceVersion: resp.Node.ModifiedIndex}, @@ -812,10 +812,10 @@ func TestEtcdListEndpoints(t *testing.T) { Node: &etcd.Node{ Nodes: []*etcd.Node{ { - Value: runtime.DefaultScheme.EncodeOrDie(&api.Endpoints{JSONBase: api.JSONBase{ID: "foo"}, Endpoints: []string{"127.0.0.1:8345"}}), + Value: runtime.EncodeOrDie(latest.Codec, &api.Endpoints{JSONBase: api.JSONBase{ID: "foo"}, Endpoints: []string{"127.0.0.1:8345"}}), }, { - Value: runtime.DefaultScheme.EncodeOrDie(&api.Endpoints{JSONBase: api.JSONBase{ID: "bar"}}), + Value: runtime.EncodeOrDie(latest.Codec, &api.Endpoints{JSONBase: api.JSONBase{ID: "bar"}}), }, }, }, @@ -841,7 +841,7 @@ func TestEtcdGetEndpoints(t *testing.T) { Endpoints: []string{"127.0.0.1:34855"}, } - fakeClient.Set("/registry/services/endpoints/foo", runtime.DefaultScheme.EncodeOrDie(endpoints), 0) + fakeClient.Set("/registry/services/endpoints/foo", runtime.EncodeOrDie(latest.Codec, endpoints), 0) got, err := registry.GetEndpoints("foo") if err != nil { @@ -862,7 +862,7 @@ func TestEtcdUpdateEndpoints(t *testing.T) { Endpoints: []string{"baz", "bar"}, } - fakeClient.Set("/registry/services/endpoints/foo", runtime.DefaultScheme.EncodeOrDie(&api.Endpoints{}), 0) + fakeClient.Set("/registry/services/endpoints/foo", runtime.EncodeOrDie(latest.Codec, &api.Endpoints{}), 0) err := registry.UpdateEndpoints(&endpoints) if err != nil { @@ -874,7 +874,7 @@ func TestEtcdUpdateEndpoints(t *testing.T) { t.Fatalf("Unexpected error %v", err) } var endpointsOut api.Endpoints - err = runtime.DefaultCodec.DecodeInto([]byte(response.Node.Value), &endpointsOut) + err = latest.Codec.DecodeInto([]byte(response.Node.Value), &endpointsOut) if !reflect.DeepEqual(endpoints, endpointsOut) { t.Errorf("Unexpected endpoints: %#v, expected %#v", endpointsOut, endpoints) } diff --git a/pkg/registry/pod/rest_test.go b/pkg/registry/pod/rest_test.go index db79805eaca..a06dc5fbe79 100644 --- a/pkg/registry/pod/rest_test.go +++ b/pkg/registry/pod/rest_test.go @@ -24,6 +24,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/fake" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest" @@ -201,13 +202,13 @@ func TestPodDecode(t *testing.T) { ID: "foo", }, } - body, err := runtime.DefaultCodec.Encode(expected) + body, err := latest.Codec.Encode(expected) if err != nil { t.Errorf("unexpected error: %v", err) } actual := storage.New() - if err := runtime.DefaultCodec.DecodeInto(body, actual); err != nil { + if err := latest.Codec.DecodeInto(body, actual); err != nil { t.Errorf("unexpected error: %v", err) } diff --git a/pkg/runtime/embedded.go b/pkg/runtime/embedded.go index 0d118e977f9..9bfb2f67555 100644 --- a/pkg/runtime/embedded.go +++ b/pkg/runtime/embedded.go @@ -20,42 +20,80 @@ import ( "gopkg.in/v1/yaml" ) +// EmbeddedObject must have an appropriate encoder and decoder functions, such that on the +// wire, it's stored as a []byte, but in memory, the contained object is accessable as an +// Object via the Get() function. Only valid API objects may be stored via EmbeddedObject. +// The purpose of this is to allow an API object of type known only at runtime to be +// embedded within other API objects. +// +// Define a Codec variable in your package and import the runtime package and +// then use the commented section below + +/* +// EmbeddedObject implements a Codec specific version of an +// embedded object. +type EmbeddedObject struct { + runtime.Object +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (a *EmbeddedObject) UnmarshalJSON(b []byte) error { + obj, err := runtime.CodecUnmarshalJSON(Codec, b) + a.Object = obj + return err +} + +// MarshalJSON implements the json.Marshaler interface. +func (a EmbeddedObject) MarshalJSON() ([]byte, error) { + return runtime.CodecMarshalJSON(Codec, a.Object) +} + +// SetYAML implements the yaml.Setter interface. +func (a *EmbeddedObject) SetYAML(tag string, value interface{}) bool { + obj, ok := runtime.CodecSetYAML(Codec, tag, value) + a.Object = obj + return ok +} + +// GetYAML implements the yaml.Getter interface. +func (a EmbeddedObject) GetYAML() (tag string, value interface{}) { + return runtime.CodecGetYAML(Codec, a.Object) +} +*/ + // Encode()/Decode() are the canonical way of converting an API object to/from // wire format. This file provides utility functions which permit doing so // recursively, such that API objects of types known only at run time can be // embedded within other API types. // UnmarshalJSON implements the json.Unmarshaler interface. -func (a *EmbeddedObject) UnmarshalJSON(b []byte) error { +func CodecUnmarshalJSON(codec Codec, b []byte) (Object, error) { // Handle JSON's "null": Decode() doesn't expect it. if len(b) == 4 && string(b) == "null" { - a.Object = nil - return nil + return nil, nil } - obj, err := DefaultCodec.Decode(b) + obj, err := codec.Decode(b) if err != nil { - return err + return nil, err } - a.Object = obj - return nil + return obj, nil } // MarshalJSON implements the json.Marshaler interface. -func (a EmbeddedObject) MarshalJSON() ([]byte, error) { - if a.Object == nil { +func CodecMarshalJSON(codec Codec, obj Object) ([]byte, error) { + if obj == nil { // Encode unset/nil objects as JSON's "null". return []byte("null"), nil } - return DefaultCodec.Encode(a.Object) + return codec.Encode(obj) } // SetYAML implements the yaml.Setter interface. -func (a *EmbeddedObject) SetYAML(tag string, value interface{}) bool { +func CodecSetYAML(codec Codec, tag string, value interface{}) (Object, bool) { if value == nil { - a.Object = nil - return true + return nil, true } // Why does the yaml package send value as a map[interface{}]interface{}? // It's especially frustrating because encoding/json does the right thing @@ -67,22 +105,21 @@ func (a *EmbeddedObject) SetYAML(tag string, value interface{}) bool { if err != nil { panic("yaml can't reverse its own object") } - obj, err := DefaultCodec.Decode(b) + obj, err := codec.Decode(b) if err != nil { - return false + return nil, false } - a.Object = obj - return true + return obj, true } // GetYAML implements the yaml.Getter interface. -func (a EmbeddedObject) GetYAML() (tag string, value interface{}) { - if a.Object == nil { +func CodecGetYAML(codec Codec, obj Object) (tag string, value interface{}) { + if obj == nil { value = "null" return } // Encode returns JSON, which is conveniently a subset of YAML. - v, err := DefaultCodec.Encode(a.Object) + v, err := codec.Encode(obj) if err != nil { panic("impossible to encode API object!") } diff --git a/pkg/runtime/embedded_test.go b/pkg/runtime/embedded_test.go index 66d4f39c377..bc461eeab7a 100644 --- a/pkg/runtime/embedded_test.go +++ b/pkg/runtime/embedded_test.go @@ -14,38 +14,72 @@ See the License for the specific language governing permissions and limitations under the License. */ -package runtime +package runtime_test import ( "encoding/json" "reflect" "testing" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" ) +var scheme = runtime.NewScheme() +var Codec = runtime.CodecFor(scheme, "v1test") + +// EmbeddedObject implements a Codec specific version of an +// embedded object. +type EmbeddedObject struct { + runtime.Object +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (a *EmbeddedObject) UnmarshalJSON(b []byte) error { + obj, err := runtime.CodecUnmarshalJSON(Codec, b) + a.Object = obj + return err +} + +// MarshalJSON implements the json.Marshaler interface. +func (a EmbeddedObject) MarshalJSON() ([]byte, error) { + return runtime.CodecMarshalJSON(Codec, a.Object) +} + +// SetYAML implements the yaml.Setter interface. +func (a *EmbeddedObject) SetYAML(tag string, value interface{}) bool { + obj, ok := runtime.CodecSetYAML(Codec, tag, value) + a.Object = obj + return ok +} + +// GetYAML implements the yaml.Getter interface. +func (a EmbeddedObject) GetYAML() (tag string, value interface{}) { + return runtime.CodecGetYAML(Codec, a.Object) +} + type EmbeddedTest struct { - JSONBase `yaml:",inline" json:",inline"` - Object EmbeddedObject `yaml:"object,omitempty" json:"object,omitempty"` - EmptyObject EmbeddedObject `yaml:"emptyObject,omitempty" json:"emptyObject,omitempty"` + runtime.JSONBase `yaml:",inline" json:",inline"` + Object EmbeddedObject `yaml:"object,omitempty" json:"object,omitempty"` + EmptyObject EmbeddedObject `yaml:"emptyObject,omitempty" json:"emptyObject,omitempty"` } func (*EmbeddedTest) IsAnAPIObject() {} func TestEmbeddedObject(t *testing.T) { - // TODO(dbsmith) fix EmbeddedObject to not use DefaultScheme. - s := DefaultScheme + s := scheme s.AddKnownTypes("", &EmbeddedTest{}) - s.AddKnownTypes("v1beta1", &EmbeddedTest{}) + s.AddKnownTypes("v1test", &EmbeddedTest{}) outer := &EmbeddedTest{ - JSONBase: JSONBase{ID: "outer"}, + JSONBase: runtime.JSONBase{ID: "outer"}, Object: EmbeddedObject{ &EmbeddedTest{ - JSONBase: JSONBase{ID: "inner"}, + JSONBase: runtime.JSONBase{ID: "inner"}, }, }, } - wire, err := s.Encode(outer) + wire, err := s.EncodeToVersion(outer, "v1test") if err != nil { t.Fatalf("Unexpected encode error '%v'", err) } diff --git a/pkg/runtime/interfaces.go b/pkg/runtime/interfaces.go index 9a49cdade8d..9abf7f8a6ab 100644 --- a/pkg/runtime/interfaces.go +++ b/pkg/runtime/interfaces.go @@ -16,13 +16,23 @@ limitations under the License. package runtime -// Codec defines methods for serializing and deserializing API objects. -type Codec interface { - Encode(obj Object) (data []byte, err error) +// Decoder defines methods for deserializing API objects into a given type +type Decoder interface { Decode(data []byte) (Object, error) DecodeInto(data []byte, obj Object) error } +// Encoder defines methods for serializing API objects into bytes +type Encoder interface { + Encode(obj Object) (data []byte, err error) +} + +// Codec defines methods for serializing and deserializing API objects. +type Codec interface { + Decoder + Encoder +} + // ResourceVersioner provides methods for setting and retrieving // the resource version from an API object. type ResourceVersioner interface { diff --git a/pkg/runtime/scheme.go b/pkg/runtime/scheme.go index 1e22560a6d3..90df7c664e2 100644 --- a/pkg/runtime/scheme.go +++ b/pkg/runtime/scheme.go @@ -17,16 +17,40 @@ limitations under the License. package runtime import ( + "encoding/json" "fmt" "reflect" "github.com/GoogleCloudPlatform/kubernetes/pkg/conversion" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "gopkg.in/v1/yaml" ) -var DefaultResourceVersioner ResourceVersioner = NewJSONBaseResourceVersioner() -var DefaultScheme = NewScheme("", "v1beta1") -var DefaultCodec Codec = DefaultScheme +// codecWrapper implements encoding to an alternative +// default version for a scheme. +type codecWrapper struct { + *Scheme + version string +} + +// Encode implements Codec +func (c *codecWrapper) Encode(obj Object) ([]byte, error) { + return c.Scheme.EncodeToVersion(obj, c.version) +} + +// CodecFor returns a Codec that invokes Encode with the provided version. +func CodecFor(scheme *Scheme, version string) Codec { + return &codecWrapper{scheme, version} +} + +// EncodeOrDie is a version of Encode which will panic instead of returning an error. For tests. +func EncodeOrDie(codec Codec, obj Object) string { + bytes, err := codec.Encode(obj) + if err != nil { + panic(err) + } + return string(bytes) +} // Scheme defines methods for serializing and deserializing API objects. It // is an adaptation of conversion's Scheme for our API objects. @@ -36,21 +60,13 @@ type Scheme struct { // fromScope gets the input version, desired output version, and desired Scheme // from a conversion.Scope. -func fromScope(s conversion.Scope) (inVersion, outVersion string, scheme *Scheme) { - scheme = DefaultScheme +func (self *Scheme) fromScope(s conversion.Scope) (inVersion, outVersion string, scheme *Scheme) { + scheme = self inVersion = s.Meta().SrcVersion outVersion = s.Meta().DestVersion return inVersion, outVersion, scheme } -func init() { - // Set up a generic mapping between RawExtension and EmbeddedObject. - DefaultScheme.AddConversionFuncs( - embeddedObjectToRawExtension, - rawExtensionToEmbeddedObject, - ) -} - // emptyPlugin is used to copy the Kind field to and from plugin objects. type emptyPlugin struct { PluginBase `json:",inline" yaml:",inline"` @@ -59,14 +75,14 @@ type emptyPlugin struct { // embeddedObjectToRawExtension does the conversion you would expect from the name, using the information // given in conversion.Scope. It's placed in the DefaultScheme as a ConversionFunc to enable plugins; // see the comment for RawExtension. -func embeddedObjectToRawExtension(in *EmbeddedObject, out *RawExtension, s conversion.Scope) error { +func (self *Scheme) embeddedObjectToRawExtension(in *EmbeddedObject, out *RawExtension, s conversion.Scope) error { if in.Object == nil { out.RawJSON = []byte("null") return nil } // Figure out the type and kind of the output object. - _, outVersion, scheme := fromScope(s) + _, outVersion, scheme := self.fromScope(s) _, kind, err := scheme.raw.ObjectVersionAndKind(in.Object) if err != nil { return err @@ -107,13 +123,13 @@ func embeddedObjectToRawExtension(in *EmbeddedObject, out *RawExtension, s conve // rawExtensionToEmbeddedObject does the conversion you would expect from the name, using the information // given in conversion.Scope. It's placed in the DefaultScheme as a ConversionFunc to enable plugins; // see the comment for RawExtension. -func rawExtensionToEmbeddedObject(in *RawExtension, out *EmbeddedObject, s conversion.Scope) error { +func (self *Scheme) rawExtensionToEmbeddedObject(in *RawExtension, out *EmbeddedObject, s conversion.Scope) error { if len(in.RawJSON) == 4 && string(in.RawJSON) == "null" { out.Object = nil return nil } // Figure out the type and kind of the output object. - inVersion, outVersion, scheme := fromScope(s) + inVersion, outVersion, scheme := self.fromScope(s) _, kind, err := scheme.raw.DataVersionAndKind(in.RawJSON) if err != nil { return err @@ -153,13 +169,15 @@ func rawExtensionToEmbeddedObject(in *RawExtension, out *EmbeddedObject, s conve return nil } -// NewScheme creates a new Scheme. A default scheme is provided and accessible -// as the "DefaultScheme" variable. -func NewScheme(internalVersion, externalVersion string) *Scheme { +// NewScheme creates a new Scheme. This scheme is pluggable by default. +func NewScheme() *Scheme { s := &Scheme{conversion.NewScheme()} - s.raw.InternalVersion = internalVersion - s.raw.ExternalVersion = externalVersion + s.raw.InternalVersion = "" s.raw.MetaInsertionFactory = metaInsertion{} + s.raw.AddConversionFuncs( + s.embeddedObjectToRawExtension, + s.rawExtensionToEmbeddedObject, + ) return s } @@ -180,6 +198,10 @@ func (s *Scheme) AddKnownTypeWithName(version, kind string, obj Object) { s.raw.AddKnownTypeWithName(version, kind, obj) } +func (s *Scheme) KnownTypes(version string) map[string]reflect.Type { + return s.raw.KnownTypes(version) +} + // New returns a new API object of the given version ("" for internal // representation) and name, or an error if it hasn't been registered. func (s *Scheme) New(versionName, typeName string) (Object, error) { @@ -236,12 +258,7 @@ func FindJSONBase(obj Object) (JSONBaseInterface, error) { return g, nil } -// EncodeOrDie is a version of Encode which will panic instead of returning an error. For tests. -func (s *Scheme) EncodeOrDie(obj Object) string { - return s.raw.EncodeOrDie(obj) -} - -// Encode turns the given api object into an appropriate JSON string. +// EncodeToVersion turns the given api object into an appropriate JSON string. // Will return an error if the object doesn't have an embedded JSONBase. // Obj may be a pointer to a struct, or a struct. If a struct, a copy // must be made. If a pointer, the object may be modified before encoding, @@ -269,17 +286,6 @@ func (s *Scheme) EncodeOrDie(obj Object) string { // change the memory format yet not break compatibility with any stored // objects, whether they be in our storage layer (e.g., etcd), or in user's // config files. -// -// TODO/next steps: When we add our second versioned type, this package will -// need a version of Encode that lets you choose the wire version. A configurable -// default will be needed, to allow operating in clusters that haven't yet -// upgraded. -// -func (s *Scheme) Encode(obj Object) (data []byte, err error) { - return s.raw.Encode(obj) -} - -// EncodeToVersion is like Encode, but lets you specify the destination version. func (s *Scheme) EncodeToVersion(obj Object, destVersion string) (data []byte, err error) { return s.raw.EncodeToVersion(obj, destVersion) } @@ -332,10 +338,10 @@ func (s *Scheme) DecodeInto(data []byte, obj Object) error { return s.raw.DecodeInto(data, obj) } -// Does a deep copy of an API object. Useful mostly for tests. +// Copy does a deep copy of an API object. Useful mostly for tests. // TODO(dbsmith): implement directly instead of via Encode/Decode func (s *Scheme) Copy(obj Object) (Object, error) { - data, err := s.Encode(obj) + data, err := s.EncodeToVersion(obj, "") if err != nil { return nil, err } @@ -350,6 +356,26 @@ func (s *Scheme) CopyOrDie(obj Object) Object { return newObj } +func ObjectDiff(a, b Object) string { + ab, err := json.Marshal(a) + if err != nil { + panic(fmt.Sprintf("a: %v", err)) + } + bb, err := json.Marshal(b) + if err != nil { + panic(fmt.Sprintf("b: %v", err)) + } + return util.StringDiff(string(ab), string(bb)) + + // An alternate diff attempt, in case json isn't showing you + // the difference. (reflect.DeepEqual makes a distinction between + // nil and empty slices, for example.) + return util.StringDiff( + fmt.Sprintf("%#v", a), + fmt.Sprintf("%#v", b), + ) +} + // metaInsertion implements conversion.MetaInsertionFactory, which lets the conversion // package figure out how to encode our object's types and versions. These fields are // located in our JSONBase. diff --git a/pkg/runtime/scheme_test.go b/pkg/runtime/scheme_test.go index c317c6f7efa..e321bbcdea2 100644 --- a/pkg/runtime/scheme_test.go +++ b/pkg/runtime/scheme_test.go @@ -43,14 +43,15 @@ func (*InternalSimple) IsAnAPIObject() {} func (*ExternalSimple) IsAnAPIObject() {} func TestScheme(t *testing.T) { - runtime.DefaultScheme.AddKnownTypeWithName("", "Simple", &InternalSimple{}) - runtime.DefaultScheme.AddKnownTypeWithName("externalVersion", "Simple", &ExternalSimple{}) + scheme := runtime.NewScheme() + scheme.AddKnownTypeWithName("", "Simple", &InternalSimple{}) + scheme.AddKnownTypeWithName("externalVersion", "Simple", &ExternalSimple{}) internalToExternalCalls := 0 externalToInternalCalls := 0 // Register functions to verify that scope.Meta() gets set correctly. - err := runtime.DefaultScheme.AddConversionFuncs( + err := scheme.AddConversionFuncs( func(in *InternalSimple, out *ExternalSimple, scope conversion.Scope) error { if e, a := "", scope.Meta().SrcVersion; e != a { t.Errorf("Expected '%v', got '%v'", e, a) @@ -85,10 +86,10 @@ func TestScheme(t *testing.T) { // Test Encode, Decode, and DecodeInto obj := runtime.Object(simple) - data, err := runtime.DefaultScheme.EncodeToVersion(obj, "externalVersion") - obj2, err2 := runtime.DefaultScheme.Decode(data) + data, err := scheme.EncodeToVersion(obj, "externalVersion") + obj2, err2 := scheme.Decode(data) obj3 := &InternalSimple{} - err3 := runtime.DefaultScheme.DecodeInto(data, obj3) + err3 := scheme.DecodeInto(data, obj3) if err != nil || err2 != nil { t.Fatalf("Failure: '%v' '%v' '%v'", err, err2, err3) } @@ -104,7 +105,7 @@ func TestScheme(t *testing.T) { // Test Convert external := &ExternalSimple{} - err = runtime.DefaultScheme.Convert(simple, external) + err = scheme.Convert(simple, external) if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -123,12 +124,13 @@ func TestScheme(t *testing.T) { } func TestBadJSONRejection(t *testing.T) { + scheme := runtime.NewScheme() badJSONMissingKind := []byte(`{ }`) - if _, err := runtime.DefaultScheme.Decode(badJSONMissingKind); err == nil { + if _, err := scheme.Decode(badJSONMissingKind); err == nil { t.Errorf("Did not reject despite lack of kind field: %s", badJSONMissingKind) } badJSONUnknownType := []byte(`{"kind": "bar"}`) - if _, err1 := runtime.DefaultScheme.Decode(badJSONUnknownType); err1 == nil { + if _, err1 := scheme.Decode(badJSONUnknownType); err1 == nil { t.Errorf("Did not reject despite use of unknown type: %s", badJSONUnknownType) } /*badJSONKindMismatch := []byte(`{"kind": "Pod"}`) @@ -163,12 +165,13 @@ func (*ExternalExtensionType) IsAnAPIObject() {} func (*InternalExtensionType) IsAnAPIObject() {} func TestExtensionMapping(t *testing.T) { - runtime.DefaultScheme.AddKnownTypeWithName("", "ExtensionType", &InternalExtensionType{}) - runtime.DefaultScheme.AddKnownTypeWithName("", "A", &ExtensionA{}) - runtime.DefaultScheme.AddKnownTypeWithName("", "B", &ExtensionB{}) - runtime.DefaultScheme.AddKnownTypeWithName("testExternal", "ExtensionType", &ExternalExtensionType{}) - runtime.DefaultScheme.AddKnownTypeWithName("testExternal", "A", &ExtensionA{}) - runtime.DefaultScheme.AddKnownTypeWithName("testExternal", "B", &ExtensionB{}) + scheme := runtime.NewScheme() + scheme.AddKnownTypeWithName("", "ExtensionType", &InternalExtensionType{}) + scheme.AddKnownTypeWithName("", "A", &ExtensionA{}) + scheme.AddKnownTypeWithName("", "B", &ExtensionB{}) + scheme.AddKnownTypeWithName("testExternal", "ExtensionType", &ExternalExtensionType{}) + scheme.AddKnownTypeWithName("testExternal", "A", &ExtensionA{}) + scheme.AddKnownTypeWithName("testExternal", "B", &ExtensionB{}) table := []struct { obj runtime.Object @@ -187,14 +190,14 @@ func TestExtensionMapping(t *testing.T) { } for _, item := range table { - gotEncoded, err := runtime.DefaultScheme.EncodeToVersion(item.obj, "testExternal") + gotEncoded, err := scheme.EncodeToVersion(item.obj, "testExternal") if err != nil { t.Errorf("unexpected error '%v' (%#v)", err, item.obj) } else if e, a := item.encoded, string(gotEncoded); e != a { t.Errorf("expected %v, got %v", e, a) } - gotDecoded, err := runtime.DefaultScheme.Decode([]byte(item.encoded)) + gotDecoded, err := scheme.Decode([]byte(item.encoded)) if err != nil { t.Errorf("unexpected error '%v' (%v)", err, item.encoded) } else if e, a := item.obj, gotDecoded; !reflect.DeepEqual(e, a) { @@ -209,3 +212,25 @@ func TestExtensionMapping(t *testing.T) { } } } + +func TestEncode(t *testing.T) { + scheme := runtime.NewScheme() + scheme.AddKnownTypeWithName("", "Simple", &InternalSimple{}) + scheme.AddKnownTypeWithName("externalVersion", "Simple", &ExternalSimple{}) + codec := runtime.CodecFor(scheme, "externalVersion") + test := &InternalSimple{ + TestString: "I'm the same", + } + obj := runtime.Object(test) + data, err := codec.Encode(obj) + obj2, err2 := codec.Decode(data) + if err != nil || err2 != nil { + t.Fatalf("Failure: '%v' '%v'", err, err2) + } + if _, ok := obj2.(*InternalSimple); !ok { + t.Fatalf("Got wrong type") + } + if !reflect.DeepEqual(obj2, test) { + t.Errorf("Expected:\n %#v,\n Got:\n %#v", &test, obj2) + } +} diff --git a/pkg/service/endpoints_controller_test.go b/pkg/service/endpoints_controller_test.go index f68f6f3c250..c1a3b73c651 100644 --- a/pkg/service/endpoints_controller_test.go +++ b/pkg/service/endpoints_controller_test.go @@ -23,6 +23,7 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" diff --git a/pkg/tools/etcd_tools_test.go b/pkg/tools/etcd_tools_test.go index f7c497f641a..32c937c8f2f 100644 --- a/pkg/tools/etcd_tools_test.go +++ b/pkg/tools/etcd_tools_test.go @@ -24,6 +24,7 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/coreos/go-etcd/etcd" @@ -42,13 +43,14 @@ type TestResource struct { func (*TestResource) IsAnAPIObject() {} var scheme *runtime.Scheme -var codec = runtime.DefaultCodec -var versioner = runtime.DefaultResourceVersioner +var codec runtime.Codec +var versioner = runtime.NewJSONBaseResourceVersioner() func init() { - scheme = runtime.NewScheme("", "v1beta1") + scheme = runtime.NewScheme() scheme.AddKnownTypes("", &TestResource{}) scheme.AddKnownTypes("v1beta1", &TestResource{}) + codec = runtime.CodecFor(scheme, "v1beta1") } func TestIsEtcdNotFound(t *testing.T) { @@ -93,7 +95,7 @@ func TestExtractList(t *testing.T) { } var got []api.Pod - helper := EtcdHelper{fakeClient, codec, versioner} + helper := EtcdHelper{fakeClient, latest.Codec, versioner} resourceVersion := uint64(0) err := helper.ExtractList("/some/key", &got, &resourceVersion) if err != nil { @@ -114,7 +116,7 @@ func TestExtractObj(t *testing.T) { fakeClient := NewFakeEtcdClient(t) expect := api.Pod{JSONBase: api.JSONBase{ID: "foo"}} fakeClient.Set("/some/key", util.EncodeJSON(expect), 0) - helper := EtcdHelper{fakeClient, codec, versioner} + helper := EtcdHelper{fakeClient, latest.Codec, versioner} var got api.Pod err := helper.ExtractObj("/some/key", &got, false) if err != nil { @@ -168,12 +170,12 @@ func TestExtractObjNotFoundErr(t *testing.T) { func TestSetObj(t *testing.T) { obj := &api.Pod{JSONBase: api.JSONBase{ID: "foo"}} fakeClient := NewFakeEtcdClient(t) - helper := EtcdHelper{fakeClient, codec, versioner} + helper := EtcdHelper{fakeClient, latest.Codec, versioner} err := helper.SetObj("/some/key", obj) if err != nil { t.Errorf("Unexpected error %#v", err) } - data, err := codec.Encode(obj) + data, err := latest.Codec.Encode(obj) if err != nil { t.Errorf("Unexpected error %#v", err) } @@ -191,18 +193,18 @@ func TestSetObjWithVersion(t *testing.T) { fakeClient.Data["/some/key"] = EtcdResponseWithError{ R: &etcd.Response{ Node: &etcd.Node{ - Value: runtime.DefaultScheme.EncodeOrDie(obj), + Value: runtime.EncodeOrDie(latest.Codec, obj), ModifiedIndex: 1, }, }, } - helper := EtcdHelper{fakeClient, codec, versioner} + helper := EtcdHelper{fakeClient, latest.Codec, versioner} err := helper.SetObj("/some/key", obj) if err != nil { t.Fatalf("Unexpected error %#v", err) } - data, err := codec.Encode(obj) + data, err := latest.Codec.Encode(obj) if err != nil { t.Fatalf("Unexpected error %#v", err) } @@ -216,12 +218,12 @@ func TestSetObjWithVersion(t *testing.T) { func TestSetObjWithoutResourceVersioner(t *testing.T) { obj := &api.Pod{JSONBase: api.JSONBase{ID: "foo"}} fakeClient := NewFakeEtcdClient(t) - helper := EtcdHelper{fakeClient, codec, nil} + helper := EtcdHelper{fakeClient, latest.Codec, nil} err := helper.SetObj("/some/key", obj) if err != nil { t.Errorf("Unexpected error %#v", err) } - data, err := codec.Encode(obj) + data, err := latest.Codec.Encode(obj) if err != nil { t.Errorf("Unexpected error %#v", err) } @@ -235,7 +237,6 @@ func TestSetObjWithoutResourceVersioner(t *testing.T) { func TestAtomicUpdate(t *testing.T) { fakeClient := NewFakeEtcdClient(t) fakeClient.TestIndex = true - codec := scheme helper := EtcdHelper{fakeClient, codec, runtime.NewJSONBaseResourceVersioner()} // Create a new node. @@ -290,7 +291,7 @@ func TestAtomicUpdate(t *testing.T) { func TestAtomicUpdateNoChange(t *testing.T) { fakeClient := NewFakeEtcdClient(t) fakeClient.TestIndex = true - helper := EtcdHelper{fakeClient, scheme, runtime.NewJSONBaseResourceVersioner()} + helper := EtcdHelper{fakeClient, codec, runtime.NewJSONBaseResourceVersioner()} // Create a new node. fakeClient.ExpectNotFoundGet("/some/key") @@ -321,7 +322,6 @@ func TestAtomicUpdateNoChange(t *testing.T) { func TestAtomicUpdate_CreateCollision(t *testing.T) { fakeClient := NewFakeEtcdClient(t) fakeClient.TestIndex = true - codec := scheme helper := EtcdHelper{fakeClient, codec, runtime.NewJSONBaseResourceVersioner()} fakeClient.ExpectNotFoundGet("/some/key") diff --git a/pkg/tools/etcd_tools_watch_test.go b/pkg/tools/etcd_tools_watch_test.go index 273ecfe3713..67af1e28f35 100644 --- a/pkg/tools/etcd_tools_watch_test.go +++ b/pkg/tools/etcd_tools_watch_test.go @@ -23,12 +23,14 @@ import ( "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" "github.com/coreos/go-etcd/etcd" ) func TestWatchInterpretations(t *testing.T) { + codec := latest.Codec // Declare some pods to make the test cases compact. podFoo := &api.Pod{JSONBase: api.JSONBase{ID: "foo"}} podBar := &api.Pod{JSONBase: api.JSONBase{ID: "bar"}} @@ -48,62 +50,62 @@ func TestWatchInterpretations(t *testing.T) { }{ "create": { actions: []string{"create", "get"}, - nodeValue: runtime.DefaultScheme.EncodeOrDie(podBar), + nodeValue: runtime.EncodeOrDie(codec, podBar), expectEmit: true, expectType: watch.Added, expectObject: podBar, }, "create but filter blocks": { actions: []string{"create", "get"}, - nodeValue: runtime.DefaultScheme.EncodeOrDie(podFoo), + nodeValue: runtime.EncodeOrDie(codec, podFoo), expectEmit: false, }, "delete": { actions: []string{"delete"}, - prevNodeValue: runtime.DefaultScheme.EncodeOrDie(podBar), + prevNodeValue: runtime.EncodeOrDie(codec, podBar), expectEmit: true, expectType: watch.Deleted, expectObject: podBar, }, "delete but filter blocks": { actions: []string{"delete"}, - nodeValue: runtime.DefaultScheme.EncodeOrDie(podFoo), + nodeValue: runtime.EncodeOrDie(codec, podFoo), expectEmit: false, }, "modify appears to create 1": { actions: []string{"set", "compareAndSwap"}, - nodeValue: runtime.DefaultScheme.EncodeOrDie(podBar), + nodeValue: runtime.EncodeOrDie(codec, podBar), expectEmit: true, expectType: watch.Added, expectObject: podBar, }, "modify appears to create 2": { actions: []string{"set", "compareAndSwap"}, - prevNodeValue: runtime.DefaultScheme.EncodeOrDie(podFoo), - nodeValue: runtime.DefaultScheme.EncodeOrDie(podBar), + prevNodeValue: runtime.EncodeOrDie(codec, podFoo), + nodeValue: runtime.EncodeOrDie(codec, podBar), expectEmit: true, expectType: watch.Added, expectObject: podBar, }, "modify appears to delete": { actions: []string{"set", "compareAndSwap"}, - prevNodeValue: runtime.DefaultScheme.EncodeOrDie(podBar), - nodeValue: runtime.DefaultScheme.EncodeOrDie(podFoo), + prevNodeValue: runtime.EncodeOrDie(codec, podBar), + nodeValue: runtime.EncodeOrDie(codec, podFoo), expectEmit: true, expectType: watch.Deleted, expectObject: podBar, // Should return last state that passed the filter! }, "modify modifies": { actions: []string{"set", "compareAndSwap"}, - prevNodeValue: runtime.DefaultScheme.EncodeOrDie(podBar), - nodeValue: runtime.DefaultScheme.EncodeOrDie(podBaz), + prevNodeValue: runtime.EncodeOrDie(codec, podBar), + nodeValue: runtime.EncodeOrDie(codec, podBaz), expectEmit: true, expectType: watch.Modified, expectObject: podBaz, }, "modify ignores": { actions: []string{"set", "compareAndSwap"}, - nodeValue: runtime.DefaultScheme.EncodeOrDie(podFoo), + nodeValue: runtime.EncodeOrDie(codec, podFoo), expectEmit: false, }, } @@ -197,6 +199,7 @@ func TestWatchInterpretation_ResponseBadData(t *testing.T) { } func TestWatch(t *testing.T) { + codec := latest.Codec fakeClient := NewFakeEtcdClient(t) fakeClient.expectNotFoundGetSet["/some/key"] = struct{}{} h := EtcdHelper{fakeClient, codec, versioner} @@ -243,6 +246,7 @@ func TestWatch(t *testing.T) { } func TestWatchEtcdState(t *testing.T) { + codec := latest.Codec type T struct { Type watch.EventType Endpoints []string @@ -259,7 +263,7 @@ func TestWatchEtcdState(t *testing.T) { { Action: "create", Node: &etcd.Node{ - Value: string(runtime.DefaultScheme.EncodeOrDie(&api.Endpoints{JSONBase: api.JSONBase{ID: "foo"}, Endpoints: []string{}})), + Value: string(runtime.EncodeOrDie(codec, &api.Endpoints{JSONBase: api.JSONBase{ID: "foo"}, Endpoints: []string{}})), }, }, }, @@ -273,12 +277,12 @@ func TestWatchEtcdState(t *testing.T) { { Action: "compareAndSwap", Node: &etcd.Node{ - Value: string(runtime.DefaultScheme.EncodeOrDie(&api.Endpoints{JSONBase: api.JSONBase{ID: "foo"}, Endpoints: []string{"127.0.0.1:9000"}})), + Value: string(runtime.EncodeOrDie(codec, &api.Endpoints{JSONBase: api.JSONBase{ID: "foo"}, Endpoints: []string{"127.0.0.1:9000"}})), CreatedIndex: 1, ModifiedIndex: 2, }, PrevNode: &etcd.Node{ - Value: string(runtime.DefaultScheme.EncodeOrDie(&api.Endpoints{JSONBase: api.JSONBase{ID: "foo"}, Endpoints: []string{}})), + Value: string(runtime.EncodeOrDie(codec, &api.Endpoints{JSONBase: api.JSONBase{ID: "foo"}, Endpoints: []string{}})), CreatedIndex: 1, ModifiedIndex: 1, }, @@ -295,7 +299,7 @@ func TestWatchEtcdState(t *testing.T) { R: &etcd.Response{ Action: "get", Node: &etcd.Node{ - Value: string(runtime.DefaultScheme.EncodeOrDie(&api.Endpoints{JSONBase: api.JSONBase{ID: "foo"}, Endpoints: []string{}})), + Value: string(runtime.EncodeOrDie(codec, &api.Endpoints{JSONBase: api.JSONBase{ID: "foo"}, Endpoints: []string{}})), CreatedIndex: 1, ModifiedIndex: 1, }, @@ -308,12 +312,12 @@ func TestWatchEtcdState(t *testing.T) { { Action: "compareAndSwap", Node: &etcd.Node{ - Value: string(runtime.DefaultScheme.EncodeOrDie(&api.Endpoints{JSONBase: api.JSONBase{ID: "foo"}, Endpoints: []string{"127.0.0.1:9000"}})), + Value: string(runtime.EncodeOrDie(codec, &api.Endpoints{JSONBase: api.JSONBase{ID: "foo"}, Endpoints: []string{"127.0.0.1:9000"}})), CreatedIndex: 1, ModifiedIndex: 2, }, PrevNode: &etcd.Node{ - Value: string(runtime.DefaultScheme.EncodeOrDie(&api.Endpoints{JSONBase: api.JSONBase{ID: "foo"}, Endpoints: []string{}})), + Value: string(runtime.EncodeOrDie(codec, &api.Endpoints{JSONBase: api.JSONBase{ID: "foo"}, Endpoints: []string{}})), CreatedIndex: 1, ModifiedIndex: 1, }, @@ -359,6 +363,7 @@ func TestWatchEtcdState(t *testing.T) { } func TestWatchFromZeroIndex(t *testing.T) { + codec := latest.Codec pod := &api.Pod{JSONBase: api.JSONBase{ID: "foo"}} testCases := map[string]struct { @@ -370,7 +375,7 @@ func TestWatchFromZeroIndex(t *testing.T) { EtcdResponseWithError{ R: &etcd.Response{ Node: &etcd.Node{ - Value: runtime.DefaultScheme.EncodeOrDie(pod), + Value: runtime.EncodeOrDie(codec, pod), CreatedIndex: 1, ModifiedIndex: 1, }, @@ -385,7 +390,7 @@ func TestWatchFromZeroIndex(t *testing.T) { EtcdResponseWithError{ R: &etcd.Response{ Node: &etcd.Node{ - Value: runtime.DefaultScheme.EncodeOrDie(pod), + Value: runtime.EncodeOrDie(codec, pod), CreatedIndex: 1, ModifiedIndex: 2, }, @@ -434,6 +439,7 @@ func TestWatchFromZeroIndex(t *testing.T) { } func TestWatchListFromZeroIndex(t *testing.T) { + codec := latest.Codec pod := &api.Pod{JSONBase: api.JSONBase{ID: "foo"}} fakeClient := NewFakeEtcdClient(t) @@ -443,13 +449,13 @@ func TestWatchListFromZeroIndex(t *testing.T) { Dir: true, Nodes: etcd.Nodes{ &etcd.Node{ - Value: runtime.DefaultScheme.EncodeOrDie(pod), + Value: runtime.EncodeOrDie(codec, pod), CreatedIndex: 1, ModifiedIndex: 1, Nodes: etcd.Nodes{}, }, &etcd.Node{ - Value: runtime.DefaultScheme.EncodeOrDie(pod), + Value: runtime.EncodeOrDie(codec, pod), CreatedIndex: 2, ModifiedIndex: 2, Nodes: etcd.Nodes{}, diff --git a/plugin/pkg/scheduler/factory/factory_test.go b/plugin/pkg/scheduler/factory/factory_test.go index 9f141ddd20e..4ad09db1862 100644 --- a/plugin/pkg/scheduler/factory/factory_test.go +++ b/plugin/pkg/scheduler/factory/factory_test.go @@ -24,6 +24,7 @@ import ( "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" @@ -113,7 +114,7 @@ func TestPollMinions(t *testing.T) { ml := &api.MinionList{Items: item.minions} handler := util.FakeHandler{ StatusCode: 200, - ResponseBody: runtime.DefaultScheme.EncodeOrDie(ml), + ResponseBody: runtime.EncodeOrDie(latest.Codec, ml), T: t, } mux := http.NewServeMux() @@ -140,7 +141,7 @@ func TestDefaultErrorFunc(t *testing.T) { testPod := &api.Pod{JSONBase: api.JSONBase{ID: "foo"}} handler := util.FakeHandler{ StatusCode: 200, - ResponseBody: runtime.DefaultScheme.EncodeOrDie(testPod), + ResponseBody: runtime.EncodeOrDie(latest.Codec, testPod), T: t, } mux := http.NewServeMux() @@ -259,7 +260,7 @@ func TestBind(t *testing.T) { t.Errorf("Unexpected error: %v", err) continue } - expectedBody := runtime.DefaultScheme.EncodeOrDie(item.binding) + expectedBody := runtime.EncodeOrDie(latest.Codec, item.binding) handler.ValidateRequest(t, "/api/v1beta1/bindings", "POST", &expectedBody) } } diff --git a/test/integration/etcd_tools_test.go b/test/integration/etcd_tools_test.go index c12df1b24ca..87b8134b9c0 100644 --- a/test/integration/etcd_tools_test.go +++ b/test/integration/etcd_tools_test.go @@ -22,6 +22,8 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/tools" "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" @@ -90,9 +92,9 @@ func TestExtractObj(t *testing.T) { func TestWatch(t *testing.T) { client := newEtcdClient() - helper := tools.EtcdHelper{Client: client, Codec: runtime.DefaultCodec, ResourceVersioner: runtime.DefaultResourceVersioner} + helper := tools.EtcdHelper{Client: client, Codec: latest.Codec, ResourceVersioner: latest.ResourceVersioner} withEtcdKey(func(key string) { - resp, err := client.Set(key, runtime.DefaultScheme.EncodeOrDie(&api.Pod{JSONBase: api.JSONBase{ID: "foo"}}), 0) + resp, err := client.Set(key, runtime.EncodeOrDie(v1beta1.Codec, &api.Pod{JSONBase: api.JSONBase{ID: "foo"}}), 0) if err != nil { t.Fatalf("unexpected error: %v", err) }