Commit 2b2d6b67 authored by Wojciech Tyczynski's avatar Wojciech Tyczynski

Refactor registry tests to reduce dependency on go-etcd.

parent 57e35b79
...@@ -23,7 +23,6 @@ import ( ...@@ -23,7 +23,6 @@ import (
"testing" "testing"
"time" "time"
"github.com/coreos/go-etcd/etcd"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/rest" "k8s.io/kubernetes/pkg/api/rest"
...@@ -544,7 +543,7 @@ func (t *Tester) testDeleteNoGraceful(obj runtime.Object, setFn SetFunc, getFn G ...@@ -544,7 +543,7 @@ func (t *Tester) testDeleteNoGraceful(obj runtime.Object, setFn SetFunc, getFn G
func (t *Tester) testDeleteNonExist(obj runtime.Object) { func (t *Tester) testDeleteNonExist(obj runtime.Object) {
objectMeta := t.getObjectMetaOrFail(obj) objectMeta := t.getObjectMetaOrFail(obj)
t.withStorageError(&etcd.EtcdError{ErrorCode: tools.EtcdErrorCodeNotFound}, func() { t.withStorageError(tools.EtcdErrorNotFound, func() {
_, err := t.storage.(rest.GracefulDeleter).Delete(t.TestContext(), objectMeta.Name, nil) _, err := t.storage.(rest.GracefulDeleter).Delete(t.TestContext(), objectMeta.Name, nil)
if err == nil || !errors.IsNotFound(err) { if err == nil || !errors.IsNotFound(err) {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
......
...@@ -17,20 +17,12 @@ limitations under the License. ...@@ -17,20 +17,12 @@ limitations under the License.
package etcd package etcd
import ( import (
"reflect"
"testing" "testing"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/testapi"
etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd"
"k8s.io/kubernetes/pkg/registry/registrytest" "k8s.io/kubernetes/pkg/registry/registrytest"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/tools" "k8s.io/kubernetes/pkg/tools"
"k8s.io/kubernetes/pkg/tools/etcdtest"
"k8s.io/kubernetes/pkg/util"
"github.com/coreos/go-etcd/etcd"
) )
var testTTL uint64 = 60 var testTTL uint64 = 60
...@@ -41,199 +33,56 @@ func newStorage(t *testing.T) (*REST, *tools.FakeEtcdClient) { ...@@ -41,199 +33,56 @@ func newStorage(t *testing.T) (*REST, *tools.FakeEtcdClient) {
return NewREST(etcdStorage, testTTL), fakeClient return NewREST(etcdStorage, testTTL), fakeClient
} }
func TestEventCreate(t *testing.T) { func validNewEvent(namespace string) *api.Event {
eventA := &api.Event{ return &api.Event{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault}, ObjectMeta: api.ObjectMeta{
Reason: "forTesting", Name: "foo",
InvolvedObject: api.ObjectReference{Name: "bar", Namespace: api.NamespaceDefault}, Namespace: namespace,
}
eventB := &api.Event{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault},
Reason: "forTesting",
InvolvedObject: api.ObjectReference{Name: "bar", Namespace: api.NamespaceDefault},
}
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,
}
ctx := api.NewDefaultContext()
key := "foo"
path, err := etcdgeneric.NamespaceKeyFunc(ctx, "/events", key)
path = etcdtest.AddPrefix(path)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
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": { Reason: "forTesting",
existing: nodeWithEventA, InvolvedObject: api.ObjectReference{
expect: nodeWithEventA, Name: "bar",
toCreate: eventB, Namespace: namespace,
errOK: errors.IsAlreadyExists,
}, },
} }
}
for name, item := range table { func TestCreate(t *testing.T) {
storage, fakeClient := newStorage(t) storage, fakeClient := newStorage(t)
fakeClient.Data[path] = item.existing test := registrytest.New(t, fakeClient, storage.Etcd)
_, err := storage.Create(ctx, item.toCreate) event := validNewEvent(test.TestNamespace())
if !item.errOK(err) { event.ObjectMeta = api.ObjectMeta{}
t.Errorf("%v: unexpected error: %v", name, err) test.TestCreate(
} // valid
event,
// nullify fields set by infrastructure // invalid
received := fakeClient.Data[path] &api.Event{},
var event api.Event )
if err := testapi.Codec().DecodeInto([]byte(received.R.Node.Value), &event); err != nil {
t.Errorf("unexpected error: %v", err)
}
event.ObjectMeta.CreationTimestamp = util.Time{}
event.ObjectMeta.UID = ""
received.R.Node.Value = runtime.EncodeOrDie(testapi.Codec(), &event)
if e, a := item.expect, received; !reflect.DeepEqual(e, a) {
t.Errorf("%v:\n%s", name, util.ObjectDiff(e, a))
}
}
} }
func TestEventUpdate(t *testing.T) { func TestUpdate(t *testing.T) {
eventA := &api.Event{ storage, fakeClient := newStorage(t)
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault}, test := registrytest.New(t, fakeClient, storage.Etcd).AllowCreateOnUpdate()
Reason: "forTesting", test.TestUpdate(
InvolvedObject: api.ObjectReference{Name: "foo", Namespace: api.NamespaceDefault}, // valid
} validNewEvent(test.TestNamespace()),
eventB := &api.Event{ // valid updateFunc
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault}, func(obj runtime.Object) runtime.Object {
Reason: "for testing again", object := obj.(*api.Event)
InvolvedObject: api.ObjectReference{Name: "foo", Namespace: api.NamespaceDefault}, object.Reason = "forDifferentTesting"
} return object
eventC := &api.Event{
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault, ResourceVersion: "1"},
Reason: "for testing again something else",
InvolvedObject: api.ObjectReference{Name: "foo", Namespace: api.NamespaceDefault},
}
nodeWithEventA := tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
Value: runtime.EncodeOrDie(testapi.Codec(), eventA),
ModifiedIndex: 1,
CreatedIndex: 1,
TTL: int64(testTTL),
},
},
E: nil,
}
nodeWithEventB := tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
Value: runtime.EncodeOrDie(testapi.Codec(), eventB),
ModifiedIndex: 1,
CreatedIndex: 1,
TTL: int64(testTTL),
},
},
E: nil,
}
nodeWithEventC := tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
Value: runtime.EncodeOrDie(testapi.Codec(), eventC),
ModifiedIndex: 1,
CreatedIndex: 1,
TTL: int64(testTTL),
},
},
E: nil,
}
emptyNode := tools.EtcdResponseWithError{
R: &etcd.Response{},
E: tools.EtcdErrorNotFound,
}
ctx := api.NewDefaultContext()
key := "foo"
path, err := etcdgeneric.NamespaceKeyFunc(ctx, "/events", key)
path = etcdtest.AddPrefix(path)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
table := map[string]struct {
existing tools.EtcdResponseWithError
expect tools.EtcdResponseWithError
toUpdate runtime.Object
errOK func(error) bool
}{
"doesNotExist": {
existing: emptyNode,
expect: nodeWithEventA,
toUpdate: eventA,
errOK: func(err error) bool { return err == nil },
},
"doesNotExist2": {
existing: emptyNode,
expect: nodeWithEventB,
toUpdate: eventB,
errOK: func(err error) bool { return err == nil },
}, },
"replaceExisting": { // invalid updateFunc
existing: nodeWithEventA, func(obj runtime.Object) runtime.Object {
expect: nodeWithEventC, object := obj.(*api.Event)
toUpdate: eventC, object.InvolvedObject.Namespace = "different-namespace"
errOK: func(err error) bool { return err == nil }, return object
}, },
} )
}
for name, item := range table { func TestDelete(t *testing.T) {
storage, fakeClient := newStorage(t) storage, fakeClient := newStorage(t)
fakeClient.Data[path] = item.existing test := registrytest.New(t, fakeClient, storage.Etcd)
_, _, err := storage.Update(ctx, item.toUpdate) test.TestDelete(validNewEvent(test.TestNamespace()))
if !item.errOK(err) {
t.Errorf("%v: unexpected error: %v", name, err)
}
// nullify fields set by infrastructure
received := fakeClient.Data[path]
var event api.Event
if err := testapi.Codec().DecodeInto([]byte(received.R.Node.Value), &event); err != nil {
t.Errorf("unexpected error: %v", err)
}
event.ObjectMeta.CreationTimestamp = util.Time{}
event.ObjectMeta.UID = ""
received.R.Node.Value = runtime.EncodeOrDie(testapi.Codec(), &event)
if e, a := item.expect, received; !reflect.DeepEqual(e, a) {
t.Errorf("%v:\n%s", name, util.ObjectGoPrintDiff(e, a))
}
}
} }
...@@ -21,29 +21,17 @@ import ( ...@@ -21,29 +21,17 @@ import (
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/expapi"
"k8s.io/kubernetes/pkg/registry/registrytest"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/storage"
etcdstorage "k8s.io/kubernetes/pkg/storage/etcd"
"k8s.io/kubernetes/pkg/tools" "k8s.io/kubernetes/pkg/tools"
"k8s.io/kubernetes/pkg/tools/etcdtest" "k8s.io/kubernetes/pkg/tools/etcdtest"
"k8s.io/kubernetes/pkg/util" "k8s.io/kubernetes/pkg/util"
"k8s.io/kubernetes/pkg/expapi"
"github.com/coreos/go-etcd/etcd"
) )
func newEtcdStorage(t *testing.T) (*tools.FakeEtcdClient, storage.Interface) { func newStorage(t *testing.T) (*ScaleREST, *tools.FakeEtcdClient) {
fakeEtcdClient := tools.NewFakeEtcdClient(t) etcdStorage, fakeClient := registrytest.NewEtcdStorage(t)
fakeEtcdClient.TestIndex = true return NewStorage(etcdStorage).Scale, fakeClient
etcdStorage := etcdstorage.NewEtcdStorage(fakeEtcdClient, testapi.Codec(), etcdtest.PathPrefix())
return fakeEtcdClient, etcdStorage
}
func newStorage(t *testing.T) (*RcREST, *ScaleREST, *tools.FakeEtcdClient, storage.Interface) {
fakeEtcdClient, etcdStorage := newEtcdStorage(t)
storage := NewStorage(etcdStorage)
return storage.ReplicationController, storage.Scale, fakeEtcdClient, etcdStorage
} }
var validPodTemplate = api.PodTemplate{ var validPodTemplate = api.PodTemplate{
...@@ -90,43 +78,32 @@ var validScale = expapi.Scale{ ...@@ -90,43 +78,32 @@ var validScale = expapi.Scale{
} }
func TestGet(t *testing.T) { func TestGet(t *testing.T) {
expect := &validScale storage, fakeClient := newStorage(t)
fakeEtcdClient, etcdStorage := newEtcdStorage(t)
ctx := api.WithNamespace(api.NewContext(), "test")
key := etcdtest.AddPrefix("/controllers/test/foo") key := etcdtest.AddPrefix("/controllers/test/foo")
fakeEtcdClient.Data[key] = tools.EtcdResponseWithError{ if _, err := fakeClient.Set(key, runtime.EncodeOrDie(testapi.Codec(), &validController), 0); err != nil {
R: &etcd.Response{ t.Fatalf("unexpected error: %v", err)
Node: &etcd.Node{
Value: runtime.EncodeOrDie(testapi.Codec(), &validController),
ModifiedIndex: 1,
},
},
} }
storage := NewStorage(etcdStorage).Scale
obj, err := storage.Get(api.WithNamespace(api.NewContext(), "test"), "foo") expect := &validScale
obj, err := storage.Get(ctx, "foo")
scale := obj.(*expapi.Scale) scale := obj.(*expapi.Scale)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
if e, a := expect, scale; !api.Semantic.DeepEqual(e, a) { if e, a := expect, scale; !api.Semantic.DeepEqual(e, a) {
t.Errorf("Unexpected scale: %s", util.ObjectDiff(e, a)) t.Errorf("unexpected scale: %s", util.ObjectDiff(e, a))
} }
} }
func TestUpdate(t *testing.T) { func TestUpdate(t *testing.T) {
fakeEtcdClient, etcdStorage := newEtcdStorage(t) storage, fakeClient := newStorage(t)
storage := NewStorage(etcdStorage).Scale
ctx := api.WithNamespace(api.NewContext(), "test")
key := etcdtest.AddPrefix("/controllers/test/foo") key := etcdtest.AddPrefix("/controllers/test/foo")
fakeEtcdClient.Data[key] = tools.EtcdResponseWithError{ if _, err := fakeClient.Set(key, runtime.EncodeOrDie(testapi.Codec(), &validController), 0); err != nil {
R: &etcd.Response{ t.Fatalf("unexpected error: %v", err)
Node: &etcd.Node{
Value: runtime.EncodeOrDie(testapi.Codec(), &validController),
ModifiedIndex: 1,
},
},
} }
replicas := 12 replicas := 12
update := expapi.Scale{ update := expapi.Scale{
...@@ -136,13 +113,12 @@ func TestUpdate(t *testing.T) { ...@@ -136,13 +113,12 @@ func TestUpdate(t *testing.T) {
}, },
} }
_, _, err := storage.Update(api.WithNamespace(api.NewContext(), "test"), &update) if _, _, err := storage.Update(ctx, &update); err != nil {
if err != nil { t.Fatalf("unexpected error: %v", err)
t.Fatalf("Unexpected error: %v", err)
} }
response, err := fakeEtcdClient.Get(key, false, false) response, err := fakeClient.Get(key, false, false)
if err != nil { if err != nil {
t.Fatalf("Unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
var controller api.ReplicationController var controller api.ReplicationController
......
...@@ -28,8 +28,6 @@ import ( ...@@ -28,8 +28,6 @@ import (
"k8s.io/kubernetes/pkg/tools" "k8s.io/kubernetes/pkg/tools"
"k8s.io/kubernetes/pkg/tools/etcdtest" "k8s.io/kubernetes/pkg/tools/etcdtest"
"k8s.io/kubernetes/pkg/util" "k8s.io/kubernetes/pkg/util"
"github.com/coreos/go-etcd/etcd"
) )
func newStorage(t *testing.T) (*REST, *tools.FakeEtcdClient) { func newStorage(t *testing.T) (*REST, *tools.FakeEtcdClient) {
...@@ -62,11 +60,11 @@ func TestCreate(t *testing.T) { ...@@ -62,11 +60,11 @@ func TestCreate(t *testing.T) {
} }
func TestCreateSetsFields(t *testing.T) { func TestCreateSetsFields(t *testing.T) {
storage, fakeClient := newStorage(t) storage, _ := newStorage(t)
namespace := validNewNamespace() namespace := validNewNamespace()
ctx := api.NewContext() ctx := api.NewContext()
_, err := storage.Create(ctx, namespace) _, err := storage.Create(ctx, namespace)
if err != fakeClient.Err { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
...@@ -129,13 +127,10 @@ func TestWatch(t *testing.T) { ...@@ -129,13 +127,10 @@ func TestWatch(t *testing.T) {
func TestDeleteNamespaceWithIncompleteFinalizers(t *testing.T) { func TestDeleteNamespaceWithIncompleteFinalizers(t *testing.T) {
storage, fakeClient := newStorage(t) storage, fakeClient := newStorage(t)
fakeClient.ChangeIndex = 1 key := etcdtest.AddPrefix("namespaces/foo")
key := etcdtest.AddPrefix("/namespaces/foo") ctx := api.NewContext()
now := util.Now() now := util.Now()
fakeClient.Data[key] = tools.EtcdResponseWithError{ namespace := &api.Namespace{
R: &etcd.Response{
Node: &etcd.Node{
Value: runtime.EncodeOrDie(testapi.Codec(), &api.Namespace{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: "foo", Name: "foo",
DeletionTimestamp: &now, DeletionTimestamp: &now,
...@@ -144,27 +139,21 @@ func TestDeleteNamespaceWithIncompleteFinalizers(t *testing.T) { ...@@ -144,27 +139,21 @@ func TestDeleteNamespaceWithIncompleteFinalizers(t *testing.T) {
Finalizers: []api.FinalizerName{api.FinalizerKubernetes}, Finalizers: []api.FinalizerName{api.FinalizerKubernetes},
}, },
Status: api.NamespaceStatus{Phase: api.NamespaceActive}, Status: api.NamespaceStatus{Phase: api.NamespaceActive},
}),
ModifiedIndex: 1,
CreatedIndex: 1,
},
},
} }
_, err := storage.Delete(api.NewContext(), "foo", nil) if _, err := fakeClient.Set(key, runtime.EncodeOrDie(testapi.Codec(), namespace), 0); err != nil {
if err == nil { t.Fatalf("unexpected error: %v", err)
t.Fatalf("expected error: %v", err) }
if _, err := storage.Delete(ctx, "foo", nil); err == nil {
t.Errorf("unexpected error: %v", err)
} }
} }
func TestDeleteNamespaceWithCompleteFinalizers(t *testing.T) { func TestDeleteNamespaceWithCompleteFinalizers(t *testing.T) {
storage, fakeClient := newStorage(t) storage, fakeClient := newStorage(t)
fakeClient.ChangeIndex = 1 key := etcdtest.AddPrefix("namespaces/foo")
key := etcdtest.AddPrefix("/namespaces/foo") ctx := api.NewContext()
now := util.Now() now := util.Now()
fakeClient.Data[key] = tools.EtcdResponseWithError{ namespace := &api.Namespace{
R: &etcd.Response{
Node: &etcd.Node{
Value: runtime.EncodeOrDie(testapi.Codec(), &api.Namespace{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: "foo", Name: "foo",
DeletionTimestamp: &now, DeletionTimestamp: &now,
...@@ -173,14 +162,11 @@ func TestDeleteNamespaceWithCompleteFinalizers(t *testing.T) { ...@@ -173,14 +162,11 @@ func TestDeleteNamespaceWithCompleteFinalizers(t *testing.T) {
Finalizers: []api.FinalizerName{}, Finalizers: []api.FinalizerName{},
}, },
Status: api.NamespaceStatus{Phase: api.NamespaceActive}, Status: api.NamespaceStatus{Phase: api.NamespaceActive},
}),
ModifiedIndex: 1,
CreatedIndex: 1,
},
},
} }
_, err := storage.Delete(api.NewContext(), "foo", nil) if _, err := fakeClient.Set(key, runtime.EncodeOrDie(testapi.Codec(), namespace), 0); err != nil {
if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
if _, err := storage.Delete(ctx, "foo", nil); err != nil {
t.Errorf("unexpected error: %v", err)
}
} }
...@@ -34,8 +34,6 @@ import ( ...@@ -34,8 +34,6 @@ import (
"k8s.io/kubernetes/pkg/tools" "k8s.io/kubernetes/pkg/tools"
"k8s.io/kubernetes/pkg/tools/etcdtest" "k8s.io/kubernetes/pkg/tools/etcdtest"
"k8s.io/kubernetes/pkg/util" "k8s.io/kubernetes/pkg/util"
"github.com/coreos/go-etcd/etcd"
) )
func newStorage(t *testing.T) (*REST, *BindingREST, *StatusREST, *tools.FakeEtcdClient) { func newStorage(t *testing.T) (*REST, *BindingREST, *StatusREST, *tools.FakeEtcdClient) {
...@@ -256,14 +254,10 @@ func TestResourceLocation(t *testing.T) { ...@@ -256,14 +254,10 @@ func TestResourceLocation(t *testing.T) {
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
for _, tc := range testCases { for _, tc := range testCases {
storage, _, _, fakeClient := newStorage(t) storage, _, _, fakeClient := newStorage(t)
key, _ := storage.Etcd.KeyFunc(ctx, "foo") key, _ := storage.KeyFunc(ctx, tc.pod.Name)
key = etcdtest.AddPrefix(key) key = etcdtest.AddPrefix(key)
fakeClient.Data[key] = tools.EtcdResponseWithError{ if _, err := fakeClient.Set(key, runtime.EncodeOrDie(testapi.Codec(), &tc.pod), 0); err != nil {
R: &etcd.Response{ t.Fatalf("unexpected error: %v", err)
Node: &etcd.Node{
Value: runtime.EncodeOrDie(testapi.Codec(), &tc.pod),
},
},
} }
redirector := rest.Redirector(storage) redirector := rest.Redirector(storage)
...@@ -284,33 +278,6 @@ func TestResourceLocation(t *testing.T) { ...@@ -284,33 +278,6 @@ func TestResourceLocation(t *testing.T) {
} }
} }
func TestDeletePod(t *testing.T) {
storage, _, _, fakeClient := newStorage(t)
fakeClient.ChangeIndex = 1
ctx := api.NewDefaultContext()
key, _ := storage.Etcd.KeyFunc(ctx, "foo")
key = etcdtest.AddPrefix(key)
fakeClient.Data[key] = tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
Value: runtime.EncodeOrDie(testapi.Codec(), &api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: api.PodSpec{NodeName: "machine"},
}),
ModifiedIndex: 1,
CreatedIndex: 1,
},
},
}
_, err := storage.Delete(api.NewDefaultContext(), "foo", nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func TestGet(t *testing.T) { func TestGet(t *testing.T) {
storage, _, _, fakeClient := newStorage(t) storage, _, _, fakeClient := newStorage(t)
test := registrytest.New(t, fakeClient, storage.Etcd) test := registrytest.New(t, fakeClient, storage.Etcd)
...@@ -351,12 +318,7 @@ func TestEtcdCreate(t *testing.T) { ...@@ -351,12 +318,7 @@ func TestEtcdCreate(t *testing.T) {
fakeClient.TestIndex = true fakeClient.TestIndex = true
key, _ := storage.KeyFunc(ctx, "foo") key, _ := storage.KeyFunc(ctx, "foo")
key = etcdtest.AddPrefix(key) key = etcdtest.AddPrefix(key)
fakeClient.Data[key] = tools.EtcdResponseWithError{ fakeClient.ExpectNotFoundGet(key)
R: &etcd.Response{
Node: nil,
},
E: tools.EtcdErrorNotFound,
}
_, err := storage.Create(ctx, validNewPod()) _, err := storage.Create(ctx, validNewPod())
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
...@@ -395,12 +357,7 @@ func TestEtcdCreateBindingNoPod(t *testing.T) { ...@@ -395,12 +357,7 @@ func TestEtcdCreateBindingNoPod(t *testing.T) {
key, _ := storage.KeyFunc(ctx, "foo") key, _ := storage.KeyFunc(ctx, "foo")
key = etcdtest.AddPrefix(key) key = etcdtest.AddPrefix(key)
fakeClient.Data[key] = tools.EtcdResponseWithError{ fakeClient.ExpectNotFoundGet(key)
R: &etcd.Response{
Node: nil,
},
E: tools.EtcdErrorNotFound,
}
// Assume that a pod has undergone the following: // Assume that a pod has undergone the following:
// - Create (apiserver) // - Create (apiserver)
// - Schedule (scheduler) // - Schedule (scheduler)
...@@ -443,12 +400,7 @@ func TestEtcdCreateWithContainersNotFound(t *testing.T) { ...@@ -443,12 +400,7 @@ func TestEtcdCreateWithContainersNotFound(t *testing.T) {
fakeClient.TestIndex = true fakeClient.TestIndex = true
key, _ := storage.KeyFunc(ctx, "foo") key, _ := storage.KeyFunc(ctx, "foo")
key = etcdtest.AddPrefix(key) key = etcdtest.AddPrefix(key)
fakeClient.Data[key] = tools.EtcdResponseWithError{ fakeClient.ExpectNotFoundGet(key)
R: &etcd.Response{
Node: nil,
},
E: tools.EtcdErrorNotFound,
}
_, err := storage.Create(ctx, validNewPod()) _, err := storage.Create(ctx, validNewPod())
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
...@@ -490,12 +442,7 @@ func TestEtcdCreateWithConflict(t *testing.T) { ...@@ -490,12 +442,7 @@ func TestEtcdCreateWithConflict(t *testing.T) {
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
fakeClient.TestIndex = true fakeClient.TestIndex = true
key, _ := storage.KeyFunc(ctx, "foo") key, _ := storage.KeyFunc(ctx, "foo")
fakeClient.Data[key] = tools.EtcdResponseWithError{ fakeClient.ExpectNotFoundGet(key)
R: &etcd.Response{
Node: nil,
},
E: tools.EtcdErrorNotFound,
}
_, err := storage.Create(ctx, validNewPod()) _, err := storage.Create(ctx, validNewPod())
if err != nil { if err != nil {
...@@ -528,12 +475,7 @@ func TestEtcdCreateWithExistingContainers(t *testing.T) { ...@@ -528,12 +475,7 @@ func TestEtcdCreateWithExistingContainers(t *testing.T) {
fakeClient.TestIndex = true fakeClient.TestIndex = true
key, _ := storage.KeyFunc(ctx, "foo") key, _ := storage.KeyFunc(ctx, "foo")
key = etcdtest.AddPrefix(key) key = etcdtest.AddPrefix(key)
fakeClient.Data[key] = tools.EtcdResponseWithError{ fakeClient.ExpectNotFoundGet(key)
R: &etcd.Response{
Node: nil,
},
E: tools.EtcdErrorNotFound,
}
_, err := storage.Create(ctx, validNewPod()) _, err := storage.Create(ctx, validNewPod())
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
...@@ -564,9 +506,7 @@ func TestEtcdCreateWithExistingContainers(t *testing.T) { ...@@ -564,9 +506,7 @@ func TestEtcdCreateWithExistingContainers(t *testing.T) {
} }
func TestEtcdCreateBinding(t *testing.T) { func TestEtcdCreateBinding(t *testing.T) {
storage, bindingStorage, _, fakeClient := newStorage(t)
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
fakeClient.TestIndex = true
testCases := map[string]struct { testCases := map[string]struct {
binding api.Binding binding api.Binding
...@@ -609,14 +549,11 @@ func TestEtcdCreateBinding(t *testing.T) { ...@@ -609,14 +549,11 @@ func TestEtcdCreateBinding(t *testing.T) {
}, },
} }
for k, test := range testCases { for k, test := range testCases {
storage, bindingStorage, _, fakeClient := newStorage(t)
key, _ := storage.KeyFunc(ctx, "foo") key, _ := storage.KeyFunc(ctx, "foo")
key = etcdtest.AddPrefix(key) key = etcdtest.AddPrefix(key)
fakeClient.Data[key] = tools.EtcdResponseWithError{ fakeClient.ExpectNotFoundGet(key)
R: &etcd.Response{
Node: nil,
},
E: tools.EtcdErrorNotFound,
}
if _, err := storage.Create(ctx, validNewPod()); err != nil { if _, err := storage.Create(ctx, validNewPod()); err != nil {
t.Fatalf("%s: unexpected error: %v", k, err) t.Fatalf("%s: unexpected error: %v", k, err)
} }
......
...@@ -20,32 +20,26 @@ import ( ...@@ -20,32 +20,26 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/coreos/go-etcd/etcd"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/registry/registrytest"
"k8s.io/kubernetes/pkg/registry/service/allocator" "k8s.io/kubernetes/pkg/registry/service/allocator"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/storage"
etcdstorage "k8s.io/kubernetes/pkg/storage/etcd"
"k8s.io/kubernetes/pkg/tools" "k8s.io/kubernetes/pkg/tools"
"k8s.io/kubernetes/pkg/tools/etcdtest" "k8s.io/kubernetes/pkg/tools/etcdtest"
) )
func newEtcdStorage(t *testing.T) (*tools.FakeEtcdClient, storage.Interface) { func newStorage(t *testing.T) (*Etcd, *tools.FakeEtcdClient, allocator.Interface) {
fakeEtcdClient := tools.NewFakeEtcdClient(t) etcdStorage, fakeClient := registrytest.NewEtcdStorage(t)
fakeEtcdClient.TestIndex = true
etcdStorage := etcdstorage.NewEtcdStorage(fakeEtcdClient, testapi.Codec(), etcdtest.PathPrefix())
return fakeEtcdClient, etcdStorage
}
func newStorage(t *testing.T) (*Etcd, allocator.Interface, *tools.FakeEtcdClient) {
fakeEtcdClient, s := newEtcdStorage(t)
mem := allocator.NewAllocationMap(100, "rangeSpecValue") mem := allocator.NewAllocationMap(100, "rangeSpecValue")
etcd := NewEtcd(mem, "/ranges/serviceips", "serviceipallocation", s) etcd := NewEtcd(mem, "/ranges/serviceips", "serviceipallocation", etcdStorage)
return etcd, fakeClient, mem
}
return etcd, mem, fakeEtcdClient func validNewRangeAllocation() *api.RangeAllocation {
return &api.RangeAllocation{
Range: "rangeSpecValue",
}
} }
func key() string { func key() string {
...@@ -54,31 +48,18 @@ func key() string { ...@@ -54,31 +48,18 @@ func key() string {
} }
func TestEmpty(t *testing.T) { func TestEmpty(t *testing.T) {
storage, _, ecli := newStorage(t) storage, fakeClient, _ := newStorage(t)
ecli.ExpectNotFoundGet(key()) fakeClient.ExpectNotFoundGet(key())
if _, err := storage.Allocate(1); !strings.Contains(err.Error(), "cannot allocate resources of type serviceipallocation at this time") { if _, err := storage.Allocate(1); !strings.Contains(err.Error(), "cannot allocate resources of type serviceipallocation at this time") {
t.Fatal(err) t.Fatal(err)
} }
} }
func initialObject(ecli *tools.FakeEtcdClient) {
ecli.Data[key()] = tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
CreatedIndex: 1,
ModifiedIndex: 2,
Value: runtime.EncodeOrDie(testapi.Codec(), &api.RangeAllocation{
Range: "rangeSpecValue",
}),
},
},
E: nil,
}
}
func TestStore(t *testing.T) { func TestStore(t *testing.T) {
storage, backing, ecli := newStorage(t) storage, fakeClient, backing := newStorage(t)
initialObject(ecli) if _, err := fakeClient.Set(key(), runtime.EncodeOrDie(testapi.Codec(), validNewRangeAllocation()), 0); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if _, err := storage.Allocate(2); err != nil { if _, err := storage.Allocate(2); err != nil {
t.Fatal(err) t.Fatal(err)
...@@ -94,7 +75,7 @@ func TestStore(t *testing.T) { ...@@ -94,7 +75,7 @@ func TestStore(t *testing.T) {
t.Fatal("Expected allocation to fail") t.Fatal("Expected allocation to fail")
} }
obj := ecli.Data[key()] obj := fakeClient.Data[key()]
if obj.R == nil || obj.R.Node == nil { if obj.R == nil || obj.R.Node == nil {
t.Fatalf("%s is empty: %#v", key(), obj) t.Fatalf("%s is empty: %#v", key(), obj)
} }
...@@ -106,7 +87,7 @@ func TestStore(t *testing.T) { ...@@ -106,7 +87,7 @@ func TestStore(t *testing.T) {
if err := storage.storage.Get(key(), allocation, false); err != nil { if err := storage.storage.Get(key(), allocation, false); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if allocation.ResourceVersion != "1" { if allocation.ResourceVersion != "2" {
t.Fatalf("%#v", allocation) t.Fatalf("%#v", allocation)
} }
if allocation.Range != "rangeSpecValue" { if allocation.Range != "rangeSpecValue" {
......
...@@ -21,29 +21,19 @@ import ( ...@@ -21,29 +21,19 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/coreos/go-etcd/etcd"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/registry/registrytest"
"k8s.io/kubernetes/pkg/registry/service/allocator" "k8s.io/kubernetes/pkg/registry/service/allocator"
allocator_etcd "k8s.io/kubernetes/pkg/registry/service/allocator/etcd" allocator_etcd "k8s.io/kubernetes/pkg/registry/service/allocator/etcd"
"k8s.io/kubernetes/pkg/registry/service/ipallocator" "k8s.io/kubernetes/pkg/registry/service/ipallocator"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/storage"
etcdstorage "k8s.io/kubernetes/pkg/storage/etcd"
"k8s.io/kubernetes/pkg/tools" "k8s.io/kubernetes/pkg/tools"
"k8s.io/kubernetes/pkg/tools/etcdtest" "k8s.io/kubernetes/pkg/tools/etcdtest"
) )
func newEtcdStorage(t *testing.T) (*tools.FakeEtcdClient, storage.Interface) { func newStorage(t *testing.T) (*tools.FakeEtcdClient, ipallocator.Interface, allocator.Interface) {
fakeEtcdClient := tools.NewFakeEtcdClient(t) etcdStorage, fakeClient := registrytest.NewEtcdStorage(t)
fakeEtcdClient.TestIndex = true
etcdStorage := etcdstorage.NewEtcdStorage(fakeEtcdClient, testapi.Codec(), etcdtest.PathPrefix())
return fakeEtcdClient, etcdStorage
}
func newStorage(t *testing.T) (ipallocator.Interface, allocator.Interface, *tools.FakeEtcdClient) {
fakeEtcdClient, etcdStorage := newEtcdStorage(t)
_, cidr, err := net.ParseCIDR("192.168.1.0/24") _, cidr, err := net.ParseCIDR("192.168.1.0/24")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
...@@ -57,7 +47,14 @@ func newStorage(t *testing.T) (ipallocator.Interface, allocator.Interface, *tool ...@@ -57,7 +47,14 @@ func newStorage(t *testing.T) (ipallocator.Interface, allocator.Interface, *tool
return etcd return etcd
}) })
return storage, backing, fakeEtcdClient return fakeClient, storage, backing
}
func validNewRangeAllocation() *api.RangeAllocation {
_, cidr, _ := net.ParseCIDR("192.168.1.0/24")
return &api.RangeAllocation{
Range: cidr.String(),
}
} }
func key() string { func key() string {
...@@ -66,44 +63,30 @@ func key() string { ...@@ -66,44 +63,30 @@ func key() string {
} }
func TestEmpty(t *testing.T) { func TestEmpty(t *testing.T) {
storage, _, ecli := newStorage(t) fakeClient, storage, _ := newStorage(t)
ecli.ExpectNotFoundGet(key()) fakeClient.ExpectNotFoundGet(key())
if err := storage.Allocate(net.ParseIP("192.168.1.2")); !strings.Contains(err.Error(), "cannot allocate resources of type serviceipallocation at this time") { if err := storage.Allocate(net.ParseIP("192.168.1.2")); !strings.Contains(err.Error(), "cannot allocate resources of type serviceipallocation at this time") {
t.Fatal(err) t.Fatal(err)
} }
} }
func TestErrors(t *testing.T) { func TestErrors(t *testing.T) {
storage, _, _ := newStorage(t) _, storage, _ := newStorage(t)
if err := storage.Allocate(net.ParseIP("192.168.0.0")); err != ipallocator.ErrNotInRange { if err := storage.Allocate(net.ParseIP("192.168.0.0")); err != ipallocator.ErrNotInRange {
t.Fatal(err) t.Fatal(err)
} }
} }
func initialObject(ecli *tools.FakeEtcdClient) {
_, cidr, _ := net.ParseCIDR("192.168.1.0/24")
ecli.Data[key()] = tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
CreatedIndex: 1,
ModifiedIndex: 2,
Value: runtime.EncodeOrDie(testapi.Codec(), &api.RangeAllocation{
Range: cidr.String(),
}),
},
},
E: nil,
}
}
func TestStore(t *testing.T) { func TestStore(t *testing.T) {
storage, r, ecli := newStorage(t) fakeClient, storage, backing := newStorage(t)
initialObject(ecli) if _, err := fakeClient.Set(key(), runtime.EncodeOrDie(testapi.Codec(), validNewRangeAllocation()), 0); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := storage.Allocate(net.ParseIP("192.168.1.2")); err != nil { if err := storage.Allocate(net.ParseIP("192.168.1.2")); err != nil {
t.Fatal(err) t.Fatal(err)
} }
ok, err := r.Allocate(1) ok, err := backing.Allocate(1)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -114,7 +97,7 @@ func TestStore(t *testing.T) { ...@@ -114,7 +97,7 @@ func TestStore(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
obj := ecli.Data[key()] obj := fakeClient.Data[key()]
if obj.R == nil || obj.R.Node == nil { if obj.R == nil || obj.R.Node == nil {
t.Fatalf("%s is empty: %#v", key(), obj) t.Fatalf("%s is empty: %#v", key(), obj)
} }
......
...@@ -20,36 +20,19 @@ import ( ...@@ -20,36 +20,19 @@ import (
"testing" "testing"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/rest/resttest"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/expapi" "k8s.io/kubernetes/pkg/expapi"
"k8s.io/kubernetes/pkg/expapi/v1" // Ensure that expapi/v1 package is initialized.
_ "k8s.io/kubernetes/pkg/expapi/v1"
"k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/registry/registrytest" "k8s.io/kubernetes/pkg/registry/registrytest"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/storage"
etcdstorage "k8s.io/kubernetes/pkg/storage/etcd"
"k8s.io/kubernetes/pkg/tools" "k8s.io/kubernetes/pkg/tools"
"k8s.io/kubernetes/pkg/tools/etcdtest"
"github.com/coreos/go-etcd/etcd"
) )
var scheme *runtime.Scheme func newStorage(t *testing.T) (*REST, *tools.FakeEtcdClient) {
var codec runtime.Codec etcdStorage, fakeClient := registrytest.NewEtcdStorage(t)
return NewREST(etcdStorage, "foo", "bar"), fakeClient
func init() {
// Ensure that expapi/v1 packege is used, so that it will get initialized and register HorizontalPodAutoscaler object.
_ = v1.ThirdPartyResourceData{}
}
func newStorage(t *testing.T) (*REST, *tools.FakeEtcdClient, storage.Interface) {
fakeEtcdClient := tools.NewFakeEtcdClient(t)
fakeEtcdClient.TestIndex = true
etcdStorage := etcdstorage.NewEtcdStorage(fakeEtcdClient, testapi.Codec(), etcdtest.PathPrefix())
storage := NewREST(etcdStorage, "foo", "bar")
return storage, fakeEtcdClient, etcdStorage
} }
func validNewThirdPartyResourceData(name string) *expapi.ThirdPartyResourceData { func validNewThirdPartyResourceData(name string) *expapi.ThirdPartyResourceData {
...@@ -63,8 +46,8 @@ func validNewThirdPartyResourceData(name string) *expapi.ThirdPartyResourceData ...@@ -63,8 +46,8 @@ func validNewThirdPartyResourceData(name string) *expapi.ThirdPartyResourceData
} }
func TestCreate(t *testing.T) { func TestCreate(t *testing.T) {
storage, fakeEtcdClient, _ := newStorage(t) storage, fakeClient := newStorage(t)
test := registrytest.New(t, fakeEtcdClient, storage.Etcd) test := registrytest.New(t, fakeClient, storage.Etcd)
rsrc := validNewThirdPartyResourceData("foo") rsrc := validNewThirdPartyResourceData("foo")
rsrc.ObjectMeta = api.ObjectMeta{} rsrc.ObjectMeta = api.ObjectMeta{}
test.TestCreate( test.TestCreate(
...@@ -76,7 +59,7 @@ func TestCreate(t *testing.T) { ...@@ -76,7 +59,7 @@ func TestCreate(t *testing.T) {
} }
func TestUpdate(t *testing.T) { func TestUpdate(t *testing.T) {
storage, fakeClient, _ := newStorage(t) storage, fakeClient := newStorage(t)
test := registrytest.New(t, fakeClient, storage.Etcd) test := registrytest.New(t, fakeClient, storage.Etcd)
test.TestUpdate( test.TestUpdate(
// valid // valid
...@@ -90,75 +73,41 @@ func TestUpdate(t *testing.T) { ...@@ -90,75 +73,41 @@ func TestUpdate(t *testing.T) {
) )
} }
func TestGet(t *testing.T) { func TestDelete(t *testing.T) {
storage, fakeEtcdClient, _ := newStorage(t) storage, fakeClient := newStorage(t)
test := resttest.New(t, storage, fakeEtcdClient.SetError) test := registrytest.New(t, fakeClient, storage.Etcd)
rsrc := validNewThirdPartyResourceData("foo") test.TestDelete(validNewThirdPartyResourceData("foo"))
test.TestGet(rsrc)
} }
func TestEmptyList(t *testing.T) { func TestGet(t *testing.T) {
ctx := api.NewDefaultContext() storage, fakeClient := newStorage(t)
registry, fakeClient, _ := newStorage(t) test := registrytest.New(t, fakeClient, storage.Etcd)
fakeClient.ChangeIndex = 1 test.TestGet(validNewThirdPartyResourceData("foo"))
key := registry.KeyRootFunc(ctx)
key = etcdtest.AddPrefix(key)
fakeClient.Data[key] = tools.EtcdResponseWithError{
R: &etcd.Response{},
E: fakeClient.NewError(tools.EtcdErrorCodeNotFound),
}
rsrcList, err := registry.List(ctx, labels.Everything(), fields.Everything())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(rsrcList.(*expapi.ThirdPartyResourceDataList).Items) != 0 {
t.Errorf("Unexpected non-zero autoscaler list: %#v", rsrcList)
}
if rsrcList.(*expapi.ThirdPartyResourceDataList).ResourceVersion != "1" {
t.Errorf("Unexpected resource version: %#v", rsrcList)
}
} }
func TestList(t *testing.T) { func TestList(t *testing.T) {
ctx := api.NewDefaultContext() storage, fakeClient := newStorage(t)
registry, fakeClient, _ := newStorage(t) test := registrytest.New(t, fakeClient, storage.Etcd)
fakeClient.ChangeIndex = 1 test.TestList(validNewThirdPartyResourceData("foo"))
key := registry.KeyRootFunc(ctx) }
key = etcdtest.AddPrefix(key)
fakeClient.Data[key] = tools.EtcdResponseWithError{ func TestWatch(t *testing.T) {
R: &etcd.Response{ storage, fakeClient := newStorage(t)
Node: &etcd.Node{ test := registrytest.New(t, fakeClient, storage.Etcd)
Nodes: []*etcd.Node{ test.TestWatch(
{ validNewThirdPartyResourceData("foo"),
Value: runtime.EncodeOrDie(testapi.Codec(), &expapi.ThirdPartyResourceData{ // matching labels
ObjectMeta: api.ObjectMeta{Name: "foo"}, []labels.Set{},
}), // not matching labels
}, []labels.Set{
{ {"foo": "bar"},
Value: runtime.EncodeOrDie(testapi.Codec(), &expapi.ThirdPartyResourceData{
ObjectMeta: api.ObjectMeta{Name: "bar"},
}),
},
},
}, },
// matching fields
[]fields.Set{},
// not matching fields
[]fields.Set{
{"metadata.name": "bar"},
{"name": "foo"},
}, },
} )
obj, err := registry.List(ctx, labels.Everything(), fields.Everything())
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
rsrcList := obj.(*expapi.ThirdPartyResourceDataList)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(rsrcList.Items) != 2 {
t.Errorf("Unexpected ThirdPartyResourceData list: %#v", rsrcList)
}
if rsrcList.Items[0].Name != "foo" {
t.Errorf("Unexpected ThirdPartyResourceData: %#v", rsrcList.Items[0])
}
if rsrcList.Items[1].Name != "bar" {
t.Errorf("Unexpected ThirdPartyResourceData: %#v", rsrcList.Items[1])
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment