Merge pull request #1438 from lavalamp/eventing3

Event impl supporting changes
This commit is contained in:
Brendan Burns 2014-09-25 22:01:09 -07:00
commit 377a9ac3d7
7 changed files with 249 additions and 48 deletions

View File

@ -120,7 +120,7 @@ func (r *Registry) CreatePod(pod *api.Pod) error {
// DesiredState.Host == "" is a signal to the scheduler that this pod needs scheduling.
pod.DesiredState.Status = api.PodRunning
pod.DesiredState.Host = ""
err := r.CreateObj(makePodKey(pod.ID), pod)
err := r.CreateObj(makePodKey(pod.ID), pod, 0)
return etcderr.InterpretCreateError(err, "pod", pod.ID)
}
@ -254,7 +254,7 @@ func (r *Registry) GetController(controllerID string) (*api.ReplicationControlle
// CreateController creates a new ReplicationController.
func (r *Registry) CreateController(controller *api.ReplicationController) error {
err := r.CreateObj(makeControllerKey(controller.ID), controller)
err := r.CreateObj(makeControllerKey(controller.ID), controller, 0)
return etcderr.InterpretCreateError(err, "replicationController", controller.ID)
}
@ -284,7 +284,7 @@ func (r *Registry) ListServices() (*api.ServiceList, error) {
// CreateService creates a new Service.
func (r *Registry) CreateService(svc *api.Service) error {
err := r.CreateObj(makeServiceKey(svc.ID), svc)
err := r.CreateObj(makeServiceKey(svc.ID), svc, 0)
return etcderr.InterpretCreateError(err, "service", svc.ID)
}

91
pkg/runtime/helper.go Normal file
View File

@ -0,0 +1,91 @@
/*
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 runtime
import (
"fmt"
"reflect"
)
// GetItemsPtr returns a pointer to the list object's Items member.
// If 'list' doesn't have an Items member, it's not really a list type
// and an error will be returned.
// This function will either return a pointer to a slice, or an error, but not both.
func GetItemsPtr(list Object) (interface{}, error) {
v := reflect.ValueOf(list)
if !v.IsValid() {
return nil, fmt.Errorf("nil list object")
}
items := v.Elem().FieldByName("Items")
if !items.IsValid() {
return nil, fmt.Errorf("no Items field in %#v", list)
}
if items.Kind() != reflect.Slice {
return nil, fmt.Errorf("Items field is not a slice")
}
return items.Addr().Interface(), nil
}
// ExtractList returns obj's Items element as an array of runtime.Objects.
// Returns an error if obj is not a List type (does not have an Items member).
func ExtractList(obj Object) ([]Object, error) {
itemsPtr, err := GetItemsPtr(obj)
if err != nil {
return nil, err
}
items := reflect.ValueOf(itemsPtr).Elem()
list := make([]Object, items.Len())
for i := range list {
raw := items.Index(i)
item, ok := raw.Addr().Interface().(Object)
if !ok {
return nil, fmt.Errorf("item in index %v isn't an object: %#v", i, raw.Interface())
}
list[i] = item
}
return list, nil
}
// SetList sets the given list object's Items member have the elements given in
// objects.
// Returns an error if list is not a List type (does not have an Items member),
// or if any of the objects are not of the right type.
func SetList(list Object, objects []Object) error {
itemsPtr, err := GetItemsPtr(list)
if err != nil {
return err
}
items := reflect.ValueOf(itemsPtr).Elem()
slice := reflect.MakeSlice(items.Type(), len(objects), len(objects))
for i := range objects {
dest := slice.Index(i)
src := reflect.ValueOf(objects[i])
if !src.IsValid() || src.IsNil() {
return fmt.Errorf("an object was nil")
}
src = src.Elem() // Object is a pointer, but the items in slice are not.
if src.Type().AssignableTo(dest.Type()) {
dest.Set(src)
} else if src.Type().ConvertibleTo(dest.Type()) {
dest.Set(src.Convert(dest.Type()))
} else {
return fmt.Errorf("wrong type: need %v, got %v", dest.Type(), src.Type())
}
}
items.Set(slice)
return nil
}

View File

@ -0,0 +1,93 @@
/*
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 runtime_test
import (
"reflect"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/google/gofuzz"
)
func TestExtractList(t *testing.T) {
pl := &api.PodList{
Items: []api.Pod{
{JSONBase: api.JSONBase{ID: "1"}},
{JSONBase: api.JSONBase{ID: "2"}},
{JSONBase: api.JSONBase{ID: "3"}},
},
}
list, err := runtime.ExtractList(pl)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
if e, a := len(list), len(pl.Items); e != a {
t.Fatalf("Expected %v, got %v", e, a)
}
for i := range list {
if e, a := list[i].(*api.Pod).ID, pl.Items[i].ID; e != a {
t.Fatalf("Expected %v, got %v", e, a)
}
}
}
func TestSetList(t *testing.T) {
pl := &api.PodList{}
list := []runtime.Object{
&api.Pod{JSONBase: api.JSONBase{ID: "1"}},
&api.Pod{JSONBase: api.JSONBase{ID: "2"}},
&api.Pod{JSONBase: api.JSONBase{ID: "3"}},
}
err := runtime.SetList(pl, list)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
if e, a := len(list), len(pl.Items); e != a {
t.Fatalf("Expected %v, got %v", e, a)
}
for i := range list {
if e, a := list[i].(*api.Pod).ID, pl.Items[i].ID; e != a {
t.Fatalf("Expected %v, got %v", e, a)
}
}
}
func TestSetExtractListRoundTrip(t *testing.T) {
fuzzer := fuzz.New().NilChance(0).NumElements(1, 5)
for i := 0; i < 5; i++ {
start := &api.PodList{}
fuzzer.Fuzz(&start.Items)
list, err := runtime.ExtractList(start)
if err != nil {
t.Errorf("Unexpected error %v", err)
continue
}
got := &api.PodList{}
err = runtime.SetList(got, list)
if err != nil {
t.Errorf("Unexpected error %v", err)
continue
}
if e, a := start, got; !reflect.DeepEqual(e, a) {
t.Fatalf("Expected %#v, got %#v", e, a)
}
}
}

View File

@ -400,28 +400,3 @@ func (metaInsertion) Interpret(in interface{}) (version, kind string) {
m := in.(*metaInsertion)
return m.JSONBase.APIVersion, m.JSONBase.Kind
}
// Extract list returns obj's Items element as an array of runtime.Objects.
// Returns an error if obj is not a List type (does not have an Items member).
func ExtractList(obj Object) ([]Object, error) {
v := reflect.ValueOf(obj)
if !v.IsValid() {
return nil, fmt.Errorf("nil object")
}
items := v.Elem().FieldByName("Items")
if !items.IsValid() {
return nil, fmt.Errorf("no Items field")
}
if items.Kind() != reflect.Slice {
return nil, fmt.Errorf("Items field is not a slice")
}
list := make([]Object, items.Len())
for i := range list {
item, ok := items.Index(i).Addr().Interface().(Object)
if !ok {
return nil, fmt.Errorf("item in index %v isn't an object", i)
}
list[i] = item
}
return list, nil
}

View File

@ -123,6 +123,7 @@ func (h *EtcdHelper) listEtcdNode(key string) ([]*etcd.Node, uint64, error) {
}
// ExtractList extracts a go object per etcd node into a slice with the resource version.
// DEPRECATED: Use ExtractToList instead, it's more convenient.
func (h *EtcdHelper) ExtractList(key string, slicePtr interface{}, resourceVersion *uint64) error {
nodes, index, err := h.listEtcdNode(key)
if resourceVersion != nil {
@ -152,6 +153,27 @@ func (h *EtcdHelper) ExtractList(key string, slicePtr interface{}, resourceVersi
return nil
}
// ExtractToList is just like ExtractList, but it works on a ThingyList api object.
// extracts a go object per etcd node into a slice with the resource version.
func (h *EtcdHelper) ExtractToList(key string, listObj runtime.Object) error {
var resourceVersion uint64
listPtr, err := runtime.GetItemsPtr(listObj)
if err != nil {
return err
}
err = h.ExtractList(key, listPtr, &resourceVersion)
if err != nil {
return err
}
if h.ResourceVersioner != nil {
err = h.ResourceVersioner.SetResourceVersion(listObj, resourceVersion)
if err != nil {
return err
}
}
return nil
}
// ExtractObj unmarshals json found at key into objPtr. On a not found error, will either return
// a zero object of the requested type, or an error, depending on ignoreNotFound. Treats
// empty responses and nil response nodes exactly like a not found error.
@ -185,8 +207,9 @@ func (h *EtcdHelper) bodyAndExtractObj(key string, objPtr runtime.Object, ignore
return body, response.Node.ModifiedIndex, err
}
// CreateObj adds a new object at a key unless it already exists.
func (h *EtcdHelper) CreateObj(key string, obj runtime.Object) error {
// CreateObj adds a new object at a key unless it already exists. 'ttl' is time-to-live in seconds,
// and 0 means forever.
func (h *EtcdHelper) CreateObj(key string, obj runtime.Object, ttl uint64) error {
data, err := h.Codec.Encode(obj)
if err != nil {
return err
@ -197,7 +220,7 @@ func (h *EtcdHelper) CreateObj(key string, obj runtime.Object) error {
}
}
_, err = h.Client.Create(key, string(data), 0)
_, err = h.Client.Create(key, string(data), ttl)
return err
}

View File

@ -65,7 +65,7 @@ func TestIsEtcdNotFound(t *testing.T) {
try(fmt.Errorf("some other kind of error"), false)
}
func TestExtractList(t *testing.T) {
func TestExtractToList(t *testing.T) {
fakeClient := NewFakeEtcdClient(t)
fakeClient.Data["/some/key"] = EtcdResponseWithError{
R: &etcd.Response{
@ -88,27 +88,23 @@ func TestExtractList(t *testing.T) {
},
},
}
expect := []api.Pod{
{JSONBase: api.JSONBase{ID: "foo", ResourceVersion: 1}},
{JSONBase: api.JSONBase{ID: "bar", ResourceVersion: 2}},
{JSONBase: api.JSONBase{ID: "baz", ResourceVersion: 3}},
expect := api.PodList{
JSONBase: api.JSONBase{ResourceVersion: 10},
Items: []api.Pod{
{JSONBase: api.JSONBase{ID: "foo", ResourceVersion: 1}},
{JSONBase: api.JSONBase{ID: "bar", ResourceVersion: 2}},
{JSONBase: api.JSONBase{ID: "baz", ResourceVersion: 3}},
},
}
var got []api.Pod
var got api.PodList
helper := EtcdHelper{fakeClient, latest.Codec, versioner}
resourceVersion := uint64(0)
err := helper.ExtractList("/some/key", &got, &resourceVersion)
err := helper.ExtractToList("/some/key", &got)
if err != nil {
t.Errorf("Unexpected error %#v", err)
t.Errorf("Unexpected error %v", err)
}
if resourceVersion != 10 {
t.Errorf("Unexpected resource version %d", resourceVersion)
}
for i := 0; i < len(expect); i++ {
if !reflect.DeepEqual(got[i], expect[i]) {
t.Errorf("\nWanted:\n%#v\nGot:\n%#v\n", expect[i], got[i])
}
if e, a := expect, got; !reflect.DeepEqual(e, a) {
t.Errorf("Expected %#v, got %#v", e, a)
}
}
@ -167,6 +163,27 @@ func TestExtractObjNotFoundErr(t *testing.T) {
try("/some/key3")
}
func TestCreateObj(t *testing.T) {
obj := &api.Pod{JSONBase: api.JSONBase{ID: "foo"}}
fakeClient := NewFakeEtcdClient(t)
helper := EtcdHelper{fakeClient, latest.Codec, versioner}
err := helper.CreateObj("/some/key", obj, 5)
if err != nil {
t.Errorf("Unexpected error %#v", err)
}
data, err := latest.Codec.Encode(obj)
if err != nil {
t.Errorf("Unexpected error %#v", err)
}
node := fakeClient.Data["/some/key"].R.Node
if e, a := string(data), node.Value; e != a {
t.Errorf("Wanted %v, got %v", e, a)
}
if e, a := uint64(5), fakeClient.LastSetTTL; e != a {
t.Errorf("Wanted %v, got %v", e, a)
}
}
func TestSetObj(t *testing.T) {
obj := &api.Pod{JSONBase: api.JSONBase{ID: "foo"}}
fakeClient := NewFakeEtcdClient(t)

View File

@ -49,6 +49,7 @@ type FakeEtcdClient struct {
Ix int
TestIndex bool
ChangeIndex uint64
LastSetTTL uint64
// Will become valid after Watch is called; tester may write to it. Tester may
// also read from it to verify that it's closed after injecting an error.
@ -135,6 +136,7 @@ func (f *FakeEtcdClient) nodeExists(key string) bool {
}
func (f *FakeEtcdClient) setLocked(key, value string, ttl uint64) (*etcd.Response, error) {
f.LastSetTTL = ttl
if f.Err != nil {
return nil, f.Err
}