Merge pull request #571 from lavalamp/master

Decode/Encode everywhere
This commit is contained in:
brendandburns 2014-07-29 17:19:33 -07:00
commit b0d18b2af0
23 changed files with 927 additions and 964 deletions

View File

@ -68,6 +68,7 @@ func TestAPIObject(t *testing.T) {
// Things that Decode would have done for us:
decodedViaJSON.Kind = ""
decodedViaJSON.APIVersion = ""
if e, a := outer, &decodedViaJSON; !reflect.DeepEqual(e, a) {
t.Errorf("Expected: %#v but got %#v", e, a)

133
pkg/api/defaultcopy.go Normal file
View File

@ -0,0 +1,133 @@
/*
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 (
"fmt"
"reflect"
)
// DefaultCopy copies API objects to/from their corresponding types
// in a versioned package (e.g., v1beta1). Only suitable for types
// in which no fields changed.
// dest and src must both be pointers to API objects.
// Not safe for objects with cyclic references!
// TODO: Allow overrides, using the same function mechanism that
// util.Fuzzer allows.
func DefaultCopy(src, dest interface{}) error {
dv, sv := reflect.ValueOf(dest), reflect.ValueOf(src)
if dv.Kind() != reflect.Ptr {
return fmt.Errorf("Need pointer, but got %#v", dest)
}
if sv.Kind() != reflect.Ptr {
return fmt.Errorf("Need pointer, but got %#v", src)
}
dv = dv.Elem()
sv = sv.Elem()
if !dv.CanAddr() {
return fmt.Errorf("Can't write to dest")
}
// Ensure there's no reversed src/dest bugs by making src unwriteable.
sv = reflect.ValueOf(sv.Interface())
if sv.CanAddr() {
return fmt.Errorf("Can write to src, shouldn't be able to.")
}
return copyValue(sv, dv)
}
// Recursively copy sv into dv
func copyValue(sv, dv reflect.Value) error {
dt, st := dv.Type(), sv.Type()
if dt.Name() != st.Name() {
return fmt.Errorf("Type names don't match: %v, %v", dt.Name(), st.Name())
}
// This should handle all simple types.
if st.AssignableTo(dt) {
dv.Set(sv)
return nil
} else if st.ConvertibleTo(dt) {
dv.Set(sv.Convert(dt))
return nil
}
// For debugging, should you need to do that.
if false {
fmt.Printf("copyVal of %v.%v (%v) -> %v.%v (%v)\n",
st.PkgPath(), st.Name(), st.Kind(),
dt.PkgPath(), dt.Name(), dt.Kind())
}
switch dv.Kind() {
case reflect.Struct:
for i := 0; i < dt.NumField(); i++ {
f := dv.Type().Field(i)
df := dv.FieldByName(f.Name)
sf := sv.FieldByName(f.Name)
if !df.IsValid() || !sf.IsValid() {
return fmt.Errorf("%v not present in source and dest.", f.Name)
}
if err := copyValue(sf, df); err != nil {
return err
}
}
case reflect.Slice:
if sv.IsNil() {
// Don't make a zero-length slice.
dv.Set(reflect.Zero(dt))
return nil
}
dv.Set(reflect.MakeSlice(dt, sv.Len(), sv.Cap()))
for i := 0; i < sv.Len(); i++ {
if err := copyValue(sv.Index(i), dv.Index(i)); err != nil {
return err
}
}
case reflect.Ptr:
if sv.IsNil() {
// Don't copy a nil ptr!
dv.Set(reflect.Zero(dt))
return nil
}
dv.Set(reflect.New(dt.Elem()))
return copyValue(sv.Elem(), dv.Elem())
case reflect.Map:
if sv.IsNil() {
// Don't copy a nil ptr!
dv.Set(reflect.Zero(dt))
return nil
}
dv.Set(reflect.MakeMap(dt))
for _, sk := range sv.MapKeys() {
dk := reflect.New(dt.Key()).Elem()
if err := copyValue(sk, dk); err != nil {
return err
}
dkv := reflect.New(dt.Elem()).Elem()
if err := copyValue(sv.MapIndex(sk), dkv); err != nil {
return err
}
dv.SetMapIndex(dk, dkv)
}
default:
return fmt.Errorf("Couldn't copy %#v (%v) into %#v (%v)",
sv.Interface(), sv.Kind(), dv.Interface(), dv.Kind())
}
return nil
}

File diff suppressed because it is too large Load Diff

View File

@ -17,12 +17,82 @@ limitations under the License.
package api
import (
"encoding/json"
"flag"
"fmt"
"reflect"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
)
var fuzzIters = flag.Int("fuzz_iters", 3, "How many fuzzing iterations to do.")
// apiObjectFuzzer can randomly populate api objects.
var apiObjectFuzzer = util.NewFuzzer(
func(j *JSONBase) {
// We have to customize the randomization of JSONBases because their
// APIVersion and Kind must remain blank in memory.
j.APIVersion = ""
j.Kind = ""
j.ID = util.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 = util.RandUint64() >> 8
j.SelfLink = util.RandString()
j.CreationTimestamp = util.RandString()
},
func(intstr *util.IntOrString) {
// util.IntOrString will panic if its kind is set wrong.
if util.RandBool() {
intstr.Kind = util.IntstrInt
intstr.IntVal = int(util.RandUint64())
intstr.StrVal = ""
} else {
intstr.Kind = util.IntstrString
intstr.IntVal = 0
intstr.StrVal = util.RandString()
}
},
func(p *PodInfo) {
// The docker container type doesn't survive fuzzing.
// TODO: fix this.
*p = nil
},
)
func objDiff(a, b interface{}) string {
// 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),
)
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))
}
func runTest(t *testing.T, source interface{}) {
name := reflect.TypeOf(source).Name()
name := reflect.TypeOf(source).Elem().Name()
apiObjectFuzzer.Fuzz(source)
j, err := FindJSONBase(source)
if err != nil {
t.Fatalf("Unexpected error %v for %#v", err, source)
}
j.SetKind("")
j.SetAPIVersion("")
data, err := Encode(source)
if err != nil {
t.Errorf("%v: %v (%#v)", name, err, source)
@ -32,58 +102,51 @@ func runTest(t *testing.T, source interface{}) {
if err != nil {
t.Errorf("%v: %v", name, err)
return
}
if !reflect.DeepEqual(source, obj2) {
t.Errorf("1: %v: wanted %#v, got %#v", name, source, obj2)
return
} else {
if !reflect.DeepEqual(source, obj2) {
t.Errorf("1: %v: diff: %v", name, objDiff(source, obj2))
return
}
}
obj3 := reflect.New(reflect.TypeOf(source).Elem()).Interface()
err = DecodeInto(data, obj3)
if err != nil {
t.Errorf("2: %v: %v", name, err)
return
}
if !reflect.DeepEqual(source, obj3) {
t.Errorf("3: %v: wanted %#v, got %#v", name, source, obj3)
return
} else {
if !reflect.DeepEqual(source, obj3) {
t.Errorf("3: %v: diff: %v", name, objDiff(source, obj3))
return
}
}
}
func TestTypes(t *testing.T) {
// TODO: auto-fill all fields.
table := []interface{}{
&Pod{
JSONBase: JSONBase{
ID: "mylittlepod",
},
Labels: map[string]string{
"name": "pinky",
},
},
&PodList{},
&Pod{},
&ServiceList{},
&Service{},
&ServiceList{
Items: []Service{
{
Labels: map[string]string{
"foo": "bar",
},
}, {
Labels: map[string]string{
"foo": "baz",
},
},
},
},
&ReplicationControllerList{},
&ReplicationController{},
&PodList{},
&MinionList{},
&Minion{},
&Status{},
&ServerOpList{},
&ServerOp{},
&ContainerManifestList{},
&Endpoints{},
}
for _, item := range table {
runTest(t, item)
// Try a few times, since runTest uses random values.
for i := 0; i < *fuzzIters; i++ {
runTest(t, item)
}
}
}
func TestNonPtr(t *testing.T) {
func TestEncode_NonPtr(t *testing.T) {
pod := Pod{
Labels: map[string]string{"name": "foo"},
}
@ -91,30 +154,30 @@ func TestNonPtr(t *testing.T) {
data, err := Encode(obj)
obj2, err2 := Decode(data)
if err != nil || err2 != nil {
t.Errorf("Failure: %v %v", err2, err2)
t.Fatalf("Failure: '%v' '%v'", err, err2)
}
if _, ok := obj2.(*Pod); !ok {
t.Errorf("Got wrong type")
t.Fatalf("Got wrong type")
}
if !reflect.DeepEqual(obj2, &pod) {
t.Errorf("Expected:\n %#v,\n Got:\n %#v", &pod, obj2)
}
}
func TestPtr(t *testing.T) {
pod := Pod{
func TestEncode_Ptr(t *testing.T) {
pod := &Pod{
Labels: map[string]string{"name": "foo"},
}
obj := interface{}(&pod)
obj := interface{}(pod)
data, err := Encode(obj)
obj2, err2 := Decode(data)
if err != nil || err2 != nil {
t.Errorf("Failure: %v %v", err2, err2)
t.Fatalf("Failure: '%v' '%v'", err, err2)
}
if _, ok := obj2.(*Pod); !ok {
t.Errorf("Got wrong type")
t.Fatalf("Got wrong type")
}
if !reflect.DeepEqual(obj2, &pod) {
if !reflect.DeepEqual(obj2, pod) {
t.Errorf("Expected:\n %#v,\n Got:\n %#v", &pod, obj2)
}
}

108
pkg/api/jsonbase.go Normal file
View File

@ -0,0 +1,108 @@
/*
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 (
"fmt"
"reflect"
)
// JSONBase lets you work with a JSONBase from any of the versioned or
// internal APIObjects.
type JSONBaseInterface interface {
APIVersion() string
SetAPIVersion(version string)
Kind() string
SetKind(kind string)
ResourceVersion() uint64
SetResourceVersion(version uint64)
}
type genericJSONBase struct {
apiVersion *string
kind *string
resourceVersion *uint64
}
func (g genericJSONBase) APIVersion() string {
return *g.apiVersion
}
func (g genericJSONBase) SetAPIVersion(version string) {
*g.apiVersion = version
}
func (g genericJSONBase) Kind() string {
return *g.kind
}
func (g genericJSONBase) SetKind(kind string) {
*g.kind = kind
}
func (g genericJSONBase) ResourceVersion() uint64 {
return *g.resourceVersion
}
func (g genericJSONBase) SetResourceVersion(version uint64) {
*g.resourceVersion = version
}
// fieldPtr puts the address address of fieldName, which must be a member of v,
// into dest, which must be an address of a variable to which this field's address
// can be assigned.
func fieldPtr(v reflect.Value, fieldName string, dest interface{}) error {
field := v.FieldByName(fieldName)
if !field.IsValid() {
return fmt.Errorf("Couldn't find %v field in %#v", fieldName, v.Interface())
}
v = reflect.ValueOf(dest)
if v.Kind() != reflect.Ptr {
return fmt.Errorf("dest should be ptr")
}
v = v.Elem()
field = field.Addr()
if field.Type().AssignableTo(v.Type()) {
v.Set(field)
return nil
}
if field.Type().ConvertibleTo(v.Type()) {
v.Set(field.Convert(v.Type()))
return nil
}
return fmt.Errorf("Couldn't assign/convert %v to %v", field.Type(), v.Type())
}
// newGenericJSONBase makes a new generic JSONBase from v, which must be an
// addressable/setable reflect.Value having the same fields as api.JSONBase.
// Returns an error if this isn't the case.
func newGenericJSONBase(v reflect.Value) (genericJSONBase, error) {
g := genericJSONBase{}
err := fieldPtr(v, "APIVersion", &g.apiVersion)
if err != nil {
return g, err
}
err = fieldPtr(v, "Kind", &g.kind)
if err != nil {
return g, err
}
err = fieldPtr(v, "ResourceVersion", &g.resourceVersion)
if err != nil {
return g, err
}
return g, nil
}

59
pkg/api/jsonbase_test.go Normal file
View File

@ -0,0 +1,59 @@
/*
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 (
"reflect"
"testing"
)
func TestGenericJSONBase(t *testing.T) {
j := JSONBase{
APIVersion: "a",
Kind: "b",
ResourceVersion: 1,
}
g, err := newGenericJSONBase(reflect.ValueOf(&j).Elem())
if err != nil {
t.Fatalf("new err: %v", err)
}
// Proove g supports JSONBaseInterface.
jbi := JSONBaseInterface(g)
if e, a := "a", jbi.APIVersion(); e != a {
t.Errorf("expected %v, got %v", e, a)
}
if e, a := "b", jbi.Kind(); e != a {
t.Errorf("expected %v, got %v", e, a)
}
if e, a := uint64(1), jbi.ResourceVersion(); e != a {
t.Errorf("expected %v, got %v", e, a)
}
jbi.SetAPIVersion("c")
jbi.SetKind("d")
jbi.SetResourceVersion(2)
if e, a := "c", j.APIVersion; e != a {
t.Errorf("expected %v, got %v", e, a)
}
if e, a := "d", j.Kind; e != a {
t.Errorf("expected %v, got %v", e, a)
}
if e, a := uint64(2), j.ResourceVersion; e != a {
t.Errorf("expected %v, got %v", e, a)
}
}

View File

@ -38,7 +38,7 @@ import (
// by the following regex:
// [a-z0-9]([-a-z0-9]*[a-z0-9])?
//
// DNS_SUBDOMAIN: This is a string, no more than 253 characters long, that conforms
// DNS_SUBDOMAIN: This is a string, no more than 253 characters long, that conforms
// to the definition of a "subdomain" in RFCs 1035 and 1123. This is captured
// by the following regex:
// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*
@ -58,6 +58,12 @@ type ContainerManifest struct {
Containers []Container `yaml:"containers" json:"containers"`
}
// ContainerManifestList is used to communicate container manifests to kubelet.
type ContainerManifestList struct {
JSONBase `json:",inline" yaml:",inline"`
Items []ContainerManifest `json:"items,omitempty" yaml:"items,omitempty"`
}
// Volume represents a named volume in a pod that may be accessed by any containers in the pod.
type Volume struct {
// Required: This must be a DNS_LABEL. Each volume in a pod must have
@ -217,7 +223,8 @@ type PodState struct {
// entry per container in the manifest. The value of this map is currently the output
// of `docker inspect`. This output format is *not* final and should not be relied
// upon.
// TODO: Make real decisions about what our info should look like.
// 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"`
}
@ -289,8 +296,8 @@ type Service struct {
// Endpoints is a collection of endpoints that implement the actual service, for example:
// Name: "mysql", Endpoints: ["10.10.1.1:1909", "10.10.2.2:8834"]
type Endpoints struct {
Name string
Endpoints []string
JSONBase `json:",inline" yaml:",inline"`
Endpoints []string `json:"endpoints,omitempty" yaml:"endpoints,omitempty"`
}
// Minion is a worker node in Kubernetenes.

View File

@ -38,7 +38,7 @@ import (
// by the following regex:
// [a-z0-9]([-a-z0-9]*[a-z0-9])?
//
// DNS_SUBDOMAIN: This is a string, no more than 253 characters long, that conforms
// DNS_SUBDOMAIN: This is a string, no more than 253 characters long, that conforms
// to the definition of a "subdomain" in RFCs 1035 and 1123. This is captured
// by the following regex:
// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*
@ -58,6 +58,12 @@ type ContainerManifest struct {
Containers []Container `yaml:"containers" json:"containers"`
}
// ContainerManifestList is used to communicate container manifests to kubelet.
type ContainerManifestList struct {
JSONBase `json:",inline" yaml:",inline"`
Items []ContainerManifest `json:"items,omitempty" yaml:"items,omitempty"`
}
// Volume represents a named volume in a pod that may be accessed by any containers in the pod.
type Volume struct {
// Required: This must be a DNS_LABEL. Each volume in a pod must have
@ -289,8 +295,8 @@ type Service struct {
// Endpoints is a collection of endpoints that implement the actual service, for example:
// Name: "mysql", Endpoints: ["10.10.1.1:1909", "10.10.2.2:8834"]
type Endpoints struct {
Name string
Endpoints []string
JSONBase `json:",inline" yaml:",inline"`
Endpoints []string `json:"endpoints,omitempty" yaml:"endpoints,omitempty"`
}
// Minion is a worker node in Kubernetenes.

View File

@ -9,7 +9,7 @@ You may obtain a copy of the License at
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 sied.
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.
*/
@ -110,12 +110,12 @@ func responseToPods(response *etcd.Response) ([]kubelet.Pod, error) {
return pods, fmt.Errorf("no nodes field: %v", response)
}
manifests := []api.ContainerManifest{}
manifests := api.ContainerManifestList{}
if err := yaml.Unmarshal([]byte(response.Node.Value), &manifests); err != nil {
return pods, fmt.Errorf("could not unmarshal manifests: %v", err)
}
for i, manifest := range manifests {
for i, manifest := range manifests.Items {
name := manifest.ID
if name == "" {
name = fmt.Sprintf("_%d", i+1)

View File

@ -24,7 +24,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/coreos/go-etcd/etcd"
)
@ -36,7 +35,9 @@ func TestGetEtcdData(t *testing.T) {
fakeClient.Data["/registry/hosts/machine/kubelet"] = tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
Value: util.MakeJSONString([]api.ContainerManifest{api.ContainerManifest{ID: "foo"}}),
Value: api.EncodeOrDie(&api.ContainerManifestList{
Items: []api.ContainerManifest{{ID: "foo"}},
}),
ModifiedIndex: 1,
},
},
@ -76,7 +77,9 @@ func TestGetEtcd(t *testing.T) {
fakeClient.Data["/registry/hosts/machine/kubelet"] = tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
Value: util.MakeJSONString([]api.ContainerManifest{api.ContainerManifest{ID: "foo"}}),
Value: api.EncodeOrDie(&api.ContainerManifestList{
Items: []api.ContainerManifest{{ID: "foo"}},
}),
ModifiedIndex: 1,
},
},
@ -103,7 +106,7 @@ func TestWatchEtcd(t *testing.T) {
fakeClient.Data["/registry/hosts/machine/kubelet"] = tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
Value: util.MakeJSONString([]api.Container{}),
Value: api.EncodeOrDie(&api.ContainerManifestList{}),
ModifiedIndex: 2,
},
},

View File

@ -127,19 +127,19 @@ func (s *endpointsStore) Merge(source string, change interface{}) error {
case ADD:
glog.Infof("Adding new endpoint from source %s : %v", source, update.Endpoints)
for _, value := range update.Endpoints {
endpoints[value.Name] = value
endpoints[value.ID] = value
}
case REMOVE:
glog.Infof("Removing an endpoint %v", update)
for _, value := range update.Endpoints {
delete(endpoints, value.Name)
delete(endpoints, value.ID)
}
case SET:
glog.Infof("Setting endpoints %v", update)
// Clear the old map entries by just creating a new map
endpoints = make(map[string]api.Endpoints)
for _, value := range update.Endpoints {
endpoints[value.Name] = value
endpoints[value.ID] = value
}
default:
glog.Infof("Received invalid update type: %v", update)

View File

@ -83,7 +83,7 @@ func (s sortedEndpoints) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s sortedEndpoints) Less(i, j int) bool {
return s[i].Name < s[j].Name
return s[i].ID < s[j].ID
}
type EndpointsHandlerMock struct {
@ -216,8 +216,14 @@ func TestNewMultipleSourcesEndpointsMultipleHandlersAddedAndNotified(t *testing.
handler2 := NewEndpointsHandlerMock()
config.RegisterHandler(handler)
config.RegisterHandler(handler2)
endpointsUpdate1 := CreateEndpointsUpdate(ADD, api.Endpoints{Name: "foo", Endpoints: []string{"endpoint1", "endpoint2"}})
endpointsUpdate2 := CreateEndpointsUpdate(ADD, api.Endpoints{Name: "bar", Endpoints: []string{"endpoint3", "endpoint4"}})
endpointsUpdate1 := CreateEndpointsUpdate(ADD, api.Endpoints{
JSONBase: api.JSONBase{ID: "foo"},
Endpoints: []string{"endpoint1", "endpoint2"},
})
endpointsUpdate2 := CreateEndpointsUpdate(ADD, api.Endpoints{
JSONBase: api.JSONBase{ID: "bar"},
Endpoints: []string{"endpoint3", "endpoint4"},
})
handler.Wait(2)
handler2.Wait(2)
channelOne <- endpointsUpdate1
@ -236,8 +242,14 @@ func TestNewMultipleSourcesEndpointsMultipleHandlersAddRemoveSetAndNotified(t *t
handler2 := NewEndpointsHandlerMock()
config.RegisterHandler(handler)
config.RegisterHandler(handler2)
endpointsUpdate1 := CreateEndpointsUpdate(ADD, api.Endpoints{Name: "foo", Endpoints: []string{"endpoint1", "endpoint2"}})
endpointsUpdate2 := CreateEndpointsUpdate(ADD, api.Endpoints{Name: "bar", Endpoints: []string{"endpoint3", "endpoint4"}})
endpointsUpdate1 := CreateEndpointsUpdate(ADD, api.Endpoints{
JSONBase: api.JSONBase{ID: "foo"},
Endpoints: []string{"endpoint1", "endpoint2"},
})
endpointsUpdate2 := CreateEndpointsUpdate(ADD, api.Endpoints{
JSONBase: api.JSONBase{ID: "bar"},
Endpoints: []string{"endpoint3", "endpoint4"},
})
handler.Wait(2)
handler2.Wait(2)
channelOne <- endpointsUpdate1
@ -248,7 +260,10 @@ func TestNewMultipleSourcesEndpointsMultipleHandlersAddRemoveSetAndNotified(t *t
handler2.ValidateEndpoints(t, endpoints)
// Add one more
endpointsUpdate3 := CreateEndpointsUpdate(ADD, api.Endpoints{Name: "foobar", Endpoints: []string{"endpoint5", "endpoint6"}})
endpointsUpdate3 := CreateEndpointsUpdate(ADD, api.Endpoints{
JSONBase: api.JSONBase{ID: "foobar"},
Endpoints: []string{"endpoint5", "endpoint6"},
})
handler.Wait(1)
handler2.Wait(1)
channelTwo <- endpointsUpdate3
@ -257,7 +272,10 @@ func TestNewMultipleSourcesEndpointsMultipleHandlersAddRemoveSetAndNotified(t *t
handler2.ValidateEndpoints(t, endpoints)
// Update the "foo" service with new endpoints
endpointsUpdate1 = CreateEndpointsUpdate(ADD, api.Endpoints{Name: "foo", Endpoints: []string{"endpoint77"}})
endpointsUpdate1 = CreateEndpointsUpdate(ADD, api.Endpoints{
JSONBase: api.JSONBase{ID: "foo"},
Endpoints: []string{"endpoint77"},
})
handler.Wait(1)
handler2.Wait(1)
channelOne <- endpointsUpdate1
@ -266,7 +284,7 @@ func TestNewMultipleSourcesEndpointsMultipleHandlersAddRemoveSetAndNotified(t *t
handler2.ValidateEndpoints(t, endpoints)
// Remove "bar" service
endpointsUpdate2 = CreateEndpointsUpdate(REMOVE, api.Endpoints{Name: "bar"})
endpointsUpdate2 = CreateEndpointsUpdate(REMOVE, api.Endpoints{JSONBase: api.JSONBase{ID: "bar"}})
handler.Wait(1)
handler2.Wait(1)
channelTwo <- endpointsUpdate2

View File

@ -34,7 +34,6 @@ limitations under the License.
package config
import (
"encoding/json"
"fmt"
"strings"
"time"
@ -127,7 +126,7 @@ func (s ConfigSourceEtcd) GetServices() ([]api.Service, []api.Endpoints, error)
// and create a Service entry for it.
for i, node := range response.Node.Nodes {
var svc api.Service
err = json.Unmarshal([]byte(node.Value), &svc)
err = api.DecodeInto([]byte(node.Value), &svc)
if err != nil {
glog.Errorf("Failed to load Service: %s (%#v)", node.Value, err)
continue
@ -154,7 +153,9 @@ func (s ConfigSourceEtcd) GetEndpoints(service string) (api.Endpoints, error) {
return api.Endpoints{}, err
}
// Parse all the endpoint specifications in this value.
return parseEndpoints(response.Node.Value)
var e api.Endpoints
err = api.DecodeInto([]byte(response.Node.Value), &e)
return e, err
}
// etcdResponseToService takes an etcd response and pulls it apart to find service.
@ -163,19 +164,13 @@ func etcdResponseToService(response *etcd.Response) (*api.Service, error) {
return nil, fmt.Errorf("invalid response from etcd: %#v", response)
}
var svc api.Service
err := json.Unmarshal([]byte(response.Node.Value), &svc)
err := api.DecodeInto([]byte(response.Node.Value), &svc)
if err != nil {
return nil, err
}
return &svc, err
}
func parseEndpoints(jsonString string) (api.Endpoints, error) {
var e api.Endpoints
err := json.Unmarshal([]byte(jsonString), &e)
return e, err
}
func (s ConfigSourceEtcd) WatchForChanges() {
glog.Info("Setting up a watch for new services")
watchChannel := make(chan *etcd.Response)
@ -220,7 +215,7 @@ func (s ConfigSourceEtcd) ProcessChange(response *etcd.Response) {
func (s ConfigSourceEtcd) ProcessEndpointResponse(response *etcd.Response) {
glog.Infof("Processing a change in endpoint configuration... %s", *response)
var endpoints api.Endpoints
err := json.Unmarshal([]byte(response.Node.Value), &endpoints)
err := api.DecodeInto([]byte(response.Node.Value), &endpoints)
if err != nil {
glog.Errorf("Failed to parse service out of etcd key: %v : %+v", response.Node.Value, err)
return

View File

@ -1,56 +0,0 @@
/*
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 config
import (
"encoding/json"
"reflect"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
)
const TomcatContainerEtcdKey = "/registry/services/tomcat/endpoints/tomcat-3bd5af34"
const TomcatService = "tomcat"
const TomcatContainerID = "tomcat-3bd5af34"
func validateJSONParsing(t *testing.T, jsonString string, expectedEndpoints api.Endpoints, expectError bool) {
endpoints, err := parseEndpoints(jsonString)
if err == nil && expectError {
t.Errorf("validateJSONParsing did not get expected error when parsing %s", jsonString)
}
if err != nil && !expectError {
t.Errorf("validateJSONParsing got unexpected error %+v when parsing %s", err, jsonString)
}
if !reflect.DeepEqual(expectedEndpoints, endpoints) {
t.Errorf("Didn't get expected endpoints %+v got: %+v", expectedEndpoints, endpoints)
}
}
func TestParseJsonEndpoints(t *testing.T) {
validateJSONParsing(t, "", api.Endpoints{}, true)
endpoints := api.Endpoints{
Name: "foo",
Endpoints: []string{"foo", "bar", "baz"},
}
data, err := json.Marshal(endpoints)
if err != nil {
t.Errorf("Unexpected error: %#v", err)
}
validateJSONParsing(t, string(data), endpoints, false)
// validateJSONParsing(t, "[{\"port\":8000,\"name\":\"mysql\",\"machine\":\"foo\"},{\"port\":9000,\"name\":\"mysql\",\"machine\":\"bar\"}]", []string{"foo:8000", "bar:9000"}, false)
}

View File

@ -112,7 +112,7 @@ func (s ConfigSourceFile) Run() {
newEndpoints := make([]api.Endpoints, len(config.Services))
for i, service := range config.Services {
newServices[i] = api.Service{JSONBase: api.JSONBase{ID: service.Name}, Port: service.Port}
newEndpoints[i] = api.Endpoints{Name: service.Name, Endpoints: service.Endpoints}
newEndpoints[i] = api.Endpoints{JSONBase: api.JSONBase{ID: service.Name}, Endpoints: service.Endpoints}
}
if !reflect.DeepEqual(lastServices, newServices) {
serviceUpdate := ServiceUpdate{Op: SET, Services: newServices}

View File

@ -52,7 +52,8 @@ func TestProxy(t *testing.T) {
}
lb := NewLoadBalancerRR()
lb.OnUpdate([]api.Endpoints{{"echo", []string{net.JoinHostPort("127.0.0.1", port)}}})
lb.OnUpdate([]api.Endpoints{
{JSONBase: api.JSONBase{ID: "echo"}, Endpoints: []string{net.JoinHostPort("127.0.0.1", port)}}})
p := NewProxier(lb)

View File

@ -89,15 +89,15 @@ func (impl LoadBalancerRR) OnUpdate(endpoints []api.Endpoints) {
defer impl.lock.Unlock()
// First update / add all new endpoints for services.
for _, value := range endpoints {
existingEndpoints, exists := impl.endpointsMap[value.Name]
existingEndpoints, exists := impl.endpointsMap[value.ID]
validEndpoints := impl.filterValidEndpoints(value.Endpoints)
if !exists || !reflect.DeepEqual(existingEndpoints, validEndpoints) {
glog.Infof("LoadBalancerRR: Setting endpoints for %s to %+v", value.Name, value.Endpoints)
impl.endpointsMap[value.Name] = validEndpoints
glog.Infof("LoadBalancerRR: Setting endpoints for %s to %+v", value.ID, value.Endpoints)
impl.endpointsMap[value.ID] = validEndpoints
// Start RR from the beginning if added or updated.
impl.rrIndex[value.Name] = 0
impl.rrIndex[value.ID] = 0
}
tmp[value.Name] = true
tmp[value.ID] = true
}
// Then remove any endpoints no longer relevant
for key, value := range impl.endpointsMap {

View File

@ -87,7 +87,10 @@ func TestLoadBalanceWorksWithSingleEndpoint(t *testing.T) {
t.Errorf("Didn't fail with non-existent service")
}
endpoints := make([]api.Endpoints, 1)
endpoints[0] = api.Endpoints{Name: "foo", Endpoints: []string{"endpoint1:40"}}
endpoints[0] = api.Endpoints{
JSONBase: api.JSONBase{ID: "foo"},
Endpoints: []string{"endpoint1:40"},
}
loadBalancer.OnUpdate(endpoints)
expectEndpoint(t, loadBalancer, "foo", "endpoint1:40")
expectEndpoint(t, loadBalancer, "foo", "endpoint1:40")
@ -102,7 +105,10 @@ func TestLoadBalanceWorksWithMultipleEndpoints(t *testing.T) {
t.Errorf("Didn't fail with non-existent service")
}
endpoints := make([]api.Endpoints, 1)
endpoints[0] = api.Endpoints{Name: "foo", Endpoints: []string{"endpoint:1", "endpoint:2", "endpoint:3"}}
endpoints[0] = api.Endpoints{
JSONBase: api.JSONBase{ID: "foo"},
Endpoints: []string{"endpoint:1", "endpoint:2", "endpoint:3"},
}
loadBalancer.OnUpdate(endpoints)
expectEndpoint(t, loadBalancer, "foo", "endpoint:1")
expectEndpoint(t, loadBalancer, "foo", "endpoint:2")
@ -117,7 +123,10 @@ func TestLoadBalanceWorksWithMultipleEndpointsAndUpdates(t *testing.T) {
t.Errorf("Didn't fail with non-existent service")
}
endpoints := make([]api.Endpoints, 1)
endpoints[0] = api.Endpoints{Name: "foo", Endpoints: []string{"endpoint:1", "endpoint:2", "endpoint:3"}}
endpoints[0] = api.Endpoints{
JSONBase: api.JSONBase{ID: "foo"},
Endpoints: []string{"endpoint:1", "endpoint:2", "endpoint:3"},
}
loadBalancer.OnUpdate(endpoints)
expectEndpoint(t, loadBalancer, "foo", "endpoint:1")
expectEndpoint(t, loadBalancer, "foo", "endpoint:2")
@ -126,14 +135,16 @@ func TestLoadBalanceWorksWithMultipleEndpointsAndUpdates(t *testing.T) {
expectEndpoint(t, loadBalancer, "foo", "endpoint:2")
// Then update the configuration with one fewer endpoints, make sure
// we start in the beginning again
endpoints[0] = api.Endpoints{Name: "foo", Endpoints: []string{"endpoint:8", "endpoint:9"}}
endpoints[0] = api.Endpoints{JSONBase: api.JSONBase{ID: "foo"},
Endpoints: []string{"endpoint:8", "endpoint:9"},
}
loadBalancer.OnUpdate(endpoints)
expectEndpoint(t, loadBalancer, "foo", "endpoint:8")
expectEndpoint(t, loadBalancer, "foo", "endpoint:9")
expectEndpoint(t, loadBalancer, "foo", "endpoint:8")
expectEndpoint(t, loadBalancer, "foo", "endpoint:9")
// Clear endpoints
endpoints[0] = api.Endpoints{Name: "foo", Endpoints: []string{}}
endpoints[0] = api.Endpoints{JSONBase: api.JSONBase{ID: "foo"}, Endpoints: []string{}}
loadBalancer.OnUpdate(endpoints)
endpoint, err = loadBalancer.LoadBalance("foo", nil)
@ -149,8 +160,14 @@ func TestLoadBalanceWorksWithServiceRemoval(t *testing.T) {
t.Errorf("Didn't fail with non-existent service")
}
endpoints := make([]api.Endpoints, 2)
endpoints[0] = api.Endpoints{Name: "foo", Endpoints: []string{"endpoint:1", "endpoint:2", "endpoint:3"}}
endpoints[1] = api.Endpoints{Name: "bar", Endpoints: []string{"endpoint:4", "endpoint:5"}}
endpoints[0] = api.Endpoints{
JSONBase: api.JSONBase{ID: "foo"},
Endpoints: []string{"endpoint:1", "endpoint:2", "endpoint:3"},
}
endpoints[1] = api.Endpoints{
JSONBase: api.JSONBase{ID: "bar"},
Endpoints: []string{"endpoint:4", "endpoint:5"},
}
loadBalancer.OnUpdate(endpoints)
expectEndpoint(t, loadBalancer, "foo", "endpoint:1")
expectEndpoint(t, loadBalancer, "foo", "endpoint:2")

View File

@ -88,7 +88,7 @@ func (e *EndpointController) SyncServiceEndpoints() error {
endpoints[ix] = net.JoinHostPort(pod.CurrentState.PodIP, strconv.Itoa(port))
}
err = e.serviceRegistry.UpdateEndpoints(api.Endpoints{
Name: service.ID,
JSONBase: api.JSONBase{ID: service.ID},
Endpoints: endpoints,
})
if err != nil {

View File

@ -112,9 +112,10 @@ func (registry *EtcdRegistry) runPod(pod api.Pod, machine string) error {
}
contKey := makeContainerKey(machine)
err = registry.helper().AtomicUpdate(contKey, &[]api.ContainerManifest{}, func(in interface{}) (interface{}, error) {
manifests := *in.(*[]api.ContainerManifest)
return append(manifests, manifest), nil
err = registry.helper().AtomicUpdate(contKey, &api.ContainerManifestList{}, func(in interface{}) (interface{}, error) {
manifests := *in.(*api.ContainerManifestList)
manifests.Items = append(manifests.Items, manifest)
return manifests, nil
})
if err != nil {
// Don't strand stuff.
@ -153,11 +154,11 @@ func (registry *EtcdRegistry) deletePodFromMachine(machine, podID string) error
// Next, remove the pod from the machine atomically.
contKey := makeContainerKey(machine)
return registry.helper().AtomicUpdate(contKey, &[]api.ContainerManifest{}, func(in interface{}) (interface{}, error) {
manifests := *in.(*[]api.ContainerManifest)
newManifests := make([]api.ContainerManifest, 0, len(manifests))
return registry.helper().AtomicUpdate(contKey, &api.ContainerManifestList{}, func(in interface{}) (interface{}, error) {
manifests := in.(*api.ContainerManifestList)
newManifests := make([]api.ContainerManifest, 0, len(manifests.Items))
found := false
for _, manifest := range manifests {
for _, manifest := range manifests.Items {
if manifest.ID != podID {
newManifests = append(newManifests, manifest)
} else {
@ -170,7 +171,8 @@ func (registry *EtcdRegistry) deletePodFromMachine(machine, podID string) error
// However it is "deleted" so log it and move on
glog.Infof("Couldn't find: %s in %#v", podID, manifests)
}
return newManifests, nil
manifests.Items = newManifests
return manifests, nil
})
}
@ -304,5 +306,5 @@ func (registry *EtcdRegistry) UpdateService(svc api.Service) error {
// UpdateEndpoints update Endpoints of a Service.
func (registry *EtcdRegistry) UpdateEndpoints(e api.Endpoints) error {
return registry.helper().SetObj("/registry/services/endpoints/"+e.Name, e)
return registry.helper().SetObj("/registry/services/endpoints/"+e.ID, e)
}

View File

@ -17,7 +17,6 @@ limitations under the License.
package registry
import (
"encoding/json"
"reflect"
"testing"
@ -70,7 +69,7 @@ func TestEtcdCreatePod(t *testing.T) {
},
E: tools.EtcdErrorNotFound,
}
fakeClient.Set("/registry/hosts/machine/kubelet", util.MakeJSONString([]api.ContainerManifest{}), 0)
fakeClient.Set("/registry/hosts/machine/kubelet", api.EncodeOrDie(&api.ContainerManifestList{}), 0)
registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"})
err := registry.CreatePod("machine", api.Pod{
JSONBase: api.JSONBase{
@ -88,18 +87,20 @@ func TestEtcdCreatePod(t *testing.T) {
})
expectNoError(t, err)
resp, err := fakeClient.Get("/registry/hosts/machine/pods/foo", false, false)
expectNoError(t, err)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
var pod api.Pod
err = json.Unmarshal([]byte(resp.Node.Value), &pod)
err = api.DecodeInto([]byte(resp.Node.Value), &pod)
expectNoError(t, err)
if pod.ID != "foo" {
t.Errorf("Unexpected pod: %#v %s", pod, resp.Node.Value)
}
var manifests []api.ContainerManifest
var manifests api.ContainerManifestList
resp, err = fakeClient.Get("/registry/hosts/machine/kubelet", false, false)
expectNoError(t, err)
err = json.Unmarshal([]byte(resp.Node.Value), &manifests)
if len(manifests) != 1 || manifests[0].ID != "foo" {
err = api.DecodeInto([]byte(resp.Node.Value), &manifests)
if len(manifests.Items) != 1 || manifests.Items[0].ID != "foo" {
t.Errorf("Unexpected manifest list: %#v", manifests)
}
}
@ -189,18 +190,20 @@ func TestEtcdCreatePodWithContainersNotFound(t *testing.T) {
})
expectNoError(t, err)
resp, err := fakeClient.Get("/registry/hosts/machine/pods/foo", false, false)
expectNoError(t, err)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
var pod api.Pod
err = json.Unmarshal([]byte(resp.Node.Value), &pod)
err = api.DecodeInto([]byte(resp.Node.Value), &pod)
expectNoError(t, err)
if pod.ID != "foo" {
t.Errorf("Unexpected pod: %#v %s", pod, resp.Node.Value)
}
var manifests []api.ContainerManifest
var manifests api.ContainerManifestList
resp, err = fakeClient.Get("/registry/hosts/machine/kubelet", false, false)
expectNoError(t, err)
err = json.Unmarshal([]byte(resp.Node.Value), &manifests)
if len(manifests) != 1 || manifests[0].ID != "foo" {
err = api.DecodeInto([]byte(resp.Node.Value), &manifests)
if len(manifests.Items) != 1 || manifests.Items[0].ID != "foo" {
t.Errorf("Unexpected manifest list: %#v", manifests)
}
}
@ -213,9 +216,9 @@ func TestEtcdCreatePodWithExistingContainers(t *testing.T) {
},
E: tools.EtcdErrorNotFound,
}
fakeClient.Set("/registry/hosts/machine/kubelet", util.MakeJSONString([]api.ContainerManifest{
{
ID: "bar",
fakeClient.Set("/registry/hosts/machine/kubelet", api.EncodeOrDie(api.ContainerManifestList{
Items: []api.ContainerManifest{
{ID: "bar"},
},
}), 0)
registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"})
@ -236,18 +239,20 @@ func TestEtcdCreatePodWithExistingContainers(t *testing.T) {
})
expectNoError(t, err)
resp, err := fakeClient.Get("/registry/hosts/machine/pods/foo", false, false)
expectNoError(t, err)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
var pod api.Pod
err = json.Unmarshal([]byte(resp.Node.Value), &pod)
err = api.DecodeInto([]byte(resp.Node.Value), &pod)
expectNoError(t, err)
if pod.ID != "foo" {
t.Errorf("Unexpected pod: %#v %s", pod, resp.Node.Value)
}
var manifests []api.ContainerManifest
var manifests api.ContainerManifestList
resp, err = fakeClient.Get("/registry/hosts/machine/kubelet", false, false)
expectNoError(t, err)
err = json.Unmarshal([]byte(resp.Node.Value), &manifests)
if len(manifests) != 2 || manifests[1].ID != "foo" {
err = api.DecodeInto([]byte(resp.Node.Value), &manifests)
if len(manifests.Items) != 2 || manifests.Items[1].ID != "foo" {
t.Errorf("Unexpected manifest list: %#v", manifests)
}
}
@ -256,9 +261,9 @@ func TestEtcdDeletePod(t *testing.T) {
fakeClient := tools.MakeFakeEtcdClient(t)
key := "/registry/hosts/machine/pods/foo"
fakeClient.Set(key, util.MakeJSONString(api.Pod{JSONBase: api.JSONBase{ID: "foo"}}), 0)
fakeClient.Set("/registry/hosts/machine/kubelet", util.MakeJSONString([]api.ContainerManifest{
{
ID: "foo",
fakeClient.Set("/registry/hosts/machine/kubelet", api.EncodeOrDie(&api.ContainerManifestList{
Items: []api.ContainerManifest{
{ID: "foo"},
},
}), 0)
registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"})
@ -269,8 +274,13 @@ func TestEtcdDeletePod(t *testing.T) {
} else if fakeClient.DeletedKeys[0] != key {
t.Errorf("Unexpected key: %s, expected %s", fakeClient.DeletedKeys[0], key)
}
response, _ := fakeClient.Get("/registry/hosts/machine/kubelet", false, false)
if response.Node.Value != "[]" {
response, err := fakeClient.Get("/registry/hosts/machine/kubelet", false, false)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
var manifests api.ContainerManifestList
api.DecodeInto([]byte(response.Node.Value), &manifests)
if len(manifests.Items) != 0 {
t.Errorf("Unexpected container set: %s, expected empty", response.Node.Value)
}
}
@ -279,9 +289,11 @@ func TestEtcdDeletePodMultipleContainers(t *testing.T) {
fakeClient := tools.MakeFakeEtcdClient(t)
key := "/registry/hosts/machine/pods/foo"
fakeClient.Set(key, util.MakeJSONString(api.Pod{JSONBase: api.JSONBase{ID: "foo"}}), 0)
fakeClient.Set("/registry/hosts/machine/kubelet", util.MakeJSONString([]api.ContainerManifest{
{ID: "foo"},
{ID: "bar"},
fakeClient.Set("/registry/hosts/machine/kubelet", api.EncodeOrDie(&api.ContainerManifestList{
Items: []api.ContainerManifest{
{ID: "foo"},
{ID: "bar"},
},
}), 0)
registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"})
err := registry.DeletePod("foo")
@ -292,13 +304,16 @@ func TestEtcdDeletePodMultipleContainers(t *testing.T) {
if fakeClient.DeletedKeys[0] != key {
t.Errorf("Unexpected key: %s, expected %s", fakeClient.DeletedKeys[0], key)
}
response, _ := fakeClient.Get("/registry/hosts/machine/kubelet", false, false)
var manifests []api.ContainerManifest
json.Unmarshal([]byte(response.Node.Value), &manifests)
if len(manifests) != 1 {
t.Errorf("Unexpected manifest set: %#v, expected empty", manifests)
response, err := fakeClient.Get("/registry/hosts/machine/kubelet", false, false)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
if manifests[0].ID != "bar" {
var manifests api.ContainerManifestList
api.DecodeInto([]byte(response.Node.Value), &manifests)
if len(manifests.Items) != 1 {
t.Fatalf("Unexpected manifest set: %#v, expected empty", manifests)
}
if manifests.Items[0].ID != "bar" {
t.Errorf("Deleted wrong manifest: %#v", manifests)
}
}
@ -476,9 +491,11 @@ func TestEtcdCreateController(t *testing.T) {
})
expectNoError(t, err)
resp, err := fakeClient.Get("/registry/controllers/foo", false, false)
expectNoError(t, err)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
var ctrl api.ReplicationController
err = json.Unmarshal([]byte(resp.Node.Value), &ctrl)
err = api.DecodeInto([]byte(resp.Node.Value), &ctrl)
expectNoError(t, err)
if ctrl.ID != "foo" {
t.Errorf("Unexpected pod: %#v %s", ctrl, resp.Node.Value)
@ -544,7 +561,7 @@ func TestEtcdCreateService(t *testing.T) {
resp, err := fakeClient.Get("/registry/services/specs/foo", false, false)
expectNoError(t, err)
var service api.Service
err = json.Unmarshal([]byte(resp.Node.Value), &service)
err = api.DecodeInto([]byte(resp.Node.Value), &service)
expectNoError(t, err)
if service.ID != "foo" {
t.Errorf("Unexpected service: %#v %s", service, resp.Node.Value)
@ -621,15 +638,17 @@ func TestEtcdUpdateEndpoints(t *testing.T) {
fakeClient := tools.MakeFakeEtcdClient(t)
registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"})
endpoints := api.Endpoints{
Name: "foo",
JSONBase: api.JSONBase{ID: "foo"},
Endpoints: []string{"baz", "bar"},
}
err := registry.UpdateEndpoints(endpoints)
expectNoError(t, err)
response, err := fakeClient.Get("/registry/services/endpoints/foo", false, false)
expectNoError(t, err)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
var endpointsOut api.Endpoints
err = json.Unmarshal([]byte(response.Node.Value), &endpointsOut)
err = api.DecodeInto([]byte(response.Node.Value), &endpointsOut)
if !reflect.DeepEqual(endpoints, endpointsOut) {
t.Errorf("Unexpected endpoints: %#v, expected %#v", endpointsOut, endpoints)
}

View File

@ -112,7 +112,7 @@ func (h *EtcdHelper) ExtractList(key string, slicePtr interface{}) error {
v := pv.Elem()
for _, node := range nodes {
obj := reflect.New(v.Type().Elem())
err = json.Unmarshal([]byte(node.Value), obj.Interface())
err = api.DecodeInto([]byte(node.Value), obj.Interface())
if err != nil {
return err
}
@ -146,9 +146,9 @@ func (h *EtcdHelper) bodyAndExtractObj(key string, objPtr interface{}, ignoreNot
return "", 0, fmt.Errorf("key '%v' found no nodes field: %#v", key, response)
}
body = response.Node.Value
err = json.Unmarshal([]byte(body), objPtr)
err = api.DecodeInto([]byte(body), objPtr)
if jsonBase, err := api.FindJSONBase(objPtr); err == nil {
jsonBase.ResourceVersion = response.Node.ModifiedIndex
jsonBase.SetResourceVersion(response.Node.ModifiedIndex)
// Note that err shadows the err returned below, so we won't
// return an error just because we failed to find a JSONBase.
// This is intentional.
@ -159,7 +159,7 @@ func (h *EtcdHelper) bodyAndExtractObj(key string, objPtr interface{}, ignoreNot
// SetObj marshals obj via json, and stores under key. Will do an
// atomic update if obj's ResourceVersion field is set.
func (h *EtcdHelper) SetObj(key string, obj interface{}) error {
data, err := json.Marshal(obj)
data, err := api.Encode(obj)
if err != nil {
return err
}

View File

@ -45,10 +45,6 @@ func TestIsNotFoundErr(t *testing.T) {
try(fmt.Errorf("some other kind of error"), false)
}
type testMarshalType struct {
ID string `json:"id"`
}
func TestExtractList(t *testing.T) {
fakeClient := MakeFakeEtcdClient(t)
fakeClient.Data["/some/key"] = EtcdResponseWithError{
@ -68,12 +64,12 @@ func TestExtractList(t *testing.T) {
},
},
}
expect := []testMarshalType{
{"foo"},
{"bar"},
{"baz"},
expect := []api.Pod{
{JSONBase: api.JSONBase{ID: "foo"}},
{JSONBase: api.JSONBase{ID: "bar"}},
{JSONBase: api.JSONBase{ID: "baz"}},
}
var got []testMarshalType
var got []api.Pod
helper := EtcdHelper{fakeClient}
err := helper.ExtractList("/some/key", &got)
if err != nil {
@ -86,10 +82,10 @@ func TestExtractList(t *testing.T) {
func TestExtractObj(t *testing.T) {
fakeClient := MakeFakeEtcdClient(t)
expect := testMarshalType{ID: "foo"}
expect := api.Pod{JSONBase: api.JSONBase{ID: "foo"}}
fakeClient.Set("/some/key", util.MakeJSONString(expect), 0)
helper := EtcdHelper{fakeClient}
var got testMarshalType
var got api.Pod
err := helper.ExtractObj("/some/key", &got, false)
if err != nil {
t.Errorf("Unexpected error %#v", err)
@ -123,7 +119,7 @@ func TestExtractObjNotFoundErr(t *testing.T) {
}
helper := EtcdHelper{fakeClient}
try := func(key string) {
var got testMarshalType
var got api.Pod
err := helper.ExtractObj(key, &got, false)
if err == nil {
t.Errorf("%s: wanted error but didn't get one", key)
@ -140,14 +136,18 @@ func TestExtractObjNotFoundErr(t *testing.T) {
}
func TestSetObj(t *testing.T) {
obj := testMarshalType{ID: "foo"}
obj := api.Pod{JSONBase: api.JSONBase{ID: "foo"}}
fakeClient := MakeFakeEtcdClient(t)
helper := EtcdHelper{fakeClient}
err := helper.SetObj("/some/key", obj)
if err != nil {
t.Errorf("Unexpected error %#v", err)
}
expect := util.MakeJSONString(obj)
data, err := api.Encode(obj)
if err != nil {
t.Errorf("Unexpected error %#v", err)
}
expect := string(data)
got := fakeClient.Data["/some/key"].R.Node.Value
if expect != got {
t.Errorf("Wanted %v, got %v", expect, got)