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)
|
||||
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
|
||||
}
|
||||
|
||||
// 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/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),
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
"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
|
||||
}
|
||||
|
|
|
@ -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{}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue