Improvements to namespace registry to align with pod model

pull/6/head
derekwaynecarr 2015-03-12 11:08:06 -04:00
parent 972a3b1998
commit 2d13dfaf13
9 changed files with 588 additions and 299 deletions

View File

@ -107,30 +107,3 @@ func (nodeStrategy) Validate(obj runtime.Object) errors.ValidationErrorList {
node := obj.(*api.Node)
return validation.ValidateMinion(node)
}
// namespaceStrategy implements behavior for nodes
type namespaceStrategy struct {
runtime.ObjectTyper
api.NameGenerator
}
// Namespaces is the default logic that applies when creating and updating Namespace
// objects.
var Namespaces RESTCreateStrategy = namespaceStrategy{api.Scheme, api.SimpleNameGenerator}
// NamespaceScoped is false for namespaces.
func (namespaceStrategy) NamespaceScoped() bool {
return false
}
// ResetBeforeCreate clears fields that are not allowed to be set by end users on creation.
func (namespaceStrategy) ResetBeforeCreate(obj runtime.Object) {
_ = obj.(*api.Namespace)
// Namespace allow *all* fields, including status, to be set.
}
// Validate validates a new namespace.
func (namespaceStrategy) Validate(obj runtime.Object) errors.ValidationErrorList {
namespace := obj.(*api.Namespace)
return validation.ValidateNamespace(namespace)
}

View File

@ -1025,3 +1025,15 @@ func ValidateNamespaceUpdate(oldNamespace *api.Namespace, namespace *api.Namespa
}
return allErrs
}
// ValidateNamespaceStatusUpdate tests to see if the update is legal for an end user to make. newNamespace is updated with fields
// that cannot be changed.
func ValidateNamespaceStatusUpdate(newNamespace, oldNamespace *api.Namespace) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
allErrs = append(allErrs, ValidateObjectMetaUpdate(&oldNamespace.ObjectMeta, &newNamespace.ObjectMeta).Prefix("metadata")...)
if newNamespace.Status.Phase != oldNamespace.Status.Phase {
allErrs = append(allErrs, errs.NewFieldInvalid("status.phase", newNamespace.Status.Phase, "namespace phase cannot be changed directly"))
}
newNamespace.Spec = oldNamespace.Spec
return allErrs
}

View File

