Skip to content

Commit 8b50d9f

Browse files
Merge pull request #10932 from liggitt/namespace-admission-wait-1.3.1
compensate for raft/cache delay in namespace admission
2 parents 82fc732 + c0307d9 commit 8b50d9f

File tree

5 files changed

+119
-149
lines changed

5 files changed

+119
-149
lines changed

pkg/project/admission/lifecycle/admission.go

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,11 @@ import (
44
"fmt"
55
"io"
66
"math/rand"
7-
"strings"
87
"time"
98

109
"github.com/golang/glog"
1110

1211
"k8s.io/kubernetes/pkg/admission"
13-
kapi "k8s.io/kubernetes/pkg/api"
14-
apierrors "k8s.io/kubernetes/pkg/api/errors"
1512
"k8s.io/kubernetes/pkg/api/meta"
1613
"k8s.io/kubernetes/pkg/apimachinery/registered"
1714
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
@@ -68,17 +65,6 @@ func (e *lifecycle) Admit(a admission.Attributes) (err error) {
6865
return nil
6966
}
7067

71-
// we want to allow someone to delete something in case it was phantom created somehow
72-
if a.GetOperation() == "DELETE" {
73-
return nil
74-
}
75-
76-
name := "Unknown"
77-
obj := a.GetObject()
78-
if obj != nil {
79-
name, _ = meta.NewAccessor().Name(obj)
80-
}
81-
8268
if !e.cache.Running() {
8369
return admission.NewForbidden(a, err)
8470
}
@@ -88,14 +74,6 @@ func (e *lifecycle) Admit(a admission.Attributes) (err error) {
8874
return admission.NewForbidden(a, err)
8975
}
9076

91-
if a.GetOperation() != "CREATE" {
92-
return nil
93-
}
94-
95-
if namespace.Status.Phase == kapi.NamespaceTerminating && !e.creatableResources.Has(strings.ToLower(a.GetResource().Resource)) {
96-
return apierrors.NewForbidden(a.GetResource().GroupResource(), name, fmt.Errorf("Namespace %s is terminating", a.GetNamespace()))
97-
}
98-
9977
// in case of concurrency issues, we will retry this logic
10078
numRetries := 10
10179
interval := time.Duration(rand.Int63n(90)+int64(10)) * time.Millisecond
@@ -125,7 +103,7 @@ func (e *lifecycle) Admit(a admission.Attributes) (err error) {
125103
}
126104

127105
func (e *lifecycle) Handles(operation admission.Operation) bool {
128-
return true
106+
return operation == admission.Create
129107
}
130108

131109
func (e *lifecycle) SetProjectCache(c *cache.ProjectCache) {

pkg/project/admission/lifecycle/admission_test.go

Lines changed: 0 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,18 @@ package lifecycle
22

33
import (
44
"fmt"
5-
"strings"
65
"testing"
7-
"time"
86

97
"k8s.io/kubernetes/pkg/admission"
108
kapi "k8s.io/kubernetes/pkg/api"
119
"k8s.io/kubernetes/pkg/api/unversioned"
1210
"k8s.io/kubernetes/pkg/client/cache"
1311
clientsetfake "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
1412
"k8s.io/kubernetes/pkg/client/unversioned/testclient"
15-
genericapiserveroptions "k8s.io/kubernetes/pkg/genericapiserver/options"
16-
kubeletclient "k8s.io/kubernetes/pkg/kubelet/client"
1713
"k8s.io/kubernetes/pkg/runtime"
18-
etcdstorage "k8s.io/kubernetes/pkg/storage/etcd"
19-
"k8s.io/kubernetes/pkg/util/sets"
2014

2115
buildapi "github.com/openshift/origin/pkg/build/api"
22-
otestclient "github.com/openshift/origin/pkg/client/testclient"
23-
"github.com/openshift/origin/pkg/cmd/server/origin"
24-
"github.com/openshift/origin/pkg/controller/shared"
2516
projectcache "github.com/openshift/origin/pkg/project/cache"
26-
"github.com/openshift/origin/pkg/quota/controller/clusterquotamapping"
27-
"github.com/openshift/origin/pkg/util/restoptions"
2817

2918
// install all APIs
3019
_ "github.com/openshift/origin/pkg/api/install"
@@ -90,105 +79,6 @@ func TestAdmissionExists(t *testing.T) {
9079
}
9180
}
9281

93-
// TestAdmissionLifecycle verifies you cannot create Origin content if namespace is terminating
94-
func TestAdmissionLifecycle(t *testing.T) {
95-
namespaceObj := &kapi.Namespace{
96-
ObjectMeta: kapi.ObjectMeta{
97-
Name: "test",
98-
Namespace: "",
99-
},
100-
Status: kapi.NamespaceStatus{
101-
Phase: kapi.NamespaceActive,
102-
},
103-
}
104-
store := projectcache.NewCacheStore(cache.IndexFuncToKeyFuncAdapter(cache.MetaNamespaceIndexFunc))
105-
store.Add(namespaceObj)
106-
mockClient := &testclient.Fake{}
107-
cache := projectcache.NewFake(mockClient.Namespaces(), store, "")
108-
109-
mockClientset := clientsetfake.NewSimpleClientset(namespaceObj)
110-
handler := &lifecycle{client: mockClientset}
111-
handler.SetProjectCache(cache)
112-
build := &buildapi.Build{
113-
ObjectMeta: kapi.ObjectMeta{Name: "buildid", Namespace: "other"},
114-
Spec: buildapi.BuildSpec{
115-
CommonSpec: buildapi.CommonSpec{
116-
Source: buildapi.BuildSource{
117-
Git: &buildapi.GitBuildSource{
118-
URI: "http://github.com/my/repository",
119-
},
120-
ContextDir: "context",
121-
},
122-
Strategy: buildapi.BuildStrategy{
123-
DockerStrategy: &buildapi.DockerBuildStrategy{},
124-
},
125-
Output: buildapi.BuildOutput{
126-
To: &kapi.ObjectReference{
127-
Kind: "DockerImage",
128-
Name: "repository/data",
129-
},
130-
},
131-
},
132-
},
133-
Status: buildapi.BuildStatus{
134-
Phase: buildapi.BuildPhaseNew,
135-
},
136-
}
137-
err := handler.Admit(admission.NewAttributesRecord(build, nil, kapi.Kind("Build").WithVersion("version"), build.Namespace, "name", kapi.Resource("builds").WithVersion("version"), "", "CREATE", nil))
138-
if err != nil {
139-
t.Errorf("Unexpected error returned from admission handler: %v", err)
140-
}
141-
142-
// change namespace state to terminating
143-
namespaceObj.Status.Phase = kapi.NamespaceTerminating
144-
store.Add(namespaceObj)
145-
146-
// verify create operations in the namespace cause an error
147-
err = handler.Admit(admission.NewAttributesRecord(build, nil, kapi.Kind("Build").WithVersion("version"), build.Namespace, "name", kapi.Resource("builds").WithVersion("version"), "", "CREATE", nil))
148-
if err == nil {
149-
t.Errorf("Expected error rejecting creates in a namespace when it is terminating")
150-
}
151-
152-
// verify update operations in the namespace can proceed
153-
err = handler.Admit(admission.NewAttributesRecord(build, build, kapi.Kind("Build").WithVersion("version"), build.Namespace, "name", kapi.Resource("builds").WithVersion("version"), "", "UPDATE", nil))
154-
if err != nil {
155-
t.Errorf("Unexpected error returned from admission handler: %v", err)
156-
}
157-
158-
// verify delete operations in the namespace can proceed
159-
err = handler.Admit(admission.NewAttributesRecord(nil, nil, kapi.Kind("Build").WithVersion("version"), build.Namespace, "name", kapi.Resource("builds").WithVersion("version"), "", "DELETE", nil))
160-
if err != nil {
161-
t.Errorf("Unexpected error returned from admission handler: %v", err)
162-
}
163-
164-
}
165-
166-
// TestCreatesAllowedDuringNamespaceDeletion checks to make sure that the resources in the whitelist are allowed
167-
func TestCreatesAllowedDuringNamespaceDeletion(t *testing.T) {
168-
etcdHelper := etcdstorage.NewEtcdStorage(nil, kapi.Codecs.LegacyCodec(), "", false, genericapiserveroptions.DefaultDeserializationCacheSize)
169-
170-
informerFactory := shared.NewInformerFactory(testclient.NewSimpleFake(), otestclient.NewSimpleFake(), shared.DefaultListerWatcherOverrides{}, 1*time.Second)
171-
config := &origin.MasterConfig{
172-
KubeletClientConfig: &kubeletclient.KubeletClientConfig{},
173-
RESTOptionsGetter: restoptions.NewSimpleGetter(etcdHelper),
174-
EtcdHelper: etcdHelper,
175-
Informers: informerFactory,
176-
ClusterQuotaMappingController: clusterquotamapping.NewClusterQuotaMappingController(informerFactory.Namespaces(), informerFactory.ClusterResourceQuotas()),
177-
}
178-
storageMap := config.GetRestStorage()
179-
resources := sets.String{}
180-
181-
for resource := range storageMap {
182-
resources.Insert(strings.ToLower(resource))
183-
}
184-
185-
for resource := range recommendedCreatableResources {
186-
if !resources.Has(resource) {
187-
t.Errorf("recommendedCreatableResources has resource %v, but that resource isn't registered.", resource)
188-
}
189-
}
190-
}
191-
19282
func TestSAR(t *testing.T) {
19383
store := projectcache.NewCacheStore(cache.IndexFuncToKeyFuncAdapter(cache.MetaNamespaceIndexFunc))
19484
mockClient := &testclient.Fake{}

pkg/project/cache/cache.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ package cache
22

33
import (
44
"fmt"
5+
"time"
56

67
kapi "k8s.io/kubernetes/pkg/api"
78
"k8s.io/kubernetes/pkg/client/cache"
89
client "k8s.io/kubernetes/pkg/client/unversioned"
910
"k8s.io/kubernetes/pkg/runtime"
1011
"k8s.io/kubernetes/pkg/watch"
1112

13+
"github.com/golang/glog"
1214
projectapi "github.com/openshift/origin/pkg/project/api"
1315
"github.com/openshift/origin/pkg/util/labelselector"
1416
)
@@ -28,18 +30,26 @@ type ProjectCache struct {
2830
}
2931

3032
func (p *ProjectCache) GetNamespace(name string) (*kapi.Namespace, error) {
33+
key := &kapi.Namespace{ObjectMeta: kapi.ObjectMeta{Name: name}}
34+
3135
// check for namespace in the cache
32-
namespaceObj, exists, err := p.Store.Get(&kapi.Namespace{
33-
ObjectMeta: kapi.ObjectMeta{
34-
Name: name,
35-
Namespace: "",
36-
},
37-
Status: kapi.NamespaceStatus{},
38-
})
36+
namespaceObj, exists, err := p.Store.Get(key)
3937
if err != nil {
4038
return nil, err
4139
}
4240

41+
if !exists {
42+
// give the cache time to observe a recent namespace creation
43+
time.Sleep(50 * time.Millisecond)
44+
namespaceObj, exists, err = p.Store.Get(key)
45+
if err != nil {
46+
return nil, err
47+
}
48+
if exists {
49+
glog.V(4).Infof("found %s in cache after waiting", name)
50+
}
51+
}
52+
4353
var namespace *kapi.Namespace
4454
if exists {
4555
namespace = namespaceObj.(*kapi.Namespace)
@@ -50,6 +60,7 @@ func (p *ProjectCache) GetNamespace(name string) (*kapi.Namespace, error) {
5060
if err != nil {
5161
return nil, fmt.Errorf("namespace %s does not exist", name)
5262
}
63+
glog.V(4).Infof("found %s via storage lookup", name)
5364
}
5465
return namespace, nil
5566
}

test/integration/namespace_lifecycle_admission_test.go

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
package integration
22

33
import (
4+
"strings"
45
"testing"
56

7+
kapi "k8s.io/kubernetes/pkg/api"
8+
9+
"github.com/openshift/origin/pkg/project/api"
10+
routeapi "github.com/openshift/origin/pkg/route/api"
611
testutil "github.com/openshift/origin/test/util"
712
testserver "github.com/openshift/origin/test/util/server"
813
)
@@ -14,14 +19,81 @@ func TestNamespaceLifecycleAdmission(t *testing.T) {
1419
if err != nil {
1520
t.Fatal(err)
1621
}
17-
clusterAdminClient, err := testutil.GetClusterAdminKubeClient(clusterAdminKubeConfig)
22+
clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig)
23+
if err != nil {
24+
t.Fatal(err)
25+
}
26+
clusterAdminKubeClient, err := testutil.GetClusterAdminKubeClient(clusterAdminKubeConfig)
1827
if err != nil {
1928
t.Fatal(err)
2029
}
2130

2231
for _, ns := range []string{"default", "openshift", "openshift-infra"} {
23-
if err := clusterAdminClient.Namespaces().Delete(ns); err == nil {
32+
if err := clusterAdminKubeClient.Namespaces().Delete(ns); err == nil {
2433
t.Fatalf("expected error deleting %q namespace, got none", ns)
2534
}
2635
}
36+
37+
// Create a namespace directly (not via a project)
38+
ns := &kapi.Namespace{ObjectMeta: kapi.ObjectMeta{Name: "test"}}
39+
ns, err = clusterAdminKubeClient.Namespaces().Create(ns)
40+
if err != nil {
41+
t.Fatal(err)
42+
}
43+
if len(ns.Spec.Finalizers) == 0 {
44+
t.Fatal("expected at least one finalizer")
45+
}
46+
found := false
47+
for _, f := range ns.Spec.Finalizers {
48+
if f == api.FinalizerOrigin {
49+
found = true
50+
break
51+
}
52+
}
53+
if found {
54+
t.Fatalf("didn't expect origin finalizer to be present, got %#v", ns.Spec.Finalizers)
55+
}
56+
57+
// Create an origin object
58+
route := &routeapi.Route{
59+
ObjectMeta: kapi.ObjectMeta{Name: "route"},
60+
Spec: routeapi.RouteSpec{To: routeapi.RouteTargetReference{Kind: "Service", Name: "test"}},
61+
}
62+
route, err = clusterAdminClient.Routes(ns.Name).Create(route)
63+
if err != nil {
64+
t.Fatal(err)
65+
}
66+
67+
// Ensure the origin finalizer is added
68+
ns, err = clusterAdminKubeClient.Namespaces().Get(ns.Name)
69+
if err != nil {
70+
t.Fatal(err)
71+
}
72+
found = false
73+
for _, f := range ns.Spec.Finalizers {
74+
if f == api.FinalizerOrigin {
75+
found = true
76+
break
77+
}
78+
}
79+
if !found {
80+
t.Fatalf("expected origin finalizer, got %#v", ns.Spec.Finalizers)
81+
}
82+
83+
// Delete the namespace
84+
// We don't have to worry about racing the namespace deletion controller because we've only started the master
85+
err = clusterAdminKubeClient.Namespaces().Delete(ns.Name)
86+
if err != nil {
87+
t.Fatal(err)
88+
}
89+
90+
// Try to create an origin object in a terminating namespace and ensure it is forbidden
91+
route = &routeapi.Route{
92+
ObjectMeta: kapi.ObjectMeta{Name: "route2"},
93+
Spec: routeapi.RouteSpec{To: routeapi.RouteTargetReference{Kind: "Service", Name: "test"}},
94+
}
95+
_, err = clusterAdminClient.Routes(ns.Name).Create(route)
96+
if err == nil || !strings.Contains(err.Error(), "it is being terminated") {
97+
t.Fatalf("Expected forbidden error because of a terminating namespace, got %v", err)
98+
}
2799
}

0 commit comments

Comments
 (0)