Namespace.Spec.Finalizer support

This commit is contained in:
derekwaynecarr 2015-03-20 12:48:12 -04:00
parent cb7c781da5
commit 29c491ef2e
21 changed files with 381 additions and 24 deletions

View File

@ -95,3 +95,10 @@ func IsServiceIPSet(service *Service) bool {
func IsServiceIPRequested(service *Service) bool { func IsServiceIPRequested(service *Service) bool {
return service.Spec.PortalIP == "" return service.Spec.PortalIP == ""
} }
var standardFinalizers = util.NewStringSet(
string(FinalizerKubernetes))
func IsStandardFinalizerName(str string) bool {
return standardFinalizers.Has(str)
}

View File

@ -198,6 +198,9 @@ func FuzzerFor(t *testing.T, version string, src rand.Source) *fuzz.Fuzzer {
c.FuzzNoCustom(s) // fuzz self without calling this function again c.FuzzNoCustom(s) // fuzz self without calling this function again
s.Type = api.SecretTypeOpaque s.Type = api.SecretTypeOpaque
}, },
func(s *api.NamespaceSpec, c fuzz.Continue) {
s.Finalizers = []api.FinalizerName{api.FinalizerKubernetes}
},
func(s *api.NamespaceStatus, c fuzz.Continue) { func(s *api.NamespaceStatus, c fuzz.Continue) {
s.Phase = api.NamespaceActive s.Phase = api.NamespaceActive
}, },

View File

@ -966,8 +966,17 @@ type NodeList struct {
// NamespaceSpec describes the attributes on a Namespace // NamespaceSpec describes the attributes on a Namespace
type NamespaceSpec struct { type NamespaceSpec struct {
// Finalizers is an opaque list of values that must be empty to permanently remove object from storage
Finalizers []FinalizerName
} }
type FinalizerName string
// These are internal finalizer values to Kubernetes, must be qualified name unless defined here
const (
FinalizerKubernetes FinalizerName = "kubernetes"
)
// NamespaceStatus is information about the current status of a Namespace. // NamespaceStatus is information about the current status of a Namespace.
type NamespaceStatus struct { type NamespaceStatus struct {
// Phase is the current lifecycle phase of the namespace. // Phase is the current lifecycle phase of the namespace.

View File

@ -1426,4 +1426,17 @@ func init() {
// If one of the conversion functions is malformed, detect it immediately. // If one of the conversion functions is malformed, detect it immediately.
panic(err) panic(err)
} }
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "namespaces",
func(label, value string) (string, string, error) {
switch label {
case "status.phase":
return label, value, nil
default:
return "", "", fmt.Errorf("field label not supported: %s", label)
}
})
if err != nil {
// If one of the conversion functions is malformed, detect it immediately.
panic(err)
}
} }

View File

@ -782,8 +782,17 @@ type MinionList struct {
Items []Minion `json:"items" description:"list of nodes"` Items []Minion `json:"items" description:"list of nodes"`
} }
type FinalizerName string
// These are internal finalizer values to Kubernetes, must be qualified name unless defined here
const (
FinalizerKubernetes FinalizerName = "kubernetes"
)
// NamespaceSpec describes the attributes on a Namespace // NamespaceSpec describes the attributes on a Namespace
type NamespaceSpec struct { type NamespaceSpec struct {
// Finalizers is an opaque list of values that must be empty to permanently remove object from storage
Finalizers []FinalizerName `json:"finalizers,omitempty" description:"an opaque list of values that must be empty to permanently remove object from storage"`
} }
// NamespaceStatus is information about the current status of a Namespace. // NamespaceStatus is information about the current status of a Namespace.

View File

@ -1354,4 +1354,17 @@ func init() {
// If one of the conversion functions is malformed, detect it immediately. // If one of the conversion functions is malformed, detect it immediately.
panic(err) panic(err)
} }
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "namespaces",
func(label, value string) (string, string, error) {
switch label {
case "status.phase":
return label, value, nil
default:
return "", "", fmt.Errorf("field label not supported: %s", label)
}
})
if err != nil {
// If one of the conversion functions is malformed, detect it immediately.
panic(err)
}
} }

View File

