Browse code

Ensure OpenShift content creation results in namespace finalizer

derekwaynecarr authored on 2015/05/28 02:55:40
Showing 12 changed files
... ...
@@ -618,7 +618,7 @@ func (c *MasterConfig) ensureOpenShiftSharedResourcesNamespace() {
618 618
 		namespace = &kapi.Namespace{
619 619
 			ObjectMeta: kapi.ObjectMeta{Name: c.Options.PolicyConfig.OpenShiftSharedResourcesNamespace},
620 620
 			Spec: kapi.NamespaceSpec{
621
-				Finalizers: []kapi.FinalizerName{projectapi.FinalizerProject},
621
+				Finalizers: []kapi.FinalizerName{projectapi.FinalizerOrigin},
622 622
 			},
623 623
 		}
624 624
 		kapi.FillObjectMetaSystemFields(api.NewContext(), &namespace.ObjectMeta)
... ...
@@ -19,6 +19,8 @@ package admission
19 19
 import (
20 20
 	"fmt"
21 21
 	"io"
22
+	"math/rand"
23
+	"time"
22 24
 
23 25
 	"github.com/golang/glog"
24 26
 
... ...
@@ -30,17 +32,19 @@ import (
30 30
 
31 31
 	"github.com/openshift/origin/pkg/api/latest"
32 32
 	"github.com/openshift/origin/pkg/project/cache"
33
+	projectutil "github.com/openshift/origin/pkg/project/util"
33 34
 )
34 35
 
35 36
 // TODO: modify the upstream plug-in so this can be collapsed
36 37
 // need ability to specify a RESTMapper on upstream version
37 38
 func init() {
38 39
 	admission.RegisterPlugin("OriginNamespaceLifecycle", func(client client.Interface, config io.Reader) (admission.Interface, error) {
39
-		return NewLifecycle()
40
+		return NewLifecycle(client)
40 41
 	})
41 42
 }
42 43
 
43 44
 type lifecycle struct {
45
+	client client.Interface
44 46
 }
45 47
 
46 48
 // Admit enforces that a namespace must exist in order to associate content with it.
... ...
@@ -56,7 +60,7 @@ func (e *lifecycle) Admit(a admission.Attributes) (err error) {
56 56
 	}
57 57
 	mapping, err := latest.RESTMapper.RESTMapping(kind, defaultVersion)
58 58
 	if err != nil {
59
-		return err
59
+		return admission.NewForbidden(a, err)
60 60
 	}
61 61
 	if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
62 62
 		return nil
... ...
@@ -75,28 +79,54 @@ func (e *lifecycle) Admit(a admission.Attributes) (err error) {
75 75
 
76 76
 	projects, err := cache.GetProjectCache()
77 77
 	if err != nil {
78
-		return err
78
+		return admission.NewForbidden(a, err)
79 79
 	}
80
+
80 81
 	namespace, err := projects.GetNamespaceObject(a.GetNamespace())
81 82
 	if err != nil {
82
-		return apierrors.NewForbidden(kind, name, err)
83
+		return admission.NewForbidden(a, err)
83 84
 	}
84 85
 
85 86
 	if a.GetOperation() != "CREATE" {
86 87
 		return nil
87 88
 	}
88 89
 
89
-	if namespace.Status.Phase != kapi.NamespaceTerminating {
90
-		return nil
90
+	if namespace.Status.Phase == kapi.NamespaceTerminating {
91
+		return apierrors.NewForbidden(kind, name, fmt.Errorf("Namespace %s is terminating", a.GetNamespace()))
91 92
 	}
92 93
 
93
-	return apierrors.NewForbidden(kind, name, fmt.Errorf("namespace %s is terminating", a.GetNamespace()))
94
+	// in case of concurrency issues, we will retry this logic
95
+	numRetries := 10
96
+	interval := time.Duration(rand.Int63n(90)+int64(10)) * time.Millisecond
97
+	for retry := 1; retry <= numRetries; retry++ {
98
+
99
+		// associate this namespace with openshift
100
+		_, err = projectutil.Associate(e.client, namespace)
101
+		if err == nil {
102
+			break
103
+		}
104
+
105
+		// we have exhausted all reasonable efforts to retry so give up now
106
+		if retry == numRetries {
107
+			return admission.NewForbidden(a, err)
108
+		}
109
+
110
+		// get the latest namespace for the next pass in case of resource version updates
111
+		time.Sleep(interval)
112
+
113
+		// it's possible the namespace actually was deleted, so just forbid if this occurs
114
+		namespace, err = e.client.Namespaces().Get(a.GetNamespace())
115
+		if err != nil {
116
+			return admission.NewForbidden(a, err)
117
+		}
118
+	}
119
+	return nil
94 120
 }
95 121
 
96 122
 func (e *lifecycle) Handles(operation admission.Operation) bool {
97 123
 	return true
98 124
 }
99 125
 
100
-func NewLifecycle() (admission.Interface, error) {
101
-	return &lifecycle{}, nil
126
+func NewLifecycle(client client.Interface) (admission.Interface, error) {
127
+	return &lifecycle{client: client}, nil
102 128
 }
... ...
@@ -33,7 +33,7 @@ func TestAdmissionExists(t *testing.T) {
33 33
 		Err: fmt.Errorf("DOES NOT EXIST"),
34 34
 	}
35 35
 	projectcache.FakeProjectCache(mockClient, cache.NewStore(cache.MetaNamespaceKeyFunc), "")
36
-	handler := &lifecycle{}
36
+	handler := &lifecycle{client: mockClient}
37 37
 	build := &buildapi.Build{
38 38
 		ObjectMeta: kapi.ObjectMeta{Name: "buildid"},
39 39
 		Parameters: buildapi.BuildParameters{
... ...
@@ -75,7 +75,7 @@ func TestAdmissionLifecycle(t *testing.T) {
75 75
 	store.Add(namespaceObj)
76 76
 	mockClient := &testclient.Fake{}
77 77
 	projectcache.FakeProjectCache(mockClient, store, "")
78
-	handler := &lifecycle{}
78
+	handler := &lifecycle{client: mockClient}
79 79
 	build := &buildapi.Build{
80 80
 		ObjectMeta: kapi.ObjectMeta{Name: "buildid", Namespace: "other"},
81 81
 		Parameters: buildapi.BuildParameters{
... ...
@@ -13,7 +13,7 @@ type ProjectList struct {
13 13
 
14 14
 // These are internal finalizer values to Origin
15 15
 const (
16
-	FinalizerProject kapi.FinalizerName = "openshift.com/project"
16
+	FinalizerOrigin kapi.FinalizerName = "openshift.io/origin"
17 17
 )
18 18
 
19 19
 // ProjectSpec describes the attributes on a Project
... ...
@@ -13,7 +13,7 @@ type ProjectList struct {
13 13
 
14 14
 // These are internal finalizer values to Origin
15 15
 const (
16
-	FinalizerProject kapi.FinalizerName = "openshift.com/project"
16
+	FinalizerOrigin kapi.FinalizerName = "openshift.io/origin"
17 17
 )
18 18
 
19 19
 // ProjectSpec describes the attributes on a Project
... ...
@@ -13,7 +13,7 @@ type ProjectList struct {
13 13
 
14 14
 // These are internal finalizer values to Origin
15 15
 const (
16
-	FinalizerProject kapi.FinalizerName = "openshift.com/project"
16
+	FinalizerOrigin kapi.FinalizerName = "openshift.io/origin"
17 17
 )
18 18
 
19 19
 // ProjectSpec describes the attributes on a Project
... ...
@@ -13,7 +13,7 @@ type ProjectList struct {
13 13
 
14 14
 // These are internal finalizer values to Origin
15 15
 const (
16
-	FinalizerProject kapi.FinalizerName = "openshift.com/project"
16
+	FinalizerOrigin kapi.FinalizerName = "openshift.io/origin"
17 17
 )
18 18
 
19 19
 // ProjectSpec describes the attributes on a Project
... ...
@@ -5,9 +5,9 @@ import (
5 5
 	kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
6 6
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
7 7
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
8
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
8
+
9 9
 	osclient "github.com/openshift/origin/pkg/client"
10
-	"github.com/openshift/origin/pkg/project/api"
10
+	projectutil "github.com/openshift/origin/pkg/project/util"
11 11
 )
12 12
 
13 13
 // NamespaceController is responsible for participating in Kubernetes Namespace termination
... ...
@@ -33,7 +33,7 @@ func (c *NamespaceController) Handle(namespace *kapi.Namespace) (err error) {
33 33
 	}
34 34
 
35 35
 	// if we already processed this namespace, ignore it
36
-	if finalized(namespace) {
36
+	if projectutil.Finalized(namespace) {
37 37
 		return nil
38 38
 	}
39 39
 
... ...
@@ -44,7 +44,7 @@ func (c *NamespaceController) Handle(namespace *kapi.Namespace) (err error) {
44 44
 	}
45 45
 
46 46
 	// we have removed content, so mark it finalized by us
47
-	err = finalize(c.KubeClient, namespace)
47
+	_, err = projectutil.Finalize(c.KubeClient, namespace)
48 48
 	if err != nil {
49 49
 		return err
50 50
 	}
... ...
@@ -52,35 +52,6 @@ func (c *NamespaceController) Handle(namespace *kapi.Namespace) (err error) {
52 52
 	return nil
53 53
 }
54 54
 
55
-// finalized returns true if the spec.finalizers does not contain the project finalizer
56
-func finalized(namespace *kapi.Namespace) bool {
57
-	for i := range namespace.Spec.Finalizers {
58
-		if api.FinalizerProject == namespace.Spec.Finalizers[i] {
59
-			return false
60
-		}
61
-	}
62
-	return true
63
-}
64
-
65
-// finalize will finalize the namespace for kubernetes
66
-func finalize(kubeClient kclient.Interface, namespace *kapi.Namespace) error {
67
-	namespaceFinalize := kapi.Namespace{}
68
-	namespaceFinalize.ObjectMeta = namespace.ObjectMeta
69
-	namespaceFinalize.Spec = namespace.Spec
70
-	finalizerSet := util.NewStringSet()
71
-	for i := range namespace.Spec.Finalizers {
72
-		if namespace.Spec.Finalizers[i] != api.FinalizerProject {
73
-			finalizerSet.Insert(string(namespace.Spec.Finalizers[i]))
74
-		}
75
-	}
76
-	namespaceFinalize.Spec.Finalizers = make([]kapi.FinalizerName, 0, len(finalizerSet))
77
-	for _, value := range finalizerSet.List() {
78
-		namespaceFinalize.Spec.Finalizers = append(namespaceFinalize.Spec.Finalizers, kapi.FinalizerName(value))
79
-	}
80
-	_, err := kubeClient.Namespaces().Finalize(&namespaceFinalize)
81
-	return err
82
-}
83
-
84 55
 // deleteAllContent will purge all content in openshift in the specified namespace
85 56
 func deleteAllContent(client osclient.Interface, namespace string) (err error) {
86 57
 	err = deleteBuildConfigs(client, namespace)
... ...
@@ -25,7 +25,7 @@ func TestSyncNamespaceThatIsTerminating(t *testing.T) {
25 25
 			DeletionTimestamp: &now,
26 26
 		},
27 27
 		Spec: kapi.NamespaceSpec{
28
-			Finalizers: []kapi.FinalizerName{kapi.FinalizerKubernetes, api.FinalizerProject},
28
+			Finalizers: []kapi.FinalizerName{kapi.FinalizerKubernetes, api.FinalizerOrigin},
29 29
 		},
30 30
 		Status: kapi.NamespaceStatus{
31 31
 			Phase: kapi.NamespaceTerminating,
... ...
@@ -75,7 +75,7 @@ func TestSyncNamespaceThatIsActive(t *testing.T) {
75 75
 			ResourceVersion: "1",
76 76
 		},
77 77
 		Spec: kapi.NamespaceSpec{
78
-			Finalizers: []kapi.FinalizerName{kapi.FinalizerKubernetes, api.FinalizerProject},
78
+			Finalizers: []kapi.FinalizerName{kapi.FinalizerKubernetes, api.FinalizerOrigin},
79 79
 		},
80 80
 		Status: kapi.NamespaceStatus{
81 81
 			Phase: kapi.NamespaceActive,
... ...
@@ -29,16 +29,16 @@ func (projectStrategy) PrepareForCreate(obj runtime.Object) {
29 29
 	project := obj.(*api.Project)
30 30
 	hasProjectFinalizer := false
31 31
 	for i := range project.Spec.Finalizers {
32
-		if project.Spec.Finalizers[i] == api.FinalizerProject {
32
+		if project.Spec.Finalizers[i] == api.FinalizerOrigin {
33 33
 			hasProjectFinalizer = true
34 34
 			break
35 35
 		}
36 36
 	}
37 37
 	if !hasProjectFinalizer {
38 38
 		if len(project.Spec.Finalizers) == 0 {
39
-			project.Spec.Finalizers = []kapi.FinalizerName{api.FinalizerProject}
39
+			project.Spec.Finalizers = []kapi.FinalizerName{api.FinalizerOrigin}
40 40
 		} else {
41
-			project.Spec.Finalizers = append(project.Spec.Finalizers, api.FinalizerProject)
41
+			project.Spec.Finalizers = append(project.Spec.Finalizers, api.FinalizerOrigin)
42 42
 		}
43 43
 	}
44 44
 }
... ...
@@ -36,7 +36,7 @@ func TestProjectStrategy(t *testing.T) {
36 36
 		ObjectMeta: kapi.ObjectMeta{Name: "foo", ResourceVersion: "10"},
37 37
 	}
38 38
 	Strategy.PrepareForCreate(project)
39
-	if len(project.Spec.Finalizers) != 1 || project.Spec.Finalizers[0] != api.FinalizerProject {
39
+	if len(project.Spec.Finalizers) != 1 || project.Spec.Finalizers[0] != api.FinalizerOrigin {
40 40
 		t.Errorf("Prepare For Create should have added project finalizer")
41 41
 	}
42 42
 	errs := Strategy.Validate(ctx, project)
... ...
@@ -48,7 +48,7 @@ func TestProjectStrategy(t *testing.T) {
48 48
 	}
49 49
 	// ensure we copy spec.finalizers from old to new
50 50
 	Strategy.PrepareForUpdate(invalidProject, project)
51
-	if len(invalidProject.Spec.Finalizers) != 1 || invalidProject.Spec.Finalizers[0] != api.FinalizerProject {
51
+	if len(invalidProject.Spec.Finalizers) != 1 || invalidProject.Spec.Finalizers[0] != api.FinalizerOrigin {
52 52
 		t.Errorf("PrepareForUpdate should have preserved old.spec.finalizers")
53 53
 	}
54 54
 	errs = Strategy.ValidateUpdate(ctx, invalidProject, project)
55 55
new file mode 100644
... ...
@@ -0,0 +1,69 @@
0
+package util
1
+
2
+import (
3
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
4
+	kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
5
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
6
+
7
+	"github.com/openshift/origin/pkg/project/api"
8
+)
9
+
10
+// Associated returns true if the spec.finalizers contains the origin finalizer
11
+func Associated(namespace *kapi.Namespace) bool {
12
+	for i := range namespace.Spec.Finalizers {
13
+		if api.FinalizerOrigin == namespace.Spec.Finalizers[i] {
14
+			return true
15
+		}
16
+	}
17
+	return false
18
+}
19
+
20
+// Associate adds the origin finalizer to spec.finalizers if its not there already
21
+func Associate(kubeClient kclient.Interface, namespace *kapi.Namespace) (*kapi.Namespace, error) {
22
+	if Associated(namespace) {
23
+		return namespace, nil
24
+	}
25
+	return finalizeInternal(kubeClient, namespace, true)
26
+}
27
+
28
+// Finalized returns true if the spec.finalizers does not contain the origin finalizer
29
+func Finalized(namespace *kapi.Namespace) bool {
30
+	for i := range namespace.Spec.Finalizers {
31
+		if api.FinalizerOrigin == namespace.Spec.Finalizers[i] {
32
+			return false
33
+		}
34
+	}
35
+	return true
36
+}
37
+
38
+// Finalize will remove the origin finalizer from the namespace
39
+func Finalize(kubeClient kclient.Interface, namespace *kapi.Namespace) (*kapi.Namespace, error) {
40
+	if Finalized(namespace) {
41
+		return namespace, nil
42
+	}
43
+	return finalizeInternal(kubeClient, namespace, false)
44
+}
45
+
46
+// finalizeInternal will update the namespace finalizer list to either have or not have origin finalizer
47
+func finalizeInternal(kubeClient kclient.Interface, namespace *kapi.Namespace, withOrigin bool) (*kapi.Namespace, error) {
48
+	namespaceFinalize := kapi.Namespace{}
49
+	namespaceFinalize.ObjectMeta = namespace.ObjectMeta
50
+	namespaceFinalize.Spec = namespace.Spec
51
+
52
+	finalizerSet := util.NewStringSet()
53
+	for i := range namespace.Spec.Finalizers {
54
+		finalizerSet.Insert(string(namespace.Spec.Finalizers[i]))
55
+	}
56
+
57
+	if withOrigin {
58
+		finalizerSet.Insert(string(api.FinalizerOrigin))
59
+	} else {
60
+		finalizerSet.Delete(string(api.FinalizerOrigin))
61
+	}
62
+
63
+	namespaceFinalize.Spec.Finalizers = make([]kapi.FinalizerName, 0, len(finalizerSet))
64
+	for _, value := range finalizerSet.List() {
65
+		namespaceFinalize.Spec.Finalizers = append(namespaceFinalize.Spec.Finalizers, kapi.FinalizerName(value))
66
+	}
67
+	return kubeClient.Namespaces().Finalize(&namespaceFinalize)
68
+}