Split generic; add test, address other review comments

pull/6/head
Daniel Smith 2014-10-10 15:46:30 -07:00
parent d3d9f7ac8b
commit b1a6b3eee8
8 changed files with 132 additions and 49 deletions

View File

@ -22,13 +22,14 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
etcderr "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
etcdgeneric "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
)
// registry implements custom changes to generic.Etcd.
type registry struct {
*generic.Etcd
*etcdgeneric.Etcd
ttl uint64
}
@ -42,7 +43,7 @@ func (r registry) Create(ctx api.Context, id string, obj runtime.Object) error {
// EtcdHelper. ttl is the time that Events will be retained by the system.
func NewEtcdRegistry(h tools.EtcdHelper, ttl uint64) generic.Registry {
return registry{
Etcd: &generic.Etcd{
Etcd: &etcdgeneric.Etcd{
NewFunc: func() runtime.Object { return &api.Event{} },
NewListFunc: func() runtime.Object { return &api.EventList{} },
EndpointName: "events",

View File

@ -89,14 +89,14 @@ func (rs *REST) getAttrs(obj runtime.Object) (objLabels, objFields labels.Set, e
return nil, nil, fmt.Errorf("invalid object type")
}
return labels.Set{}, labels.Set{
"InvolvedObject.Kind": event.InvolvedObject.Kind,
"InvolvedObject.Name": event.InvolvedObject.Name,
"InvolvedObject.UID": event.InvolvedObject.UID,
"InvolvedObject.APIVersion": event.InvolvedObject.APIVersion,
"InvolvedObject.ResourceVersion": fmt.Sprintf("%s", event.InvolvedObject.ResourceVersion),
"InvolvedObject.FieldPath": event.InvolvedObject.FieldPath,
"Status": event.Status,
"Reason": event.Reason,
"involvedObject.kind": event.InvolvedObject.Kind,
"involvedObject.name": event.InvolvedObject.Name,
"involvedObject.uid": event.InvolvedObject.UID,
"involvedObject.apiVersion": event.InvolvedObject.APIVersion,
"involvedObject.resourceVersion": fmt.Sprintf("%s", event.InvolvedObject.ResourceVersion),
"involvedObject.fieldPath": event.InvolvedObject.FieldPath,
"status": event.Status,
"reason": event.Reason,
}, nil
}

View File

@ -114,14 +114,14 @@ func TestRESTgetAttrs(t *testing.T) {
t.Errorf("diff: %s", util.ObjectDiff(e, a))
}
expect := labels.Set{
"InvolvedObject.Kind": "Pod",
"InvolvedObject.Name": "foo",
"InvolvedObject.UID": "long uid string",
"InvolvedObject.APIVersion": testapi.Version(),
"InvolvedObject.ResourceVersion": "0",
"InvolvedObject.FieldPath": "",
"Status": "tested",
"Reason": "forTesting",
"involvedObject.kind": "Pod",
"involvedObject.name": "foo",
"involvedObject.uid": "long uid string",
"involvedObject.apiVersion": testapi.Version(),
"involvedObject.resourceVersion": "0",
"involvedObject.fieldPath": "",
"status": "tested",
"reason": "forTesting",
}
if e, a := expect, field; !reflect.DeepEqual(e, a) {
t.Errorf("diff: %s", util.ObjectDiff(e, a))
@ -186,7 +186,7 @@ func TestRESTList(t *testing.T) {
reg.ObjectList = &api.EventList{
Items: []api.Event{*eventA, *eventB, *eventC},
}
got, err := rest.List(api.NewContext(), labels.Everything(), labels.Set{"Status": "tested"}.AsSelector())
got, err := rest.List(api.NewContext(), labels.Everything(), labels.Set{"status": "tested"}.AsSelector())
if err != nil {
t.Fatalf("Unexpected error %v", err)
}

View File

@ -0,0 +1,19 @@
/*
Copyright 2014 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package etcd has a generic implementation of a registry that
// stores things in etcd.
package etcd

View File

@ -14,11 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package generic
package etcd
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
etcderr "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
@ -50,35 +51,13 @@ type Etcd struct {
}
// List returns a list of all the items matching m.
func (e *Etcd) List(ctx api.Context, m Matcher) (runtime.Object, error) {
func (e *Etcd) List(ctx api.Context, m generic.Matcher) (runtime.Object, error) {
list := e.NewListFunc()
err := e.Helper.ExtractToList(e.KeyRoot, list)
if err != nil {
return nil, err
}
return FilterList(list, m)
}
// FilterList filters any list object that conforms to the api conventions,
// provided that 'm' works with the concrete type of list.
func FilterList(list runtime.Object, m Matcher) (filtered runtime.Object, err error) {
// TODO: push a matcher down into tools.EtcdHelper to avoid all this
// nonsense. This is a lot of unnecessary copies.
items, err := runtime.ExtractList(list)
if err != nil {
return nil, err
}
var filteredItems []runtime.Object
for _, obj := range items {
if match, err := m.Matches(obj); err == nil && match {
filteredItems = append(filteredItems, obj)
}
}
err = runtime.SetList(list, filteredItems)
if err != nil {
return nil, err
}
return list, nil
return generic.FilterList(list, m)
}
// Create inserts a new item.
@ -89,6 +68,7 @@ func (e *Etcd) Create(ctx api.Context, id string, obj runtime.Object) error {
// Update updates the item.
func (e *Etcd) Update(ctx api.Context, id string, obj runtime.Object) error {
// TODO: verify that SetObj checks ResourceVersion before succeeding.
err := e.Helper.SetObj(e.KeyFunc(id), obj)
return etcderr.InterpretUpdateError(err, e.EndpointName, id)
}
@ -111,7 +91,7 @@ func (e *Etcd) Delete(ctx api.Context, id string) error {
// Watch starts a watch for the items that m matches.
// TODO: Detect if m references a single object instead of a list.
func (e *Etcd) Watch(ctx api.Context, m Matcher, resourceVersion uint64) (watch.Interface, error) {
func (e *Etcd) Watch(ctx api.Context, m generic.Matcher, resourceVersion uint64) (watch.Interface, error) {
return e.Helper.WatchList(e.KeyRoot, resourceVersion, func(obj runtime.Object) bool {
matches, err := m.Matches(obj)
return err == nil && matches

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package generic
package etcd
import (
"fmt"
@ -25,6 +25,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
@ -90,7 +91,7 @@ func TestEtcdList(t *testing.T) {
table := map[string]struct {
in tools.EtcdResponseWithError
m Matcher
m generic.Matcher
out runtime.Object
succeed bool
}{

View File

@ -54,6 +54,19 @@ type Matcher interface {
Matches(obj runtime.Object) (bool, error)
}
// MatcherFunc makes a matcher from the provided function. For easy definition
// of matchers for testing.
func MatcherFunc(f func(obj runtime.Object) (bool, error)) Matcher {
return matcherFunc(f)
}
type matcherFunc func(obj runtime.Object) (bool, error)
// Matches calls the embedded function.
func (m matcherFunc) Matches(obj runtime.Object) (bool, error) {
return m(obj)
}
// Registry knows how to store & list any runtime.Object. Can be used for
// any object types which don't require special features from the storage
// layer.
@ -65,3 +78,29 @@ type Registry interface {
Delete(ctx api.Context, id string) error
Watch(ctx api.Context, m Matcher, resourceVersion uint64) (watch.Interface, error)
}
// FilterList filters any list object that conforms to the api conventions,
// provided that 'm' works with the concrete type of list.
func FilterList(list runtime.Object, m Matcher) (filtered runtime.Object, err error) {
// TODO: push a matcher down into tools.EtcdHelper to avoid all this
// nonsense. This is a lot of unnecessary copies.
items, err := runtime.ExtractList(list)
if err != nil {
return nil, err
}
var filteredItems []runtime.Object
for _, obj := range items {
match, err := m.Matches(obj)
if err != nil {
return nil, err
}
if match {
filteredItems = append(filteredItems, obj)
}
}
err = runtime.SetList(list, filteredItems)
if err != nil {
return nil, err
}
return list, nil
}

View File

@ -18,15 +18,23 @@ package generic
import (
"errors"
"reflect"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
)
type Ignored struct{}
type Ignored struct {
ID string
}
func (*Ignored) IsAnAPIObject() {}
type IgnoredList struct {
Items []Ignored
}
func (*Ignored) IsAnAPIObject() {}
func (*IgnoredList) IsAnAPIObject() {}
func TestSelectionPredicate(t *testing.T) {
table := map[string]struct {
@ -90,3 +98,38 @@ func TestSelectionPredicate(t *testing.T) {
}
}
}
func TestFilterList(t *testing.T) {
try := &IgnoredList{
Items: []Ignored{
{"foo"},
{"bar"},
{"baz"},
{"qux"},
{"zot"},
},
}
expect := &IgnoredList{
Items: []Ignored{
{"bar"},
{"baz"},
},
}
got, err := FilterList(try,
MatcherFunc(func(obj runtime.Object) (bool, error) {
i, ok := obj.(*Ignored)
if !ok {
return false, errors.New("wrong type")
}
return i.ID[0] == 'b', nil
}),
)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
if e, a := expect, got; !reflect.DeepEqual(e, a) {
t.Errorf("Expected %#v, got %#v", e, a)
}
}