Skip to content

Commit

Permalink
Detect if a TPR update represents a soft delete (openshift#836)
Browse files Browse the repository at this point in the history
* detect if a TPR update represents a soft delete

if it does, then intercept the PUT, remove the deletion timestamp and
deletion grace period, and add a finalizer

this commit also splits some of the CRUD actions into common functions,
so they can be reused

* adding more finalizer-related metadata funcs

* modify the delete function to remove finalizers

* modify a delete test to check for finalizers

* fixing final test for deletion

* adding tests for the metadata accessors

* modifying the update to check for deletion flags earlier

also modifying the delete func to accept a code

* fixing format string directive

* change the fake REST client to return the expected HTTP response code

deleting other resources in Kubernetes shows that the endpoint returns
a 200. For example, here’s deleting a namespace:

```console
ENG000656:service-catalog aaronschlesinger$ k delete ns catalog -v 10
I0511 16:43:04.375166   54273 loader.go:354] Config loaded from file
/Users/aaronschlesinger/.kube/config
I0511 16:43:04.379679   54273 cached_discovery.go:71] returning cached
discovery info from
/Users/aaronschlesinger/.kube/cache/discovery/192.168.99.100_8443/autosc
aling/v1/serverresources.json
<snip>
I0511 16:43:04.383012   54273 round_trippers.go:398] curl -k -v
-XDELETE  -H "Accept: application/json" -H "User-Agent: kubectl/v0.0.0
(darwin/amd64) kubernetes/$Format"
https://192.168.99.100:8443/api/v1/namespaces/catalog
I0511 16:43:04.412668   54273 round_trippers.go:417] DELETE
https://192.168.99.100:8443/api/v1/namespaces/catalog 200 OK in 29
milliseconds
```

* Moving metadata functions to a new package

and using the new package everywhere

* fixing typo in comment

* improving error messages from binding tests

* updating the rest client to soft-delete

* adding more functions to the deletion metadata suite

* refactoring the storage interface's update func

so that it does complete updates for soft deletion, and has better docs

* returning the appropriate content types from the fake server

* fetching codecs in a type-agnostic way

* ensuring that deletion timestamp can't be changed

instead of preventing it from being passed at all

* returning errors properly in the rest client

* returning errors properly in the storage interface

* renaming headers util file

* fixing deletion timestamp deletion timestamp equality checks

* fixing a unit test

* adding the option to make the TPR storage interface hard-delete

this is necessary for service class resources

* adding missing godoc

* fixing test compile errors

* removing test regression

and commenting

* fixing format string value

* remove incorrect, early return from the update func

* splitting functions and test into more logical files

* implementing remaining TODO tests

* fix compile error

the generated code has changed, and this field name has also changed

* adding docs for the singular and list funcs

* removing unused arg

* fixing test compile errs

* Adding more godoc to the RemoveFinalizer func
  • Loading branch information
arschles authored and Doug Davis committed May 24, 2017
1 parent 952477a commit f41516f
Show file tree
Hide file tree
Showing 25 changed files with 1,093 additions and 191 deletions.
73 changes: 73 additions & 0 deletions pkg/api/meta/deletion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
Copyright 2017 The Kubernetes Authors.
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 meta

import (
"errors"
"time"

"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)

var (
// ErrNoDeletionTimestamp is the error returned by GetDeletionTimestamp when there is no
// deletion timestamp set on the object
ErrNoDeletionTimestamp = errors.New("no deletion timestamp set")
)

// DeletionTimestampExists returns true if a deletion timestamp exists on obj, or a non-nil
// error if that couldn't be reliably determined
func DeletionTimestampExists(obj runtime.Object) (bool, error) {
_, err := GetDeletionTimestamp(obj)
if err == ErrNoDeletionTimestamp {
// if GetDeletionTimestamp reported that no deletion timestamp exists, return false
// and no error
return false, nil
}
if err != nil {
// otherwise, if GetDeletionTimestamp returned an unknown error, return the error
return false, err
}
return true, nil
}

// GetDeletionTimestamp returns the deletion timestamp on obj, or a non-nil error if there was
// an error getting it or it isn't set. Returns ErrNoDeletionTimestamp if there was none set
func GetDeletionTimestamp(obj runtime.Object) (*metav1.Time, error) {
accessor, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
t := accessor.GetDeletionTimestamp()
if t == nil {
return nil, ErrNoDeletionTimestamp
}
return t, nil
}

// SetDeletionTimestamp sets the deletion timestamp on obj to t
func SetDeletionTimestamp(obj runtime.Object, t time.Time) error {
accessor, err := meta.Accessor(obj)
if err != nil {
return err
}
metaTime := metav1.NewTime(t)
accessor.SetDeletionTimestamp(&metaTime)
return nil
}
74 changes: 74 additions & 0 deletions pkg/api/meta/deletion_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
Copyright 2017 The Kubernetes Authors.
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 meta

import (
"testing"
"time"

sc "github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestDeletionTimestampExists(t *testing.T) {
obj := &sc.Instance{
ObjectMeta: metav1.ObjectMeta{},
}
exists, err := DeletionTimestampExists(obj)
if err != nil {
t.Fatal(err)
}
if exists {
t.Fatalf("deletion timestamp reported as exists when it didn't")
}
tme := metav1.NewTime(time.Now())
obj.DeletionTimestamp = &tme
exists, err = DeletionTimestampExists(obj)
if err != nil {
t.Fatal(err)
}
if !exists {
t.Fatal("deletion timestamp reported as missing when it isn't")
}
}

func TestRoundTripDeletionTimestamp(t *testing.T) {
t1 := metav1.NewTime(time.Now())
t2 := metav1.NewTime(time.Now().Add(1 * time.Hour))
obj := &sc.Instance{
ObjectMeta: metav1.ObjectMeta{
DeletionTimestamp: &t1,
},
}
t1Ret, err := GetDeletionTimestamp(obj)
if err != nil {
t.Fatalf("error getting 1st deletion timestamp (%s)", err)
}
if !t1.Equal(*t1Ret) {
t.Fatalf("expected deletion timestamp %s, got %s", t1, *t1Ret)
}
if err := SetDeletionTimestamp(obj, t2.Time); err != nil {
t.Fatalf("error setting deletion timestamp (%s)", err)
}
t2Ret, err := GetDeletionTimestamp(obj)
if err != nil {
t.Fatalf("error getting 2nd deletion timestamp (%s)", err)
}
if !t2.Equal(*t2Ret) {
t.Fatalf("expected deletion timestamp %s, got %s", t2, *t2Ret)
}
}
67 changes: 67 additions & 0 deletions pkg/api/meta/finalizers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
Copyright 2017 The Kubernetes Authors.
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 meta

import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
)

// GetFinalizers gets the list of finalizers on obj
func GetFinalizers(obj runtime.Object) ([]string, error) {
accessor, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
return accessor.GetFinalizers(), nil
}

// AddFinalizer adds value to the list of finalizers on obj
func AddFinalizer(obj runtime.Object, value string) error {
accessor, err := meta.Accessor(obj)
if err != nil {
return err
}
finalizers := append(accessor.GetFinalizers(), value)
accessor.SetFinalizers(finalizers)
return nil
}

// RemoveFinalizer removes the given value from the list of finalizers in obj, then returns the list
// of finalizers after value has been removed. The returned slice will have the same ordering as
// the list of finalizers that was in obj.
//
// If value doesn't exist in the finalizers in obj, the returned slice is the same as the finalizers
// that were in obj.
//
// All of the finalizers that match value will be removed from the list in obj.
func RemoveFinalizer(obj runtime.Object, value string) ([]string, error) {
accessor, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
finalizers := accessor.GetFinalizers()
newFinalizers := []string{}
for _, finalizer := range finalizers {
if finalizer == value {
continue
}
newFinalizers = append(newFinalizers, finalizer)
}
accessor.SetFinalizers(newFinalizers)
return newFinalizers, nil
}
88 changes: 88 additions & 0 deletions pkg/api/meta/finalizers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
Copyright 2017 The Kubernetes Authors.
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 meta

import (
"testing"

sc "github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
testFinalizer = "testfinalizer"
)

func TestGetFinalizers(t *testing.T) {
obj := &sc.Instance{
ObjectMeta: metav1.ObjectMeta{Finalizers: []string{testFinalizer}},
}
finalizers, err := GetFinalizers(obj)
if err != nil {
t.Fatal(err)
}
if len(finalizers) != 1 {
t.Fatalf("expected 1 finalizer, got %d", len(finalizers))
}
if finalizers[0] != testFinalizer {
t.Fatalf("expected finalizer %s, got %s", testFinalizer, finalizers[0])
}
}

func TestAddFinalizer(t *testing.T) {
obj := &sc.Instance{
ObjectMeta: metav1.ObjectMeta{},
}
if err := AddFinalizer(obj, testFinalizer); err != nil {
t.Fatal(err)
}
if len(obj.Finalizers) != 1 {
t.Fatalf("expected 1 finalizer, got %d", len(obj.Finalizers))
}
if obj.Finalizers[0] != testFinalizer {
t.Fatalf("expected finalizer %s, got %s", testFinalizer, obj.Finalizers[0])
}
}

func TestRemoveFinalizer(t *testing.T) {
obj := &sc.Instance{
ObjectMeta: metav1.ObjectMeta{Finalizers: []string{testFinalizer}},
}
newFinalizers, err := RemoveFinalizer(obj, testFinalizer+"-noexist")
if err != nil {
t.Fatalf("error removing non-existent finalizer (%s)", err)
}
if len(newFinalizers) != 1 {
t.Fatalf("number of returned finalizers wasn't 1")
}
if len(obj.Finalizers) != 1 {
t.Fatalf("finalizer was removed when it shouldn't have been")
}
if obj.Finalizers[0] != testFinalizer {
t.Fatalf("expected finalizer %s, got %s", testFinalizer, obj.Finalizers[0])
}
newFinalizers, err = RemoveFinalizer(obj, testFinalizer)
if err != nil {
t.Fatalf("error removing existent finalizer (%s)", err)
}
if len(newFinalizers) != 0 {
t.Fatalf("number of returned finalizers wasn't 0")
}
if len(obj.Finalizers) != 0 {
t.Fatalf("expected no finalizers, got %d", len(obj.Finalizers))
}
}
8 changes: 1 addition & 7 deletions pkg/storage/tpr/metadata_access.go → pkg/api/meta/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

package tpr
package meta

import (
"k8s.io/apimachinery/pkg/api/meta"
Expand All @@ -31,9 +31,3 @@ var (
func GetAccessor() meta.MetadataAccessor {
return accessor
}

// GetNamespace returns the namespace for the given object, if there is one. If not, returns
// the empty string and a non-nil error
func GetNamespace(obj runtime.Object) (string, error) {
return selfLinker.Namespace(obj)
}
27 changes: 27 additions & 0 deletions pkg/api/meta/init_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
Copyright 2017 The Kubernetes Authors.
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 meta

import (
"testing"
)

func TestGetAccessor(t *testing.T) {
if GetAccessor() != accessor {
t.Fatalf("GetAccessor didn't return the pre-initialized accessor")
}
}
27 changes: 27 additions & 0 deletions pkg/api/meta/namespace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
Copyright 2017 The Kubernetes Authors.
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 meta

import (
"k8s.io/apimachinery/pkg/runtime"
)

// GetNamespace returns the namespace for the given object, if there is one. If not, returns
// the empty string and a non-nil error
func GetNamespace(obj runtime.Object) (string, error) {
return selfLinker.Namespace(obj)
}
Loading

0 comments on commit f41516f

Please sign in to comment.