sigs.k8s.io/gateway-api@v1.0.0/apis/v1beta1/validation/gateway_test.go (about) 1 /* 2 Copyright 2021 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 validation 18 19 import ( 20 "fmt" 21 "testing" 22 23 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 "k8s.io/apimachinery/pkg/util/validation/field" 25 26 gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" 27 gatewayv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" 28 ) 29 30 func TestValidateGateway(t *testing.T) { 31 listeners := []gatewayv1b1.Listener{ 32 { 33 Hostname: nil, 34 }, 35 } 36 addresses := []gatewayv1b1.GatewayAddress{ 37 { 38 Type: nil, 39 }, 40 } 41 baseGateway := gatewayv1b1.Gateway{ 42 ObjectMeta: metav1.ObjectMeta{ 43 Name: "foo", 44 Namespace: metav1.NamespaceDefault, 45 }, 46 Spec: gatewayv1b1.GatewaySpec{ 47 GatewayClassName: "foo", 48 Listeners: listeners, 49 Addresses: addresses, 50 }, 51 } 52 tlsConfig := gatewayv1b1.GatewayTLSConfig{} 53 54 testCases := map[string]struct { 55 mutate func(gw *gatewayv1b1.Gateway) 56 expectErrs []field.Error 57 }{ 58 "tls config present with http protocol": { 59 mutate: func(gw *gatewayv1b1.Gateway) { 60 gw.Spec.Listeners[0].Protocol = gatewayv1.HTTPProtocolType 61 gw.Spec.Listeners[0].TLS = &tlsConfig 62 }, 63 expectErrs: []field.Error{ 64 { 65 Type: field.ErrorTypeForbidden, 66 Field: "spec.listeners[0].tls", 67 Detail: "should be empty for protocol HTTP", 68 BadValue: "", 69 }, 70 }, 71 }, 72 "tls config present with tcp protocol": { 73 mutate: func(gw *gatewayv1b1.Gateway) { 74 gw.Spec.Listeners[0].Protocol = gatewayv1.TCPProtocolType 75 gw.Spec.Listeners[0].TLS = &tlsConfig 76 }, 77 expectErrs: []field.Error{ 78 { 79 Type: field.ErrorTypeForbidden, 80 Field: "spec.listeners[0].tls", 81 Detail: "should be empty for protocol TCP", 82 BadValue: "", 83 }, 84 }, 85 }, 86 "tls config not set with https protocol": { 87 mutate: func(gw *gatewayv1b1.Gateway) { 88 gw.Spec.Listeners[0].Protocol = gatewayv1.HTTPSProtocolType 89 }, 90 expectErrs: []field.Error{ 91 { 92 Type: field.ErrorTypeForbidden, 93 Field: "spec.listeners[0].tls", 94 Detail: "must be set for protocol HTTPS", 95 BadValue: "", 96 }, 97 }, 98 }, 99 "tls config not set with tls protocol": { 100 mutate: func(gw *gatewayv1b1.Gateway) { 101 gw.Spec.Listeners[0].Protocol = gatewayv1.TLSProtocolType 102 }, 103 expectErrs: []field.Error{ 104 { 105 Type: field.ErrorTypeForbidden, 106 Field: "spec.listeners[0].tls", 107 Detail: "must be set for protocol TLS", 108 BadValue: "", 109 }, 110 }, 111 }, 112 "tls config not set with http protocol": { 113 mutate: func(gw *gatewayv1b1.Gateway) { 114 gw.Spec.Listeners[0].Protocol = gatewayv1.HTTPProtocolType 115 }, 116 expectErrs: nil, 117 }, 118 "tls config not set with tcp protocol": { 119 mutate: func(gw *gatewayv1b1.Gateway) { 120 gw.Spec.Listeners[0].Protocol = gatewayv1.TCPProtocolType 121 }, 122 expectErrs: nil, 123 }, 124 "tls config not set with udp protocol": { 125 mutate: func(gw *gatewayv1b1.Gateway) { 126 gw.Spec.Listeners[0].Protocol = gatewayv1.UDPProtocolType 127 }, 128 expectErrs: nil, 129 }, 130 "hostname present with tcp protocol": { 131 mutate: func(gw *gatewayv1b1.Gateway) { 132 hostname := gatewayv1b1.Hostname("foo.bar.com") 133 gw.Spec.Listeners[0].Hostname = &hostname 134 gw.Spec.Listeners[0].Protocol = gatewayv1.TCPProtocolType 135 }, 136 expectErrs: []field.Error{ 137 { 138 Type: field.ErrorTypeForbidden, 139 Field: "spec.listeners[0].hostname", 140 Detail: "should be empty for protocol TCP", 141 BadValue: "", 142 }, 143 }, 144 }, 145 "hostname present with udp protocol": { 146 mutate: func(gw *gatewayv1b1.Gateway) { 147 hostname := gatewayv1b1.Hostname("foo.bar.com") 148 gw.Spec.Listeners[0].Hostname = &hostname 149 gw.Spec.Listeners[0].Protocol = gatewayv1.UDPProtocolType 150 }, 151 expectErrs: []field.Error{ 152 { 153 Type: field.ErrorTypeForbidden, 154 Field: "spec.listeners[0].hostname", 155 Detail: "should be empty for protocol UDP", 156 BadValue: "", 157 }, 158 }, 159 }, 160 "certificatedRefs not set with https protocol and TLS terminate mode": { 161 mutate: func(gw *gatewayv1b1.Gateway) { 162 hostname := gatewayv1b1.Hostname("foo.bar.com") 163 tlsMode := gatewayv1.TLSModeType("Terminate") 164 gw.Spec.Listeners[0].Protocol = gatewayv1.HTTPSProtocolType 165 gw.Spec.Listeners[0].Hostname = &hostname 166 gw.Spec.Listeners[0].TLS = &tlsConfig 167 gw.Spec.Listeners[0].TLS.Mode = &tlsMode 168 }, 169 expectErrs: []field.Error{ 170 { 171 Type: field.ErrorTypeForbidden, 172 Field: "spec.listeners[0].tls.certificateRefs", 173 Detail: "should be set and not empty when TLSModeType is Terminate", 174 BadValue: "", 175 }, 176 }, 177 }, 178 "certificatedRefs not set with tls protocol and TLS terminate mode": { 179 mutate: func(gw *gatewayv1b1.Gateway) { 180 hostname := gatewayv1b1.Hostname("foo.bar.com") 181 tlsMode := gatewayv1.TLSModeType("Terminate") 182 gw.Spec.Listeners[0].Protocol = gatewayv1.TLSProtocolType 183 gw.Spec.Listeners[0].Hostname = &hostname 184 gw.Spec.Listeners[0].TLS = &tlsConfig 185 gw.Spec.Listeners[0].TLS.Mode = &tlsMode 186 }, 187 expectErrs: []field.Error{ 188 { 189 Type: field.ErrorTypeForbidden, 190 Field: "spec.listeners[0].tls.certificateRefs", 191 Detail: "should be set and not empty when TLSModeType is Terminate", 192 BadValue: "", 193 }, 194 }, 195 }, 196 "names are not unique within the Gateway": { 197 mutate: func(gw *gatewayv1b1.Gateway) { 198 hostnameFoo := gatewayv1b1.Hostname("foo.com") 199 hostnameBar := gatewayv1b1.Hostname("bar.com") 200 gw.Spec.Listeners[0].Name = "foo" 201 gw.Spec.Listeners[0].Hostname = &hostnameFoo 202 gw.Spec.Listeners = append(gw.Spec.Listeners, 203 gatewayv1b1.Listener{ 204 Name: "foo", 205 Hostname: &hostnameBar, 206 }, 207 ) 208 }, 209 expectErrs: []field.Error{ 210 { 211 Type: field.ErrorTypeDuplicate, 212 Field: "spec.listeners[1].name", 213 BadValue: "must be unique within the Gateway", 214 }, 215 }, 216 }, 217 "combination of port, protocol, and hostname are not unique for each listener": { 218 mutate: func(gw *gatewayv1b1.Gateway) { 219 hostnameFoo := gatewayv1b1.Hostname("foo.com") 220 gw.Spec.Listeners[0].Name = "foo" 221 gw.Spec.Listeners[0].Hostname = &hostnameFoo 222 gw.Spec.Listeners[0].Protocol = gatewayv1.HTTPProtocolType 223 gw.Spec.Listeners[0].Port = 80 224 gw.Spec.Listeners = append(gw.Spec.Listeners, 225 gatewayv1b1.Listener{ 226 Name: "bar", 227 Hostname: &hostnameFoo, 228 Protocol: gatewayv1.HTTPProtocolType, 229 Port: 80, 230 }, 231 ) 232 }, 233 expectErrs: []field.Error{ 234 { 235 Type: field.ErrorTypeDuplicate, 236 Field: "spec.listeners[1]", 237 BadValue: "combination of port, protocol, and hostname must be unique for each listener", 238 }, 239 }, 240 }, 241 "combination of port and protocol are not unique for each listener when hostnames not set": { 242 mutate: func(gw *gatewayv1b1.Gateway) { 243 gw.Spec.Listeners[0].Name = "foo" 244 gw.Spec.Listeners[0].Protocol = gatewayv1.HTTPProtocolType 245 gw.Spec.Listeners[0].Port = 80 246 gw.Spec.Listeners = append(gw.Spec.Listeners, 247 gatewayv1b1.Listener{ 248 Name: "bar", 249 Protocol: gatewayv1.HTTPProtocolType, 250 Port: 80, 251 }, 252 ) 253 }, 254 expectErrs: []field.Error{ 255 { 256 Type: field.ErrorTypeDuplicate, 257 Field: "spec.listeners[1]", 258 BadValue: "combination of port, protocol, and hostname must be unique for each listener", 259 }, 260 }, 261 }, 262 "port is unique when protocol and hostname are the same": { 263 mutate: func(gw *gatewayv1b1.Gateway) { 264 hostnameFoo := gatewayv1b1.Hostname("foo.com") 265 gw.Spec.Listeners[0].Name = "foo" 266 gw.Spec.Listeners[0].Hostname = &hostnameFoo 267 gw.Spec.Listeners[0].Protocol = gatewayv1.HTTPProtocolType 268 gw.Spec.Listeners[0].Port = 80 269 gw.Spec.Listeners = append(gw.Spec.Listeners, 270 gatewayv1b1.Listener{ 271 Name: "bar", 272 Hostname: &hostnameFoo, 273 Protocol: gatewayv1.HTTPProtocolType, 274 Port: 8080, 275 }, 276 ) 277 }, 278 expectErrs: nil, 279 }, 280 "hostname is unique when protocol and port are the same": { 281 mutate: func(gw *gatewayv1b1.Gateway) { 282 hostnameFoo := gatewayv1b1.Hostname("foo.com") 283 hostnameBar := gatewayv1b1.Hostname("bar.com") 284 gw.Spec.Listeners[0].Name = "foo" 285 gw.Spec.Listeners[0].Hostname = &hostnameFoo 286 gw.Spec.Listeners[0].Protocol = gatewayv1.HTTPProtocolType 287 gw.Spec.Listeners[0].Port = 80 288 gw.Spec.Listeners = append(gw.Spec.Listeners, 289 gatewayv1b1.Listener{ 290 Name: "bar", 291 Hostname: &hostnameBar, 292 Protocol: gatewayv1.HTTPProtocolType, 293 Port: 80, 294 }, 295 ) 296 }, 297 expectErrs: nil, 298 }, 299 "protocol is unique when port and hostname are the same": { 300 mutate: func(gw *gatewayv1b1.Gateway) { 301 hostnameFoo := gatewayv1b1.Hostname("foo.com") 302 tlsConfigFoo := tlsConfig 303 tlsModeFoo := gatewayv1.TLSModeType("Terminate") 304 tlsConfigFoo.Mode = &tlsModeFoo 305 tlsConfigFoo.CertificateRefs = []gatewayv1b1.SecretObjectReference{ 306 { 307 Name: "FooCertificateRefs", 308 }, 309 } 310 gw.Spec.Listeners[0].Name = "foo" 311 gw.Spec.Listeners[0].Hostname = &hostnameFoo 312 gw.Spec.Listeners[0].Protocol = gatewayv1.HTTPSProtocolType 313 gw.Spec.Listeners[0].Port = 8000 314 gw.Spec.Listeners[0].TLS = &tlsConfigFoo 315 gw.Spec.Listeners = append(gw.Spec.Listeners, 316 gatewayv1b1.Listener{ 317 Name: "bar", 318 Hostname: &hostnameFoo, 319 Protocol: gatewayv1.TLSProtocolType, 320 Port: 8000, 321 TLS: &tlsConfigFoo, 322 }, 323 ) 324 }, 325 expectErrs: nil, 326 }, 327 "ip address and hostname in addresses are valid": { 328 mutate: func(gw *gatewayv1b1.Gateway) { 329 gw.Spec.Addresses = []gatewayv1b1.GatewayAddress{ 330 { 331 Type: ptrTo(gatewayv1b1.IPAddressType), 332 Value: "1.2.3.4", 333 }, 334 { 335 Type: ptrTo(gatewayv1b1.IPAddressType), 336 Value: "1111:2222:3333:4444::", 337 }, 338 { 339 Type: ptrTo(gatewayv1b1.HostnameAddressType), 340 Value: "foo.bar", 341 }, 342 } 343 }, 344 expectErrs: nil, 345 }, 346 "ip address and hostname in addresses are invalid": { 347 mutate: func(gw *gatewayv1b1.Gateway) { 348 gw.Spec.Addresses = []gatewayv1b1.GatewayAddress{ 349 { 350 Type: ptrTo(gatewayv1b1.IPAddressType), 351 Value: "1.2.3.4:8080", 352 }, 353 { 354 Type: ptrTo(gatewayv1b1.HostnameAddressType), 355 Value: "*foo/bar", 356 }, 357 { 358 Type: ptrTo(gatewayv1b1.HostnameAddressType), 359 Value: "12:34:56::", 360 }, 361 } 362 }, 363 expectErrs: []field.Error{ 364 { 365 Type: field.ErrorTypeInvalid, 366 Field: "spec.addresses[0]", 367 Detail: "invalid ip address", 368 BadValue: "1.2.3.4:8080", 369 }, 370 { 371 Type: field.ErrorTypeInvalid, 372 Field: "spec.addresses[1]", 373 Detail: fmt.Sprintf("must only contain valid characters (matching %s)", validHostnameAddress), 374 BadValue: "*foo/bar", 375 }, 376 { 377 Type: field.ErrorTypeInvalid, 378 Field: "spec.addresses[2]", 379 Detail: fmt.Sprintf("must only contain valid characters (matching %s)", validHostnameAddress), 380 BadValue: "12:34:56::", 381 }, 382 }, 383 }, 384 "duplicate ip address or hostname": { 385 mutate: func(gw *gatewayv1b1.Gateway) { 386 gw.Spec.Addresses = []gatewayv1b1.GatewayAddress{ 387 { 388 Type: ptrTo(gatewayv1b1.IPAddressType), 389 Value: "1.2.3.4", 390 }, 391 { 392 Type: ptrTo(gatewayv1b1.IPAddressType), 393 Value: "1.2.3.4", 394 }, 395 { 396 Type: ptrTo(gatewayv1b1.HostnameAddressType), 397 Value: "foo.bar", 398 }, 399 { 400 Type: ptrTo(gatewayv1b1.HostnameAddressType), 401 Value: "foo.bar", 402 }, 403 } 404 }, 405 expectErrs: []field.Error{ 406 { 407 Type: field.ErrorTypeDuplicate, 408 Field: "spec.addresses[1]", 409 BadValue: "1.2.3.4", 410 }, 411 { 412 Type: field.ErrorTypeDuplicate, 413 Field: "spec.addresses[3]", 414 BadValue: "foo.bar", 415 }, 416 }, 417 }, 418 } 419 420 for name, tc := range testCases { 421 tc := tc 422 t.Run(name, func(t *testing.T) { 423 gw := baseGateway.DeepCopy() 424 tc.mutate(gw) 425 errs := ValidateGateway(gw) 426 if len(tc.expectErrs) != len(errs) { 427 t.Fatalf("Expected %d errors, got %d errors: %v", len(tc.expectErrs), len(errs), errs) 428 } 429 for i, err := range errs { 430 if err.Type != tc.expectErrs[i].Type { 431 t.Errorf("Expected error on type: %s, got: %s", tc.expectErrs[i].Type, err.Type) 432 } 433 if err.Field != tc.expectErrs[i].Field { 434 t.Errorf("Expected error on field: %s, got: %s", tc.expectErrs[i].Field, err.Field) 435 } 436 if err.Detail != tc.expectErrs[i].Detail { 437 t.Errorf("Expected error on detail: %s, got: %s", tc.expectErrs[i].Detail, err.Detail) 438 } 439 if err.BadValue != tc.expectErrs[i].BadValue { 440 t.Errorf("Expected error on bad value: %s, got: %s", tc.expectErrs[i].BadValue, err.BadValue) 441 } 442 } 443 }) 444 } 445 }