Add event registry and type

This commit is contained in:
Daniel Smith 2014-10-09 15:21:44 -07:00
parent 9a9362e896
commit 3e076e12fe
5 changed files with 527 additions and 0 deletions

19
pkg/registry/event/doc.go Normal file
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 event provides Registry interface and it's REST
// implementation for storing Event api objects.
package event

View File

@ -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 event
import (
"path"
"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"
)
// registry implements custom changes to generic.Etcd.
type registry struct {
*generic.Etcd
ttl uint64
}
// Create stores the object with a ttl, so that events don't stay in the system forever.
func (r registry) Create(ctx api.Context, id string, obj runtime.Object) error {
err := r.Etcd.Helper.CreateObj(r.Etcd.KeyFunc(id), obj, r.ttl)
return etcderr.InterpretCreateError(err, r.Etcd.EndpointName, id)
}
// NewEtcdRegistry returns a registry which will store Events in the given
// 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{
NewFunc: func() runtime.Object { return &api.Event{} },
NewListFunc: func() runtime.Object { return &api.EventList{} },
EndpointName: "events",
KeyRoot: "/registry/events",
KeyFunc: func(id string) string {
return path.Join("/registry/events", id)
},
Helper: h,
},
ttl: ttl,
}
}

View File

@ -0,0 +1,104 @@
/*
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 event
import (
"reflect"
"testing"
"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/coreos/go-etcd/etcd"
)
var testTTL uint64 = 60
func NewTestEventEtcdRegistry(t *testing.T) (*tools.FakeEtcdClient, generic.Registry) {
f := tools.NewFakeEtcdClient(t)
f.TestIndex = true
h := tools.EtcdHelper{f, testapi.Codec(), tools.RuntimeVersionAdapter{testapi.ResourceVersioner()}}
return f, NewEtcdRegistry(h, testTTL)
}
func TestEventCreate(t *testing.T) {
eventA := &api.Event{
TypeMeta: api.TypeMeta{ID: "foo"},
Reason: "forTesting",
}
eventB := &api.Event{
TypeMeta: api.TypeMeta{ID: "foo"},
Reason: "forTesting",
}
nodeWithEventA := tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
Value: runtime.EncodeOrDie(testapi.Codec(), eventA),
ModifiedIndex: 1,
CreatedIndex: 1,
TTL: int64(testTTL),
},
},
E: nil,
}
emptyNode := tools.EtcdResponseWithError{
R: &etcd.Response{},
E: tools.EtcdErrorNotFound,
}
path := "/registry/events/foo"
key := "foo"
table := map[string]struct {
existing tools.EtcdResponseWithError
expect tools.EtcdResponseWithError
toCreate runtime.Object
errOK func(error) bool
}{
"normal": {
existing: emptyNode,
expect: nodeWithEventA,
toCreate: eventA,
errOK: func(err error) bool { return err == nil },
},
"preExisting": {
existing: nodeWithEventA,
expect: nodeWithEventA,
toCreate: eventB,
errOK: errors.IsAlreadyExists,
},
}
for name, item := range table {
fakeClient, registry := NewTestEventEtcdRegistry(t)
fakeClient.Data[path] = item.existing
err := registry.Create(api.NewContext(), key, item.toCreate)
if !item.errOK(err) {
t.Errorf("%v: unexpected error: %v", name, err)
}
if e, a := item.expect, fakeClient.Data[path]; !reflect.DeepEqual(e, a) {
t.Errorf("%v:\n%s", name, runtime.ObjectDiff(e, a))
}
}
}

121
pkg/registry/event/rest.go Normal file
View File

@ -0,0 +1,121 @@
/*
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 event
import (
"fmt"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
)
// REST adapts an event registry into apiserver's RESTStorage model.
type REST struct {
registry generic.Registry
}
// NewREST returns a new REST. You must use a registry created by
// NewEtcdRegistry unless you're testing.
func NewREST(registry generic.Registry) *REST {
return &REST{
registry: registry,
}
}
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) {
event, ok := obj.(*api.Event)
if !ok {
return nil, fmt.Errorf("invalid object type")
}
event.CreationTimestamp = util.Now()
return apiserver.MakeAsync(func() (runtime.Object, error) {
err := rs.registry.Create(ctx, event.ID, event)
if err != nil {
return nil, err
}
return rs.registry.Get(ctx, event.ID)
}), nil
}
func (rs *REST) Delete(ctx api.Context, id string) (<-chan runtime.Object, error) {
obj, err := rs.registry.Get(ctx, id)
if err != nil {
return nil, err
}
_, ok := obj.(*api.Event)
if !ok {
return nil, fmt.Errorf("invalid object type")
}
return apiserver.MakeAsync(func() (runtime.Object, error) {
return &api.Status{Status: api.StatusSuccess}, rs.registry.Delete(ctx, id)
}), nil
}
func (rs *REST) Get(ctx api.Context, id string) (runtime.Object, error) {
obj, err := rs.registry.Get(ctx, id)
if err != nil {
return nil, err
}
event, ok := obj.(*api.Event)
if !ok {
return nil, fmt.Errorf("invalid object type")
}
return event, err
}
func (rs *REST) getAttrs(obj runtime.Object) (objLabels, objFields labels.Set, err error) {
event, ok := obj.(*api.Event)
if !ok {
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,
}, nil
}
func (rs *REST) List(ctx api.Context, label, field labels.Selector) (runtime.Object, error) {
return rs.registry.List(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs})
}
// Watch returns Events events via a watch.Interface.
// It implements apiserver.ResourceWatcher.
func (rs *REST) Watch(ctx api.Context, label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
return rs.registry.Watch(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}, resourceVersion)
}
// New returns a new api.Event
func (*REST) New() runtime.Object {
return &api.Event{}
}
// Update returns an error: Events are not mutable.
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) {
return nil, fmt.Errorf("not allowed: 'Event' objects are not mutable")
}

View File

@ -0,0 +1,226 @@
/*
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 event
import (
"reflect"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
)
type testRegistry struct {
*registrytest.GenericRegistry
}
func NewTestREST() (testRegistry, *REST) {
reg := testRegistry{registrytest.NewGeneric(nil)}
return reg, NewREST(reg)
}
func TestRESTCreate(t *testing.T) {
_, rest := NewTestREST()
eventA := &api.Event{
TypeMeta: api.TypeMeta{ID: "foo"},
Reason: "forTesting",
}
c, err := rest.Create(api.NewContext(), eventA)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
if e, a := eventA, <-c; !reflect.DeepEqual(e, a) {
t.Errorf("diff: %s", runtime.ObjectDiff(e, a))
}
}
func TestRESTDelete(t *testing.T) {
_, rest := NewTestREST()
eventA := &api.Event{
TypeMeta: api.TypeMeta{ID: "foo"},
Reason: "forTesting",
}
c, err := rest.Create(api.NewContext(), eventA)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
<-c
c, err = rest.Delete(api.NewContext(), eventA.ID)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
if stat := (<-c).(*api.Status); stat.Status != api.StatusSuccess {
t.Errorf("unexpected status: %v", stat)
}
}
func TestRESTGet(t *testing.T) {
_, rest := NewTestREST()
eventA := &api.Event{
TypeMeta: api.TypeMeta{ID: "foo"},
Reason: "forTesting",
}
c, err := rest.Create(api.NewContext(), eventA)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
<-c
got, err := rest.Get(api.NewContext(), eventA.ID)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
if e, a := eventA, got; !reflect.DeepEqual(e, a) {
t.Errorf("diff: %s", runtime.ObjectDiff(e, a))
}
}
func TestRESTgetAttrs(t *testing.T) {
_, rest := NewTestREST()
eventA := &api.Event{
InvolvedObject: api.ObjectReference{
Kind: "Pod",
Name: "foo",
UID: "long uid string",
APIVersion: testapi.Version(),
ResourceVersion: "0",
FieldPath: "",
},
Status: "tested",
Reason: "forTesting",
}
label, field, err := rest.getAttrs(eventA)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
if e, a := label, (labels.Set{}); !reflect.DeepEqual(e, a) {
t.Errorf("diff: %s", runtime.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",
}
if e, a := expect, field; !reflect.DeepEqual(e, a) {
t.Errorf("diff: %s", runtime.ObjectDiff(e, a))
}
}
func TestRESTUpdate(t *testing.T) {
_, rest := NewTestREST()
eventA := &api.Event{
TypeMeta: api.TypeMeta{ID: "foo"},
Reason: "forTesting",
}
c, err := rest.Create(api.NewContext(), eventA)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
<-c
_, err = rest.Update(api.NewContext(), eventA)
if err == nil {
t.Errorf("unexpected non-error")
}
}
func TestRESTList(t *testing.T) {
reg, rest := NewTestREST()
eventA := &api.Event{
InvolvedObject: api.ObjectReference{
Kind: "Pod",
Name: "foo",
UID: "long uid string",
APIVersion: testapi.Version(),
ResourceVersion: "0",
FieldPath: "",
},
Status: "tested",
Reason: "forTesting",
}
eventB := &api.Event{
InvolvedObject: api.ObjectReference{
Kind: "Pod",
Name: "bar",
UID: "other long uid string",
APIVersion: testapi.Version(),
ResourceVersion: "0",
FieldPath: "",
},
Status: "tested",
Reason: "forTesting",
}
eventC := &api.Event{
InvolvedObject: api.ObjectReference{
Kind: "Pod",
Name: "baz",
UID: "yet another long uid string",
APIVersion: testapi.Version(),
ResourceVersion: "0",
FieldPath: "",
},
Status: "untested",
Reason: "forTesting",
}
reg.ObjectList = &api.EventList{
Items: []api.Event{*eventA, *eventB, *eventC},
}
got, err := rest.List(api.NewContext(), labels.Everything(), labels.Set{"Status": "tested"}.AsSelector())
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
expect := &api.EventList{
Items: []api.Event{*eventA, *eventB},
}
if e, a := expect, got; !reflect.DeepEqual(e, a) {
t.Errorf("diff: %s", runtime.ObjectDiff(e, a))
}
}
func TestRESTWatch(t *testing.T) {
eventA := &api.Event{
InvolvedObject: api.ObjectReference{
Kind: "Pod",
Name: "foo",
UID: "long uid string",
APIVersion: testapi.Version(),
ResourceVersion: "0",
FieldPath: "",
},
Status: "tested",
Reason: "forTesting",
}
reg, rest := NewTestREST()
wi, err := rest.Watch(api.NewContext(), labels.Everything(), labels.Everything(), 0)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
go func() {
reg.Mux.Action(watch.Added, eventA)
}()
got := <-wi.ResultChan()
if e, a := eventA, got.Object; !reflect.DeepEqual(e, a) {
t.Errorf("diff: %s", runtime.ObjectDiff(e, a))
}
}