mirror of https://github.com/k3s-io/k3s
Improvements to namespace registry to align with pod model
parent
972a3b1998
commit
2d13dfaf13
|
@ -107,30 +107,3 @@ func (nodeStrategy) Validate(obj runtime.Object) errors.ValidationErrorList {
|
||||||
node := obj.(*api.Node)
|
node := obj.(*api.Node)
|
||||||
return validation.ValidateMinion(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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -1025,3 +1025,15 @@ func ValidateNamespaceUpdate(oldNamespace *api.Namespace, namespace *api.Namespa
|
||||||
}
|
}
|
||||||
return allErrs
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -45,10 +45,10 @@ import (
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/endpoint"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/endpoint"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/etcd"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/etcd"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/event"
|
"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/limitrange"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/minion"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/minion"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/namespace"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/namespace"
|
||||||
|
namespaceetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/namespace/etcd"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod"
|
||||||
podetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod/etcd"
|
podetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod/etcd"
|
||||||
resourcequotaetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/resourcequota/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
|
// TODO: define the internal typed interface in a way that clients can
|
||||||
// also be replaced
|
// also be replaced
|
||||||
nodeRegistry minion.Registry
|
nodeRegistry minion.Registry
|
||||||
namespaceRegistry generic.Registry
|
namespaceRegistry namespace.Registry
|
||||||
serviceRegistry service.Registry
|
serviceRegistry service.Registry
|
||||||
endpointRegistry endpoint.Registry
|
endpointRegistry endpoint.Registry
|
||||||
|
|
||||||
|
@ -379,7 +379,9 @@ func (m *Master) init(c *Config) {
|
||||||
|
|
||||||
resourceQuotaStorage, resourceQuotaStatusStorage := resourcequotaetcd.NewREST(c.EtcdHelper)
|
resourceQuotaStorage, resourceQuotaStatusStorage := resourcequotaetcd.NewREST(c.EtcdHelper)
|
||||||
secretRegistry := secret.NewEtcdRegistry(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
|
// TODO: split me up into distinct storage registries
|
||||||
registry := etcd.NewRegistry(c.EtcdHelper, podRegistry)
|
registry := etcd.NewRegistry(c.EtcdHelper, podRegistry)
|
||||||
|
@ -421,7 +423,7 @@ func (m *Master) init(c *Config) {
|
||||||
"limitRanges": limitrange.NewREST(limitRangeRegistry),
|
"limitRanges": limitrange.NewREST(limitRangeRegistry),
|
||||||
"resourceQuotas": resourceQuotaStorage,
|
"resourceQuotas": resourceQuotaStorage,
|
||||||
"resourceQuotas/status": resourceQuotaStatusStorage,
|
"resourceQuotas/status": resourceQuotaStatusStorage,
|
||||||
"namespaces": namespace.NewREST(m.namespaceRegistry),
|
"namespaces": namespaceStorage,
|
||||||
"secrets": secret.NewREST(secretRegistry),
|
"secrets": secret.NewREST(secretRegistry),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
// createMasterNamespaceIfNeeded will create the namespace that contains the master services if it doesn't already exist
|
||||||
func (m *Master) createMasterNamespaceIfNeeded(ns string) error {
|
func (m *Master) createMasterNamespaceIfNeeded(ns string) error {
|
||||||
ctx := api.NewContext()
|
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
|
// the namespace already exists
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -18,31 +18,83 @@ package namespace
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
||||||
etcdgeneric "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic/etcd"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"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
|
// Registry is an interface implemented by things that know how to store Namespace objects.
|
||||||
type registry struct {
|
type Registry interface {
|
||||||
*etcdgeneric.Etcd
|
// 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.
|
// Storage is an interface for a standard REST Storage backend
|
||||||
func NewEtcdRegistry(h tools.EtcdHelper) generic.Registry {
|
// TODO: move me somewhere common
|
||||||
return registry{
|
type Storage interface {
|
||||||
Etcd: &etcdgeneric.Etcd{
|
apiserver.RESTDeleter
|
||||||
NewFunc: func() runtime.Object { return &api.Namespace{} },
|
apiserver.RESTLister
|
||||||
NewListFunc: func() runtime.Object { return &api.NamespaceList{} },
|
apiserver.RESTGetter
|
||||||
EndpointName: "namespaces",
|
apiserver.ResourceWatcher
|
||||||
KeyRootFunc: func(ctx api.Context) string {
|
|
||||||
return "/registry/namespaces"
|
Create(ctx api.Context, obj runtime.Object) (runtime.Object, error)
|
||||||
},
|
Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error)
|
||||||
KeyFunc: func(ctx api.Context, id string) (string, error) {
|
|
||||||
return "/registry/namespaces/" + id, nil
|
|
||||||
},
|
|
||||||
Helper: h,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,108 +20,81 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
kerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// REST provides the RESTStorage access patterns to work with Namespace objects.
|
// namespaceStrategy implements behavior for Namespaces
|
||||||
type REST struct {
|
type namespaceStrategy struct {
|
||||||
registry generic.Registry
|
runtime.ObjectTyper
|
||||||
|
api.NameGenerator
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewREST returns a new REST. You must use a registry created by
|
// Strategy is the default logic that applies when creating and updating Namespace
|
||||||
// NewEtcdRegistry unless you're testing.
|
// objects via the REST API.
|
||||||
func NewREST(registry generic.Registry) *REST {
|
var Strategy = namespaceStrategy{api.Scheme, api.SimpleNameGenerator}
|
||||||
return &REST{
|
|
||||||
registry: registry,
|
// NamespaceScoped is true for namespaces.
|
||||||
}
|
func (namespaceStrategy) NamespaceScoped() bool {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create creates a Namespace object
|
// ResetBeforeCreate clears fields that are not allowed to be set by end users on creation.
|
||||||
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) {
|
func (namespaceStrategy) ResetBeforeCreate(obj runtime.Object) {
|
||||||
namespace := obj.(*api.Namespace)
|
namespace := obj.(*api.Namespace)
|
||||||
if err := rest.BeforeCreate(rest.Namespaces, ctx, obj); err != nil {
|
namespace.Status = api.NamespaceStatus{
|
||||||
return nil, err
|
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.
|
// Validate validates a new namespace.
|
||||||
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
|
func (namespaceStrategy) Validate(obj runtime.Object) errors.ValidationErrorList {
|
||||||
namespace, ok := obj.(*api.Namespace)
|
namespace := obj.(*api.Namespace)
|
||||||
|
return validation.ValidateNamespace(namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
if !ok {
|
||||||
return nil, false, fmt.Errorf("not a namespace: %#v", obj)
|
return false, fmt.Errorf("not a namespace")
|
||||||
|
}
|
||||||
|
fields := NamespaceToSelectableFields(namespaceObj)
|
||||||
|
return label.Matches(labels.Set(namespaceObj.Labels)) && field.Matches(fields), nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
oldObj, err := rs.registry.Get(ctx, namespace.Name)
|
// NamespaceToSelectableFields returns a label set that represents the object
|
||||||
if err != nil {
|
// TODO: fields are not labels, and the validation rules for them do not apply.
|
||||||
return nil, false, err
|
func NamespaceToSelectableFields(namespace *api.Namespace) labels.Set {
|
||||||
|
return labels.Set{
|
||||||
|
"name": namespace.Name,
|
||||||
|
"status.phase": string(namespace.Status.Phase),
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
_, 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{}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,169 +17,24 @@ limitations under the License.
|
||||||
package namespace
|
package namespace
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"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 {
|
func TestNamespaceStrategy(t *testing.T) {
|
||||||
*registrytest.GenericRegistry
|
if Strategy.NamespaceScoped() {
|
||||||
|
t.Errorf("Namespaces should not be namespace scoped")
|
||||||
}
|
}
|
||||||
|
if Strategy.AllowCreateOnUpdate() {
|
||||||
func NewTestREST() (testRegistry, *REST) {
|
t.Errorf("Namespaces should not allow create on update")
|
||||||
reg := testRegistry{registrytest.NewGeneric(nil)}
|
|
||||||
return reg, NewREST(reg)
|
|
||||||
}
|
}
|
||||||
|
namespace := &api.Namespace{
|
||||||
func testNamespace(name string) *api.Namespace {
|
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||||
return &api.Namespace{
|
Status: api.NamespaceStatus{Phase: api.NamespaceTerminating},
|
||||||
ObjectMeta: api.ObjectMeta{
|
}
|
||||||
Name: name,
|
Strategy.ResetBeforeCreate(namespace)
|
||||||
},
|
if namespace.Status.Phase != api.NamespaceActive {
|
||||||
}
|
t.Errorf("Namespaces do not allow setting phase on create")
|
||||||
}
|
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue