diff --git a/cmd/integration/integration.go b/cmd/integration/integration.go index 9ec635336a..18c2fcdb86 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 0f9ea4fb5e..83b45c99d6 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" diff --git a/examples/examples_test.go b/examples/examples_test.go index ca369e82d0..1f300762d3 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" diff --git a/pkg/api/conversion.go b/pkg/api/conversion.go new file mode 100644 index 0000000000..36ba27675b --- /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 0000000000..bb76f61a03 --- /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 0000000000..2c835101b7 --- /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 0000000000..997d536bc3 --- /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 909bb906d2..6935cb1b53 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 1ef750a30c..ef84085a7e 100644 --- a/pkg/api/serialization_test.go +++ b/pkg/api/serialization_test.go @@ -17,14 +17,14 @@ 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" @@ -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 := latest.Codec.Encode(source) + data, err := codec.Encode(source) if err != nil { t.Errorf("%v: %v (%#v)", name, err, source) return } - obj2, err := latest.Codec.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 = latest.Codec.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,8 @@ 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) } } } diff --git a/pkg/api/types.go b/pkg/api/types.go index 50b3b1b85c..ec1419b6e7 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 d83c001c2d..14bb3956b6 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 2c15144557..18f68918ee 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 7dd8f83c55..980aa7fc00 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 1dba02b5f4..b85fec9670 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 index 6db40b9e64..fa7c913a0e 100644 --- a/pkg/api/v1beta2/conversion.go +++ b/pkg/api/v1beta2/conversion.go @@ -17,11 +17,67 @@ 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() { - 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 + 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 diff --git a/pkg/api/v1beta2/register.go b/pkg/api/v1beta2/register.go index d8b0333537..79f545330c 100644 --- a/pkg/api/v1beta2/register.go +++ b/pkg/api/v1beta2/register.go @@ -17,14 +17,15 @@ limitations under the License. package v1beta2 import ( + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" ) -// Codec encodes runtime objects to the v1beta2 scheme -var Codec = runtime.CodecFor(runtime.DefaultScheme, "v1beta2") +// Codec encodes internal objects to the v1beta2 scheme +var Codec = runtime.CodecFor(api.Scheme, "v1beta2") func init() { - runtime.DefaultScheme.AddKnownTypes("v1beta2", + api.Scheme.AddKnownTypes("v1beta2", &PodList{}, &Pod{}, &ReplicationControllerList{}, diff --git a/pkg/api/v1beta2/types.go b/pkg/api/v1beta2/types.go index 98742bc386..4816d5f590 100644 --- a/pkg/api/v1beta2/types.go +++ b/pkg/api/v1beta2/types.go @@ -17,9 +17,7 @@ limitations under the License. package v1beta2 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"` @@ -166,7 +164,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 +207,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 +248,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 +297,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 +328,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"` } @@ -550,14 +545,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 +560,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 +633,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/v1beta3/types.go b/pkg/api/v1beta3/types.go index 1dba02b5f4..661bc85574 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 0000000000..288c4cb245 --- /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 0000000000..228568da11 --- /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 aba20f47f6..f926b85b2f 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" @@ -46,8 +47,8 @@ func convert(obj runtime.Object) (runtime.Object, error) { var codec = latest.Codec func init() { - latest.Codec.AddKnownTypes("", &Simple{}, &SimpleList{}) - latest.Codec.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 latest.Codec.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) { diff --git a/pkg/apiserver/watch.go b/pkg/apiserver/watch.go index 38b3ace500..29c2941365 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 70027c1d44..51d5228554 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 0ee69a54c4..5147dce1d6 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" diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go index ebc89b70e1..1f0b2018fb 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" diff --git a/pkg/client/fake.go b/pkg/client/fake.go index c89c7aa3bc..b745f57091 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 f6a6471082..7dd1060caf 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 a0ba4c07b8..24bebadceb 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" @@ -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 0c1778a23c..2a6f91e070 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 e818ba4e04..52d57c0b19 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 ed6ee63ead..1012ed01e0 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" @@ -169,7 +171,7 @@ func TestSyncReplicationControllerCreates(t *testing.T) { } func TestCreateReplica(t *testing.T) { - body, _ := latest.Codec.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: latest.Codec.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 c2c67e7209..23a4a11963 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 e82ddce923..0f17f6057a 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 c77b80e470..d771126562 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 d59f6dde45..e75708478a 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 092c397889..120f4e3d03 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 ffed49805d..25db54f26f 100644 --- a/pkg/kubecfg/parse_test.go +++ b/pkg/kubecfg/parse_test.go @@ -21,6 +21,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" "gopkg.in/v1/yaml" ) @@ -125,8 +127,9 @@ type TestParseType struct { func (*TestParseType) IsAnAPIObject() {} func TestParseCustomType(t *testing.T) { - latest.Codec.AddKnownTypes("", &TestParseType{}) - latest.Codec.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 6059ec0354..4517bbc0c2 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. diff --git a/pkg/kubecfg/resource_printer.go b/pkg/kubecfg/resource_printer.go index 474d6fec97..cbc0d920ec 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" diff --git a/pkg/kubecfg/resource_printer_test.go b/pkg/kubecfg/resource_printer_test.go index e9c738cccc..c8cf8b5c6f 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" ) diff --git a/pkg/kubelet/config/etcd.go b/pkg/kubelet/config/etcd.go index 77bcf290cc..1c4672c558 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" @@ -48,7 +47,7 @@ func NewSourceEtcd(key string, client tools.EtcdClient, updates chan<- interface helper := tools.EtcdHelper{ client, latest.Codec, - runtime.DefaultResourceVersioner, + latest.ResourceVersioner, } source := &SourceEtcd{ key: key, diff --git a/pkg/master/master.go b/pkg/master/master.go index 425862e90a..7c949a8c4e 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 2de91d41a5..1bb6ab1554 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" diff --git a/pkg/registry/binding/rest_test.go b/pkg/registry/binding/rest_test.go index ac254f545b..3bbc216f28 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) { diff --git a/pkg/registry/controller/rest_test.go b/pkg/registry/controller/rest_test.go index eaaeabef1a..8768d7847b 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) { diff --git a/pkg/registry/etcd/etcd.go b/pkg/registry/etcd/etcd.go index b3c57a5ca9..c74b78a9fa 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" @@ -46,7 +47,7 @@ func NewRegistry(client tools.EtcdClient) *Registry { EtcdHelper: tools.EtcdHelper{ client, latest.Codec, - runtime.DefaultResourceVersioner, + latest.ResourceVersioner, }, } registry.manifestFactory = &BasicManifestFactory{ diff --git a/pkg/registry/etcd/etcd_test.go b/pkg/registry/etcd/etcd_test.go index 1de1db8aba..322c58826b 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", latest.Codec.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", latest.Codec.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{ @@ -133,7 +133,7 @@ func TestEtcdCreatePodAlreadyExisting(t *testing.T) { fakeClient.Data["/registry/pods/foo"] = tools.EtcdResponseWithError{ R: &etcd.Response{ Node: &etcd.Node{ - Value: latest.Codec.EncodeOrDie(&api.Pod{JSONBase: api.JSONBase{ID: "foo"}}), + Value: runtime.EncodeOrDie(latest.Codec, &api.Pod{JSONBase: api.JSONBase{ID: "foo"}}), }, }, E: nil, @@ -264,7 +264,7 @@ func TestEtcdCreatePodWithExistingContainers(t *testing.T) { }, E: tools.EtcdErrorNotFound, } - fakeClient.Set("/registry/hosts/machine/kubelet", latest.Codec.EncodeOrDie(&api.ContainerManifestList{ + fakeClient.Set("/registry/hosts/machine/kubelet", runtime.EncodeOrDie(latest.Codec, &api.ContainerManifestList{ Items: []api.ContainerManifest{ {ID: "bar"}, }, @@ -325,11 +325,11 @@ func TestEtcdDeletePod(t *testing.T) { fakeClient.TestIndex = true key := "/registry/pods/foo" - fakeClient.Set(key, latest.Codec.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", latest.Codec.EncodeOrDie(&api.ContainerManifestList{ + fakeClient.Set("/registry/hosts/machine/kubelet", runtime.EncodeOrDie(latest.Codec, &api.ContainerManifestList{ Items: []api.ContainerManifest{ {ID: "foo"}, }, @@ -361,11 +361,11 @@ func TestEtcdDeletePodMultipleContainers(t *testing.T) { fakeClient.TestIndex = true key := "/registry/pods/foo" - fakeClient.Set(key, latest.Codec.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", latest.Codec.EncodeOrDie(&api.ContainerManifestList{ + fakeClient.Set("/registry/hosts/machine/kubelet", runtime.EncodeOrDie(latest.Codec, &api.ContainerManifestList{ Items: []api.ContainerManifest{ {ID: "foo"}, {ID: "bar"}, @@ -445,13 +445,13 @@ func TestEtcdListPods(t *testing.T) { Node: &etcd.Node{ Nodes: []*etcd.Node{ { - Value: latest.Codec.EncodeOrDie(&api.Pod{ + Value: runtime.EncodeOrDie(latest.Codec, &api.Pod{ JSONBase: api.JSONBase{ID: "foo"}, DesiredState: api.PodState{Host: "machine"}, }), }, { - Value: latest.Codec.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: latest.Codec.EncodeOrDie(&api.ReplicationController{JSONBase: api.JSONBase{ID: "foo"}}), + Value: runtime.EncodeOrDie(latest.Codec, &api.ReplicationController{JSONBase: api.JSONBase{ID: "foo"}}), }, { - Value: latest.Codec.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", latest.Codec.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 { @@ -619,7 +619,7 @@ func TestEtcdCreateController(t *testing.T) { func TestEtcdCreateControllerAlreadyExisting(t *testing.T) { fakeClient := tools.NewFakeEtcdClient(t) - fakeClient.Set("/registry/controllers/foo", latest.Codec.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", latest.Codec.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: latest.Codec.EncodeOrDie(&api.Service{JSONBase: api.JSONBase{ID: "foo"}}), + Value: runtime.EncodeOrDie(latest.Codec, &api.Service{JSONBase: api.JSONBase{ID: "foo"}}), }, { - Value: latest.Codec.EncodeOrDie(&api.Service{JSONBase: api.JSONBase{ID: "bar"}}), + Value: runtime.EncodeOrDie(latest.Codec, &api.Service{JSONBase: api.JSONBase{ID: "bar"}}), }, }, }, @@ -711,7 +711,7 @@ func TestEtcdCreateService(t *testing.T) { func TestEtcdCreateServiceAlreadyExisting(t *testing.T) { fakeClient := tools.NewFakeEtcdClient(t) - fakeClient.Set("/registry/services/specs/foo", latest.Codec.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", latest.Codec.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", latest.Codec.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: latest.Codec.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: latest.Codec.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", latest.Codec.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", latest.Codec.EncodeOrDie(&api.Endpoints{}), 0) + fakeClient.Set("/registry/services/endpoints/foo", runtime.EncodeOrDie(latest.Codec, &api.Endpoints{}), 0) err := registry.UpdateEndpoints(&endpoints) if err != nil { diff --git a/pkg/registry/pod/rest_test.go b/pkg/registry/pod/rest_test.go index 8078dd3e75..a06dc5fbe7 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" diff --git a/pkg/runtime/embedded.go b/pkg/runtime/embedded.go index 0d118e977f..9bfb2f6755 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 66d4f39c37..bc461eeab7 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 9a49cdade8..9abf7f8a6a 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 1e22560a6d..90df7c664e 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 c317c6f7ef..e321bbcdea 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 f68f6f3c25..c1a3b73c65 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 b4d93e5cd0..32c937c8f2 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 = latest.Codec -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: latest.Codec.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 25f266eadf..67af1e28f3 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: latest.Codec.EncodeOrDie(podBar), + nodeValue: runtime.EncodeOrDie(codec, podBar), expectEmit: true, expectType: watch.Added, expectObject: podBar, }, "create but filter blocks": { actions: []string{"create", "get"}, - nodeValue: latest.Codec.EncodeOrDie(podFoo), + nodeValue: runtime.EncodeOrDie(codec, podFoo), expectEmit: false, }, "delete": { actions: []string{"delete"}, - prevNodeValue: latest.Codec.EncodeOrDie(podBar), + prevNodeValue: runtime.EncodeOrDie(codec, podBar), expectEmit: true, expectType: watch.Deleted, expectObject: podBar, }, "delete but filter blocks": { actions: []string{"delete"}, - nodeValue: latest.Codec.EncodeOrDie(podFoo), + nodeValue: runtime.EncodeOrDie(codec, podFoo), expectEmit: false, }, "modify appears to create 1": { actions: []string{"set", "compareAndSwap"}, - nodeValue: latest.Codec.EncodeOrDie(podBar), + nodeValue: runtime.EncodeOrDie(codec, podBar), expectEmit: true, expectType: watch.Added, expectObject: podBar, }, "modify appears to create 2": { actions: []string{"set", "compareAndSwap"}, - prevNodeValue: latest.Codec.EncodeOrDie(podFoo), - nodeValue: latest.Codec.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: latest.Codec.EncodeOrDie(podBar), - nodeValue: latest.Codec.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: latest.Codec.EncodeOrDie(podBar), - nodeValue: latest.Codec.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: latest.Codec.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(latest.Codec.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(latest.Codec.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(latest.Codec.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(latest.Codec.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(latest.Codec.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(latest.Codec.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: latest.Codec.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: latest.Codec.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: latest.Codec.EncodeOrDie(pod), + Value: runtime.EncodeOrDie(codec, pod), CreatedIndex: 1, ModifiedIndex: 1, Nodes: etcd.Nodes{}, }, &etcd.Node{ - Value: latest.Codec.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 02a8b5b221..4ad09db186 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: latest.Codec.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: latest.Codec.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 := latest.Codec.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 3b194e35a3..87b8134b9c 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: latest.Codec, ResourceVersioner: runtime.DefaultResourceVersioner} + helper := tools.EtcdHelper{Client: client, Codec: latest.Codec, ResourceVersioner: latest.ResourceVersioner} withEtcdKey(func(key string) { - resp, err := client.Set(key, latest.Codec.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) }