Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Event impl supporting changes #1438

Merged
merged 3 commits into from
Sep 26, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions pkg/registry/etcd/etcd.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,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)
}

Expand Down Expand Up @@ -253,7 +253,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)
}

Expand Down Expand Up @@ -283,7 +283,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)
}

Expand Down
91 changes: 91 additions & 0 deletions pkg/runtime/helper.go
Original file line number Diff line number Diff line change
@@ -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
}
93 changes: 93 additions & 0 deletions pkg/runtime/helper_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
25 changes: 0 additions & 25 deletions pkg/runtime/scheme.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
29 changes: 26 additions & 3 deletions pkg/tools/etcd_tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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
}

Expand Down
51 changes: 34 additions & 17 deletions pkg/tools/etcd_tools_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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)
}
if resourceVersion != 10 {
t.Errorf("Unexpected resource version %d", resourceVersion)
t.Errorf("Unexpected error %v", err)
}

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)
}
}

Expand Down Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions pkg/tools/fake_etcd_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
Expand Down