@ -45,10 +45,10 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/endpoint"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/event"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/limitrange"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/minion"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/namespace"
namespaceetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/namespace/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod"
podetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod/etcd"
resourcequotaetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/resourcequota/etcd"
@ -159,7 +159,7 @@ type Master struct {
// TODO: define the internal typed interface in a way that clients can
// also be replaced
nodeRegistry minion.Registry
namespaceRegistry generic.Registry
namespaceRegistry namespace.Registry
serviceRegistry service.Registry
endpointRegistry endpoint.Registry
@ -379,7 +379,9 @@ func (m *Master) init(c *Config) {
resourceQuotaStorage, resourceQuotaStatusStorage := resourcequotaetcd.NewREST(c.EtcdHelper)
secretRegistry := secret.NewEtcdRegistry(c.EtcdHelper)
m.namespaceRegistry = namespace.NewEtcdRegistry(c.EtcdHelper)
namespaceStorage := namespaceetcd.NewREST(c.EtcdHelper)
m.namespaceRegistry = namespace.NewRegistry(namespaceStorage)
// TODO: split me up into distinct storage registries
registry := etcd.NewRegistry(c.EtcdHelper, podRegistry)
@ -421,7 +423,7 @@ func (m *Master) init(c *Config) {
"limitRanges": limitrange.NewREST(limitRangeRegistry),
"resourceQuotas": resourceQuotaStorage,
"resourceQuotas/status": resourceQuotaStatusStorage,
"namespaces": namespace.NewREST(m.namespaceRegistry),
"namespaces": namespaceStorage,
"secrets": secret.NewREST(secretRegistry),
}

View File

@ -80,7 +80,7 @@ func (m *Master) roServiceWriterLoop(stop chan struct{}) {
// createMasterNamespaceIfNeeded will create the namespace that contains the master services if it doesn't already exist
func (m *Master) createMasterNamespaceIfNeeded(ns string) error {
ctx := api.NewContext()
if _, err := m.namespaceRegistry.Get(ctx, api.NamespaceDefault); err == nil {
if _, err := m.namespaceRegistry.GetNamespace(ctx, api.NamespaceDefault); err == nil {
// the namespace already exists
return nil
}

View File

@ -0,0 +1,101 @@
/*
Copyright 2015 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
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
etcdgeneric "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/namespace"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
)
// rest implements a RESTStorage for namespaces against etcd
type REST struct {
store *etcdgeneric.Etcd
}
// NewREST returns a RESTStorage object that will work against namespaces
func NewREST(h tools.EtcdHelper) *REST {
store := &etcdgeneric.Etcd{
NewFunc: func() runtime.Object { return &api.Namespace{} },
NewListFunc: func() runtime.Object { return &api.NamespaceList{} },
KeyRootFunc: func(ctx api.Context) string {
return "/registry/namespaces"
},
KeyFunc: func(ctx api.Context, name string) (string, error) {
return "/registry/namespaces/" + name, nil
},
ObjectNameFunc: func(obj runtime.Object) (string, error) {
return obj.(*api.Namespace).Name, nil
},
PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher {
return namespace.MatchNamespace(label, field)
},
EndpointName: "namespaces",
Helper: h,
}
store.CreateStrategy = namespace.Strategy
store.UpdateStrategy = namespace.Strategy
store.ReturnDeletedObject = true
return &REST{store: store}
}
// New returns a new object
func (r *REST) New() runtime.Object {
return r.store.NewFunc()
}
// NewList returns a new list object
func (r *REST) NewList() runtime.Object {
return r.store.NewListFunc()
}
// List obtains a list of namespaces with labels that match selector.
func (r *REST) List(ctx api.Context, label labels.Selector, field fields.Selector) (runtime.Object, error) {
return r.store.List(ctx, label, field)
}
// Watch begins watching for new, changed, or deleted namespaces
func (r *REST) Watch(ctx api.Context, label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
return r.store.Watch(ctx, label, field, resourceVersion)
}
// Get gets a specific namespace specified by its name
func (r *REST) Get(ctx api.Context, name string) (runtime.Object, error) {
return r.store.Get(ctx, name)
}
// Create creates a namespace based on a specification.
func (r *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) {
return r.store.Create(ctx, obj)
}
// Update changes a namespace specification.
func (r *REST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
return r.store.Update(ctx, obj)
}
// Delete deletes an existing namespace specified by its name
func (r *REST) Delete(ctx api.Context, name string) (runtime.Object, error) {
return r.store.Delete(ctx, name)
}

View File

@ -0,0 +1,321 @@
/*
Copyright 2015 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
import (
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest/resttest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/namespace"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/coreos/go-etcd/etcd"
)
func newHelper(t *testing.T) (*tools.FakeEtcdClient, tools.EtcdHelper) {
fakeEtcdClient := tools.NewFakeEtcdClient(t)
fakeEtcdClient.TestIndex = true
helper := tools.EtcdHelper{Client: fakeEtcdClient, Codec: latest.Codec, ResourceVersioner: tools.RuntimeVersionAdapter{latest.ResourceVersioner}}
return fakeEtcdClient, helper
}
func newStorage(t *testing.T) (*REST, *tools.FakeEtcdClient, tools.EtcdHelper) {
fakeEtcdClient, h := newHelper(t)
storage := NewREST(h)
return storage, fakeEtcdClient, h
}
func validNewNamespace() *api.Namespace {
return &api.Namespace{
ObjectMeta: api.ObjectMeta{
Name: "foo",
},
}
}
func validChangedNamespace() *api.Namespace {
namespace := validNewNamespace()
namespace.ResourceVersion = "1"
namespace.Labels = map[string]string{
"foo": "bar",
}
return namespace
}
func TestStorage(t *testing.T) {
storage, _, _ := newStorage(t)
namespace.NewRegistry(storage)
}
func TestCreate(t *testing.T) {
fakeEtcdClient, helper := newHelper(t)
storage := NewREST(helper)
test := resttest.New(t, storage, fakeEtcdClient.SetError)
namespace := validNewNamespace()
namespace.ObjectMeta = api.ObjectMeta{}
test.TestCreate(
// valid
namespace,
// invalid
&api.Namespace{
ObjectMeta: api.ObjectMeta{Namespace: "nope"},
},
)
}
func expectNamespace(t *testing.T, out runtime.Object) (*api.Namespace, bool) {
namespace, ok := out.(*api.Namespace)
if !ok || namespace == nil {
t.Errorf("Expected an api.Namespace object, was %#v", out)
return nil, false
}
return namespace, true
}
func TestCreateSetsFields(t *testing.T) {
fakeEtcdClient, helper := newHelper(t)
storage := NewREST(helper)
namespace := validNewNamespace()
_, err := storage.Create(api.NewDefaultContext(), namespace)
if err != fakeEtcdClient.Err {
t.Fatalf("unexpected error: %v", err)
}
actual := &api.Namespace{}
if err := helper.ExtractObj("/registry/namespaces/foo", actual, false); err != nil {
t.Fatalf("unexpected extraction error: %v", err)
}
if actual.Name != namespace.Name {
t.Errorf("unexpected namespace: %#v", actual)
}
if len(actual.UID) == 0 {
t.Errorf("expected namespace UID to be set: %#v", actual)
}
if actual.Status.Phase != api.NamespaceActive {
t.Errorf("expected namespace phase to be set to active, but %v", actual.Status.Phase)
}
}
func TestListEmptyNamespaceList(t *testing.T) {
fakeEtcdClient, helper := newHelper(t)
fakeEtcdClient.ChangeIndex = 1
fakeEtcdClient.Data["/registry/namespaces"] = tools.EtcdResponseWithError{
R: &etcd.Response{},
E: fakeEtcdClient.NewError(tools.EtcdErrorCodeNotFound),
}
storage := NewREST(helper)
namespaces, err := storage.List(api.NewContext(), labels.Everything(), fields.Everything())
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if len(namespaces.(*api.NamespaceList).Items) != 0 {
t.Errorf("Unexpected non-zero namespace list: %#v", namespaces)
}
if namespaces.(*api.NamespaceList).ResourceVersion != "1" {
t.Errorf("Unexpected resource version: %#v", namespaces)
}
}
func TestListNamespaceList(t *testing.T) {
fakeEtcdClient, helper := newHelper(t)
fakeEtcdClient.Data["/registry/namespaces"] = tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
Nodes: []*etcd.Node{
{
Value: runtime.EncodeOrDie(latest.Codec, &api.Namespace{
ObjectMeta: api.ObjectMeta{Name: "foo"},
}),
},
{
Value: runtime.EncodeOrDie(latest.Codec, &api.Namespace{
ObjectMeta: api.ObjectMeta{Name: "bar"},
}),
},
},
},
},
}
storage := NewREST(helper)
namespacesObj, err := storage.List(api.NewDefaultContext(), labels.Everything(), fields.Everything())
namespaces := namespacesObj.(*api.NamespaceList)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(namespaces.Items) != 2 {
t.Errorf("Unexpected namespaces list: %#v", namespaces)
}
if namespaces.Items[0].Name != "foo" || namespaces.Items[0].Status.Phase != api.NamespaceActive {
t.Errorf("Unexpected namespace: %#v", namespaces.Items[0])
}
if namespaces.Items[1].Name != "bar" {
t.Errorf("Unexpected namespace: %#v", namespaces.Items[1])
}
}
func TestListNamespaceListSelection(t *testing.T) {
fakeEtcdClient, helper := newHelper(t)
fakeEtcdClient.Data["/registry/namespaces"] = tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
Nodes: []*etcd.Node{
{Value: runtime.EncodeOrDie(latest.Codec, &api.Namespace{
ObjectMeta: api.ObjectMeta{Name: "foo"},
})},
{Value: runtime.EncodeOrDie(latest.Codec, &api.Namespace{
ObjectMeta: api.ObjectMeta{Name: "bar"},
})},
{Value: runtime.EncodeOrDie(latest.Codec, &api.Namespace{
ObjectMeta: api.ObjectMeta{Name: "baz"},
Status: api.NamespaceStatus{Phase: api.NamespaceTerminating},
})},
{Value: runtime.EncodeOrDie(latest.Codec, &api.Namespace{
ObjectMeta: api.ObjectMeta{
Name: "qux",
Labels: map[string]string{"label": "qux"},
},
})},
{Value: runtime.EncodeOrDie(latest.Codec, &api.Namespace{
ObjectMeta: api.ObjectMeta{Name: "zot"},
})},
},
},
},
}
storage := NewREST(helper)
ctx := api.NewDefaultContext()
table := []struct {
label, field string
expectedIDs util.StringSet
}{
{
expectedIDs: util.NewStringSet("foo", "bar", "baz", "qux", "zot"),
}, {
field: "name=zot",
expectedIDs: util.NewStringSet("zot"),
}, {
label: "label=qux",
expectedIDs: util.NewStringSet("qux"),
}, {
field: "status.phase=Terminating",
expectedIDs: util.NewStringSet("baz"),
},
}
for index, item := range table {
label, err := labels.Parse(item.label)
if err != nil {
t.Errorf("unexpected error: %v", err)
continue
}
field, err := fields.ParseSelector(item.field)
if err != nil {
t.Errorf("unexpected error: %v", err)
continue
}
namespacesObj, err := storage.List(ctx, label, field)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
namespaces := namespacesObj.(*api.NamespaceList)
set := util.NewStringSet()
for i := range namespaces.Items {
set.Insert(namespaces.Items[i].Name)
}
if e, a := len(item.expectedIDs), len(set); e != a {
t.Errorf("%v: Expected %v, got %v", index, item.expectedIDs, set)
}
}
}
func TestNamespaceDecode(t *testing.T) {
storage := NewREST(tools.EtcdHelper{})
expected := validNewNamespace()
expected.Status.Phase = api.NamespaceActive
body, err := latest.Codec.Encode(expected)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
actual := storage.New()
if err := latest.Codec.DecodeInto(body, actual); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !api.Semantic.DeepEqual(expected, actual) {
t.Errorf("mismatch: %s", util.ObjectDiff(expected, actual))
}
}
func TestGet(t *testing.T) {
expect := validNewNamespace()
expect.Status.Phase = api.NamespaceActive
fakeEtcdClient, helper := newHelper(t)
fakeEtcdClient.Data["/registry/namespaces/foo"] = tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
Value: runtime.EncodeOrDie(latest.Codec, expect),
},
},
}
storage := NewREST(helper)
obj, err := storage.Get(api.NewContext(), "foo")
namespace := obj.(*api.Namespace)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expect.Status.Phase = api.NamespaceActive
if e, a := expect, namespace; !api.Semantic.DeepEqual(e, a) {
t.Errorf("Unexpected namespace: %s", util.ObjectDiff(e, a))
}
}
func TestDeleteNamespace(t *testing.T) {
fakeEtcdClient, helper := newHelper(t)
fakeEtcdClient.ChangeIndex = 1
fakeEtcdClient.Data["/registry/namespaces/foo"] = tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
Value: runtime.EncodeOrDie(latest.Codec, &api.Namespace{
ObjectMeta: api.ObjectMeta{
Name: "foo",
},
Status: api.NamespaceStatus{Phase: api.NamespaceActive},
}),
ModifiedIndex: 1,
CreatedIndex: 1,
},
},
}
storage := NewREST(helper)
_, err := storage.Delete(api.NewDefaultContext(), "foo")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// TODO: when we add life-cycle, this will go to Terminating, and then we need to test Terminating to gone
}

View File

@ -18,31 +18,83 @@ package namespace
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
etcdgeneric "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
)
// registry implements custom changes to generic.Etcd for Namespace storage
type registry struct {
*etcdgeneric.Etcd
// Registry is an interface implemented by things that know how to store Namespace objects.
type Registry interface {
// ListNamespaces obtains a list of namespaces having labels which match selector.
ListNamespaces(ctx api.Context, selector labels.Selector) (*api.NamespaceList, error)
// Watch for new/changed/deleted namespaces
WatchNamespaces(ctx api.Context, label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error)
// Get a specific namespace
GetNamespace(ctx api.Context, namespaceID string) (*api.Namespace, error)
// Create a namespace based on a specification.
CreateNamespace(ctx api.Context, namespace *api.Namespace) error
// Update an existing namespace
UpdateNamespace(ctx api.Context, namespace *api.Namespace) error
// Delete an existing namespace
DeleteNamespace(ctx api.Context, namespaceID string) error
}
// NewEtcdRegistry returns a registry which will store Namespace objects in the given EtcdHelper.
func NewEtcdRegistry(h tools.EtcdHelper) generic.Registry {
return registry{
Etcd: &etcdgeneric.Etcd{
NewFunc: func() runtime.Object { return &api.Namespace{} },
NewListFunc: func() runtime.Object { return &api.NamespaceList{} },
EndpointName: "namespaces",
KeyRootFunc: func(ctx api.Context) string {
return "/registry/namespaces"
},
KeyFunc: func(ctx api.Context, id string) (string, error) {
return "/registry/namespaces/" + id, nil
},
Helper: h,
},
}
// Storage is an interface for a standard REST Storage backend
// TODO: move me somewhere common
type Storage interface {
apiserver.RESTDeleter
apiserver.RESTLister
apiserver.RESTGetter
apiserver.ResourceWatcher
Create(ctx api.Context, obj runtime.Object) (runtime.Object, error)
Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error)
}
// storage puts strong typing around storage calls
type storage struct {
Storage
}
// NewRegistry returns a new Registry interface for the given Storage. Any mismatched
// types will panic.
func NewRegistry(s Storage) Registry {
return &storage{s}
}
func (s *storage) ListNamespaces(ctx api.Context, label labels.Selector) (*api.NamespaceList, error) {
obj, err := s.List(ctx, label, fields.Everything())
if err != nil {
return nil, err
}
return obj.(*api.NamespaceList), nil
}
func (s *storage) WatchNamespaces(ctx api.Context, label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
return s.Watch(ctx, label, field, resourceVersion)
}
func (s *storage) GetNamespace(ctx api.Context, namespaceName string) (*api.Namespace, error) {
obj, err := s.Get(ctx, namespaceName)
if err != nil {
return nil, err
}
return obj.(*api.Namespace), nil
}
func (s *storage) CreateNamespace(ctx api.Context, namespace *api.Namespace) error {
_, err := s.Create(ctx, namespace)
return err
}
func (s *storage) UpdateNamespace(ctx api.Context, namespace *api.Namespace) error {
_, _, err := s.Update(ctx, namespace)
return err
}
func (s *storage) DeleteNamespace(ctx api.Context, namespaceID string) error {
_, err := s.Delete(ctx, namespaceID)
return err
}

View File

@ -20,108 +20,81 @@ import (
"fmt"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
kerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
)
// REST provides the RESTStorage access patterns to work with Namespace objects.
type REST struct {
registry generic.Registry
// namespaceStrategy implements behavior for Namespaces
type namespaceStrategy struct {
runtime.ObjectTyper
api.NameGenerator
}
// 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,
}
// Strategy is the default logic that applies when creating and updating Namespace
// objects via the REST API.
var Strategy = namespaceStrategy{api.Scheme, api.SimpleNameGenerator}
// NamespaceScoped is true for namespaces.
func (namespaceStrategy) NamespaceScoped() bool {
return false
}
// Create creates a Namespace object
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) {
// ResetBeforeCreate clears fields that are not allowed to be set by end users on creation.
func (namespaceStrategy) ResetBeforeCreate(obj runtime.Object) {
namespace := obj.(*api.Namespace)
if err := rest.BeforeCreate(rest.Namespaces, ctx, obj); err != nil {
return nil, err
namespace.Status = api.NamespaceStatus{
Phase: api.NamespaceActive,
}
if err := rs.registry.CreateWithName(ctx, namespace.Name, namespace); err != nil {
err = rest.CheckGeneratedNameError(rest.Namespaces, err, namespace)
return nil, err
}
return rs.registry.Get(ctx, namespace.Name)
}
// Update updates a Namespace object.
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
namespace, ok := obj.(*api.Namespace)
if !ok {
return nil, false, fmt.Errorf("not a namespace: %#v", obj)
}
oldObj, err := rs.registry.Get(ctx, namespace.Name)
if err != nil {
return nil, false, err
}
oldNamespace := oldObj.(*api.Namespace)
if errs := validation.ValidateNamespaceUpdate(oldNamespace, namespace); len(errs) > 0 {
return nil, false, kerrors.NewInvalid("namespace", namespace.Name, errs)
}
if err := rs.registry.UpdateWithName(ctx, oldNamespace.Name, oldNamespace); err != nil {
return nil, false, err
}
out, err := rs.registry.Get(ctx, oldNamespace.Name)
return out, false, err
// Validate validates a new namespace.
func (namespaceStrategy) Validate(obj runtime.Object) errors.ValidationErrorList {
namespace := obj.(*api.Namespace)
return validation.ValidateNamespace(namespace)
}
// Delete deletes the Namespace with the specified name
func (rs *REST) Delete(ctx api.Context, name string) (runtime.Object, error) {
obj, err := rs.registry.Get(ctx, name)
if err != nil {
return nil, err
// AllowCreateOnUpdate is false for namespaces.
func (namespaceStrategy) AllowCreateOnUpdate() bool {
return false
}
// ValidateUpdate is the default update validation for an end user.
func (namespaceStrategy) ValidateUpdate(obj, old runtime.Object) errors.ValidationErrorList {
return validation.ValidateNamespaceUpdate(obj.(*api.Namespace), old.(*api.Namespace))
}
type namespaceStatusStrategy struct {
namespaceStrategy
}
var StatusStrategy = namespaceStatusStrategy{Strategy}
func (namespaceStatusStrategy) ValidateUpdate(obj, old runtime.Object) errors.ValidationErrorList {
// TODO: merge valid fields after update
return validation.ValidateNamespaceStatusUpdate(obj.(*api.Namespace), old.(*api.Namespace))
}
// MatchNamespace returns a generic matcher for a given label and field selector.
func MatchNamespace(label labels.Selector, field fields.Selector) generic.Matcher {
return generic.MatcherFunc(func(obj runtime.Object) (bool, error) {
namespaceObj, ok := obj.(*api.Namespace)
if !ok {
return false, fmt.Errorf("not a namespace")
}
fields := NamespaceToSelectableFields(namespaceObj)
return label.Matches(labels.Set(namespaceObj.Labels)) && field.Matches(fields), nil
})
}
// NamespaceToSelectableFields returns a label set that represents the object
// TODO: fields are not labels, and the validation rules for them do not apply.
func NamespaceToSelectableFields(namespace *api.Namespace) labels.Set {
return labels.Set{
"name": namespace.Name,
"status.phase": string(namespace.Status.Phase),
}
_, ok := obj.(*api.Namespace)
if !ok {
return nil, fmt.Errorf("invalid object type")
}
return rs.registry.Delete(ctx, name)
}
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
}
namespace, ok := obj.(*api.Namespace)
if !ok {
return nil, fmt.Errorf("invalid object type")
}
return namespace, err
}
func (rs *REST) getAttrs(obj runtime.Object) (objLabels labels.Set, objFields fields.Set, err error) {
return labels.Set{}, fields.Set{}, nil
}
func (rs *REST) List(ctx api.Context, label labels.Selector, field fields.Selector) (runtime.Object, error) {
return rs.registry.ListPredicate(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs})
}
func (rs *REST) Watch(ctx api.Context, label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
return rs.registry.WatchPredicate(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}, resourceVersion)
}
// New returns a new api.Namespace
func (*REST) New() runtime.Object {
return &api.Namespace{}
}
func (*REST) NewList() runtime.Object {
return &api.NamespaceList{}
}

View File

@ -17,169 +17,24 @@ limitations under the License.
package namespace
import (
"reflect"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"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 testNamespace(name string) *api.Namespace {
return &api.Namespace{
ObjectMeta: api.ObjectMeta{
Name: name,
},
}
}
func TestRESTCreate(t *testing.T) {
table := []struct {
ctx api.Context
namespace *api.Namespace
valid bool
}{
{
ctx: api.NewContext(),
namespace: testNamespace("foo"),
valid: true,
}, {
ctx: api.NewContext(),
namespace: testNamespace("bar"),
valid: true,
},
}
for _, item := range table {
_, rest := NewTestREST()
c, err := rest.Create(item.ctx, item.namespace)
if !item.valid {
if err == nil {
t.Errorf("unexpected non-error for %v", item.namespace.Name)
}
continue
}
if err != nil {
t.Errorf("%v: Unexpected error %v", item.namespace.Name, err)
continue
}
if !api.HasObjectMetaSystemFieldValues(&item.namespace.ObjectMeta) {
t.Errorf("storage did not populate object meta field values")
}
if e, a := item.namespace, c; !reflect.DeepEqual(e, a) {
t.Errorf("diff: %s", util.ObjectDiff(e, a))
}
// Ensure we implement the interface
_ = apiserver.ResourceWatcher(rest)
}
}
func TestRESTUpdate(t *testing.T) {
_, rest := NewTestREST()
namespaceA := testNamespace("foo")
_, err := rest.Create(api.NewDefaultContext(), namespaceA)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
got, err := rest.Get(api.NewDefaultContext(), namespaceA.Name)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
if e, a := namespaceA, got; !reflect.DeepEqual(e, a) {
t.Errorf("diff: %s", util.ObjectDiff(e, a))
}
namespaceB := testNamespace("foo")
_, _, err = rest.Update(api.NewDefaultContext(), namespaceB)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
got2, err := rest.Get(api.NewDefaultContext(), namespaceB.Name)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
if e, a := namespaceB, got2; !reflect.DeepEqual(e, a) {
t.Errorf("diff: %s", util.ObjectDiff(e, a))
}
}
func TestRESTDelete(t *testing.T) {
_, rest := NewTestREST()
namespaceA := testNamespace("foo")
_, err := rest.Create(api.NewContext(), namespaceA)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
c, err := rest.Delete(api.NewContext(), namespaceA.Name)
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()
namespaceA := testNamespace("foo")
_, err := rest.Create(api.NewContext(), namespaceA)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
got, err := rest.Get(api.NewContext(), namespaceA.Name)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
if e, a := namespaceA, got; !reflect.DeepEqual(e, a) {
t.Errorf("diff: %s", util.ObjectDiff(e, a))
}
}
func TestRESTList(t *testing.T) {
reg, rest := NewTestREST()
namespaceA := testNamespace("foo")
namespaceB := testNamespace("bar")
namespaceC := testNamespace("baz")
reg.ObjectList = &api.NamespaceList{
Items: []api.Namespace{*namespaceA, *namespaceB, *namespaceC},
}
got, err := rest.List(api.NewContext(), labels.Everything(), fields.Everything())
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
expect := &api.NamespaceList{
Items: []api.Namespace{*namespaceA, *namespaceB, *namespaceC},
}
if e, a := expect, got; !reflect.DeepEqual(e, a) {
t.Errorf("diff: %s", util.ObjectDiff(e, a))
}
}
func TestRESTWatch(t *testing.T) {
namespaceA := testNamespace("foo")
reg, rest := NewTestREST()
wi, err := rest.Watch(api.NewContext(), labels.Everything(), fields.Everything(), "0")
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
go func() {
reg.Broadcaster.Action(watch.Added, namespaceA)
}()
got := <-wi.ResultChan()
if e, a := namespaceA, got.Object; !reflect.DeepEqual(e, a) {
t.Errorf("diff: %s", util.ObjectDiff(e, a))
func TestNamespaceStrategy(t *testing.T) {
if Strategy.NamespaceScoped() {
t.Errorf("Namespaces should not be namespace scoped")
}
if Strategy.AllowCreateOnUpdate() {
t.Errorf("Namespaces should not allow create on update")
}
namespace := &api.Namespace{
ObjectMeta: api.ObjectMeta{Name: "foo"},
Status: api.NamespaceStatus{Phase: api.NamespaceTerminating},
}
Strategy.ResetBeforeCreate(namespace)
if namespace.Status.Phase != api.NamespaceActive {
t.Errorf("Namespaces do not allow setting phase on create")
}
}