1
16
17 package network
18
19 import (
20 "context"
21 "fmt"
22 "time"
23
24 networkingv1 "k8s.io/api/networking/v1"
25 apierrors "k8s.io/apimachinery/pkg/api/errors"
26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27 types "k8s.io/apimachinery/pkg/types"
28
29 "k8s.io/apimachinery/pkg/util/wait"
30 "k8s.io/apimachinery/pkg/watch"
31 clientset "k8s.io/client-go/kubernetes"
32 "k8s.io/kubernetes/test/e2e/feature"
33 "k8s.io/kubernetes/test/e2e/framework"
34 "k8s.io/kubernetes/test/e2e/network/common"
35 admissionapi "k8s.io/pod-security-admission/api"
36 utilpointer "k8s.io/utils/pointer"
37
38 "github.com/onsi/ginkgo/v2"
39 "github.com/onsi/gomega"
40 )
41
42 var _ = common.SIGDescribe("IngressClass", feature.Ingress, func() {
43 f := framework.NewDefaultFramework("ingressclass")
44 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
45 var cs clientset.Interface
46 ginkgo.BeforeEach(func() {
47 cs = f.ClientSet
48 })
49
50 f.It("should set default value on new IngressClass", f.WithSerial(), func(ctx context.Context) {
51 ingressClass1, err := createIngressClass(ctx, cs, "ingressclass1", true, f.UniqueName)
52 framework.ExpectNoError(err)
53 ginkgo.DeferCleanup(deleteIngressClass, cs, ingressClass1.Name)
54
55 ctx, cancel := context.WithCancel(ctx)
56 defer cancel()
57 lastFailure := ""
58
59
60 if err := wait.PollWithContext(ctx, time.Second, time.Minute, func(ctx context.Context) (bool, error) {
61 lastFailure = ""
62
63 ingress, err := createBasicIngress(ctx, cs, f.Namespace.Name)
64 if err != nil {
65 lastFailure = err.Error()
66 return false, err
67 }
68 defer func() {
69 err := cs.NetworkingV1().Ingresses(ingress.Namespace).Delete(ctx, ingress.Name, metav1.DeleteOptions{})
70 framework.Logf("%v", err)
71 }()
72
73 if ingress.Spec.IngressClassName == nil {
74 lastFailure = "Expected IngressClassName to be set by Admission Controller"
75 return false, nil
76 } else if *ingress.Spec.IngressClassName != ingressClass1.Name {
77 lastFailure = fmt.Sprintf("Expected IngressClassName to be %s, got %s", ingressClass1.Name, *ingress.Spec.IngressClassName)
78 return false, nil
79 }
80 return true, nil
81
82 }); err != nil {
83 framework.Failf("%v, final err= %v", lastFailure, err)
84 }
85 })
86
87 f.It("should not set default value if no default IngressClass", f.WithSerial(), func(ctx context.Context) {
88 ingressClass1, err := createIngressClass(ctx, cs, "ingressclass1", false, f.UniqueName)
89 framework.ExpectNoError(err)
90 ginkgo.DeferCleanup(deleteIngressClass, cs, ingressClass1.Name)
91
92 ctx, cancel := context.WithCancel(ctx)
93 defer cancel()
94 lastFailure := ""
95
96
97 if err := wait.PollWithContext(ctx, time.Second, time.Minute, func(ctx context.Context) (bool, error) {
98 lastFailure = ""
99
100 ingress, err := createBasicIngress(ctx, cs, f.Namespace.Name)
101 if err != nil {
102 lastFailure = err.Error()
103 return false, err
104 }
105 defer func() {
106 err := cs.NetworkingV1().Ingresses(ingress.Namespace).Delete(ctx, ingress.Name, metav1.DeleteOptions{})
107 framework.Logf("%v", err)
108 }()
109
110 if ingress.Spec.IngressClassName != nil {
111 lastFailure = fmt.Sprintf("Expected IngressClassName to be nil, got %s", *ingress.Spec.IngressClassName)
112 return false, nil
113 }
114 return true, nil
115
116 }); err != nil {
117 framework.Failf("%v, final err= %v", lastFailure, err)
118 }
119 })
120
121 f.It("should choose the one with the later CreationTimestamp, if equal the one with the lower name when two ingressClasses are marked as default", f.WithSerial(), func(ctx context.Context) {
122 ingressClass1, err := createIngressClass(ctx, cs, "ingressclass1", true, f.UniqueName)
123 framework.ExpectNoError(err)
124 ginkgo.DeferCleanup(deleteIngressClass, cs, ingressClass1.Name)
125
126 ingressClass2, err := createIngressClass(ctx, cs, "ingressclass2", true, f.UniqueName)
127 framework.ExpectNoError(err)
128 ginkgo.DeferCleanup(deleteIngressClass, cs, ingressClass2.Name)
129
130 expectedName := ingressClass1.Name
131 if ingressClass2.CreationTimestamp.UnixNano() > ingressClass1.CreationTimestamp.UnixNano() {
132 expectedName = ingressClass2.Name
133 }
134
135 ctx, cancel := context.WithCancel(ctx)
136 defer cancel()
137
138
139 if err := wait.Poll(time.Second, time.Minute, func() (bool, error) {
140 classes, err := cs.NetworkingV1().IngressClasses().List(ctx, metav1.ListOptions{})
141 if err != nil {
142 return false, nil
143 }
144 cntDefault := 0
145 for _, class := range classes.Items {
146 if class.Annotations[networkingv1.AnnotationIsDefaultIngressClass] == "true" {
147 cntDefault++
148 }
149 }
150 if cntDefault < 2 {
151 return false, nil
152 }
153 ingress, err := createBasicIngress(ctx, cs, f.Namespace.Name)
154 if err != nil {
155 return false, nil
156 }
157 if ingress.Spec.IngressClassName == nil {
158 return false, fmt.Errorf("expected IngressClassName to be set by Admission Controller")
159 }
160 if *ingress.Spec.IngressClassName != expectedName {
161 return false, fmt.Errorf("expected ingress class %s but created with %s", expectedName, *ingress.Spec.IngressClassName)
162 }
163 return true, nil
164 }); err != nil {
165 framework.Failf("Failed to create ingress when two ingressClasses are marked as default ,got error %v", err)
166 }
167 })
168
169 f.It("should allow IngressClass to have Namespace-scoped parameters", f.WithSerial(), func(ctx context.Context) {
170 ingressClass := &networkingv1.IngressClass{
171 ObjectMeta: metav1.ObjectMeta{
172 Name: "ingressclass1",
173 Labels: map[string]string{
174 "ingressclass": f.UniqueName,
175 "special-label": "generic",
176 },
177 },
178 Spec: networkingv1.IngressClassSpec{
179 Controller: "example.com/controller",
180 Parameters: &networkingv1.IngressClassParametersReference{
181 Scope: utilpointer.String("Namespace"),
182 Namespace: utilpointer.String("foo-ns"),
183 Kind: "fookind",
184 Name: "fooname",
185 APIGroup: utilpointer.String("example.com"),
186 },
187 },
188 }
189 createdIngressClass, err := cs.NetworkingV1().IngressClasses().Create(ctx, ingressClass, metav1.CreateOptions{})
190 framework.ExpectNoError(err)
191 ginkgo.DeferCleanup(deleteIngressClass, cs, createdIngressClass.Name)
192
193 if createdIngressClass.Spec.Parameters == nil {
194 framework.Failf("Expected IngressClass.spec.parameters to be set")
195 }
196 scope := ""
197 if createdIngressClass.Spec.Parameters.Scope != nil {
198 scope = *createdIngressClass.Spec.Parameters.Scope
199 }
200
201 if scope != "Namespace" {
202 framework.Failf("Expected IngressClass.spec.parameters.scope to be set to 'Namespace', got %v", scope)
203 }
204 })
205
206 })
207
208 func createIngressClass(ctx context.Context, cs clientset.Interface, name string, isDefault bool, uniqueName string) (*networkingv1.IngressClass, error) {
209 ingressClass := &networkingv1.IngressClass{
210 ObjectMeta: metav1.ObjectMeta{
211 Name: name,
212 Labels: map[string]string{
213 "ingressclass": uniqueName,
214 "special-label": "generic",
215 },
216 },
217 Spec: networkingv1.IngressClassSpec{
218 Controller: "example.com/controller",
219 },
220 }
221
222 if isDefault {
223 ingressClass.Annotations = map[string]string{networkingv1.AnnotationIsDefaultIngressClass: "true"}
224 }
225
226 return cs.NetworkingV1().IngressClasses().Create(ctx, ingressClass, metav1.CreateOptions{})
227 }
228
229 func createBasicIngress(ctx context.Context, cs clientset.Interface, namespace string) (*networkingv1.Ingress, error) {
230 return cs.NetworkingV1().Ingresses(namespace).Create(ctx, &networkingv1.Ingress{
231 ObjectMeta: metav1.ObjectMeta{
232 Name: "ingress1",
233 },
234 Spec: networkingv1.IngressSpec{
235 DefaultBackend: &networkingv1.IngressBackend{
236 Service: &networkingv1.IngressServiceBackend{
237 Name: "defaultbackend",
238 Port: networkingv1.ServiceBackendPort{
239 Number: 80,
240 },
241 },
242 },
243 },
244 }, metav1.CreateOptions{})
245 }
246
247 func deleteIngressClass(ctx context.Context, cs clientset.Interface, name string) {
248 err := cs.NetworkingV1().IngressClasses().Delete(ctx, name, metav1.DeleteOptions{})
249 framework.ExpectNoError(err)
250 }
251
252 var _ = common.SIGDescribe("IngressClass API", func() {
253 f := framework.NewDefaultFramework("ingressclass")
254 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
255 var cs clientset.Interface
256 ginkgo.BeforeEach(func() {
257 cs = f.ClientSet
258 })
259
268 framework.ConformanceIt("should support creating IngressClass API operations", func(ctx context.Context) {
269
270
271 icClient := f.ClientSet.NetworkingV1().IngressClasses()
272 icVersion := "v1"
273
274
275 ginkgo.By("getting /apis")
276 {
277 discoveryGroups, err := f.ClientSet.Discovery().ServerGroups()
278 framework.ExpectNoError(err)
279 found := false
280 for _, group := range discoveryGroups.Groups {
281 if group.Name == networkingv1.GroupName {
282 for _, version := range group.Versions {
283 if version.Version == icVersion {
284 found = true
285 break
286 }
287 }
288 }
289 }
290 if !found {
291 framework.Failf("expected networking API group/version, got %#v", discoveryGroups.Groups)
292 }
293 }
294 ginkgo.By("getting /apis/networking.k8s.io")
295 {
296 group := &metav1.APIGroup{}
297 err := f.ClientSet.Discovery().RESTClient().Get().AbsPath("/apis/networking.k8s.io").Do(ctx).Into(group)
298 framework.ExpectNoError(err)
299 found := false
300 for _, version := range group.Versions {
301 if version.Version == icVersion {
302 found = true
303 break
304 }
305 }
306 if !found {
307 framework.Failf("expected networking API version, got %#v", group.Versions)
308 }
309 }
310
311 ginkgo.By("getting /apis/networking.k8s.io" + icVersion)
312 {
313 resources, err := f.ClientSet.Discovery().ServerResourcesForGroupVersion(networkingv1.SchemeGroupVersion.String())
314 framework.ExpectNoError(err)
315 foundIC := false
316 for _, resource := range resources.APIResources {
317 switch resource.Name {
318 case "ingressclasses":
319 foundIC = true
320 }
321 }
322 if !foundIC {
323 framework.Failf("expected ingressclasses, got %#v", resources.APIResources)
324 }
325 }
326
327
328 ginkgo.By("creating")
329 ingressClass1, err := createIngressClass(ctx, cs, "ingressclass1", false, f.UniqueName)
330 framework.ExpectNoError(err)
331 _, err = createIngressClass(ctx, cs, "ingressclass2", false, f.UniqueName)
332 framework.ExpectNoError(err)
333 _, err = createIngressClass(ctx, cs, "ingressclass3", false, f.UniqueName)
334 framework.ExpectNoError(err)
335
336 ginkgo.By("getting")
337 gottenIC, err := icClient.Get(ctx, ingressClass1.Name, metav1.GetOptions{})
338 framework.ExpectNoError(err)
339 gomega.Expect(gottenIC.UID).To(gomega.Equal(ingressClass1.UID))
340 gomega.Expect(gottenIC.UID).To(gomega.Equal(ingressClass1.UID))
341
342 ginkgo.By("listing")
343 ics, err := icClient.List(ctx, metav1.ListOptions{LabelSelector: "special-label=generic"})
344 framework.ExpectNoError(err)
345 gomega.Expect(ics.Items).To(gomega.HaveLen(3), "filtered list should have 3 items")
346
347 ginkgo.By("watching")
348 framework.Logf("starting watch")
349 icWatch, err := icClient.Watch(ctx, metav1.ListOptions{ResourceVersion: ics.ResourceVersion, LabelSelector: "ingressclass=" + f.UniqueName})
350 framework.ExpectNoError(err)
351
352 ginkgo.By("patching")
353 patchedIC, err := icClient.Patch(ctx, ingressClass1.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"patched":"true"}}}`), metav1.PatchOptions{})
354 framework.ExpectNoError(err)
355 gomega.Expect(patchedIC.Annotations).To(gomega.HaveKeyWithValue("patched", "true"), "patched object should have the applied annotation")
356
357 ginkgo.By("updating")
358 icToUpdate := patchedIC.DeepCopy()
359 icToUpdate.Annotations["updated"] = "true"
360 updatedIC, err := icClient.Update(ctx, icToUpdate, metav1.UpdateOptions{})
361 framework.ExpectNoError(err)
362 gomega.Expect(updatedIC.Annotations).To(gomega.HaveKeyWithValue("updated", "true"), "updated object should have the applied annotation")
363
364 framework.Logf("waiting for watch events with expected annotations")
365 for sawAnnotations := false; !sawAnnotations; {
366 select {
367 case evt, ok := <-icWatch.ResultChan():
368 if !ok {
369 framework.Fail("watch channel should not close")
370 }
371 gomega.Expect(evt.Type).To(gomega.Equal(watch.Modified))
372 watchedIngress, isIngress := evt.Object.(*networkingv1.IngressClass)
373 if !isIngress {
374 framework.Failf("expected Ingress, got %T", evt.Object)
375 }
376 if watchedIngress.Annotations["patched"] == "true" {
377 framework.Logf("saw patched and updated annotations")
378 sawAnnotations = true
379 icWatch.Stop()
380 } else {
381 framework.Logf("missing expected annotations, waiting: %#v", watchedIngress.Annotations)
382 }
383 case <-time.After(wait.ForeverTestTimeout):
384 framework.Fail("timed out waiting for watch event")
385 }
386 }
387
388
389 ginkgo.By("deleting")
390 err = icClient.Delete(ctx, ingressClass1.Name, metav1.DeleteOptions{})
391 framework.ExpectNoError(err)
392 _, err = icClient.Get(ctx, ingressClass1.Name, metav1.GetOptions{})
393 if !apierrors.IsNotFound(err) {
394 framework.Failf("expected 404, got %#v", err)
395 }
396 ics, err = icClient.List(ctx, metav1.ListOptions{LabelSelector: "ingressclass=" + f.UniqueName})
397 framework.ExpectNoError(err)
398 gomega.Expect(ics.Items).To(gomega.HaveLen(2), "filtered list should have 2 items")
399
400 ginkgo.By("deleting a collection")
401 err = icClient.DeleteCollection(ctx, metav1.DeleteOptions{}, metav1.ListOptions{LabelSelector: "ingressclass=" + f.UniqueName})
402 framework.ExpectNoError(err)
403 ics, err = icClient.List(ctx, metav1.ListOptions{LabelSelector: "ingressclass=" + f.UniqueName})
404 framework.ExpectNoError(err)
405 gomega.Expect(ics.Items).To(gomega.BeEmpty(), "filtered list should have 0 items")
406 })
407
408 })
409
View as plain text