...

Source file src/sigs.k8s.io/gateway-api/apis/v1beta1/validation/gateway_test.go

Documentation: sigs.k8s.io/gateway-api/apis/v1beta1/validation

     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  }
   446  

View as plain text