@ -798,8 +798,17 @@ type MinionList struct {
Items []Minion `json:"items" description:"list of nodes"` Items []Minion `json:"items" description:"list of nodes"`
} }
type FinalizerName string
// These are internal finalizer values to Kubernetes, must be qualified name unless defined here
const (
FinalizerKubernetes FinalizerName = "kubernetes"
)
// NamespaceSpec describes the attributes on a Namespace // NamespaceSpec describes the attributes on a Namespace
type NamespaceSpec struct { type NamespaceSpec struct {
// Finalizers is an opaque list of values that must be empty to permanently remove object from storage
Finalizers []FinalizerName `json:"finalizers,omitempty" description:"an opaque list of values that must be empty to permanently remove object from storage"`
} }
// NamespaceStatus is information about the current status of a Namespace. // NamespaceStatus is information about the current status of a Namespace.

View File

@ -60,4 +60,17 @@ func init() {
// If one of the conversion functions is malformed, detect it immediately. // If one of the conversion functions is malformed, detect it immediately.
panic(err) panic(err)
} }
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "namespaces",
func(label, value string) (string, string, error) {
switch label {
case "status.phase":
return label, value, nil
default:
return "", "", fmt.Errorf("field label not supported: %s", label)
}
})
if err != nil {
// If one of the conversion functions is malformed, detect it immediately.
panic(err)
}
} }

View File

@ -950,8 +950,17 @@ type NodeList struct {
Items []Node `json:"items" description:"list of nodes"` Items []Node `json:"items" description:"list of nodes"`
} }
type FinalizerName string
// These are internal finalizer values to Kubernetes, must be qualified name unless defined here
const (
FinalizerKubernetes FinalizerName = "kubernetes"
)
// NamespaceSpec describes the attributes on a Namespace // NamespaceSpec describes the attributes on a Namespace
type NamespaceSpec struct { type NamespaceSpec struct {
// Finalizers is an opaque list of values that must be empty to permanently remove object from storage
Finalizers []FinalizerName `json:"finalizers,omitempty" description:"an opaque list of values that must be empty to permanently remove object from storage"`
} }
// NamespaceStatus is information about the current status of a Namespace. // NamespaceStatus is information about the current status of a Namespace.

View File

