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