Skip to content

Commit

Permalink
Namespace.Spec.Finalizer support
Browse files Browse the repository at this point in the history
  • Loading branch information
derekwaynecarr committed Mar 24, 2015
1 parent cb7c781 commit 29c491e
Show file tree
Hide file tree
Showing 21 changed files with 381 additions and 24 deletions.
7 changes: 7 additions & 0 deletions pkg/api/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,10 @@ func IsServiceIPSet(service *Service) bool {
func IsServiceIPRequested(service *Service) bool {
return service.Spec.PortalIP == ""
}

var standardFinalizers = util.NewStringSet(
string(FinalizerKubernetes))

func IsStandardFinalizerName(str string) bool {
return standardFinalizers.Has(str)
}
3 changes: 3 additions & 0 deletions pkg/api/testing/fuzzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
s.Type = api.SecretTypeOpaque
},
func(s *api.NamespaceSpec, c fuzz.Continue) {
s.Finalizers = []api.FinalizerName{api.FinalizerKubernetes}
},
func(s *api.NamespaceStatus, c fuzz.Continue) {
s.Phase = api.NamespaceActive
},
Expand Down
9 changes: 9 additions & 0 deletions pkg/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -966,8 +966,17 @@ type NodeList struct {

// NamespaceSpec describes the attributes on a Namespace
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.
type NamespaceStatus struct {
// Phase is the current lifecycle phase of the namespace.
Expand Down
13 changes: 13 additions & 0 deletions pkg/api/v1beta1/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -1426,4 +1426,17 @@ func init() {
// If one of the conversion functions is malformed, detect it immediately.
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)
}
}
9 changes: 9 additions & 0 deletions pkg/api/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -782,8 +782,17 @@ type MinionList struct {
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
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.
Expand Down
13 changes: 13 additions & 0 deletions pkg/api/v1beta2/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -1354,4 +1354,17 @@ func init() {
// If one of the conversion functions is malformed, detect it immediately.
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)
}
}
9 changes: 9 additions & 0 deletions pkg/api/v1beta2/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -798,8 +798,17 @@ type MinionList struct {
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
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.
Expand Down
13 changes: 13 additions & 0 deletions pkg/api/v1beta3/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,17 @@ func init() {
// If one of the conversion functions is malformed, detect it immediately.
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)
}
}
9 changes: 9 additions & 0 deletions pkg/api/v1beta3/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -950,8 +950,17 @@ type NodeList struct {
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
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.
Expand Down
37 changes: 34 additions & 3 deletions pkg/api/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -1011,9 +1011,28 @@ func ValidateResourceQuotaStatusUpdate(newResourceQuota, oldResourceQuota *api.R
func ValidateNamespace(namespace *api.Namespace) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
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
}

// 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.
func ValidateNamespaceUpdate(oldNamespace *api.Namespace, namespace *api.Namespace) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
Expand All @@ -1022,6 +1041,7 @@ func ValidateNamespaceUpdate(oldNamespace *api.Namespace, namespace *api.Namespa
// TODO: move reset function to its own location
// Ignore metadata changes now that they have been tested
oldNamespace.ObjectMeta = namespace.ObjectMeta
oldNamespace.Spec.Finalizers = namespace.Spec.Finalizers

// TODO: Add a 'real' ValidationError type for this error and provide print actual diffs.
if !api.Semantic.DeepEqual(oldNamespace, namespace) {
Expand All @@ -1036,9 +1056,20 @@ func ValidateNamespaceUpdate(oldNamespace *api.Namespace, namespace *api.Namespa
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
}

// 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
}
84 changes: 84 additions & 0 deletions pkg/api/validation/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
tests := []struct {
oldNamespace api.Namespace
Expand Down
12 changes: 11 additions & 1 deletion pkg/client/fake_namespaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type FakeNamespaces struct {
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"})
return api.Scheme.CopyOrDie(&c.Fake.NamespacesList).(*api.NamespaceList), nil
}
Expand Down Expand Up @@ -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})
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
}
34 changes: 31 additions & 3 deletions pkg/client/namespaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@ type NamespacesInterface interface {
type NamespaceInterface interface {
Create(item *api.Namespace) (*api.Namespace, 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
Update(item *api.Namespace) (*api.Namespace, 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
Expand All @@ -57,9 +59,13 @@ func (c *namespaces) Create(namespace *api.Namespace) (*api.Namespace, error) {
}

// 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{}
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
}

Expand All @@ -74,6 +80,28 @@ func (c *namespaces) Update(namespace *api.Namespace) (result *api.Namespace, er
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
func (c *namespaces) Get(name string) (*api.Namespace, error) {
if len(name) == 0 {
Expand Down
Loading

0 comments on commit 29c491e

Please sign in to comment.