@ -1011,9 +1011,28 @@ func ValidateResourceQuotaStatusUpdate(newResourceQuota, oldResourceQuota *api.R
func ValidateNamespace(namespace *api.Namespace) errs.ValidationErrorList { func ValidateNamespace(namespace *api.Namespace) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{} allErrs := errs.ValidationErrorList{}
allErrs = append(allErrs, ValidateObjectMeta(&namespace.ObjectMeta, false, ValidateNamespaceName).Prefix("metadata")...) allErrs = append(allErrs, ValidateObjectMeta(&namespace.ObjectMeta, false, ValidateNamespaceName).Prefix("metadata")...)
for i := range namespace.Spec.Finalizers {
allErrs = append(allErrs, validateFinalizerName(string(namespace.Spec.Finalizers[i]))...)
}
return allErrs return allErrs
} }
// Validate finalizer names
func validateFinalizerName(stringValue string) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
if !util.IsQualifiedName(stringValue) {
return append(allErrs, fmt.Errorf("finalizer name: %v, %v", stringValue, qualifiedNameErrorMsg))
}
if len(strings.Split(stringValue, "/")) == 1 {
if !api.IsStandardFinalizerName(stringValue) {
return append(allErrs, fmt.Errorf("finalizer name: %v is neither a standard finalizer name nor is it fully qualified", stringValue))
}
}
return errs.ValidationErrorList{}
}
// ValidateNamespaceUpdate tests to make sure a mamespace update can be applied. Modifies oldNamespace. // ValidateNamespaceUpdate tests to make sure a mamespace update can be applied. Modifies oldNamespace.
func ValidateNamespaceUpdate(oldNamespace *api.Namespace, namespace *api.Namespace) errs.ValidationErrorList { func ValidateNamespaceUpdate(oldNamespace *api.Namespace, namespace *api.Namespace) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{} allErrs := errs.ValidationErrorList{}
@ -1022,6 +1041,7 @@ func ValidateNamespaceUpdate(oldNamespace *api.Namespace, namespace *api.Namespa
// TODO: move reset function to its own location // TODO: move reset function to its own location
// Ignore metadata changes now that they have been tested // Ignore metadata changes now that they have been tested
oldNamespace.ObjectMeta = namespace.ObjectMeta oldNamespace.ObjectMeta = namespace.ObjectMeta
oldNamespace.Spec.Finalizers = namespace.Spec.Finalizers
// TODO: Add a 'real' ValidationError type for this error and provide print actual diffs. // TODO: Add a 'real' ValidationError type for this error and provide print actual diffs.
if !api.Semantic.DeepEqual(oldNamespace, namespace) { if !api.Semantic.DeepEqual(oldNamespace, namespace) {
@ -1036,9 +1056,20 @@ func ValidateNamespaceUpdate(oldNamespace *api.Namespace, namespace *api.Namespa
func ValidateNamespaceStatusUpdate(newNamespace, oldNamespace *api.Namespace) errs.ValidationErrorList { func ValidateNamespaceStatusUpdate(newNamespace, oldNamespace *api.Namespace) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{} allErrs := errs.ValidationErrorList{}
allErrs = append(allErrs, ValidateObjectMetaUpdate(&oldNamespace.ObjectMeta, &newNamespace.ObjectMeta).Prefix("metadata")...) 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 newNamespace.Spec = oldNamespace.Spec
return allErrs return allErrs
} }
// ValidateNamespaceFinalizeUpdate tests to see if the update is legal for an end user to make. newNamespace is updated with fields
// that cannot be changed.
func ValidateNamespaceFinalizeUpdate(newNamespace, oldNamespace *api.Namespace) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
allErrs = append(allErrs, ValidateObjectMetaUpdate(&oldNamespace.ObjectMeta, &newNamespace.ObjectMeta).Prefix("metadata")...)
for i := range newNamespace.Spec.Finalizers {
allErrs = append(allErrs, validateFinalizerName(string(newNamespace.Spec.Finalizers[i]))...)
}
newNamespace.ObjectMeta = oldNamespace.ObjectMeta
newNamespace.Status = oldNamespace.Status
fmt.Printf("NEW NAMESPACE FINALIZERS : %v\n", newNamespace.Spec.Finalizers)
return allErrs
}

View File

@ -2206,6 +2206,90 @@ func TestValidateNamespace(t *testing.T) {
} }
} }
func TestValidateNamespaceFinalizeUpdate(t *testing.T) {
tests := []struct {
oldNamespace api.Namespace
namespace api.Namespace
valid bool
}{
{api.Namespace{}, api.Namespace{}, true},
{api.Namespace{
ObjectMeta: api.ObjectMeta{
Name: "foo"}},
api.Namespace{
ObjectMeta: api.ObjectMeta{
Name: "foo"},
Spec: api.NamespaceSpec{
Finalizers: []api.FinalizerName{"Foo"},
},
}, false},
{api.Namespace{
ObjectMeta: api.ObjectMeta{
Name: "foo"},
Spec: api.NamespaceSpec{
Finalizers: []api.FinalizerName{"foo.com/bar"},
},
},
api.Namespace{
ObjectMeta: api.ObjectMeta{
Name: "foo"},
Spec: api.NamespaceSpec{
Finalizers: []api.FinalizerName{"foo.com/bar", "what.com/bar"},
},
}, true},
}
for i, test := range tests {
errs := ValidateNamespaceFinalizeUpdate(&test.namespace, &test.oldNamespace)
if test.valid && len(errs) > 0 {
t.Errorf("%d: Unexpected error: %v", i, errs)
t.Logf("%#v vs %#v", test.oldNamespace, test.namespace)
}
if !test.valid && len(errs) == 0 {
t.Errorf("%d: Unexpected non-error", i)
}
}
}
func TestValidateNamespaceStatusUpdate(t *testing.T) {
tests := []struct {
oldNamespace api.Namespace
namespace api.Namespace
valid bool
}{
{api.Namespace{}, api.Namespace{}, true},
{api.Namespace{
ObjectMeta: api.ObjectMeta{
Name: "foo"}},
api.Namespace{
ObjectMeta: api.ObjectMeta{
Name: "foo"},
Status: api.NamespaceStatus{
Phase: api.NamespaceTerminating,
},
}, true},
{api.Namespace{
ObjectMeta: api.ObjectMeta{
Name: "foo"}},
api.Namespace{
ObjectMeta: api.ObjectMeta{
Name: "bar"},
Status: api.NamespaceStatus{
Phase: api.NamespaceTerminating,
},
}, false},
}
for i, test := range tests {
errs := ValidateNamespaceStatusUpdate(&test.oldNamespace, &test.namespace)
if test.valid && len(errs) > 0 {
t.Errorf("%d: Unexpected error: %v", i, errs)
t.Logf("%#v vs %#v", test.oldNamespace.ObjectMeta, test.namespace.ObjectMeta)
}
if !test.valid && len(errs) == 0 {
t.Errorf("%d: Unexpected non-error", i)
}
}
}
func TestValidateNamespaceUpdate(t *testing.T) { func TestValidateNamespaceUpdate(t *testing.T) {
tests := []struct { tests := []struct {
oldNamespace api.Namespace oldNamespace api.Namespace

View File

@ -29,7 +29,7 @@ type FakeNamespaces struct {
Fake *Fake Fake *Fake
} }
func (c *FakeNamespaces) List(selector labels.Selector) (*api.NamespaceList, error) { func (c *FakeNamespaces) List(labels labels.Selector, field fields.Selector) (*api.NamespaceList, error) {
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "list-namespaces"}) c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "list-namespaces"})
return api.Scheme.CopyOrDie(&c.Fake.NamespacesList).(*api.NamespaceList), nil return api.Scheme.CopyOrDie(&c.Fake.NamespacesList).(*api.NamespaceList), nil
} }
@ -58,3 +58,13 @@ func (c *FakeNamespaces) Watch(label labels.Selector, field fields.Selector, res
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "watch-namespaces", Value: resourceVersion}) c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "watch-namespaces", Value: resourceVersion})
return c.Fake.Watch, nil return c.Fake.Watch, nil
} }
func (c *FakeNamespaces) Finalize(namespace *api.Namespace) (*api.Namespace, error) {
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "finalize-namespace", Value: namespace.Name})
return &api.Namespace{}, nil
}
func (c *FakeNamespaces) Status(namespace *api.Namespace) (*api.Namespace, error) {
c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "status-namespace", Value: namespace.Name})
return &api.Namespace{}, nil
}

