k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/plugin/pkg/admission/network/defaultingressclass/admission_test.go (about) 1 /* 2 Copyright 2020 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package defaultingressclass 18 19 import ( 20 "context" 21 "reflect" 22 "testing" 23 "time" 24 25 networkingv1 "k8s.io/api/networking/v1" 26 networkingv1beta1 "k8s.io/api/networking/v1beta1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apiserver/pkg/admission" 29 admissiontesting "k8s.io/apiserver/pkg/admission/testing" 30 "k8s.io/client-go/informers" 31 api "k8s.io/kubernetes/pkg/apis/core" 32 "k8s.io/kubernetes/pkg/apis/networking" 33 "k8s.io/kubernetes/pkg/controller" 34 utilpointer "k8s.io/utils/pointer" 35 ) 36 37 func TestAdmission(t *testing.T) { 38 defaultClass1 := &networkingv1.IngressClass{ 39 TypeMeta: metav1.TypeMeta{ 40 Kind: "IngressClass", 41 }, 42 ObjectMeta: metav1.ObjectMeta{ 43 Name: "default1", 44 Annotations: map[string]string{ 45 networkingv1.AnnotationIsDefaultIngressClass: "true", 46 }, 47 }, 48 } 49 defaultClass2 := &networkingv1.IngressClass{ 50 ObjectMeta: metav1.ObjectMeta{ 51 Name: "default2", 52 Annotations: map[string]string{ 53 networkingv1.AnnotationIsDefaultIngressClass: "true", 54 }, 55 }, 56 } 57 // Class that has explicit default = false 58 classWithFalseDefault := &networkingv1.IngressClass{ 59 TypeMeta: metav1.TypeMeta{ 60 Kind: "IngressClass", 61 }, 62 ObjectMeta: metav1.ObjectMeta{ 63 Name: "nondefault1", 64 Annotations: map[string]string{ 65 networkingv1.AnnotationIsDefaultIngressClass: "false", 66 }, 67 }, 68 } 69 // Class with missing default annotation (=non-default) 70 classWithNoDefault := &networkingv1.IngressClass{ 71 TypeMeta: metav1.TypeMeta{ 72 Kind: "IngressClass", 73 }, 74 ObjectMeta: metav1.ObjectMeta{ 75 Name: "nondefault2", 76 }, 77 } 78 // Class with empty default annotation (=non-default) 79 classWithEmptyDefault := &networkingv1.IngressClass{ 80 TypeMeta: metav1.TypeMeta{ 81 Kind: "IngressClass", 82 }, 83 ObjectMeta: metav1.ObjectMeta{ 84 Name: "nondefault2", 85 Annotations: map[string]string{ 86 networkingv1.AnnotationIsDefaultIngressClass: "", 87 }, 88 }, 89 } 90 91 defaultClassWithCreateTime1 := &networkingv1.IngressClass{ 92 TypeMeta: metav1.TypeMeta{ 93 Kind: "IngressClass", 94 }, 95 ObjectMeta: metav1.ObjectMeta{ 96 Name: "default1", 97 CreationTimestamp: metav1.NewTime(time.Date(2022, time.Month(1), 1, 0, 0, 0, 1, time.UTC)), 98 Annotations: map[string]string{ 99 networkingv1.AnnotationIsDefaultIngressClass: "true", 100 }, 101 }, 102 } 103 defaultClassWithCreateTime2 := &networkingv1.IngressClass{ 104 TypeMeta: metav1.TypeMeta{ 105 Kind: "IngressClass", 106 }, 107 ObjectMeta: metav1.ObjectMeta{ 108 Name: "default2", 109 CreationTimestamp: metav1.NewTime(time.Date(2022, time.Month(1), 1, 0, 0, 0, 0, time.UTC)), 110 Annotations: map[string]string{ 111 networkingv1.AnnotationIsDefaultIngressClass: "true", 112 }, 113 }, 114 } 115 116 testCases := []struct { 117 name string 118 classes []*networkingv1.IngressClass 119 classField *string 120 classAnnotation *string 121 expectedClass *string 122 expectedError error 123 }{ 124 { 125 name: "no default, no modification of Ingress", 126 classes: []*networkingv1.IngressClass{classWithFalseDefault, classWithNoDefault, classWithEmptyDefault}, 127 classField: nil, 128 classAnnotation: nil, 129 expectedClass: nil, 130 expectedError: nil, 131 }, 132 { 133 name: "one default, modify Ingress with class=nil", 134 classes: []*networkingv1.IngressClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault}, 135 classField: nil, 136 classAnnotation: nil, 137 expectedClass: utilpointer.String(defaultClass1.Name), 138 expectedError: nil, 139 }, 140 { 141 name: "one default, no modification of Ingress with class field=''", 142 classes: []*networkingv1.IngressClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault}, 143 classField: utilpointer.String(""), 144 classAnnotation: nil, 145 expectedClass: utilpointer.String(""), 146 expectedError: nil, 147 }, 148 { 149 name: "one default, no modification of Ingress with class field='foo'", 150 classes: []*networkingv1.IngressClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault}, 151 classField: utilpointer.String("foo"), 152 classAnnotation: nil, 153 expectedClass: utilpointer.String("foo"), 154 expectedError: nil, 155 }, 156 { 157 name: "one default, no modification of Ingress with class annotation='foo'", 158 classes: []*networkingv1.IngressClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault}, 159 classField: nil, 160 classAnnotation: utilpointer.String("foo"), 161 expectedClass: nil, 162 expectedError: nil, 163 }, 164 { 165 name: "two defaults with the same creation time, choose the one with the lower name", 166 classes: []*networkingv1.IngressClass{defaultClass1, defaultClass2, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault}, 167 classField: nil, 168 classAnnotation: nil, 169 expectedClass: utilpointer.String(defaultClass1.Name), 170 expectedError: nil, 171 }, 172 { 173 name: "two defaults, no modification with Ingress with class field=''", 174 classes: []*networkingv1.IngressClass{defaultClass1, defaultClass2, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault}, 175 classField: utilpointer.String(""), 176 classAnnotation: nil, 177 expectedClass: utilpointer.String(""), 178 expectedError: nil, 179 }, 180 { 181 name: "two defaults, choose the one with the newer creation time", 182 classes: []*networkingv1.IngressClass{defaultClassWithCreateTime1, defaultClassWithCreateTime2, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault}, 183 classField: nil, 184 classAnnotation: nil, 185 expectedClass: utilpointer.String(defaultClassWithCreateTime1.Name), 186 expectedError: nil, 187 }, 188 } 189 190 for _, testCase := range testCases { 191 t.Run(testCase.name, func(t *testing.T) { 192 ctrl := newPlugin() 193 informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) 194 ctrl.SetExternalKubeInformerFactory(informerFactory) 195 for _, c := range testCase.classes { 196 informerFactory.Networking().V1().IngressClasses().Informer().GetStore().Add(c) 197 } 198 199 ingress := &networking.Ingress{ObjectMeta: metav1.ObjectMeta{Name: "testing", Namespace: "testing"}} 200 if testCase.classField != nil { 201 ingress.Spec.IngressClassName = testCase.classField 202 } 203 if testCase.classAnnotation != nil { 204 ingress.Annotations = map[string]string{networkingv1beta1.AnnotationIngressClass: *testCase.classAnnotation} 205 } 206 207 attrs := admission.NewAttributesRecord( 208 ingress, // new object 209 nil, // old object 210 api.Kind("Ingress").WithVersion("version"), 211 ingress.Namespace, 212 ingress.Name, 213 networkingv1.Resource("ingresses").WithVersion("version"), 214 "", // subresource 215 admission.Create, 216 &metav1.CreateOptions{}, 217 false, // dryRun 218 nil, // userInfo 219 ) 220 221 err := admissiontesting.WithReinvocationTesting(t, ctrl).Admit(context.TODO(), attrs, nil) 222 if !reflect.DeepEqual(err, testCase.expectedError) { 223 t.Errorf("Expected error: %v, got %v", testCase.expectedError, err) 224 } 225 if !reflect.DeepEqual(testCase.expectedClass, ingress.Spec.IngressClassName) { 226 t.Errorf("Expected class name %+v, got %+v", *testCase.expectedClass, ingress.Spec.IngressClassName) 227 } 228 }) 229 } 230 }