-
Notifications
You must be signed in to change notification settings - Fork 38.8k
/
namespace.go
475 lines (415 loc) · 19.9 KB
/
namespace.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
/*
Copyright 2014 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 apimachinery
import (
"context"
"encoding/json"
"fmt"
"strings"
"sync"
"time"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
utilrand "k8s.io/apimachinery/pkg/util/rand"
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/apimachinery/pkg/util/wait"
clientscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/util/retry"
"k8s.io/kubernetes/test/e2e/framework"
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
imageutils "k8s.io/kubernetes/test/utils/image"
admissionapi "k8s.io/pod-security-admission/api"
"github.com/onsi/ginkgo/v2"
"k8s.io/apimachinery/pkg/types"
)
func extinguish(ctx context.Context, f *framework.Framework, totalNS int, maxAllowedAfterDel int, maxSeconds int) {
ginkgo.By("Creating testing namespaces")
wg := &sync.WaitGroup{}
wg.Add(totalNS)
for n := 0; n < totalNS; n++ {
go func(n int) {
defer wg.Done()
defer ginkgo.GinkgoRecover()
ns := fmt.Sprintf("nslifetest-%v", n)
_, err := f.CreateNamespace(ctx, ns, nil)
framework.ExpectNoError(err, "failed to create namespace: %s", ns)
}(n)
}
wg.Wait()
//Wait 10 seconds, then SEND delete requests for all the namespaces.
ginkgo.By("Waiting 10 seconds")
time.Sleep(10 * time.Second)
deleteFilter := []string{"nslifetest"}
deleted, err := framework.DeleteNamespaces(ctx, f.ClientSet, deleteFilter, nil /* skipFilter */)
framework.ExpectNoError(err, "failed to delete namespace(s) containing: %s", deleteFilter)
framework.ExpectEqual(len(deleted), totalNS)
ginkgo.By("Waiting for namespaces to vanish")
//Now POLL until all namespaces have been eradicated.
framework.ExpectNoError(wait.Poll(2*time.Second, time.Duration(maxSeconds)*time.Second,
func() (bool, error) {
var cnt = 0
nsList, err := f.ClientSet.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
if err != nil {
return false, err
}
for _, item := range nsList.Items {
if strings.Contains(item.Name, "nslifetest") {
cnt++
}
}
if cnt > maxAllowedAfterDel {
framework.Logf("Remaining namespaces : %v", cnt)
return false, nil
}
return true, nil
}))
}
func ensurePodsAreRemovedWhenNamespaceIsDeleted(ctx context.Context, f *framework.Framework) {
ginkgo.By("Creating a test namespace")
namespaceName := "nsdeletetest"
namespace, err := f.CreateNamespace(ctx, namespaceName, nil)
framework.ExpectNoError(err, "failed to create namespace: %s", namespaceName)
ginkgo.By("Waiting for a default service account to be provisioned in namespace")
err = framework.WaitForDefaultServiceAccountInNamespace(ctx, f.ClientSet, namespace.Name)
framework.ExpectNoError(err, "failure while waiting for a default service account to be provisioned in namespace: %s", namespace.Name)
ginkgo.By("Creating a pod in the namespace")
podName := "test-pod"
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: podName,
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "nginx",
Image: imageutils.GetPauseImageName(),
},
},
},
}
pod, err = f.ClientSet.CoreV1().Pods(namespace.Name).Create(ctx, pod, metav1.CreateOptions{})
framework.ExpectNoError(err, "failed to create pod %s in namespace: %s", podName, namespace.Name)
ginkgo.By("Waiting for the pod to have running status")
framework.ExpectNoError(e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, pod))
ginkgo.By("Deleting the namespace")
err = f.ClientSet.CoreV1().Namespaces().Delete(ctx, namespace.Name, metav1.DeleteOptions{})
framework.ExpectNoError(err, "failed to delete namespace: %s", namespace.Name)
ginkgo.By("Waiting for the namespace to be removed.")
maxWaitSeconds := int64(60) + *pod.Spec.TerminationGracePeriodSeconds
framework.ExpectNoError(wait.Poll(1*time.Second, time.Duration(maxWaitSeconds)*time.Second,
func() (bool, error) {
_, err = f.ClientSet.CoreV1().Namespaces().Get(ctx, namespace.Name, metav1.GetOptions{})
if err != nil && apierrors.IsNotFound(err) {
return true, nil
}
return false, nil
}))
ginkgo.By("Recreating the namespace")
namespace, err = f.CreateNamespace(ctx, namespaceName, nil)
framework.ExpectNoError(err, "failed to create namespace: %s", namespaceName)
ginkgo.By("Verifying there are no pods in the namespace")
_, err = f.ClientSet.CoreV1().Pods(namespace.Name).Get(ctx, pod.Name, metav1.GetOptions{})
framework.ExpectError(err, "failed to get pod %s in namespace: %s", pod.Name, namespace.Name)
}
func ensureServicesAreRemovedWhenNamespaceIsDeleted(ctx context.Context, f *framework.Framework) {
var err error
ginkgo.By("Creating a test namespace")
namespaceName := "nsdeletetest"
namespace, err := f.CreateNamespace(ctx, namespaceName, nil)
framework.ExpectNoError(err, "failed to create namespace: %s", namespaceName)
ginkgo.By("Waiting for a default service account to be provisioned in namespace")
err = framework.WaitForDefaultServiceAccountInNamespace(ctx, f.ClientSet, namespace.Name)
framework.ExpectNoError(err, "failure while waiting for a default service account to be provisioned in namespace: %s", namespace.Name)
ginkgo.By("Creating a service in the namespace")
serviceName := "test-service"
labels := map[string]string{
"foo": "bar",
"baz": "blah",
}
service := &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: serviceName,
},
Spec: v1.ServiceSpec{
Selector: labels,
Ports: []v1.ServicePort{{
Port: 80,
TargetPort: intstr.FromInt(80),
}},
},
}
service, err = f.ClientSet.CoreV1().Services(namespace.Name).Create(ctx, service, metav1.CreateOptions{})
framework.ExpectNoError(err, "failed to create service %s in namespace %s", serviceName, namespace.Name)
ginkgo.By("Deleting the namespace")
err = f.ClientSet.CoreV1().Namespaces().Delete(ctx, namespace.Name, metav1.DeleteOptions{})
framework.ExpectNoError(err, "failed to delete namespace: %s", namespace.Name)
ginkgo.By("Waiting for the namespace to be removed.")
maxWaitSeconds := int64(60)
framework.ExpectNoError(wait.Poll(1*time.Second, time.Duration(maxWaitSeconds)*time.Second,
func() (bool, error) {
_, err = f.ClientSet.CoreV1().Namespaces().Get(ctx, namespace.Name, metav1.GetOptions{})
if err != nil && apierrors.IsNotFound(err) {
return true, nil
}
return false, nil
}))
ginkgo.By("Recreating the namespace")
namespace, err = f.CreateNamespace(ctx, namespaceName, nil)
framework.ExpectNoError(err, "failed to create namespace: %s", namespaceName)
ginkgo.By("Verifying there is no service in the namespace")
_, err = f.ClientSet.CoreV1().Services(namespace.Name).Get(ctx, service.Name, metav1.GetOptions{})
framework.ExpectError(err, "failed to get service %s in namespace: %s", service.Name, namespace.Name)
}
// This test must run [Serial] due to the impact of running other parallel
// tests can have on its performance. Each test that follows the common
// test framework follows this pattern:
// 1. Create a Namespace
// 2. Do work that generates content in that namespace
// 3. Delete a Namespace
//
// Creation of a Namespace is non-trivial since it requires waiting for a
// ServiceAccount to be generated.
// Deletion of a Namespace is non-trivial and performance intensive since
// its an orchestrated process. The controller that handles deletion must
// query the namespace for all existing content, and then delete each piece
// of content in turn. As the API surface grows to add more KIND objects
// that could exist in a Namespace, the number of calls that the namespace
// controller must orchestrate grows since it must LIST, DELETE (1x1) each
// KIND.
// There is work underway to improve this, but it's
// most likely not going to get significantly better until etcd v3.
// Going back to this test, this test generates 100 Namespace objects, and then
// rapidly deletes all of them. This causes the NamespaceController to observe
// and attempt to process a large number of deletes concurrently. In effect,
// it's like running 100 traditional e2e tests in parallel. If the namespace
// controller orchestrating deletes is slowed down deleting another test's
// content then this test may fail. Since the goal of this test is to soak
// Namespace creation, and soak Namespace deletion, its not appropriate to
// further soak the cluster with other parallel Namespace deletion activities
// that each have a variable amount of content in the associated Namespace.
// When run in [Serial] this test appears to delete Namespace objects at a
// rate of approximately 1 per second.
var _ = SIGDescribe("Namespaces [Serial]", func() {
f := framework.NewDefaultFramework("namespaces")
f.NamespacePodSecurityEnforceLevel = admissionapi.LevelBaseline
/*
Release: v1.11
Testname: namespace-deletion-removes-pods
Description: Ensure that if a namespace is deleted then all pods are removed from that namespace.
*/
framework.ConformanceIt("should ensure that all pods are removed when a namespace is deleted", func(ctx context.Context) {
ensurePodsAreRemovedWhenNamespaceIsDeleted(ctx, f)
})
/*
Release: v1.11
Testname: namespace-deletion-removes-services
Description: Ensure that if a namespace is deleted then all services are removed from that namespace.
*/
framework.ConformanceIt("should ensure that all services are removed when a namespace is deleted", func(ctx context.Context) {
ensureServicesAreRemovedWhenNamespaceIsDeleted(ctx, f)
})
ginkgo.It("should delete fast enough (90 percent of 100 namespaces in 150 seconds)", func(ctx context.Context) {
extinguish(ctx, f, 100, 10, 150)
})
// On hold until etcd3; see #7372
ginkgo.It("should always delete fast (ALL of 100 namespaces in 150 seconds) [Feature:ComprehensiveNamespaceDraining]", func(ctx context.Context) {
extinguish(ctx, f, 100, 0, 150)
})
/*
Release: v1.18
Testname: Namespace patching
Description: A Namespace is created.
The Namespace is patched.
The Namespace and MUST now include the new Label.
*/
framework.ConformanceIt("should patch a Namespace", func(ctx context.Context) {
ginkgo.By("creating a Namespace")
namespaceName := "nspatchtest-" + string(uuid.NewUUID())
ns, err := f.CreateNamespace(ctx, namespaceName, nil)
framework.ExpectNoError(err, "failed creating Namespace")
namespaceName = ns.ObjectMeta.Name
ginkgo.By("patching the Namespace")
nspatch, err := json.Marshal(map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]string{"testLabel": "testValue"},
},
})
framework.ExpectNoError(err, "failed to marshal JSON patch data")
_, err = f.ClientSet.CoreV1().Namespaces().Patch(ctx, namespaceName, types.StrategicMergePatchType, nspatch, metav1.PatchOptions{})
framework.ExpectNoError(err, "failed to patch Namespace")
ginkgo.By("get the Namespace and ensuring it has the label")
namespace, err := f.ClientSet.CoreV1().Namespaces().Get(ctx, namespaceName, metav1.GetOptions{})
framework.ExpectNoError(err, "failed to get Namespace")
framework.ExpectEqual(namespace.ObjectMeta.Labels["testLabel"], "testValue", "namespace not patched")
})
/*
Release: v1.25
Testname: Namespace, apply changes to a namespace status
Description: Getting the current namespace status MUST succeed. The reported status
phase MUST be active. Given the patching of the namespace status, the fields MUST
equal the new values. Given the updating of the namespace status, the fields MUST
equal the new values.
*/
framework.ConformanceIt("should apply changes to a namespace status", func(ctx context.Context) {
ns := f.Namespace.Name
dc := f.DynamicClient
nsResource := v1.SchemeGroupVersion.WithResource("namespaces")
nsClient := f.ClientSet.CoreV1().Namespaces()
ginkgo.By("Read namespace status")
unstruct, err := dc.Resource(nsResource).Get(ctx, ns, metav1.GetOptions{}, "status")
framework.ExpectNoError(err, "failed to fetch NamespaceStatus %s", ns)
nsStatus, err := unstructuredToNamespace(unstruct)
framework.ExpectNoError(err, "Getting the status of the namespace %s", ns)
framework.ExpectEqual(nsStatus.Status.Phase, v1.NamespaceActive, "The phase returned was %v", nsStatus.Status.Phase)
framework.Logf("Status: %#v", nsStatus.Status)
ginkgo.By("Patch namespace status")
nsCondition := v1.NamespaceCondition{
Type: "StatusPatch",
Status: v1.ConditionTrue,
Reason: "E2E",
Message: "Patched by an e2e test",
}
nsConditionJSON, err := json.Marshal(nsCondition)
framework.ExpectNoError(err, "failed to marshal namespace condition")
patchedStatus, err := nsClient.Patch(ctx, ns, types.MergePatchType,
[]byte(`{"metadata":{"annotations":{"e2e-patched-ns-status":"`+ns+`"}},"status":{"conditions":[`+string(nsConditionJSON)+`]}}`),
metav1.PatchOptions{}, "status")
framework.ExpectNoError(err, "Failed to patch status. err: %v ", err)
framework.ExpectEqual(patchedStatus.Annotations["e2e-patched-ns-status"], ns, "patched object should have the applied annotation")
framework.ExpectEqual(patchedStatus.Status.Conditions[len(patchedStatus.Status.Conditions)-1].Reason, "E2E", "The Reason returned was %v", patchedStatus.Status.Conditions[0].Reason)
framework.ExpectEqual(patchedStatus.Status.Conditions[len(patchedStatus.Status.Conditions)-1].Message, "Patched by an e2e test", "The Message returned was %v", patchedStatus.Status.Conditions[0].Message)
framework.Logf("Status.Condition: %#v", patchedStatus.Status.Conditions[len(patchedStatus.Status.Conditions)-1])
ginkgo.By("Update namespace status")
var statusUpdated *v1.Namespace
err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
unstruct, err := dc.Resource(nsResource).Get(ctx, ns, metav1.GetOptions{}, "status")
framework.ExpectNoError(err, "failed to fetch NamespaceStatus %s", ns)
statusToUpdate, err := unstructuredToNamespace(unstruct)
framework.ExpectNoError(err, "Getting the status of the namespace %s", ns)
statusToUpdate.Status.Conditions = append(statusToUpdate.Status.Conditions, v1.NamespaceCondition{
Type: "StatusUpdate",
Status: v1.ConditionTrue,
Reason: "E2E",
Message: "Updated by an e2e test",
})
statusUpdated, err = nsClient.UpdateStatus(ctx, statusToUpdate, metav1.UpdateOptions{})
return err
})
framework.ExpectNoError(err, "failed to update namespace status %s", ns)
framework.ExpectEqual(len(statusUpdated.Status.Conditions), len(statusUpdated.Status.Conditions), fmt.Sprintf("updated object should have the applied condition, got %#v", statusUpdated.Status.Conditions))
framework.ExpectEqual(string(statusUpdated.Status.Conditions[len(statusUpdated.Status.Conditions)-1].Type), "StatusUpdate", fmt.Sprintf("updated object should have the approved condition, got %#v", statusUpdated.Status.Conditions))
framework.ExpectEqual(statusUpdated.Status.Conditions[len(statusUpdated.Status.Conditions)-1].Message, "Updated by an e2e test", "The Message returned was %v", statusUpdated.Status.Conditions[0].Message)
framework.Logf("Status.Condition: %#v", statusUpdated.Status.Conditions[len(statusUpdated.Status.Conditions)-1])
})
/*
Release: v1.26
Testname: Namespace, apply update to a namespace
Description: When updating the namespace it MUST
succeed and the field MUST equal the new value.
*/
framework.ConformanceIt("should apply an update to a Namespace", func(ctx context.Context) {
var err error
var updatedNamespace *v1.Namespace
ns := f.Namespace.Name
cs := f.ClientSet
ginkgo.By(fmt.Sprintf("Updating Namespace %q", ns))
err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
updatedNamespace, err = cs.CoreV1().Namespaces().Get(ctx, ns, metav1.GetOptions{})
framework.ExpectNoError(err, "Unable to get Namespace %q", ns)
updatedNamespace.Labels[ns] = "updated"
updatedNamespace, err = cs.CoreV1().Namespaces().Update(ctx, updatedNamespace, metav1.UpdateOptions{})
return err
})
framework.ExpectNoError(err, "failed to update Namespace: %q", ns)
framework.ExpectEqual(updatedNamespace.ObjectMeta.Labels[ns], "updated", "Failed to update namespace %q. Current Labels: %#v", ns, updatedNamespace.Labels)
framework.Logf("Namespace %q now has labels, %#v", ns, updatedNamespace.Labels)
})
/*
Release: v1.26
Testname: Namespace, apply finalizer to a namespace
Description: Attempt to create a Namespace which MUST be succeed.
Updating the namespace with a fake finalizer MUST succeed. The
fake finalizer MUST be found. Removing the fake finalizer from
the namespace MUST succeed and MUST NOT be found.
*/
framework.ConformanceIt("should apply a finalizer to a Namespace", func(ctx context.Context) {
fakeFinalizer := v1.FinalizerName("e2e.example.com/fakeFinalizer")
var updatedNamespace *v1.Namespace
nsName := "e2e-ns-" + utilrand.String(5)
ginkgo.By(fmt.Sprintf("Creating namespace %q", nsName))
testNamespace, err := f.CreateNamespace(ctx, nsName, nil)
framework.ExpectNoError(err, "failed creating Namespace")
ns := testNamespace.ObjectMeta.Name
nsClient := f.ClientSet.CoreV1().Namespaces()
framework.Logf("Namespace %q has %#v", testNamespace.Name, testNamespace.Spec.Finalizers)
ginkgo.By(fmt.Sprintf("Adding e2e finalizer to namespace %q", ns))
err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
updateNamespace, err := nsClient.Get(ctx, ns, metav1.GetOptions{})
framework.ExpectNoError(err, "Unable to get Namespace %q", ns)
updateNamespace.Spec.Finalizers = append(updateNamespace.Spec.Finalizers, fakeFinalizer)
updatedNamespace, err = nsClient.Finalize(ctx, updateNamespace, metav1.UpdateOptions{})
return err
})
framework.ExpectNoError(err, "failed to add finalizer to the namespace: %q", ns)
var foundFinalizer bool
for _, item := range updatedNamespace.Spec.Finalizers {
if item == fakeFinalizer {
foundFinalizer = true
break
}
}
if !foundFinalizer {
framework.Failf("Finalizer %q was not found. Namespace %q has %#v", fakeFinalizer, updatedNamespace.Name, updatedNamespace.Spec.Finalizers)
}
framework.Logf("Namespace %q has %#v", updatedNamespace.Name, updatedNamespace.Spec.Finalizers)
ginkgo.By(fmt.Sprintf("Removing e2e finalizer from namespace %q", ns))
err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
updatedNamespace, err = nsClient.Get(ctx, ns, metav1.GetOptions{})
framework.ExpectNoError(err, "Unable to get namespace %q", ns)
var finalizerList []v1.FinalizerName
for _, item := range updatedNamespace.Spec.Finalizers {
if item != fakeFinalizer {
finalizerList = append(finalizerList, item)
}
}
updatedNamespace.Spec.Finalizers = finalizerList
updatedNamespace, err = nsClient.Finalize(ctx, updatedNamespace, metav1.UpdateOptions{})
return err
})
framework.ExpectNoError(err, "failed to remove finalizer from namespace: %q", ns)
foundFinalizer = false
for _, item := range updatedNamespace.Spec.Finalizers {
if item == fakeFinalizer {
foundFinalizer = true
break
}
}
if foundFinalizer {
framework.Failf("Finalizer %q was found. Namespace %q has %#v", fakeFinalizer, updatedNamespace.Name, updatedNamespace.Spec.Finalizers)
}
framework.Logf("Namespace %q has %#v", updatedNamespace.Name, updatedNamespace.Spec.Finalizers)
})
})
func unstructuredToNamespace(obj *unstructured.Unstructured) (*v1.Namespace, error) {
json, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
if err != nil {
return nil, err
}
ns := &v1.Namespace{}
err = runtime.DecodeInto(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), json, ns)
return ns, err
}