View File

@ -33,10 +33,12 @@ type NamespacesInterface interface {
type NamespaceInterface interface { type NamespaceInterface interface {
Create(item *api.Namespace) (*api.Namespace, error) Create(item *api.Namespace) (*api.Namespace, error)
Get(name string) (result *api.Namespace, err error) Get(name string) (result *api.Namespace, err error)
List(selector labels.Selector) (*api.NamespaceList, error) List(label labels.Selector, field fields.Selector) (*api.NamespaceList, error)
Delete(name string) error Delete(name string) error
Update(item *api.Namespace) (*api.Namespace, error) Update(item *api.Namespace) (*api.Namespace, error)
Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error)
Finalize(item *api.Namespace) (*api.Namespace, error)
Status(item *api.Namespace) (*api.Namespace, error)
} }
// namespaces implements NamespacesInterface // namespaces implements NamespacesInterface
@ -57,9 +59,13 @@ func (c *namespaces) Create(namespace *api.Namespace) (*api.Namespace, error) {
} }
// List lists all the namespaces in the cluster. // List lists all the namespaces in the cluster.
func (c *namespaces) List(selector labels.Selector) (*api.NamespaceList, error) { func (c *namespaces) List(label labels.Selector, field fields.Selector) (*api.NamespaceList, error) {
result := &api.NamespaceList{} result := &api.NamespaceList{}
err := c.r.Get().Resource("namespaces").LabelsSelectorParam(api.LabelSelectorQueryParam(c.r.APIVersion()), selector).Do().Into(result) err := c.r.Get().
Resource("namespaces").
LabelsSelectorParam(api.LabelSelectorQueryParam(c.r.APIVersion()), label).
FieldsSelectorParam(api.FieldSelectorQueryParam(c.r.APIVersion()), field).
Do().Into(result)
return result, err return result, err
} }
@ -74,6 +80,28 @@ func (c *namespaces) Update(namespace *api.Namespace) (result *api.Namespace, er
return return
} }
// Finalize takes the representation of a namespace to update. Returns the server's representation of the namespace, and an error, if it occurs.
func (c *namespaces) Finalize(namespace *api.Namespace) (result *api.Namespace, err error) {
result = &api.Namespace{}
if len(namespace.ResourceVersion) == 0 {
err = fmt.Errorf("invalid update object, missing resource version: %v", namespace)
return
}
err = c.r.Put().Resource("namespaces").Name(namespace.Name).SubResource("finalize").Body(namespace).Do().Into(result)
return
}
// Status takes the representation of a namespace to update. Returns the server's representation of the namespace, and an error, if it occurs.
func (c *namespaces) Status(namespace *api.Namespace) (result *api.Namespace, err error) {
result = &api.Namespace{}
if len(namespace.ResourceVersion) == 0 {
err = fmt.Errorf("invalid update object, missing resource version: %v", namespace)
return
}
err = c.r.Put().Resource("namespaces").Name(namespace.Name).SubResource("status").Body(namespace).Do().Into(result)
return
}
// Get gets an existing namespace // Get gets an existing namespace
func (c *namespaces) Get(name string) (*api.Namespace, error) { func (c *namespaces) Get(name string) (*api.Namespace, error) {
if len(name) == 0 { if len(name) == 0 {

View File

@ -91,7 +91,7 @@ func TestNamespaceList(t *testing.T) {
}, },
Response: Response{StatusCode: 200, Body: namespaceList}, Response: Response{StatusCode: 200, Body: namespaceList},
} }
response, err := c.Setup().Namespaces().List(labels.Everything()) response, err := c.Setup().Namespaces().List(labels.Everything(), fields.Everything())
if err != nil { if err != nil {
t.Errorf("%#v should be nil.", err) t.Errorf("%#v should be nil.", err)
@ -117,6 +117,9 @@ func TestNamespaceUpdate(t *testing.T) {
"name": "baz", "name": "baz",
}, },
}, },
Spec: api.NamespaceSpec{
Finalizers: []api.FinalizerName{api.FinalizerKubernetes},
},
} }
c := &testClient{ c := &testClient{
Request: testRequest{Method: "PUT", Path: "/namespaces/foo"}, Request: testRequest{Method: "PUT", Path: "/namespaces/foo"},
@ -126,6 +129,28 @@ func TestNamespaceUpdate(t *testing.T) {
c.Validate(t, receivedNamespace, err) c.Validate(t, receivedNamespace, err)
} }
func TestNamespaceFinalize(t *testing.T) {
requestNamespace := &api.Namespace{
ObjectMeta: api.ObjectMeta{
Name: "foo",
ResourceVersion: "1",
Labels: map[string]string{
"foo": "bar",
"name": "baz",
},
},
Spec: api.NamespaceSpec{
Finalizers: []api.FinalizerName{api.FinalizerKubernetes},
},
}
c := &testClient{
Request: testRequest{Method: "PUT", Path: "/namespaces/foo/finalize"},
Response: Response{StatusCode: 200, Body: requestNamespace},
}
receivedNamespace, err := c.Setup().Namespaces().Finalize(requestNamespace)
c.Validate(t, receivedNamespace, err)
}
func TestNamespaceDelete(t *testing.T) { func TestNamespaceDelete(t *testing.T) {
c := &testClient{ c := &testClient{
Request: testRequest{Method: "DELETE", Path: "/namespaces/foo"}, Request: testRequest{Method: "DELETE", Path: "/namespaces/foo"},

View File

@ -358,7 +358,7 @@ func (m *Master) init(c *Config) {
resourceQuotaStorage, resourceQuotaStatusStorage := resourcequotaetcd.NewStorage(c.EtcdHelper) resourceQuotaStorage, resourceQuotaStatusStorage := resourcequotaetcd.NewStorage(c.EtcdHelper)
secretRegistry := secret.NewEtcdRegistry(c.EtcdHelper) secretRegistry := secret.NewEtcdRegistry(c.EtcdHelper)
namespaceStorage := namespaceetcd.NewStorage(c.EtcdHelper) namespaceStorage, namespaceStatusStorage, namespaceFinalizeStorage := namespaceetcd.NewStorage(c.EtcdHelper)
m.namespaceRegistry = namespace.NewRegistry(namespaceStorage) m.namespaceRegistry = namespace.NewRegistry(namespaceStorage)
// TODO: split me up into distinct storage registries // TODO: split me up into distinct storage registries
@ -402,6 +402,8 @@ func (m *Master) init(c *Config) {
"resourceQuotas": resourceQuotaStorage, "resourceQuotas": resourceQuotaStorage,
"resourceQuotas/status": resourceQuotaStatusStorage, "resourceQuotas/status": resourceQuotaStatusStorage,
"namespaces": namespaceStorage, "namespaces": namespaceStorage,
"namespaces/status": namespaceStatusStorage,
"namespaces/finalize": namespaceFinalizeStorage,
"secrets": secret.NewStorage(secretRegistry), "secrets": secret.NewStorage(secretRegistry),
} }

View File

@ -17,6 +17,8 @@ limitations under the License.
package etcd package etcd
import ( import (
"fmt"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields" "github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
@ -25,15 +27,27 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/namespace" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/namespace"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools" "github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
) )
// rest implements a RESTStorage for namespaces against etcd // rest implements a RESTStorage for namespaces against etcd
type REST struct { type REST struct {
*etcdgeneric.Etcd *etcdgeneric.Etcd
status *etcdgeneric.Etcd
}
// StatusREST implements the REST endpoint for changing the status of a namespace.
type StatusREST struct {
store *etcdgeneric.Etcd
}
// FinalizeREST implements the REST endpoint for finalizing a namespace.
type FinalizeREST struct {
store *etcdgeneric.Etcd
} }
// NewStorage returns a RESTStorage object that will work against namespaces // NewStorage returns a RESTStorage object that will work against namespaces
func NewStorage(h tools.EtcdHelper) *REST { func NewStorage(h tools.EtcdHelper) (*REST, *StatusREST, *FinalizeREST) {
store := &etcdgeneric.Etcd{ store := &etcdgeneric.Etcd{
NewFunc: func() runtime.Object { return &api.Namespace{} }, NewFunc: func() runtime.Object { return &api.Namespace{} },
NewListFunc: func() runtime.Object { return &api.NamespaceList{} }, NewListFunc: func() runtime.Object { return &api.NamespaceList{} },
@ -56,5 +70,54 @@ func NewStorage(h tools.EtcdHelper) *REST {
store.UpdateStrategy = namespace.Strategy store.UpdateStrategy = namespace.Strategy
store.ReturnDeletedObject = true store.ReturnDeletedObject = true
return &REST{Etcd: store} statusStore := *store
statusStore.UpdateStrategy = namespace.StatusStrategy
finalizeStore := *store
finalizeStore.UpdateStrategy = namespace.FinalizeStrategy
return &REST{Etcd: store, status: &statusStore}, &StatusREST{store: &statusStore}, &FinalizeREST{store: &finalizeStore}
}
// Delete enforces life-cycle rules for namespace termination
func (r *REST) Delete(ctx api.Context, name string, options *api.DeleteOptions) (runtime.Object, error) {
nsObj, err := r.Get(ctx, name)
if err != nil {
return nil, err
}
namespace := nsObj.(*api.Namespace)
// upon first request to delete, we switch the phase to start namespace termination
if namespace.DeletionTimestamp == nil {
now := util.Now()
namespace.DeletionTimestamp = &now
namespace.Status.Phase = api.NamespaceTerminating
result, _, err := r.status.Update(ctx, namespace)
return result, err
}
// prior to final deletion, we must ensure that finalizers is empty
if len(namespace.Spec.Finalizers) != 0 {
err = fmt.Errorf("Unable to delete namespace %v because finalizers is not empty %v", namespace.Name, namespace.Spec.Finalizers)
}
return r.Etcd.Delete(ctx, name, nil)
}
func (r *StatusREST) New() runtime.Object {
return &api.Namespace{}
}
// Update alters the status subset of an object.
func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
return r.store.Update(ctx, obj)
}
func (r *FinalizeREST) New() runtime.Object {
return &api.Namespace{}
}
// Update alters the status finalizers subset of an object.
func (r *FinalizeREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
return r.store.Update(ctx, obj)
} }

View File

@ -41,7 +41,7 @@ func newHelper(t *testing.T) (*tools.FakeEtcdClient, tools.EtcdHelper) {
func newStorage(t *testing.T) (*REST, *tools.FakeEtcdClient, tools.EtcdHelper) { func newStorage(t *testing.T) (*REST, *tools.FakeEtcdClient, tools.EtcdHelper) {
fakeEtcdClient, h := newHelper(t) fakeEtcdClient, h := newHelper(t)
storage := NewStorage(h) storage, _, _ := NewStorage(h)
return storage, fakeEtcdClient, h return storage, fakeEtcdClient, h
} }
@ -69,7 +69,7 @@ func TestStorage(t *testing.T) {
func TestCreate(t *testing.T) { func TestCreate(t *testing.T) {
fakeEtcdClient, helper := newHelper(t) fakeEtcdClient, helper := newHelper(t)
storage := NewStorage(helper) storage, _, _ := NewStorage(helper)
test := resttest.New(t, storage, fakeEtcdClient.SetError) test := resttest.New(t, storage, fakeEtcdClient.SetError)
namespace := validNewNamespace() namespace := validNewNamespace()
namespace.ObjectMeta = api.ObjectMeta{} namespace.ObjectMeta = api.ObjectMeta{}
@ -94,7 +94,7 @@ func expectNamespace(t *testing.T, out runtime.Object) (*api.Namespace, bool) {
func TestCreateSetsFields(t *testing.T) { func TestCreateSetsFields(t *testing.T) {
fakeEtcdClient, helper := newHelper(t) fakeEtcdClient, helper := newHelper(t)
storage := NewStorage(helper) storage, _, _ := NewStorage(helper)
namespace := validNewNamespace() namespace := validNewNamespace()
_, err := storage.Create(api.NewDefaultContext(), namespace) _, err := storage.Create(api.NewDefaultContext(), namespace)
if err != fakeEtcdClient.Err { if err != fakeEtcdClient.Err {
@ -124,7 +124,7 @@ func TestListEmptyNamespaceList(t *testing.T) {
E: fakeEtcdClient.NewError(tools.EtcdErrorCodeNotFound), E: fakeEtcdClient.NewError(tools.EtcdErrorCodeNotFound),
} }
storage := NewStorage(helper) storage, _, _ := NewStorage(helper)
namespaces, err := storage.List(api.NewContext(), labels.Everything(), fields.Everything()) namespaces, err := storage.List(api.NewContext(), labels.Everything(), fields.Everything())
if err != nil { if err != nil {
t.Errorf("Unexpected error: %v", err) t.Errorf("Unexpected error: %v", err)
@ -157,7 +157,7 @@ func TestListNamespaceList(t *testing.T) {
}, },
}, },
} }
storage := NewStorage(helper) storage, _, _ := NewStorage(helper)
namespacesObj, err := storage.List(api.NewDefaultContext(), labels.Everything(), fields.Everything()) namespacesObj, err := storage.List(api.NewDefaultContext(), labels.Everything(), fields.Everything())
namespaces := namespacesObj.(*api.NamespaceList) namespaces := namespacesObj.(*api.NamespaceList)
if err != nil { if err != nil {
@ -204,7 +204,7 @@ func TestListNamespaceListSelection(t *testing.T) {
}, },
}, },
} }
storage := NewStorage(helper) storage, _, _ := NewStorage(helper)
ctx := api.NewDefaultContext() ctx := api.NewDefaultContext()
table := []struct { table := []struct {
label, field string label, field string
@ -252,9 +252,11 @@ func TestListNamespaceListSelection(t *testing.T) {
} }
func TestNamespaceDecode(t *testing.T) { func TestNamespaceDecode(t *testing.T) {
storage := NewStorage(tools.EtcdHelper{}) _, helper := newHelper(t)
storage, _, _ := NewStorage(helper)
expected := validNewNamespace() expected := validNewNamespace()
expected.Status.Phase = api.NamespaceActive expected.Status.Phase = api.NamespaceActive
expected.Spec.Finalizers = []api.FinalizerName{api.FinalizerKubernetes}
body, err := latest.Codec.Encode(expected) body, err := latest.Codec.Encode(expected)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
@ -281,7 +283,7 @@ func TestGet(t *testing.T) {
}, },
}, },
} }
storage := NewStorage(helper) storage, _, _ := NewStorage(helper)
obj, err := storage.Get(api.NewContext(), "foo") obj, err := storage.Get(api.NewContext(), "foo")
namespace := obj.(*api.Namespace) namespace := obj.(*api.Namespace)
if err != nil { if err != nil {
@ -311,8 +313,9 @@ func TestDeleteNamespace(t *testing.T) {
}, },
}, },
} }
storage := NewStorage(helper) storage, _, _ := NewStorage(helper)
_, err := storage.Delete(api.NewDefaultContext(), "foo", nil) _, err := storage.Delete(api.NewDefaultContext(), "foo", nil)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }

View File

@ -45,10 +45,27 @@ func (namespaceStrategy) NamespaceScoped() bool {
// ResetBeforeCreate clears fields that are not allowed to be set by end users on creation. // ResetBeforeCreate clears fields that are not allowed to be set by end users on creation.
func (namespaceStrategy) ResetBeforeCreate(obj runtime.Object) { func (namespaceStrategy) ResetBeforeCreate(obj runtime.Object) {
// on create, status is active
namespace := obj.(*api.Namespace) namespace := obj.(*api.Namespace)
namespace.Status = api.NamespaceStatus{ namespace.Status = api.NamespaceStatus{
Phase: api.NamespaceActive, Phase: api.NamespaceActive,
} }
// on create, we require the kubernetes value
// we cannot use this in defaults conversion because we let it get removed over life of object
hasKubeFinalizer := false
for i := range namespace.Spec.Finalizers {
if namespace.Spec.Finalizers[i] == api.FinalizerKubernetes {
hasKubeFinalizer = true
break
}
}
if !hasKubeFinalizer {
if len(namespace.Spec.Finalizers) == 0 {
namespace.Spec.Finalizers = []api.FinalizerName{api.FinalizerKubernetes}
} else {
namespace.Spec.Finalizers = append(namespace.Spec.Finalizers, api.FinalizerKubernetes)
}
}
} }
// Validate validates a new namespace. // Validate validates a new namespace.
@ -74,10 +91,19 @@ type namespaceStatusStrategy struct {
var StatusStrategy = namespaceStatusStrategy{Strategy} var StatusStrategy = namespaceStatusStrategy{Strategy}
func (namespaceStatusStrategy) ValidateUpdate(obj, old runtime.Object) fielderrors.ValidationErrorList { func (namespaceStatusStrategy) ValidateUpdate(obj, old runtime.Object) fielderrors.ValidationErrorList {
// TODO: merge valid fields after update
return validation.ValidateNamespaceStatusUpdate(obj.(*api.Namespace), old.(*api.Namespace)) return validation.ValidateNamespaceStatusUpdate(obj.(*api.Namespace), old.(*api.Namespace))
} }
type namespaceFinalizeStrategy struct {
namespaceStrategy
}
var FinalizeStrategy = namespaceFinalizeStrategy{Strategy}
func (namespaceFinalizeStrategy) ValidateUpdate(obj, old runtime.Object) fielderrors.ValidationErrorList {
return validation.ValidateNamespaceFinalizeUpdate(obj.(*api.Namespace), old.(*api.Namespace))
}
// MatchNamespace returns a generic matcher for a given label and field selector. // MatchNamespace returns a generic matcher for a given label and field selector.
func MatchNamespace(label labels.Selector, field fields.Selector) generic.Matcher { func MatchNamespace(label labels.Selector, field fields.Selector) generic.Matcher {
return generic.MatcherFunc(func(obj runtime.Object) (bool, error) { return generic.MatcherFunc(func(obj runtime.Object) (bool, error) {

View File

@ -87,7 +87,7 @@ func NewProvision(c client.Interface) admission.Interface {
reflector := cache.NewReflector( reflector := cache.NewReflector(
&cache.ListWatch{ &cache.ListWatch{
ListFunc: func() (runtime.Object, error) { ListFunc: func() (runtime.Object, error) {
return c.Namespaces().List(labels.Everything()) return c.Namespaces().List(labels.Everything(), fields.Everything())
}, },
WatchFunc: func(resourceVersion string) (watch.Interface, error) { WatchFunc: func(resourceVersion string) (watch.Interface, error) {
return c.Namespaces().Watch(labels.Everything(), fields.Everything(), resourceVersion) return c.Namespaces().Watch(labels.Everything(), fields.Everything(), resourceVersion)

View File

@ -86,7 +86,7 @@ func NewExists(c client.Interface) admission.Interface {
reflector := cache.NewReflector( reflector := cache.NewReflector(
&cache.ListWatch{ &cache.ListWatch{
ListFunc: func() (runtime.Object, error) { ListFunc: func() (runtime.Object, error) {
return c.Namespaces().List(labels.Everything()) return c.Namespaces().List(labels.Everything(), fields.Everything())
}, },
WatchFunc: func(resourceVersion string) (watch.Interface, error) { WatchFunc: func(resourceVersion string) (watch.Interface, error) {
return c.Namespaces().Watch(labels.Everything(), fields.Everything(), resourceVersion) return c.Namespaces().Watch(labels.Everything(), fields.Everything(), resourceVersion)

View File

@ -91,7 +91,7 @@ func NewLifecycle(c client.Interface) admission.Interface {
reflector := cache.NewReflector( reflector := cache.NewReflector(
&cache.ListWatch{ &cache.ListWatch{
ListFunc: func() (runtime.Object, error) { ListFunc: func() (runtime.Object, error) {
return c.Namespaces().List(labels.Everything()) return c.Namespaces().List(labels.Everything(), fields.Everything())
}, },
WatchFunc: func(resourceVersion string) (watch.Interface, error) { WatchFunc: func(resourceVersion string) (watch.Interface, error) {
return c.Namespaces().Watch(labels.Everything(), fields.Everything(), resourceVersion) return c.Namespaces().Watch(labels.Everything(), fields.Everything(), resourceVersion)