...

Source file src/k8s.io/kubernetes/pkg/apis/networking/validation/validation_test.go

Documentation: k8s.io/kubernetes/pkg/apis/networking/validation

     1  /*
     2  Copyright 2014 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  	"strings"
    22  	"testing"
    23  
    24  	apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/util/intstr"
    27  	"k8s.io/apimachinery/pkg/util/validation/field"
    28  	api "k8s.io/kubernetes/pkg/apis/core"
    29  	"k8s.io/kubernetes/pkg/apis/networking"
    30  	utilpointer "k8s.io/utils/pointer"
    31  )
    32  
    33  func makeValidNetworkPolicy() *networking.NetworkPolicy {
    34  	return &networking.NetworkPolicy{
    35  		ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
    36  		Spec: networking.NetworkPolicySpec{
    37  			PodSelector: metav1.LabelSelector{
    38  				MatchLabels: map[string]string{"a": "b"},
    39  			},
    40  		},
    41  	}
    42  }
    43  
    44  type netpolTweak func(networkPolicy *networking.NetworkPolicy)
    45  
    46  func makeNetworkPolicyCustom(tweaks ...netpolTweak) *networking.NetworkPolicy {
    47  	networkPolicy := makeValidNetworkPolicy()
    48  	for _, fn := range tweaks {
    49  		fn(networkPolicy)
    50  	}
    51  	return networkPolicy
    52  }
    53  
    54  func makePort(proto *api.Protocol, port intstr.IntOrString, endPort int32) networking.NetworkPolicyPort {
    55  	r := networking.NetworkPolicyPort{
    56  		Protocol: proto,
    57  		Port:     nil,
    58  	}
    59  	if port != intstr.FromInt32(0) && port != intstr.FromString("") && port != intstr.FromString("0") {
    60  		r.Port = &port
    61  	}
    62  	if endPort != 0 {
    63  		r.EndPort = utilpointer.Int32(endPort)
    64  	}
    65  	return r
    66  }
    67  
    68  func TestValidateNetworkPolicy(t *testing.T) {
    69  	protocolTCP := api.ProtocolTCP
    70  	protocolUDP := api.ProtocolUDP
    71  	protocolICMP := api.Protocol("ICMP")
    72  	protocolSCTP := api.ProtocolSCTP
    73  
    74  	// Tweaks used below.
    75  	setIngressEmptyFirstElement := func(networkPolicy *networking.NetworkPolicy) {
    76  		networkPolicy.Spec.Ingress = []networking.NetworkPolicyIngressRule{{}}
    77  	}
    78  
    79  	setIngressFromEmptyFirstElement := func(networkPolicy *networking.NetworkPolicy) {
    80  		if networkPolicy.Spec.Ingress == nil {
    81  			setIngressEmptyFirstElement(networkPolicy)
    82  		}
    83  		networkPolicy.Spec.Ingress[0].From = []networking.NetworkPolicyPeer{{}}
    84  	}
    85  
    86  	setIngressFromIfEmpty := func(networkPolicy *networking.NetworkPolicy) {
    87  		if networkPolicy.Spec.Ingress == nil {
    88  			setIngressEmptyFirstElement(networkPolicy)
    89  		}
    90  		if networkPolicy.Spec.Ingress[0].From == nil {
    91  			setIngressFromEmptyFirstElement(networkPolicy)
    92  		}
    93  	}
    94  
    95  	setIngressEmptyPorts := func(networkPolicy *networking.NetworkPolicy) {
    96  		networkPolicy.Spec.Ingress = []networking.NetworkPolicyIngressRule{{
    97  			Ports: []networking.NetworkPolicyPort{{}},
    98  		}}
    99  	}
   100  
   101  	setIngressPorts := func(ports ...networking.NetworkPolicyPort) netpolTweak {
   102  		return func(np *networking.NetworkPolicy) {
   103  			if np.Spec.Ingress == nil {
   104  				setIngressEmptyFirstElement(np)
   105  			}
   106  			np.Spec.Ingress[0].Ports = make([]networking.NetworkPolicyPort, len(ports))
   107  			copy(np.Spec.Ingress[0].Ports, ports)
   108  		}
   109  	}
   110  
   111  	setIngressFromPodSelector := func(k, v string) func(*networking.NetworkPolicy) {
   112  		return func(networkPolicy *networking.NetworkPolicy) {
   113  			setIngressFromIfEmpty(networkPolicy)
   114  			networkPolicy.Spec.Ingress[0].From[0].PodSelector = &metav1.LabelSelector{
   115  				MatchLabels: map[string]string{k: v},
   116  			}
   117  		}
   118  	}
   119  
   120  	setIngressFromNamespaceSelector := func(networkPolicy *networking.NetworkPolicy) {
   121  		setIngressFromIfEmpty(networkPolicy)
   122  		networkPolicy.Spec.Ingress[0].From[0].NamespaceSelector = &metav1.LabelSelector{
   123  			MatchLabels: map[string]string{"c": "d"},
   124  		}
   125  	}
   126  
   127  	setIngressFromIPBlockIPV4 := func(networkPolicy *networking.NetworkPolicy) {
   128  		setIngressFromIfEmpty(networkPolicy)
   129  		networkPolicy.Spec.Ingress[0].From[0].IPBlock = &networking.IPBlock{
   130  			CIDR:   "192.168.0.0/16",
   131  			Except: []string{"192.168.3.0/24", "192.168.4.0/24"},
   132  		}
   133  	}
   134  
   135  	setIngressFromIPBlockIPV6 := func(networkPolicy *networking.NetworkPolicy) {
   136  		setIngressFromIfEmpty(networkPolicy)
   137  		networkPolicy.Spec.Ingress[0].From[0].IPBlock = &networking.IPBlock{
   138  			CIDR:   "fd00:192:168::/48",
   139  			Except: []string{"fd00:192:168:3::/64", "fd00:192:168:4::/64"},
   140  		}
   141  	}
   142  
   143  	setEgressEmptyFirstElement := func(networkPolicy *networking.NetworkPolicy) {
   144  		networkPolicy.Spec.Egress = []networking.NetworkPolicyEgressRule{{}}
   145  	}
   146  
   147  	setEgressToEmptyFirstElement := func(networkPolicy *networking.NetworkPolicy) {
   148  		if networkPolicy.Spec.Egress == nil {
   149  			setEgressEmptyFirstElement(networkPolicy)
   150  		}
   151  		networkPolicy.Spec.Egress[0].To = []networking.NetworkPolicyPeer{{}}
   152  	}
   153  
   154  	setEgressToIfEmpty := func(networkPolicy *networking.NetworkPolicy) {
   155  		if networkPolicy.Spec.Egress == nil {
   156  			setEgressEmptyFirstElement(networkPolicy)
   157  		}
   158  		if networkPolicy.Spec.Egress[0].To == nil {
   159  			setEgressToEmptyFirstElement(networkPolicy)
   160  		}
   161  	}
   162  
   163  	setEgressToNamespaceSelector := func(networkPolicy *networking.NetworkPolicy) {
   164  		setEgressToIfEmpty(networkPolicy)
   165  		networkPolicy.Spec.Egress[0].To[0].NamespaceSelector = &metav1.LabelSelector{
   166  			MatchLabels: map[string]string{"c": "d"},
   167  		}
   168  	}
   169  
   170  	setEgressToPodSelector := func(networkPolicy *networking.NetworkPolicy) {
   171  		setEgressToIfEmpty(networkPolicy)
   172  		networkPolicy.Spec.Egress[0].To[0].PodSelector = &metav1.LabelSelector{
   173  			MatchLabels: map[string]string{"c": "d"},
   174  		}
   175  	}
   176  
   177  	setEgressToIPBlockIPV4 := func(networkPolicy *networking.NetworkPolicy) {
   178  		setEgressToIfEmpty(networkPolicy)
   179  		networkPolicy.Spec.Egress[0].To[0].IPBlock = &networking.IPBlock{
   180  			CIDR:   "192.168.0.0/16",
   181  			Except: []string{"192.168.3.0/24", "192.168.4.0/24"},
   182  		}
   183  	}
   184  
   185  	setEgressToIPBlockIPV6 := func(networkPolicy *networking.NetworkPolicy) {
   186  		setEgressToIfEmpty(networkPolicy)
   187  		networkPolicy.Spec.Egress[0].To[0].IPBlock = &networking.IPBlock{
   188  			CIDR:   "fd00:192:168::/48",
   189  			Except: []string{"fd00:192:168:3::/64", "fd00:192:168:4::/64"},
   190  		}
   191  	}
   192  
   193  	setEgressPorts := func(ports ...networking.NetworkPolicyPort) netpolTweak {
   194  		return func(np *networking.NetworkPolicy) {
   195  			if np.Spec.Egress == nil {
   196  				setEgressEmptyFirstElement(np)
   197  			}
   198  			np.Spec.Egress[0].Ports = make([]networking.NetworkPolicyPort, len(ports))
   199  			copy(np.Spec.Egress[0].Ports, ports)
   200  		}
   201  	}
   202  
   203  	setPolicyTypesEgress := func(networkPolicy *networking.NetworkPolicy) {
   204  		networkPolicy.Spec.PolicyTypes = []networking.PolicyType{networking.PolicyTypeEgress}
   205  	}
   206  
   207  	setPolicyTypesIngressEgress := func(networkPolicy *networking.NetworkPolicy) {
   208  		networkPolicy.Spec.PolicyTypes = []networking.PolicyType{networking.PolicyTypeIngress, networking.PolicyTypeEgress}
   209  	}
   210  
   211  	successCases := []*networking.NetworkPolicy{
   212  		makeNetworkPolicyCustom(setIngressEmptyFirstElement),
   213  		makeNetworkPolicyCustom(setIngressFromEmptyFirstElement, setIngressEmptyPorts),
   214  		makeNetworkPolicyCustom(setIngressPorts(
   215  			makePort(nil, intstr.FromInt32(80), 0),
   216  			makePort(&protocolTCP, intstr.FromInt32(0), 0),
   217  			makePort(&protocolTCP, intstr.FromInt32(443), 0),
   218  			makePort(&protocolUDP, intstr.FromString("dns"), 0),
   219  			makePort(&protocolSCTP, intstr.FromInt32(7777), 0),
   220  		)),
   221  		makeNetworkPolicyCustom(setIngressFromPodSelector("c", "d")),
   222  		makeNetworkPolicyCustom(setIngressFromNamespaceSelector),
   223  		makeNetworkPolicyCustom(setIngressFromPodSelector("e", "f"), setIngressFromNamespaceSelector),
   224  		makeNetworkPolicyCustom(setEgressToNamespaceSelector, setIngressFromIPBlockIPV4),
   225  		makeNetworkPolicyCustom(setIngressFromIPBlockIPV4),
   226  		makeNetworkPolicyCustom(setEgressToIPBlockIPV4, setPolicyTypesEgress),
   227  		makeNetworkPolicyCustom(setEgressToIPBlockIPV4, setPolicyTypesIngressEgress),
   228  		makeNetworkPolicyCustom(setEgressPorts(
   229  			makePort(nil, intstr.FromInt32(80), 0),
   230  			makePort(&protocolTCP, intstr.FromInt32(0), 0),
   231  			makePort(&protocolTCP, intstr.FromInt32(443), 0),
   232  			makePort(&protocolUDP, intstr.FromString("dns"), 0),
   233  			makePort(&protocolSCTP, intstr.FromInt32(7777), 0),
   234  		)),
   235  		makeNetworkPolicyCustom(setEgressToNamespaceSelector, setIngressFromIPBlockIPV6),
   236  		makeNetworkPolicyCustom(setIngressFromIPBlockIPV6),
   237  		makeNetworkPolicyCustom(setEgressToIPBlockIPV6, setPolicyTypesEgress),
   238  		makeNetworkPolicyCustom(setEgressToIPBlockIPV6, setPolicyTypesIngressEgress),
   239  		makeNetworkPolicyCustom(setEgressPorts(makePort(nil, intstr.FromInt32(32000), 32768), makePort(&protocolUDP, intstr.FromString("dns"), 0))),
   240  		makeNetworkPolicyCustom(
   241  			setEgressToNamespaceSelector,
   242  			setEgressPorts(
   243  				makePort(nil, intstr.FromInt32(30000), 32768),
   244  				makePort(nil, intstr.FromInt32(32000), 32768),
   245  			),
   246  			setIngressFromPodSelector("e", "f"),
   247  			setIngressPorts(makePort(&protocolTCP, intstr.FromInt32(32768), 32768))),
   248  	}
   249  
   250  	// Success cases are expected to pass validation.
   251  
   252  	for k, v := range successCases {
   253  		if errs := ValidateNetworkPolicy(v, NetworkPolicyValidationOptions{AllowInvalidLabelValueInSelector: true}); len(errs) != 0 {
   254  			t.Errorf("Expected success for the success validation test number %d, got %v", k, errs)
   255  		}
   256  	}
   257  
   258  	invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
   259  
   260  	errorCases := map[string]*networking.NetworkPolicy{
   261  		"namespaceSelector and ipBlock": makeNetworkPolicyCustom(setIngressFromNamespaceSelector, setIngressFromIPBlockIPV4),
   262  		"podSelector and ipBlock":       makeNetworkPolicyCustom(setEgressToPodSelector, setEgressToIPBlockIPV4),
   263  		"missing from and to type":      makeNetworkPolicyCustom(setIngressFromEmptyFirstElement, setEgressToEmptyFirstElement),
   264  		"invalid spec.podSelector": makeNetworkPolicyCustom(setIngressFromNamespaceSelector, func(networkPolicy *networking.NetworkPolicy) {
   265  			networkPolicy.Spec = networking.NetworkPolicySpec{
   266  				PodSelector: metav1.LabelSelector{
   267  					MatchLabels: invalidSelector,
   268  				},
   269  			}
   270  		}),
   271  		"invalid ingress.ports.protocol":   makeNetworkPolicyCustom(setIngressPorts(makePort(&protocolICMP, intstr.FromInt32(80), 0))),
   272  		"invalid ingress.ports.port (int)": makeNetworkPolicyCustom(setIngressPorts(makePort(&protocolTCP, intstr.FromInt32(123456789), 0))),
   273  		"invalid ingress.ports.port (str)": makeNetworkPolicyCustom(
   274  			setIngressPorts(makePort(&protocolTCP, intstr.FromString("!@#$"), 0))),
   275  		"invalid ingress.from.podSelector": makeNetworkPolicyCustom(setIngressFromEmptyFirstElement, func(networkPolicy *networking.NetworkPolicy) {
   276  			networkPolicy.Spec.Ingress[0].From[0].PodSelector = &metav1.LabelSelector{
   277  				MatchLabels: invalidSelector,
   278  			}
   279  		}),
   280  		"invalid egress.to.podSelector": makeNetworkPolicyCustom(setEgressToEmptyFirstElement, func(networkPolicy *networking.NetworkPolicy) {
   281  			networkPolicy.Spec.Egress[0].To[0].PodSelector = &metav1.LabelSelector{
   282  				MatchLabels: invalidSelector,
   283  			}
   284  		}),
   285  		"invalid egress.ports.protocol":   makeNetworkPolicyCustom(setEgressPorts(makePort(&protocolICMP, intstr.FromInt32(80), 0))),
   286  		"invalid egress.ports.port (int)": makeNetworkPolicyCustom(setEgressPorts(makePort(&protocolTCP, intstr.FromInt32(123456789), 0))),
   287  		"invalid egress.ports.port (str)": makeNetworkPolicyCustom(setEgressPorts(makePort(&protocolTCP, intstr.FromString("!@#$"), 0))),
   288  		"invalid ingress.from.namespaceSelector": makeNetworkPolicyCustom(setIngressFromEmptyFirstElement, func(networkPolicy *networking.NetworkPolicy) {
   289  			networkPolicy.Spec.Ingress[0].From[0].NamespaceSelector = &metav1.LabelSelector{
   290  				MatchLabels: invalidSelector,
   291  			}
   292  		}),
   293  		"missing cidr field": makeNetworkPolicyCustom(setIngressFromIPBlockIPV4, func(networkPolicy *networking.NetworkPolicy) {
   294  			networkPolicy.Spec.Ingress[0].From[0].IPBlock.CIDR = ""
   295  		}),
   296  		"invalid cidr format": makeNetworkPolicyCustom(setIngressFromIPBlockIPV4, func(networkPolicy *networking.NetworkPolicy) {
   297  			networkPolicy.Spec.Ingress[0].From[0].IPBlock.CIDR = "192.168.5.6"
   298  		}),
   299  		"invalid ipv6 cidr format": makeNetworkPolicyCustom(setIngressFromIPBlockIPV6, func(networkPolicy *networking.NetworkPolicy) {
   300  			networkPolicy.Spec.Ingress[0].From[0].IPBlock.CIDR = "fd00:192:168::"
   301  		}),
   302  		"except field is an empty string": makeNetworkPolicyCustom(setIngressFromIPBlockIPV4, func(networkPolicy *networking.NetworkPolicy) {
   303  			networkPolicy.Spec.Ingress[0].From[0].IPBlock.Except = []string{""}
   304  		}),
   305  		"except field is an space string": makeNetworkPolicyCustom(setIngressFromIPBlockIPV4, func(networkPolicy *networking.NetworkPolicy) {
   306  			networkPolicy.Spec.Ingress[0].From[0].IPBlock.Except = []string{" "}
   307  		}),
   308  		"except field is an invalid ip": makeNetworkPolicyCustom(setIngressFromIPBlockIPV4, func(networkPolicy *networking.NetworkPolicy) {
   309  			networkPolicy.Spec.Ingress[0].From[0].IPBlock.Except = []string{"300.300.300.300"}
   310  		}),
   311  		"except IP is outside of CIDR range": makeNetworkPolicyCustom(setIngressFromEmptyFirstElement, func(networkPolicy *networking.NetworkPolicy) {
   312  			networkPolicy.Spec.Ingress[0].From[0].IPBlock = &networking.IPBlock{
   313  				CIDR:   "192.168.8.0/24",
   314  				Except: []string{"192.168.9.1/24"},
   315  			}
   316  		}),
   317  		"except IP is not strictly within CIDR range": makeNetworkPolicyCustom(setIngressFromEmptyFirstElement, func(networkPolicy *networking.NetworkPolicy) {
   318  			networkPolicy.Spec.Ingress[0].From[0].IPBlock = &networking.IPBlock{
   319  				CIDR:   "192.168.0.0/24",
   320  				Except: []string{"192.168.0.0/24"},
   321  			}
   322  		}),
   323  		"except IPv6 is outside of CIDR range": makeNetworkPolicyCustom(setIngressFromEmptyFirstElement, func(networkPolicy *networking.NetworkPolicy) {
   324  			networkPolicy.Spec.Ingress[0].From[0].IPBlock = &networking.IPBlock{
   325  				CIDR:   "fd00:192:168:1::/64",
   326  				Except: []string{"fd00:192:168:2::/64"},
   327  			}
   328  		}),
   329  		"invalid policyTypes": makeNetworkPolicyCustom(setEgressToIPBlockIPV4, func(networkPolicy *networking.NetworkPolicy) {
   330  			networkPolicy.Spec.PolicyTypes = []networking.PolicyType{"foo", "bar"}
   331  		}),
   332  		"too many policyTypes": makeNetworkPolicyCustom(setEgressToIPBlockIPV4, func(networkPolicy *networking.NetworkPolicy) {
   333  			networkPolicy.Spec.PolicyTypes = []networking.PolicyType{"foo", "bar", "baz"}
   334  		}),
   335  		"multiple ports defined, one port range is invalid": makeNetworkPolicyCustom(
   336  			setEgressToNamespaceSelector,
   337  			setEgressPorts(
   338  				makePort(&protocolUDP, intstr.FromInt32(35000), 32768),
   339  				makePort(nil, intstr.FromInt32(32000), 32768),
   340  			),
   341  		),
   342  		"endPort defined with named/string port": makeNetworkPolicyCustom(
   343  			setEgressToNamespaceSelector,
   344  			setEgressPorts(
   345  				makePort(&protocolUDP, intstr.FromString("dns"), 32768),
   346  				makePort(nil, intstr.FromInt32(32000), 32768),
   347  			),
   348  		),
   349  		"endPort defined without port defined": makeNetworkPolicyCustom(
   350  			setEgressToNamespaceSelector,
   351  			setEgressPorts(makePort(&protocolTCP, intstr.FromInt32(0), 32768)),
   352  		),
   353  		"port is greater than endPort": makeNetworkPolicyCustom(
   354  			setEgressToNamespaceSelector,
   355  			setEgressPorts(makePort(&protocolSCTP, intstr.FromInt32(35000), 32768)),
   356  		),
   357  		"multiple invalid port ranges defined": makeNetworkPolicyCustom(
   358  			setEgressToNamespaceSelector,
   359  			setEgressPorts(
   360  				makePort(&protocolUDP, intstr.FromInt32(35000), 32768),
   361  				makePort(&protocolTCP, intstr.FromInt32(0), 32768),
   362  				makePort(&protocolTCP, intstr.FromString("https"), 32768),
   363  			),
   364  		),
   365  		"invalid endport range defined": makeNetworkPolicyCustom(setEgressToNamespaceSelector, setEgressPorts(makePort(&protocolTCP, intstr.FromInt32(30000), 65537))),
   366  	}
   367  
   368  	// Error cases are not expected to pass validation.
   369  	for testName, networkPolicy := range errorCases {
   370  		if errs := ValidateNetworkPolicy(networkPolicy, NetworkPolicyValidationOptions{AllowInvalidLabelValueInSelector: true}); len(errs) == 0 {
   371  			t.Errorf("Expected failure for test: %s", testName)
   372  		}
   373  	}
   374  }
   375  
   376  func TestValidateNetworkPolicyUpdate(t *testing.T) {
   377  	type npUpdateTest struct {
   378  		old    networking.NetworkPolicy
   379  		update networking.NetworkPolicy
   380  	}
   381  	successCases := map[string]npUpdateTest{
   382  		"no change": {
   383  			old: networking.NetworkPolicy{
   384  				ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
   385  				Spec: networking.NetworkPolicySpec{
   386  					PodSelector: metav1.LabelSelector{
   387  						MatchLabels: map[string]string{"a": "b"},
   388  					},
   389  					Ingress: []networking.NetworkPolicyIngressRule{},
   390  				},
   391  			},
   392  			update: networking.NetworkPolicy{
   393  				ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
   394  				Spec: networking.NetworkPolicySpec{
   395  					PodSelector: metav1.LabelSelector{
   396  						MatchLabels: map[string]string{"a": "b"},
   397  					},
   398  					Ingress: []networking.NetworkPolicyIngressRule{},
   399  				},
   400  			},
   401  		},
   402  		"change spec": {
   403  			old: networking.NetworkPolicy{
   404  				ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
   405  				Spec: networking.NetworkPolicySpec{
   406  					PodSelector: metav1.LabelSelector{},
   407  					Ingress:     []networking.NetworkPolicyIngressRule{},
   408  				},
   409  			},
   410  			update: networking.NetworkPolicy{
   411  				ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
   412  				Spec: networking.NetworkPolicySpec{
   413  					PodSelector: metav1.LabelSelector{
   414  						MatchLabels: map[string]string{"a": "b"},
   415  					},
   416  					Ingress: []networking.NetworkPolicyIngressRule{},
   417  				},
   418  			},
   419  		},
   420  	}
   421  
   422  	for testName, successCase := range successCases {
   423  		successCase.old.ObjectMeta.ResourceVersion = "1"
   424  		successCase.update.ObjectMeta.ResourceVersion = "1"
   425  		if errs := ValidateNetworkPolicyUpdate(&successCase.update, &successCase.old, NetworkPolicyValidationOptions{false}); len(errs) != 0 {
   426  			t.Errorf("expected success (%s): %v", testName, errs)
   427  		}
   428  	}
   429  
   430  	errorCases := map[string]npUpdateTest{
   431  		"change name": {
   432  			old: networking.NetworkPolicy{
   433  				ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
   434  				Spec: networking.NetworkPolicySpec{
   435  					PodSelector: metav1.LabelSelector{},
   436  					Ingress:     []networking.NetworkPolicyIngressRule{},
   437  				},
   438  			},
   439  			update: networking.NetworkPolicy{
   440  				ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "bar"},
   441  				Spec: networking.NetworkPolicySpec{
   442  					PodSelector: metav1.LabelSelector{},
   443  					Ingress:     []networking.NetworkPolicyIngressRule{},
   444  				},
   445  			},
   446  		},
   447  	}
   448  
   449  	for testName, errorCase := range errorCases {
   450  		errorCase.old.ObjectMeta.ResourceVersion = "1"
   451  		errorCase.update.ObjectMeta.ResourceVersion = "1"
   452  		if errs := ValidateNetworkPolicyUpdate(&errorCase.update, &errorCase.old, NetworkPolicyValidationOptions{false}); len(errs) == 0 {
   453  			t.Errorf("expected failure: %s", testName)
   454  		}
   455  	}
   456  }
   457  
   458  func TestValidateIngress(t *testing.T) {
   459  	serviceBackend := &networking.IngressServiceBackend{
   460  		Name: "defaultbackend",
   461  		Port: networking.ServiceBackendPort{
   462  			Name:   "",
   463  			Number: 80,
   464  		},
   465  	}
   466  	defaultBackend := networking.IngressBackend{
   467  		Service: serviceBackend,
   468  	}
   469  	pathTypePrefix := networking.PathTypePrefix
   470  	pathTypeImplementationSpecific := networking.PathTypeImplementationSpecific
   471  	pathTypeFoo := networking.PathType("foo")
   472  
   473  	baseIngress := networking.Ingress{
   474  		ObjectMeta: metav1.ObjectMeta{
   475  			Name:      "foo",
   476  			Namespace: metav1.NamespaceDefault,
   477  		},
   478  		Spec: networking.IngressSpec{
   479  			DefaultBackend: &defaultBackend,
   480  			Rules: []networking.IngressRule{{
   481  				Host: "foo.bar.com",
   482  				IngressRuleValue: networking.IngressRuleValue{
   483  					HTTP: &networking.HTTPIngressRuleValue{
   484  						Paths: []networking.HTTPIngressPath{{
   485  							Path:     "/foo",
   486  							PathType: &pathTypeImplementationSpecific,
   487  							Backend:  defaultBackend,
   488  						}},
   489  					},
   490  				},
   491  			}},
   492  		},
   493  		Status: networking.IngressStatus{
   494  			LoadBalancer: networking.IngressLoadBalancerStatus{
   495  				Ingress: []networking.IngressLoadBalancerIngress{
   496  					{IP: "127.0.0.1"},
   497  				},
   498  			},
   499  		},
   500  	}
   501  
   502  	testCases := map[string]struct {
   503  		tweakIngress       func(ing *networking.Ingress)
   504  		expectErrsOnFields []string
   505  	}{
   506  		"empty path (implementation specific)": {
   507  			tweakIngress: func(ing *networking.Ingress) {
   508  				ing.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].Path = ""
   509  			},
   510  			expectErrsOnFields: []string{},
   511  		},
   512  		"valid path": {
   513  			tweakIngress: func(ing *networking.Ingress) {
   514  				ing.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].Path = "/valid"
   515  			},
   516  			expectErrsOnFields: []string{},
   517  		},
   518  		// invalid use cases
   519  		"backend with no service": {
   520  			tweakIngress: func(ing *networking.Ingress) {
   521  				ing.Spec.DefaultBackend.Service.Name = ""
   522  			},
   523  			expectErrsOnFields: []string{
   524  				"spec.defaultBackend.service.name",
   525  			},
   526  		},
   527  		"invalid path type": {
   528  			tweakIngress: func(ing *networking.Ingress) {
   529  				ing.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].PathType = &pathTypeFoo
   530  			},
   531  			expectErrsOnFields: []string{
   532  				"spec.rules[0].http.paths[0].pathType",
   533  			},
   534  		},
   535  		"empty path (prefix)": {
   536  			tweakIngress: func(ing *networking.Ingress) {
   537  				ing.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].Path = ""
   538  				ing.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].PathType = &pathTypePrefix
   539  			},
   540  			expectErrsOnFields: []string{
   541  				"spec.rules[0].http.paths[0].path",
   542  			},
   543  		},
   544  		"no paths": {
   545  			tweakIngress: func(ing *networking.Ingress) {
   546  				ing.Spec.Rules[0].IngressRuleValue.HTTP.Paths = []networking.HTTPIngressPath{}
   547  			},
   548  			expectErrsOnFields: []string{
   549  				"spec.rules[0].http.paths",
   550  			},
   551  		},
   552  		"invalid host (foobar:80)": {
   553  			tweakIngress: func(ing *networking.Ingress) {
   554  				ing.Spec.Rules[0].Host = "foobar:80"
   555  			},
   556  			expectErrsOnFields: []string{
   557  				"spec.rules[0].host",
   558  			},
   559  		},
   560  		"invalid host (127.0.0.1)": {
   561  			tweakIngress: func(ing *networking.Ingress) {
   562  				ing.Spec.Rules[0].Host = "127.0.0.1"
   563  			},
   564  			expectErrsOnFields: []string{
   565  				"spec.rules[0].host",
   566  			},
   567  		},
   568  		"valid wildcard host": {
   569  			tweakIngress: func(ing *networking.Ingress) {
   570  				ing.Spec.Rules[0].Host = "*.bar.com"
   571  			},
   572  			expectErrsOnFields: []string{},
   573  		},
   574  		"invalid wildcard host (foo.*.bar.com)": {
   575  			tweakIngress: func(ing *networking.Ingress) {
   576  				ing.Spec.Rules[0].Host = "foo.*.bar.com"
   577  			},
   578  			expectErrsOnFields: []string{
   579  				"spec.rules[0].host",
   580  			},
   581  		},
   582  		"invalid wildcard host (*)": {
   583  			tweakIngress: func(ing *networking.Ingress) {
   584  				ing.Spec.Rules[0].Host = "*"
   585  			},
   586  			expectErrsOnFields: []string{
   587  				"spec.rules[0].host",
   588  			},
   589  		},
   590  		"path resource backend and service name are not allowed together": {
   591  			tweakIngress: func(ing *networking.Ingress) {
   592  				ing.Spec.Rules[0].IngressRuleValue = networking.IngressRuleValue{
   593  					HTTP: &networking.HTTPIngressRuleValue{
   594  						Paths: []networking.HTTPIngressPath{{
   595  							Path:     "/foo",
   596  							PathType: &pathTypeImplementationSpecific,
   597  							Backend: networking.IngressBackend{
   598  								Service: serviceBackend,
   599  								Resource: &api.TypedLocalObjectReference{
   600  									APIGroup: utilpointer.String("example.com"),
   601  									Kind:     "foo",
   602  									Name:     "bar",
   603  								},
   604  							},
   605  						}},
   606  					},
   607  				}
   608  			},
   609  			expectErrsOnFields: []string{
   610  				"spec.rules[0].http.paths[0].backend",
   611  			},
   612  		},
   613  		"path resource backend and service port are not allowed together": {
   614  			tweakIngress: func(ing *networking.Ingress) {
   615  				ing.Spec.Rules[0].IngressRuleValue = networking.IngressRuleValue{
   616  					HTTP: &networking.HTTPIngressRuleValue{
   617  						Paths: []networking.HTTPIngressPath{{
   618  							Path:     "/foo",
   619  							PathType: &pathTypeImplementationSpecific,
   620  							Backend: networking.IngressBackend{
   621  								Service: serviceBackend,
   622  								Resource: &api.TypedLocalObjectReference{
   623  									APIGroup: utilpointer.String("example.com"),
   624  									Kind:     "foo",
   625  									Name:     "bar",
   626  								},
   627  							},
   628  						}},
   629  					},
   630  				}
   631  			},
   632  			expectErrsOnFields: []string{
   633  				"spec.rules[0].http.paths[0].backend",
   634  			},
   635  		},
   636  		"spec.backend resource and service name are not allowed together": {
   637  			tweakIngress: func(ing *networking.Ingress) {
   638  				ing.Spec.DefaultBackend = &networking.IngressBackend{
   639  					Service: serviceBackend,
   640  					Resource: &api.TypedLocalObjectReference{
   641  						APIGroup: utilpointer.String("example.com"),
   642  						Kind:     "foo",
   643  						Name:     "bar",
   644  					},
   645  				}
   646  			},
   647  			expectErrsOnFields: []string{
   648  				"spec.defaultBackend",
   649  			},
   650  		},
   651  		"spec.backend resource and service port are not allowed together": {
   652  			tweakIngress: func(ing *networking.Ingress) {
   653  				ing.Spec.DefaultBackend = &networking.IngressBackend{
   654  					Service: serviceBackend,
   655  					Resource: &api.TypedLocalObjectReference{
   656  						APIGroup: utilpointer.String("example.com"),
   657  						Kind:     "foo",
   658  						Name:     "bar",
   659  					},
   660  				}
   661  			},
   662  			expectErrsOnFields: []string{
   663  				"spec.defaultBackend",
   664  			},
   665  		},
   666  	}
   667  
   668  	for name, testCase := range testCases {
   669  		t.Run(name, func(t *testing.T) {
   670  			ingress := baseIngress.DeepCopy()
   671  			testCase.tweakIngress(ingress)
   672  			errs := validateIngress(ingress, IngressValidationOptions{})
   673  			if len(testCase.expectErrsOnFields) != len(errs) {
   674  				t.Fatalf("Expected %d errors, got %d errors: %v", len(testCase.expectErrsOnFields), len(errs), errs)
   675  			}
   676  			for i, err := range errs {
   677  				if err.Field != testCase.expectErrsOnFields[i] {
   678  					t.Errorf("Expected error on field: %s, got: %s", testCase.expectErrsOnFields[i], err.Error())
   679  				}
   680  			}
   681  		})
   682  	}
   683  }
   684  
   685  func TestValidateIngressRuleValue(t *testing.T) {
   686  	serviceBackend := networking.IngressServiceBackend{
   687  		Name: "defaultbackend",
   688  		Port: networking.ServiceBackendPort{
   689  			Name:   "",
   690  			Number: 80,
   691  		},
   692  	}
   693  	fldPath := field.NewPath("testing.http.paths[0].path")
   694  	testCases := map[string]struct {
   695  		pathType     networking.PathType
   696  		path         string
   697  		expectedErrs field.ErrorList
   698  	}{
   699  		"implementation specific: no leading slash": {
   700  			pathType:     networking.PathTypeImplementationSpecific,
   701  			path:         "foo",
   702  			expectedErrs: field.ErrorList{field.Invalid(fldPath, "foo", "must be an absolute path")},
   703  		},
   704  		"implementation specific: leading slash": {
   705  			pathType:     networking.PathTypeImplementationSpecific,
   706  			path:         "/foo",
   707  			expectedErrs: field.ErrorList{},
   708  		},
   709  		"implementation specific: many slashes": {
   710  			pathType:     networking.PathTypeImplementationSpecific,
   711  			path:         "/foo/bar/foo",
   712  			expectedErrs: field.ErrorList{},
   713  		},
   714  		"implementation specific: repeating slashes": {
   715  			pathType:     networking.PathTypeImplementationSpecific,
   716  			path:         "/foo//bar/foo",
   717  			expectedErrs: field.ErrorList{},
   718  		},
   719  		"prefix: no leading slash": {
   720  			pathType:     networking.PathTypePrefix,
   721  			path:         "foo",
   722  			expectedErrs: field.ErrorList{field.Invalid(fldPath, "foo", "must be an absolute path")},
   723  		},
   724  		"prefix: leading slash": {
   725  			pathType:     networking.PathTypePrefix,
   726  			path:         "/foo",
   727  			expectedErrs: field.ErrorList{},
   728  		},
   729  		"prefix: many slashes": {
   730  			pathType:     networking.PathTypePrefix,
   731  			path:         "/foo/bar/foo",
   732  			expectedErrs: field.ErrorList{},
   733  		},
   734  		"prefix: repeating slashes": {
   735  			pathType:     networking.PathTypePrefix,
   736  			path:         "/foo//bar/foo",
   737  			expectedErrs: field.ErrorList{field.Invalid(fldPath, "/foo//bar/foo", "must not contain '//'")},
   738  		},
   739  		"exact: no leading slash": {
   740  			pathType:     networking.PathTypeExact,
   741  			path:         "foo",
   742  			expectedErrs: field.ErrorList{field.Invalid(fldPath, "foo", "must be an absolute path")},
   743  		},
   744  		"exact: leading slash": {
   745  			pathType:     networking.PathTypeExact,
   746  			path:         "/foo",
   747  			expectedErrs: field.ErrorList{},
   748  		},
   749  		"exact: many slashes": {
   750  			pathType:     networking.PathTypeExact,
   751  			path:         "/foo/bar/foo",
   752  			expectedErrs: field.ErrorList{},
   753  		},
   754  		"exact: repeating slashes": {
   755  			pathType:     networking.PathTypeExact,
   756  			path:         "/foo//bar/foo",
   757  			expectedErrs: field.ErrorList{field.Invalid(fldPath, "/foo//bar/foo", "must not contain '//'")},
   758  		},
   759  		"prefix: with /./": {
   760  			pathType:     networking.PathTypePrefix,
   761  			path:         "/foo/./foo",
   762  			expectedErrs: field.ErrorList{field.Invalid(fldPath, "/foo/./foo", "must not contain '/./'")},
   763  		},
   764  		"exact: with /../": {
   765  			pathType:     networking.PathTypeExact,
   766  			path:         "/foo/../foo",
   767  			expectedErrs: field.ErrorList{field.Invalid(fldPath, "/foo/../foo", "must not contain '/../'")},
   768  		},
   769  		"prefix: with %2f": {
   770  			pathType:     networking.PathTypePrefix,
   771  			path:         "/foo/%2f/foo",
   772  			expectedErrs: field.ErrorList{field.Invalid(fldPath, "/foo/%2f/foo", "must not contain '%2f'")},
   773  		},
   774  		"exact: with %2F": {
   775  			pathType:     networking.PathTypeExact,
   776  			path:         "/foo/%2F/foo",
   777  			expectedErrs: field.ErrorList{field.Invalid(fldPath, "/foo/%2F/foo", "must not contain '%2F'")},
   778  		},
   779  	}
   780  
   781  	for name, testCase := range testCases {
   782  		t.Run(name, func(t *testing.T) {
   783  			irv := &networking.IngressRuleValue{
   784  				HTTP: &networking.HTTPIngressRuleValue{
   785  					Paths: []networking.HTTPIngressPath{{
   786  						Path:     testCase.path,
   787  						PathType: &testCase.pathType,
   788  						Backend: networking.IngressBackend{
   789  							Service: &serviceBackend,
   790  						},
   791  					}},
   792  				},
   793  			}
   794  			errs := validateIngressRuleValue(irv, field.NewPath("testing"), IngressValidationOptions{})
   795  			if len(errs) != len(testCase.expectedErrs) {
   796  				t.Fatalf("Expected %d errors, got %d (%+v)", len(testCase.expectedErrs), len(errs), errs)
   797  			}
   798  
   799  			for i, err := range errs {
   800  				if err.Error() != testCase.expectedErrs[i].Error() {
   801  					t.Fatalf("Expected error: %v, got %v", testCase.expectedErrs[i], err)
   802  				}
   803  			}
   804  		})
   805  	}
   806  }
   807  
   808  func TestValidateIngressCreate(t *testing.T) {
   809  	implementationPathType := networking.PathTypeImplementationSpecific
   810  	exactPathType := networking.PathTypeExact
   811  	serviceBackend := &networking.IngressServiceBackend{
   812  		Name: "defaultbackend",
   813  		Port: networking.ServiceBackendPort{
   814  			Number: 80,
   815  		},
   816  	}
   817  	defaultBackend := networking.IngressBackend{
   818  		Service: serviceBackend,
   819  	}
   820  	resourceBackend := &api.TypedLocalObjectReference{
   821  		APIGroup: utilpointer.String("example.com"),
   822  		Kind:     "foo",
   823  		Name:     "bar",
   824  	}
   825  	baseIngress := networking.Ingress{
   826  		ObjectMeta: metav1.ObjectMeta{
   827  			Name:            "test123",
   828  			Namespace:       "test123",
   829  			ResourceVersion: "1234",
   830  		},
   831  		Spec: networking.IngressSpec{
   832  			DefaultBackend: &defaultBackend,
   833  			Rules:          []networking.IngressRule{},
   834  		},
   835  	}
   836  
   837  	testCases := map[string]struct {
   838  		tweakIngress func(ingress *networking.Ingress)
   839  		expectedErrs field.ErrorList
   840  	}{
   841  		"class field set": {
   842  			tweakIngress: func(ingress *networking.Ingress) {
   843  				ingress.Spec.IngressClassName = utilpointer.String("bar")
   844  			},
   845  			expectedErrs: field.ErrorList{},
   846  		},
   847  		"class annotation set": {
   848  			tweakIngress: func(ingress *networking.Ingress) {
   849  				ingress.Annotations = map[string]string{annotationIngressClass: "foo"}
   850  			},
   851  			expectedErrs: field.ErrorList{},
   852  		},
   853  		"class field and annotation set with same value": {
   854  			tweakIngress: func(ingress *networking.Ingress) {
   855  				ingress.Spec.IngressClassName = utilpointer.String("foo")
   856  				ingress.Annotations = map[string]string{annotationIngressClass: "foo"}
   857  			},
   858  			expectedErrs: field.ErrorList{},
   859  		},
   860  		"class field and annotation set with different value": {
   861  			tweakIngress: func(ingress *networking.Ingress) {
   862  				ingress.Spec.IngressClassName = utilpointer.String("bar")
   863  				ingress.Annotations = map[string]string{annotationIngressClass: "foo"}
   864  			},
   865  			expectedErrs: field.ErrorList{field.Invalid(field.NewPath("annotations").Child(annotationIngressClass), "foo", "must match `ingressClassName` when both are specified")},
   866  		},
   867  		"valid regex path": {
   868  			tweakIngress: func(ingress *networking.Ingress) {
   869  				ingress.Spec.Rules = []networking.IngressRule{{
   870  					Host: "foo.bar.com",
   871  					IngressRuleValue: networking.IngressRuleValue{
   872  						HTTP: &networking.HTTPIngressRuleValue{
   873  							Paths: []networking.HTTPIngressPath{{
   874  								Path:     "/([a-z0-9]*)",
   875  								PathType: &implementationPathType,
   876  								Backend:  defaultBackend,
   877  							}},
   878  						},
   879  					},
   880  				}}
   881  			},
   882  			expectedErrs: field.ErrorList{},
   883  		},
   884  		"invalid regex path allowed (v1)": {
   885  			tweakIngress: func(ingress *networking.Ingress) {
   886  				ingress.Spec.Rules = []networking.IngressRule{{
   887  					Host: "foo.bar.com",
   888  					IngressRuleValue: networking.IngressRuleValue{
   889  						HTTP: &networking.HTTPIngressRuleValue{
   890  							Paths: []networking.HTTPIngressPath{{
   891  								Path:     "/([a-z0-9]*)[",
   892  								PathType: &implementationPathType,
   893  								Backend:  defaultBackend,
   894  							}},
   895  						},
   896  					},
   897  				}}
   898  			},
   899  			expectedErrs: field.ErrorList{},
   900  		},
   901  		"Spec.Backend.Resource field allowed on create": {
   902  			tweakIngress: func(ingress *networking.Ingress) {
   903  				ingress.Spec.DefaultBackend = &networking.IngressBackend{
   904  					Resource: resourceBackend}
   905  			},
   906  			expectedErrs: field.ErrorList{},
   907  		},
   908  		"Paths.Backend.Resource field allowed on create": {
   909  			tweakIngress: func(ingress *networking.Ingress) {
   910  				ingress.Spec.Rules = []networking.IngressRule{{
   911  					Host: "foo.bar.com",
   912  					IngressRuleValue: networking.IngressRuleValue{
   913  						HTTP: &networking.HTTPIngressRuleValue{
   914  							Paths: []networking.HTTPIngressPath{{
   915  								Path:     "/([a-z0-9]*)",
   916  								PathType: &implementationPathType,
   917  								Backend: networking.IngressBackend{
   918  									Resource: resourceBackend},
   919  							}},
   920  						},
   921  					},
   922  				}}
   923  			},
   924  			expectedErrs: field.ErrorList{},
   925  		},
   926  		"valid secret": {
   927  			tweakIngress: func(ingress *networking.Ingress) {
   928  				ingress.Spec.TLS = []networking.IngressTLS{{SecretName: "valid"}}
   929  			},
   930  		},
   931  		"invalid secret": {
   932  			tweakIngress: func(ingress *networking.Ingress) {
   933  				ingress.Spec.TLS = []networking.IngressTLS{{SecretName: "invalid name"}}
   934  			},
   935  			expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec").Child("tls").Index(0).Child("secretName"), "invalid name", `a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`)},
   936  		},
   937  		"valid rules with wildcard host": {
   938  			tweakIngress: func(ingress *networking.Ingress) {
   939  				ingress.Spec.TLS = []networking.IngressTLS{{Hosts: []string{"*.bar.com"}}}
   940  				ingress.Spec.Rules = []networking.IngressRule{{
   941  					Host: "*.foo.com",
   942  					IngressRuleValue: networking.IngressRuleValue{
   943  						HTTP: &networking.HTTPIngressRuleValue{
   944  							Paths: []networking.HTTPIngressPath{{
   945  								Path:     "/foo",
   946  								PathType: &exactPathType,
   947  								Backend:  defaultBackend,
   948  							}},
   949  						},
   950  					},
   951  				}}
   952  			},
   953  		},
   954  		"invalid rules with wildcard host": {
   955  			tweakIngress: func(ingress *networking.Ingress) {
   956  				ingress.Spec.TLS = []networking.IngressTLS{{Hosts: []string{"*.bar.com"}}}
   957  				ingress.Spec.Rules = []networking.IngressRule{{
   958  					Host: "*.foo.com",
   959  					IngressRuleValue: networking.IngressRuleValue{
   960  						HTTP: &networking.HTTPIngressRuleValue{
   961  							Paths: []networking.HTTPIngressPath{{
   962  								Path:     "foo",
   963  								PathType: &exactPathType,
   964  								Backend:  defaultBackend,
   965  							}},
   966  						},
   967  					},
   968  				}}
   969  			},
   970  			expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("http").Child("paths").Index(0).Child("path"), "foo", `must be an absolute path`)},
   971  		},
   972  	}
   973  
   974  	for name, testCase := range testCases {
   975  		t.Run(name, func(t *testing.T) {
   976  			newIngress := baseIngress.DeepCopy()
   977  			testCase.tweakIngress(newIngress)
   978  			errs := ValidateIngressCreate(newIngress)
   979  			if len(errs) != len(testCase.expectedErrs) {
   980  				t.Fatalf("Expected %d errors, got %d (%+v)", len(testCase.expectedErrs), len(errs), errs)
   981  			}
   982  
   983  			for i, err := range errs {
   984  				if err.Error() != testCase.expectedErrs[i].Error() {
   985  					t.Fatalf("Expected error: %v, got %v", testCase.expectedErrs[i].Error(), err.Error())
   986  				}
   987  			}
   988  		})
   989  	}
   990  }
   991  
   992  func TestValidateIngressUpdate(t *testing.T) {
   993  	implementationPathType := networking.PathTypeImplementationSpecific
   994  	exactPathType := networking.PathTypeExact
   995  	serviceBackend := &networking.IngressServiceBackend{
   996  		Name: "defaultbackend",
   997  		Port: networking.ServiceBackendPort{
   998  			Number: 80,
   999  		},
  1000  	}
  1001  	defaultBackend := networking.IngressBackend{
  1002  		Service: serviceBackend,
  1003  	}
  1004  	resourceBackend := &api.TypedLocalObjectReference{
  1005  		APIGroup: utilpointer.String("example.com"),
  1006  		Kind:     "foo",
  1007  		Name:     "bar",
  1008  	}
  1009  	baseIngress := networking.Ingress{
  1010  		ObjectMeta: metav1.ObjectMeta{
  1011  			Name:            "test123",
  1012  			Namespace:       "test123",
  1013  			ResourceVersion: "1234",
  1014  		},
  1015  		Spec: networking.IngressSpec{
  1016  			DefaultBackend: &defaultBackend,
  1017  		},
  1018  	}
  1019  
  1020  	testCases := map[string]struct {
  1021  		tweakIngresses func(newIngress, oldIngress *networking.Ingress)
  1022  		expectedErrs   field.ErrorList
  1023  	}{
  1024  		"class field set": {
  1025  			tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
  1026  				newIngress.Spec.IngressClassName = utilpointer.String("bar")
  1027  			},
  1028  			expectedErrs: field.ErrorList{},
  1029  		},
  1030  		"class annotation set": {
  1031  			tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
  1032  				newIngress.Annotations = map[string]string{annotationIngressClass: "foo"}
  1033  			},
  1034  			expectedErrs: field.ErrorList{},
  1035  		},
  1036  		"class field and annotation set": {
  1037  			tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
  1038  				newIngress.Spec.IngressClassName = utilpointer.String("bar")
  1039  				newIngress.Annotations = map[string]string{annotationIngressClass: "foo"}
  1040  			},
  1041  			expectedErrs: field.ErrorList{},
  1042  		},
  1043  		"valid regex path -> valid regex path": {
  1044  			tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
  1045  				oldIngress.Spec.Rules = []networking.IngressRule{{
  1046  					Host: "foo.bar.com",
  1047  					IngressRuleValue: networking.IngressRuleValue{
  1048  						HTTP: &networking.HTTPIngressRuleValue{
  1049  							Paths: []networking.HTTPIngressPath{{
  1050  								Path:     "/([a-z0-9]*)",
  1051  								PathType: &implementationPathType,
  1052  								Backend:  defaultBackend,
  1053  							}},
  1054  						},
  1055  					},
  1056  				}}
  1057  				newIngress.Spec.Rules = []networking.IngressRule{{
  1058  					Host: "foo.bar.com",
  1059  					IngressRuleValue: networking.IngressRuleValue{
  1060  						HTTP: &networking.HTTPIngressRuleValue{
  1061  							Paths: []networking.HTTPIngressPath{{
  1062  								Path:     "/([a-z0-9%]*)",
  1063  								PathType: &implementationPathType,
  1064  								Backend:  defaultBackend,
  1065  							}},
  1066  						},
  1067  					},
  1068  				}}
  1069  			},
  1070  			expectedErrs: field.ErrorList{},
  1071  		},
  1072  		"valid regex path -> invalid regex path": {
  1073  			tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
  1074  				oldIngress.Spec.Rules = []networking.IngressRule{{
  1075  					Host: "foo.bar.com",
  1076  					IngressRuleValue: networking.IngressRuleValue{
  1077  						HTTP: &networking.HTTPIngressRuleValue{
  1078  							Paths: []networking.HTTPIngressPath{{
  1079  								Path:     "/([a-z0-9]*)",
  1080  								PathType: &implementationPathType,
  1081  								Backend:  defaultBackend,
  1082  							}},
  1083  						},
  1084  					},
  1085  				}}
  1086  				newIngress.Spec.Rules = []networking.IngressRule{{
  1087  					Host: "foo.bar.com",
  1088  					IngressRuleValue: networking.IngressRuleValue{
  1089  						HTTP: &networking.HTTPIngressRuleValue{
  1090  							Paths: []networking.HTTPIngressPath{{
  1091  								Path:     "/bar[",
  1092  								PathType: &implementationPathType,
  1093  								Backend:  defaultBackend,
  1094  							}},
  1095  						},
  1096  					},
  1097  				}}
  1098  			},
  1099  			expectedErrs: field.ErrorList{},
  1100  		},
  1101  		"invalid regex path -> valid regex path": {
  1102  			tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
  1103  				oldIngress.Spec.Rules = []networking.IngressRule{{
  1104  					Host: "foo.bar.com",
  1105  					IngressRuleValue: networking.IngressRuleValue{
  1106  						HTTP: &networking.HTTPIngressRuleValue{
  1107  							Paths: []networking.HTTPIngressPath{{
  1108  								Path:     "/bar[",
  1109  								PathType: &implementationPathType,
  1110  								Backend:  defaultBackend,
  1111  							}},
  1112  						},
  1113  					},
  1114  				}}
  1115  				newIngress.Spec.Rules = []networking.IngressRule{{
  1116  					Host: "foo.bar.com",
  1117  					IngressRuleValue: networking.IngressRuleValue{
  1118  						HTTP: &networking.HTTPIngressRuleValue{
  1119  							Paths: []networking.HTTPIngressPath{{
  1120  								Path:     "/([a-z0-9]*)",
  1121  								PathType: &implementationPathType,
  1122  								Backend:  defaultBackend,
  1123  							}},
  1124  						},
  1125  					},
  1126  				}}
  1127  			},
  1128  			expectedErrs: field.ErrorList{},
  1129  		},
  1130  		"invalid regex path -> invalid regex path": {
  1131  			tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
  1132  				oldIngress.Spec.Rules = []networking.IngressRule{{
  1133  					Host: "foo.bar.com",
  1134  					IngressRuleValue: networking.IngressRuleValue{
  1135  						HTTP: &networking.HTTPIngressRuleValue{
  1136  							Paths: []networking.HTTPIngressPath{{
  1137  								Path:     "/foo[",
  1138  								PathType: &implementationPathType,
  1139  								Backend:  defaultBackend,
  1140  							}},
  1141  						},
  1142  					},
  1143  				}}
  1144  				newIngress.Spec.Rules = []networking.IngressRule{{
  1145  					Host: "foo.bar.com",
  1146  					IngressRuleValue: networking.IngressRuleValue{
  1147  						HTTP: &networking.HTTPIngressRuleValue{
  1148  							Paths: []networking.HTTPIngressPath{{
  1149  								Path:     "/bar[",
  1150  								PathType: &implementationPathType,
  1151  								Backend:  defaultBackend,
  1152  							}},
  1153  						},
  1154  					},
  1155  				}}
  1156  			},
  1157  			expectedErrs: field.ErrorList{},
  1158  		},
  1159  		"new Backend.Resource allowed on update": {
  1160  			tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
  1161  				oldIngress.Spec.DefaultBackend = &defaultBackend
  1162  				newIngress.Spec.DefaultBackend = &networking.IngressBackend{
  1163  					Resource: resourceBackend}
  1164  			},
  1165  			expectedErrs: field.ErrorList{},
  1166  		},
  1167  		"old DefaultBackend.Resource allowed on update": {
  1168  			tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
  1169  				oldIngress.Spec.DefaultBackend = &networking.IngressBackend{
  1170  					Resource: resourceBackend}
  1171  				newIngress.Spec.DefaultBackend = &networking.IngressBackend{
  1172  					Resource: resourceBackend}
  1173  			},
  1174  			expectedErrs: field.ErrorList{},
  1175  		},
  1176  		"changing spec.backend from resource -> no resource": {
  1177  			tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
  1178  				oldIngress.Spec.DefaultBackend = &networking.IngressBackend{
  1179  					Resource: resourceBackend}
  1180  				newIngress.Spec.DefaultBackend = &defaultBackend
  1181  			},
  1182  			expectedErrs: field.ErrorList{},
  1183  		},
  1184  		"changing path backend from resource -> no resource": {
  1185  			tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
  1186  				oldIngress.Spec.Rules = []networking.IngressRule{{
  1187  					Host: "foo.bar.com",
  1188  					IngressRuleValue: networking.IngressRuleValue{
  1189  						HTTP: &networking.HTTPIngressRuleValue{
  1190  							Paths: []networking.HTTPIngressPath{{
  1191  								Path:     "/foo[",
  1192  								PathType: &implementationPathType,
  1193  								Backend: networking.IngressBackend{
  1194  									Resource: resourceBackend},
  1195  							}},
  1196  						},
  1197  					},
  1198  				}}
  1199  				newIngress.Spec.Rules = []networking.IngressRule{{
  1200  					Host: "foo.bar.com",
  1201  					IngressRuleValue: networking.IngressRuleValue{
  1202  						HTTP: &networking.HTTPIngressRuleValue{
  1203  							Paths: []networking.HTTPIngressPath{{
  1204  								Path:     "/bar[",
  1205  								PathType: &implementationPathType,
  1206  								Backend:  defaultBackend,
  1207  							}},
  1208  						},
  1209  					},
  1210  				}}
  1211  			},
  1212  			expectedErrs: field.ErrorList{},
  1213  		},
  1214  		"changing path backend from resource -> resource": {
  1215  			tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
  1216  				oldIngress.Spec.Rules = []networking.IngressRule{{
  1217  					Host: "foo.bar.com",
  1218  					IngressRuleValue: networking.IngressRuleValue{
  1219  						HTTP: &networking.HTTPIngressRuleValue{
  1220  							Paths: []networking.HTTPIngressPath{{
  1221  								Path:     "/foo[",
  1222  								PathType: &implementationPathType,
  1223  								Backend: networking.IngressBackend{
  1224  									Resource: resourceBackend},
  1225  							}},
  1226  						},
  1227  					},
  1228  				}}
  1229  				newIngress.Spec.Rules = []networking.IngressRule{{
  1230  					Host: "foo.bar.com",
  1231  					IngressRuleValue: networking.IngressRuleValue{
  1232  						HTTP: &networking.HTTPIngressRuleValue{
  1233  							Paths: []networking.HTTPIngressPath{{
  1234  								Path:     "/bar[",
  1235  								PathType: &implementationPathType,
  1236  								Backend: networking.IngressBackend{
  1237  									Resource: resourceBackend},
  1238  							}},
  1239  						},
  1240  					},
  1241  				}}
  1242  			},
  1243  			expectedErrs: field.ErrorList{},
  1244  		},
  1245  		"changing path backend from non-resource -> non-resource": {
  1246  			tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
  1247  				oldIngress.Spec.Rules = []networking.IngressRule{{
  1248  					Host: "foo.bar.com",
  1249  					IngressRuleValue: networking.IngressRuleValue{
  1250  						HTTP: &networking.HTTPIngressRuleValue{
  1251  							Paths: []networking.HTTPIngressPath{{
  1252  								Path:     "/foo[",
  1253  								PathType: &implementationPathType,
  1254  								Backend:  defaultBackend,
  1255  							}},
  1256  						},
  1257  					},
  1258  				}}
  1259  				newIngress.Spec.Rules = []networking.IngressRule{{
  1260  					Host: "foo.bar.com",
  1261  					IngressRuleValue: networking.IngressRuleValue{
  1262  						HTTP: &networking.HTTPIngressRuleValue{
  1263  							Paths: []networking.HTTPIngressPath{{
  1264  								Path:     "/bar[",
  1265  								PathType: &implementationPathType,
  1266  								Backend:  defaultBackend,
  1267  							}},
  1268  						},
  1269  					},
  1270  				}}
  1271  			},
  1272  			expectedErrs: field.ErrorList{},
  1273  		},
  1274  		"changing path backend from non-resource -> resource": {
  1275  			tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
  1276  				oldIngress.Spec.Rules = []networking.IngressRule{{
  1277  					Host: "foo.bar.com",
  1278  					IngressRuleValue: networking.IngressRuleValue{
  1279  						HTTP: &networking.HTTPIngressRuleValue{
  1280  							Paths: []networking.HTTPIngressPath{{
  1281  								Path:     "/foo[",
  1282  								PathType: &implementationPathType,
  1283  								Backend:  defaultBackend,
  1284  							}},
  1285  						},
  1286  					},
  1287  				}}
  1288  				newIngress.Spec.Rules = []networking.IngressRule{{
  1289  					Host: "foo.bar.com",
  1290  					IngressRuleValue: networking.IngressRuleValue{
  1291  						HTTP: &networking.HTTPIngressRuleValue{
  1292  							Paths: []networking.HTTPIngressPath{{
  1293  								Path:     "/bar[",
  1294  								PathType: &implementationPathType,
  1295  								Backend: networking.IngressBackend{
  1296  									Resource: resourceBackend},
  1297  							}},
  1298  						},
  1299  					},
  1300  				}}
  1301  			},
  1302  			expectedErrs: field.ErrorList{},
  1303  		},
  1304  		"change valid secret -> invalid secret": {
  1305  			tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
  1306  				oldIngress.Spec.TLS = []networking.IngressTLS{{SecretName: "valid"}}
  1307  				newIngress.Spec.TLS = []networking.IngressTLS{{SecretName: "invalid name"}}
  1308  			},
  1309  			expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec").Child("tls").Index(0).Child("secretName"), "invalid name", `a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`)},
  1310  		},
  1311  		"change invalid secret -> invalid secret": {
  1312  			tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
  1313  				oldIngress.Spec.TLS = []networking.IngressTLS{{SecretName: "invalid name 1"}}
  1314  				newIngress.Spec.TLS = []networking.IngressTLS{{SecretName: "invalid name 2"}}
  1315  			},
  1316  		},
  1317  		"change valid rules with wildcard host -> invalid rules": {
  1318  			tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
  1319  				oldIngress.Spec.TLS = []networking.IngressTLS{{Hosts: []string{"*.bar.com"}}}
  1320  				oldIngress.Spec.Rules = []networking.IngressRule{{
  1321  					Host: "*.foo.com",
  1322  					IngressRuleValue: networking.IngressRuleValue{
  1323  						HTTP: &networking.HTTPIngressRuleValue{
  1324  							Paths: []networking.HTTPIngressPath{{
  1325  								Path:     "/foo",
  1326  								PathType: &exactPathType,
  1327  								Backend:  defaultBackend,
  1328  							}},
  1329  						},
  1330  					},
  1331  				}}
  1332  				newIngress.Spec.TLS = []networking.IngressTLS{{Hosts: []string{"*.bar.com"}}}
  1333  				newIngress.Spec.Rules = []networking.IngressRule{{
  1334  					Host: "*.foo.com",
  1335  					IngressRuleValue: networking.IngressRuleValue{
  1336  						HTTP: &networking.HTTPIngressRuleValue{
  1337  							Paths: []networking.HTTPIngressPath{{
  1338  								Path:     "foo",
  1339  								PathType: &exactPathType,
  1340  								Backend:  defaultBackend,
  1341  							}},
  1342  						},
  1343  					},
  1344  				}}
  1345  			},
  1346  			expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("http").Child("paths").Index(0).Child("path"), "foo", `must be an absolute path`)},
  1347  		},
  1348  		"change invalid rules with wildcard host -> invalid rules": {
  1349  			tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
  1350  				oldIngress.Spec.TLS = []networking.IngressTLS{{Hosts: []string{"*.bar.com"}}}
  1351  				oldIngress.Spec.Rules = []networking.IngressRule{{
  1352  					Host: "*.foo.com",
  1353  					IngressRuleValue: networking.IngressRuleValue{
  1354  						HTTP: &networking.HTTPIngressRuleValue{
  1355  							Paths: []networking.HTTPIngressPath{{
  1356  								Path:     "foo",
  1357  								PathType: &exactPathType,
  1358  								Backend:  defaultBackend,
  1359  							}},
  1360  						},
  1361  					},
  1362  				}}
  1363  				newIngress.Spec.TLS = []networking.IngressTLS{{Hosts: []string{"*.bar.com"}}}
  1364  				newIngress.Spec.Rules = []networking.IngressRule{{
  1365  					Host: "*.foo.com",
  1366  					IngressRuleValue: networking.IngressRuleValue{
  1367  						HTTP: &networking.HTTPIngressRuleValue{
  1368  							Paths: []networking.HTTPIngressPath{{
  1369  								Path:     "bar",
  1370  								PathType: &exactPathType,
  1371  								Backend:  defaultBackend,
  1372  							}},
  1373  						},
  1374  					},
  1375  				}}
  1376  			},
  1377  		},
  1378  	}
  1379  
  1380  	for name, testCase := range testCases {
  1381  		t.Run(name, func(t *testing.T) {
  1382  			newIngress := baseIngress.DeepCopy()
  1383  			oldIngress := baseIngress.DeepCopy()
  1384  			testCase.tweakIngresses(newIngress, oldIngress)
  1385  
  1386  			errs := ValidateIngressUpdate(newIngress, oldIngress)
  1387  
  1388  			if len(errs) != len(testCase.expectedErrs) {
  1389  				t.Fatalf("Expected %d errors, got %d (%+v)", len(testCase.expectedErrs), len(errs), errs)
  1390  			}
  1391  
  1392  			for i, err := range errs {
  1393  				if err.Error() != testCase.expectedErrs[i].Error() {
  1394  					t.Fatalf("Expected error: %v, got %v", testCase.expectedErrs[i].Error(), err.Error())
  1395  				}
  1396  			}
  1397  		})
  1398  	}
  1399  }
  1400  
  1401  type netIngressTweak func(ingressClass *networking.IngressClass)
  1402  
  1403  func makeValidIngressClass(name, controller string, tweaks ...netIngressTweak) *networking.IngressClass {
  1404  	ingressClass := &networking.IngressClass{
  1405  		ObjectMeta: metav1.ObjectMeta{
  1406  			Name: name,
  1407  		},
  1408  		Spec: networking.IngressClassSpec{
  1409  			Controller: controller,
  1410  		},
  1411  	}
  1412  
  1413  	for _, fn := range tweaks {
  1414  		fn(ingressClass)
  1415  	}
  1416  	return ingressClass
  1417  }
  1418  
  1419  func makeIngressClassParams(apiGroup *string, kind, name string, scope, namespace *string) *networking.IngressClassParametersReference {
  1420  	return &networking.IngressClassParametersReference{
  1421  		APIGroup:  apiGroup,
  1422  		Kind:      kind,
  1423  		Name:      name,
  1424  		Scope:     scope,
  1425  		Namespace: namespace,
  1426  	}
  1427  }
  1428  
  1429  func TestValidateIngressClass(t *testing.T) {
  1430  	setParams := func(params *networking.IngressClassParametersReference) netIngressTweak {
  1431  		return func(ingressClass *networking.IngressClass) {
  1432  			ingressClass.Spec.Parameters = params
  1433  		}
  1434  	}
  1435  
  1436  	testCases := map[string]struct {
  1437  		ingressClass *networking.IngressClass
  1438  		expectedErrs field.ErrorList
  1439  	}{
  1440  		"valid name, valid controller": {
  1441  			ingressClass: makeValidIngressClass("test123", "foo.co/bar"),
  1442  			expectedErrs: field.ErrorList{},
  1443  		},
  1444  		"invalid name, valid controller": {
  1445  			ingressClass: makeValidIngressClass("test*123", "foo.co/bar"),
  1446  			expectedErrs: field.ErrorList{field.Invalid(field.NewPath("metadata.name"), "test*123", "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')")},
  1447  		},
  1448  		"valid name, empty controller": {
  1449  			ingressClass: makeValidIngressClass("test123", ""),
  1450  			expectedErrs: field.ErrorList{field.Required(field.NewPath("spec.controller"), "")},
  1451  		},
  1452  		"valid name, controller max length": {
  1453  			ingressClass: makeValidIngressClass("test123", "foo.co/"+strings.Repeat("a", 243)),
  1454  			expectedErrs: field.ErrorList{},
  1455  		},
  1456  		"valid name, controller too long": {
  1457  			ingressClass: makeValidIngressClass("test123", "foo.co/"+strings.Repeat("a", 244)),
  1458  			expectedErrs: field.ErrorList{field.TooLong(field.NewPath("spec.controller"), "", 250)},
  1459  		},
  1460  		"valid name, valid controller, valid params": {
  1461  			ingressClass: makeValidIngressClass("test123", "foo.co/bar",
  1462  				setParams(makeIngressClassParams(utilpointer.String("example.com"), "foo", "bar", utilpointer.String("Cluster"), nil)),
  1463  			),
  1464  			expectedErrs: field.ErrorList{},
  1465  		},
  1466  		"valid name, valid controller, invalid params (no kind)": {
  1467  			ingressClass: makeValidIngressClass("test123", "foo.co/bar",
  1468  				setParams(makeIngressClassParams(utilpointer.String("example.com"), "", "bar", utilpointer.String("Cluster"), nil)),
  1469  			),
  1470  			expectedErrs: field.ErrorList{field.Required(field.NewPath("spec.parameters.kind"), "kind is required")},
  1471  		},
  1472  		"valid name, valid controller, invalid params (no name)": {
  1473  			ingressClass: makeValidIngressClass("test123", "foo.co/bar",
  1474  				setParams(makeIngressClassParams(utilpointer.String("example.com"), "foo", "", utilpointer.String("Cluster"), nil)),
  1475  			),
  1476  			expectedErrs: field.ErrorList{field.Required(field.NewPath("spec.parameters.name"), "name is required")},
  1477  		},
  1478  		"valid name, valid controller, invalid params (bad kind)": {
  1479  			ingressClass: makeValidIngressClass("test123", "foo.co/bar",
  1480  				setParams(makeIngressClassParams(nil, "foo/", "bar", utilpointer.String("Cluster"), nil)),
  1481  			),
  1482  			expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec.parameters.kind"), "foo/", "may not contain '/'")},
  1483  		},
  1484  		"valid name, valid controller, invalid params (bad scope)": {
  1485  			ingressClass: makeValidIngressClass("test123", "foo.co/bar",
  1486  				setParams(makeIngressClassParams(nil, "foo", "bar", utilpointer.String("bad-scope"), nil)),
  1487  			),
  1488  			expectedErrs: field.ErrorList{field.NotSupported(field.NewPath("spec.parameters.scope"),
  1489  				"bad-scope", []string{"Cluster", "Namespace"})},
  1490  		},
  1491  		"valid name, valid controller, valid Namespace scope": {
  1492  			ingressClass: makeValidIngressClass("test123", "foo.co/bar",
  1493  				setParams(makeIngressClassParams(nil, "foo", "bar", utilpointer.String("Namespace"), utilpointer.String("foo-ns"))),
  1494  			),
  1495  			expectedErrs: field.ErrorList{},
  1496  		},
  1497  		"valid name, valid controller, valid scope, invalid namespace": {
  1498  			ingressClass: makeValidIngressClass("test123", "foo.co/bar",
  1499  				setParams(makeIngressClassParams(nil, "foo", "bar", utilpointer.String("Namespace"), utilpointer.String("foo_ns"))),
  1500  			),
  1501  			expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec.parameters.namespace"), "foo_ns",
  1502  				"a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-',"+
  1503  					" and must start and end with an alphanumeric character (e.g. 'my-name',  or '123-abc', "+
  1504  					"regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')")},
  1505  		},
  1506  		"valid name, valid controller, valid Cluster scope": {
  1507  			ingressClass: makeValidIngressClass("test123", "foo.co/bar",
  1508  				setParams(makeIngressClassParams(nil, "foo", "bar", utilpointer.String("Cluster"), nil)),
  1509  			),
  1510  			expectedErrs: field.ErrorList{},
  1511  		},
  1512  		"valid name, valid controller, invalid scope": {
  1513  			ingressClass: makeValidIngressClass("test123", "foo.co/bar",
  1514  				setParams(makeIngressClassParams(nil, "foo", "bar", nil, utilpointer.String("foo_ns"))),
  1515  			),
  1516  			expectedErrs: field.ErrorList{
  1517  				field.Required(field.NewPath("spec.parameters.scope"), ""),
  1518  			},
  1519  		},
  1520  		"namespace not set when scope is Namespace": {
  1521  			ingressClass: makeValidIngressClass("test123", "foo.co/bar",
  1522  				setParams(makeIngressClassParams(nil, "foo", "bar", utilpointer.String("Namespace"), nil)),
  1523  			),
  1524  			expectedErrs: field.ErrorList{field.Required(field.NewPath("spec.parameters.namespace"),
  1525  				"`parameters.scope` is set to 'Namespace'")},
  1526  		},
  1527  		"namespace is forbidden when scope is Cluster": {
  1528  			ingressClass: makeValidIngressClass("test123", "foo.co/bar",
  1529  				setParams(makeIngressClassParams(nil, "foo", "bar", utilpointer.String("Cluster"), utilpointer.String("foo-ns"))),
  1530  			),
  1531  			expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec.parameters.namespace"),
  1532  				"`parameters.scope` is set to 'Cluster'")},
  1533  		},
  1534  		"empty namespace is forbidden when scope is Cluster": {
  1535  			ingressClass: makeValidIngressClass("test123", "foo.co/bar",
  1536  				setParams(makeIngressClassParams(nil, "foo", "bar", utilpointer.String("Cluster"), utilpointer.String(""))),
  1537  			),
  1538  			expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec.parameters.namespace"),
  1539  				"`parameters.scope` is set to 'Cluster'")},
  1540  		},
  1541  	}
  1542  
  1543  	for name, testCase := range testCases {
  1544  		t.Run(name, func(t *testing.T) {
  1545  			errs := ValidateIngressClass(testCase.ingressClass)
  1546  
  1547  			if len(errs) != len(testCase.expectedErrs) {
  1548  				t.Fatalf("Expected %d errors, got %d (%+v)", len(testCase.expectedErrs), len(errs), errs)
  1549  			}
  1550  
  1551  			for i, err := range errs {
  1552  				if err.Error() != testCase.expectedErrs[i].Error() {
  1553  					t.Fatalf("Expected error: %v, got %v", testCase.expectedErrs[i].Error(), err.Error())
  1554  				}
  1555  			}
  1556  		})
  1557  	}
  1558  }
  1559  
  1560  func TestValidateIngressClassUpdate(t *testing.T) {
  1561  	setResourceVersion := func(version string) netIngressTweak {
  1562  		return func(ingressClass *networking.IngressClass) {
  1563  			ingressClass.ObjectMeta.ResourceVersion = version
  1564  		}
  1565  	}
  1566  
  1567  	setParams := func(params *networking.IngressClassParametersReference) netIngressTweak {
  1568  		return func(ingressClass *networking.IngressClass) {
  1569  			ingressClass.Spec.Parameters = params
  1570  		}
  1571  	}
  1572  
  1573  	testCases := map[string]struct {
  1574  		newIngressClass *networking.IngressClass
  1575  		oldIngressClass *networking.IngressClass
  1576  		expectedErrs    field.ErrorList
  1577  	}{
  1578  		"name change": {
  1579  			newIngressClass: makeValidIngressClass("test123", "foo.co/bar", setResourceVersion("2")),
  1580  			oldIngressClass: makeValidIngressClass("test123", "foo.co/different"),
  1581  			expectedErrs:    field.ErrorList{field.Invalid(field.NewPath("spec").Child("controller"), "foo.co/bar", apimachineryvalidation.FieldImmutableErrorMsg)},
  1582  		},
  1583  		"parameters change": {
  1584  			newIngressClass: makeValidIngressClass("test123", "foo.co/bar",
  1585  				setResourceVersion("2"),
  1586  				setParams(
  1587  					makeIngressClassParams(utilpointer.String("v1"), "ConfigMap", "foo", utilpointer.String("Namespace"), utilpointer.String("bar")),
  1588  				),
  1589  			),
  1590  			oldIngressClass: makeValidIngressClass("test123", "foo.co/bar"),
  1591  			expectedErrs:    field.ErrorList{},
  1592  		},
  1593  	}
  1594  
  1595  	for name, testCase := range testCases {
  1596  		t.Run(name, func(t *testing.T) {
  1597  			errs := ValidateIngressClassUpdate(testCase.newIngressClass, testCase.oldIngressClass)
  1598  
  1599  			if len(errs) != len(testCase.expectedErrs) {
  1600  				t.Fatalf("Expected %d errors, got %d (%+v)", len(testCase.expectedErrs), len(errs), errs)
  1601  			}
  1602  
  1603  			for i, err := range errs {
  1604  				if err.Error() != testCase.expectedErrs[i].Error() {
  1605  					t.Fatalf("Expected error: %v, got %v", testCase.expectedErrs[i].Error(), err.Error())
  1606  				}
  1607  			}
  1608  		})
  1609  	}
  1610  }
  1611  
  1612  func TestValidateIngressTLS(t *testing.T) {
  1613  	pathTypeImplementationSpecific := networking.PathTypeImplementationSpecific
  1614  	serviceBackend := &networking.IngressServiceBackend{
  1615  		Name: "defaultbackend",
  1616  		Port: networking.ServiceBackendPort{
  1617  			Number: 80,
  1618  		},
  1619  	}
  1620  	defaultBackend := networking.IngressBackend{
  1621  		Service: serviceBackend,
  1622  	}
  1623  	newValid := func() networking.Ingress {
  1624  		return networking.Ingress{
  1625  			ObjectMeta: metav1.ObjectMeta{
  1626  				Name:      "foo",
  1627  				Namespace: metav1.NamespaceDefault,
  1628  			},
  1629  			Spec: networking.IngressSpec{
  1630  				DefaultBackend: &defaultBackend,
  1631  				Rules: []networking.IngressRule{{
  1632  					Host: "foo.bar.com",
  1633  					IngressRuleValue: networking.IngressRuleValue{
  1634  						HTTP: &networking.HTTPIngressRuleValue{
  1635  							Paths: []networking.HTTPIngressPath{{
  1636  								Path:     "/foo",
  1637  								PathType: &pathTypeImplementationSpecific,
  1638  								Backend:  defaultBackend,
  1639  							}},
  1640  						},
  1641  					},
  1642  				}},
  1643  			},
  1644  			Status: networking.IngressStatus{
  1645  				LoadBalancer: networking.IngressLoadBalancerStatus{
  1646  					Ingress: []networking.IngressLoadBalancerIngress{
  1647  						{IP: "127.0.0.1"},
  1648  					},
  1649  				},
  1650  			},
  1651  		}
  1652  	}
  1653  
  1654  	errorCases := map[string]networking.Ingress{}
  1655  
  1656  	wildcardHost := "foo.*.bar.com"
  1657  	badWildcardTLS := newValid()
  1658  	badWildcardTLS.Spec.Rules[0].Host = "*.foo.bar.com"
  1659  	badWildcardTLS.Spec.TLS = []networking.IngressTLS{{
  1660  		Hosts: []string{wildcardHost},
  1661  	}}
  1662  	badWildcardTLSErr := fmt.Sprintf("spec.tls[0].hosts[0]: Invalid value: '%v'", wildcardHost)
  1663  	errorCases[badWildcardTLSErr] = badWildcardTLS
  1664  
  1665  	for k, v := range errorCases {
  1666  		errs := validateIngress(&v, IngressValidationOptions{})
  1667  		if len(errs) == 0 {
  1668  			t.Errorf("expected failure for %q", k)
  1669  		} else {
  1670  			s := strings.Split(k, ":")
  1671  			err := errs[0]
  1672  			if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) {
  1673  				t.Errorf("unexpected error: %q, expected: %q", err, k)
  1674  			}
  1675  		}
  1676  	}
  1677  
  1678  	// Test for wildcard host and wildcard TLS
  1679  	validCases := map[string]networking.Ingress{}
  1680  	wildHost := "*.bar.com"
  1681  	goodWildcardTLS := newValid()
  1682  	goodWildcardTLS.Spec.Rules[0].Host = "*.bar.com"
  1683  	goodWildcardTLS.Spec.TLS = []networking.IngressTLS{{
  1684  		Hosts: []string{wildHost},
  1685  	}}
  1686  	validCases[fmt.Sprintf("spec.tls[0].hosts: Valid value: '%v'", wildHost)] = goodWildcardTLS
  1687  	for k, v := range validCases {
  1688  		errs := validateIngress(&v, IngressValidationOptions{})
  1689  		if len(errs) != 0 {
  1690  			t.Errorf("expected success for %q", k)
  1691  		}
  1692  	}
  1693  }
  1694  
  1695  // TestValidateEmptyIngressTLS verifies that an empty TLS configuration can be
  1696  // specified, which ingress controllers may interpret to mean that TLS should be
  1697  // used with a default certificate that the ingress controller furnishes.
  1698  func TestValidateEmptyIngressTLS(t *testing.T) {
  1699  	pathTypeImplementationSpecific := networking.PathTypeImplementationSpecific
  1700  	serviceBackend := &networking.IngressServiceBackend{
  1701  		Name: "defaultbackend",
  1702  		Port: networking.ServiceBackendPort{
  1703  			Number: 443,
  1704  		},
  1705  	}
  1706  	defaultBackend := networking.IngressBackend{
  1707  		Service: serviceBackend,
  1708  	}
  1709  	newValid := func() networking.Ingress {
  1710  		return networking.Ingress{
  1711  			ObjectMeta: metav1.ObjectMeta{
  1712  				Name:      "foo",
  1713  				Namespace: metav1.NamespaceDefault,
  1714  			},
  1715  			Spec: networking.IngressSpec{
  1716  				Rules: []networking.IngressRule{{
  1717  					Host: "foo.bar.com",
  1718  					IngressRuleValue: networking.IngressRuleValue{
  1719  						HTTP: &networking.HTTPIngressRuleValue{
  1720  							Paths: []networking.HTTPIngressPath{{
  1721  								PathType: &pathTypeImplementationSpecific,
  1722  								Backend:  defaultBackend,
  1723  							}},
  1724  						},
  1725  					},
  1726  				}},
  1727  			},
  1728  		}
  1729  	}
  1730  
  1731  	validCases := map[string]networking.Ingress{}
  1732  	goodEmptyTLS := newValid()
  1733  	goodEmptyTLS.Spec.TLS = []networking.IngressTLS{
  1734  		{},
  1735  	}
  1736  	validCases[fmt.Sprintf("spec.tls[0]: Valid value: %v", goodEmptyTLS.Spec.TLS[0])] = goodEmptyTLS
  1737  	goodEmptyHosts := newValid()
  1738  	goodEmptyHosts.Spec.TLS = []networking.IngressTLS{{
  1739  		Hosts: []string{},
  1740  	}}
  1741  	validCases[fmt.Sprintf("spec.tls[0]: Valid value: %v", goodEmptyHosts.Spec.TLS[0])] = goodEmptyHosts
  1742  	for k, v := range validCases {
  1743  		errs := validateIngress(&v, IngressValidationOptions{})
  1744  		if len(errs) != 0 {
  1745  			t.Errorf("expected success for %q", k)
  1746  		}
  1747  	}
  1748  }
  1749  
  1750  func TestValidateIngressStatusUpdate(t *testing.T) {
  1751  	serviceBackend := &networking.IngressServiceBackend{
  1752  		Name: "defaultbackend",
  1753  		Port: networking.ServiceBackendPort{
  1754  			Number: 80,
  1755  		},
  1756  	}
  1757  	defaultBackend := networking.IngressBackend{
  1758  		Service: serviceBackend,
  1759  	}
  1760  
  1761  	newValid := func() networking.Ingress {
  1762  		return networking.Ingress{
  1763  			ObjectMeta: metav1.ObjectMeta{
  1764  				Name:            "foo",
  1765  				Namespace:       metav1.NamespaceDefault,
  1766  				ResourceVersion: "9",
  1767  			},
  1768  			Spec: networking.IngressSpec{
  1769  				DefaultBackend: &defaultBackend,
  1770  				Rules: []networking.IngressRule{{
  1771  					Host: "foo.bar.com",
  1772  					IngressRuleValue: networking.IngressRuleValue{
  1773  						HTTP: &networking.HTTPIngressRuleValue{
  1774  							Paths: []networking.HTTPIngressPath{{
  1775  								Path:    "/foo",
  1776  								Backend: defaultBackend,
  1777  							}},
  1778  						},
  1779  					},
  1780  				}},
  1781  			},
  1782  			Status: networking.IngressStatus{
  1783  				LoadBalancer: networking.IngressLoadBalancerStatus{
  1784  					Ingress: []networking.IngressLoadBalancerIngress{
  1785  						{IP: "127.0.0.1", Hostname: "foo.bar.com"},
  1786  					},
  1787  				},
  1788  			},
  1789  		}
  1790  	}
  1791  	oldValue := newValid()
  1792  	newValue := newValid()
  1793  	newValue.Status = networking.IngressStatus{
  1794  		LoadBalancer: networking.IngressLoadBalancerStatus{
  1795  			Ingress: []networking.IngressLoadBalancerIngress{
  1796  				{IP: "127.0.0.2", Hostname: "foo.com"},
  1797  			},
  1798  		},
  1799  	}
  1800  	invalidIP := newValid()
  1801  	invalidIP.Status = networking.IngressStatus{
  1802  		LoadBalancer: networking.IngressLoadBalancerStatus{
  1803  			Ingress: []networking.IngressLoadBalancerIngress{
  1804  				{IP: "abcd", Hostname: "foo.com"},
  1805  			},
  1806  		},
  1807  	}
  1808  	invalidHostname := newValid()
  1809  	invalidHostname.Status = networking.IngressStatus{
  1810  		LoadBalancer: networking.IngressLoadBalancerStatus{
  1811  			Ingress: []networking.IngressLoadBalancerIngress{
  1812  				{IP: "127.0.0.1", Hostname: "127.0.0.1"},
  1813  			},
  1814  		},
  1815  	}
  1816  
  1817  	errs := ValidateIngressStatusUpdate(&newValue, &oldValue)
  1818  	if len(errs) != 0 {
  1819  		t.Errorf("Unexpected error %v", errs)
  1820  	}
  1821  
  1822  	errorCases := map[string]networking.Ingress{
  1823  		"status.loadBalancer.ingress[0].ip: Invalid value":       invalidIP,
  1824  		"status.loadBalancer.ingress[0].hostname: Invalid value": invalidHostname,
  1825  	}
  1826  	for k, v := range errorCases {
  1827  		errs := ValidateIngressStatusUpdate(&v, &oldValue)
  1828  		if len(errs) == 0 {
  1829  			t.Errorf("expected failure for %s", k)
  1830  		} else {
  1831  			s := strings.Split(k, ":")
  1832  			err := errs[0]
  1833  			if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) {
  1834  				t.Errorf("unexpected error: %q, expected: %q", err, k)
  1835  			}
  1836  		}
  1837  	}
  1838  }
  1839  
  1840  func TestValidateIPAddress(t *testing.T) {
  1841  	testCases := map[string]struct {
  1842  		expectedErrors int
  1843  		ipAddress      *networking.IPAddress
  1844  	}{
  1845  		"empty-ipaddress-bad-name": {
  1846  			expectedErrors: 1,
  1847  			ipAddress: &networking.IPAddress{
  1848  				ObjectMeta: metav1.ObjectMeta{
  1849  					Name: "test-name",
  1850  				},
  1851  				Spec: networking.IPAddressSpec{
  1852  					ParentRef: &networking.ParentReference{
  1853  						Group:     "",
  1854  						Resource:  "services",
  1855  						Name:      "foo",
  1856  						Namespace: "bar",
  1857  					},
  1858  				},
  1859  			},
  1860  		},
  1861  		"empty-ipaddress-bad-name-no-parent-reference": {
  1862  			expectedErrors: 2,
  1863  			ipAddress: &networking.IPAddress{
  1864  				ObjectMeta: metav1.ObjectMeta{
  1865  					Name: "test-name",
  1866  				},
  1867  			},
  1868  		},
  1869  
  1870  		"good-ipaddress": {
  1871  			expectedErrors: 0,
  1872  			ipAddress: &networking.IPAddress{
  1873  				ObjectMeta: metav1.ObjectMeta{
  1874  					Name: "192.168.1.1",
  1875  				},
  1876  				Spec: networking.IPAddressSpec{
  1877  					ParentRef: &networking.ParentReference{
  1878  						Group:     "",
  1879  						Resource:  "services",
  1880  						Name:      "foo",
  1881  						Namespace: "bar",
  1882  					},
  1883  				},
  1884  			},
  1885  		},
  1886  		"good-ipaddress-gateway": {
  1887  			expectedErrors: 0,
  1888  			ipAddress: &networking.IPAddress{
  1889  				ObjectMeta: metav1.ObjectMeta{
  1890  					Name: "192.168.1.1",
  1891  				},
  1892  				Spec: networking.IPAddressSpec{
  1893  					ParentRef: &networking.ParentReference{
  1894  						Group:     "gateway.networking.k8s.io",
  1895  						Resource:  "gateway",
  1896  						Name:      "foo",
  1897  						Namespace: "bar",
  1898  					},
  1899  				},
  1900  			},
  1901  		},
  1902  		"good-ipv6address": {
  1903  			expectedErrors: 0,
  1904  			ipAddress: &networking.IPAddress{
  1905  				ObjectMeta: metav1.ObjectMeta{
  1906  					Name: "2001:4860:4860::8888",
  1907  				},
  1908  				Spec: networking.IPAddressSpec{
  1909  					ParentRef: &networking.ParentReference{
  1910  						Group:     "",
  1911  						Resource:  "services",
  1912  						Name:      "foo",
  1913  						Namespace: "bar",
  1914  					},
  1915  				},
  1916  			},
  1917  		},
  1918  		"non-canonica-ipv6address": {
  1919  			expectedErrors: 1,
  1920  			ipAddress: &networking.IPAddress{
  1921  				ObjectMeta: metav1.ObjectMeta{
  1922  					Name: "2001:4860:4860:0::8888",
  1923  				},
  1924  				Spec: networking.IPAddressSpec{
  1925  					ParentRef: &networking.ParentReference{
  1926  						Group:     "",
  1927  						Resource:  "services",
  1928  						Name:      "foo",
  1929  						Namespace: "bar",
  1930  					},
  1931  				},
  1932  			},
  1933  		},
  1934  		"missing-ipaddress-reference": {
  1935  			expectedErrors: 1,
  1936  			ipAddress: &networking.IPAddress{
  1937  				ObjectMeta: metav1.ObjectMeta{
  1938  					Name: "192.168.1.1",
  1939  				},
  1940  			},
  1941  		},
  1942  		"wrong-ipaddress-reference": {
  1943  			expectedErrors: 1,
  1944  			ipAddress: &networking.IPAddress{
  1945  				ObjectMeta: metav1.ObjectMeta{
  1946  					Name: "192.168.1.1",
  1947  				},
  1948  				Spec: networking.IPAddressSpec{
  1949  					ParentRef: &networking.ParentReference{
  1950  						Group:     "custom.resource.com",
  1951  						Resource:  "services",
  1952  						Name:      "foo$%&",
  1953  						Namespace: "",
  1954  					},
  1955  				},
  1956  			},
  1957  		},
  1958  		"wrong-ipaddress-reference-multiple-errors": {
  1959  			expectedErrors: 4,
  1960  			ipAddress: &networking.IPAddress{
  1961  				ObjectMeta: metav1.ObjectMeta{
  1962  					Name: "192.168.1.1",
  1963  				},
  1964  				Spec: networking.IPAddressSpec{
  1965  					ParentRef: &networking.ParentReference{
  1966  						Group:     ".cust@m.resource.com",
  1967  						Resource:  "",
  1968  						Name:      "",
  1969  						Namespace: "bar$$$$$%@",
  1970  					},
  1971  				},
  1972  			},
  1973  		},
  1974  	}
  1975  
  1976  	for name, testCase := range testCases {
  1977  		t.Run(name, func(t *testing.T) {
  1978  			errs := ValidateIPAddress(testCase.ipAddress)
  1979  			if len(errs) != testCase.expectedErrors {
  1980  				t.Errorf("Expected %d errors, got %d errors: %v", testCase.expectedErrors, len(errs), errs)
  1981  			}
  1982  		})
  1983  	}
  1984  }
  1985  
  1986  func TestValidateIPAddressUpdate(t *testing.T) {
  1987  	old := &networking.IPAddress{
  1988  		ObjectMeta: metav1.ObjectMeta{
  1989  			Name:            "192.168.1.1",
  1990  			ResourceVersion: "1",
  1991  		},
  1992  		Spec: networking.IPAddressSpec{
  1993  			ParentRef: &networking.ParentReference{
  1994  				Group:     "custom.resource.com",
  1995  				Resource:  "services",
  1996  				Name:      "foo",
  1997  				Namespace: "bar",
  1998  			},
  1999  		},
  2000  	}
  2001  
  2002  	testCases := []struct {
  2003  		name      string
  2004  		new       func(svc *networking.IPAddress) *networking.IPAddress
  2005  		expectErr bool
  2006  	}{{
  2007  		name: "Successful update, no changes",
  2008  		new: func(old *networking.IPAddress) *networking.IPAddress {
  2009  			out := old.DeepCopy()
  2010  			return out
  2011  		},
  2012  		expectErr: false,
  2013  	},
  2014  
  2015  		{
  2016  			name: "Failed update, update spec.ParentRef",
  2017  			new: func(svc *networking.IPAddress) *networking.IPAddress {
  2018  				out := svc.DeepCopy()
  2019  				out.Spec.ParentRef = &networking.ParentReference{
  2020  					Group:     "custom.resource.com",
  2021  					Resource:  "Gateway",
  2022  					Name:      "foo",
  2023  					Namespace: "bar",
  2024  				}
  2025  
  2026  				return out
  2027  			}, expectErr: true,
  2028  		}, {
  2029  			name: "Failed update, delete spec.ParentRef",
  2030  			new: func(svc *networking.IPAddress) *networking.IPAddress {
  2031  				out := svc.DeepCopy()
  2032  				out.Spec.ParentRef = nil
  2033  				return out
  2034  			}, expectErr: true,
  2035  		},
  2036  	}
  2037  	for _, testCase := range testCases {
  2038  		t.Run(testCase.name, func(t *testing.T) {
  2039  			err := ValidateIPAddressUpdate(testCase.new(old), old)
  2040  			if !testCase.expectErr && err != nil {
  2041  				t.Errorf("ValidateIPAddressUpdate must be successful for test '%s', got %v", testCase.name, err)
  2042  			}
  2043  			if testCase.expectErr && err == nil {
  2044  				t.Errorf("ValidateIPAddressUpdate must return error for test: %s, but got nil", testCase.name)
  2045  			}
  2046  		})
  2047  	}
  2048  }
  2049  
  2050  func TestValidateServiceCIDR(t *testing.T) {
  2051  
  2052  	testCases := map[string]struct {
  2053  		expectedErrors int
  2054  		ipRange        *networking.ServiceCIDR
  2055  	}{
  2056  		"empty-iprange": {
  2057  			expectedErrors: 1,
  2058  			ipRange: &networking.ServiceCIDR{
  2059  				ObjectMeta: metav1.ObjectMeta{
  2060  					Name: "test-name",
  2061  				},
  2062  			},
  2063  		},
  2064  		"three-ipranges": {
  2065  			expectedErrors: 1,
  2066  			ipRange: &networking.ServiceCIDR{
  2067  				ObjectMeta: metav1.ObjectMeta{
  2068  					Name: "test-name",
  2069  				},
  2070  				Spec: networking.ServiceCIDRSpec{
  2071  					CIDRs: []string{"192.168.0.0/24", "fd00::/64", "10.0.0.0/16"},
  2072  				},
  2073  			},
  2074  		},
  2075  		"good-iprange-ipv4": {
  2076  			expectedErrors: 0,
  2077  			ipRange: &networking.ServiceCIDR{
  2078  				ObjectMeta: metav1.ObjectMeta{
  2079  					Name: "test-name",
  2080  				},
  2081  				Spec: networking.ServiceCIDRSpec{
  2082  					CIDRs: []string{"192.168.0.0/24"},
  2083  				},
  2084  			},
  2085  		},
  2086  		"good-iprange-ipv6": {
  2087  			expectedErrors: 0,
  2088  			ipRange: &networking.ServiceCIDR{
  2089  				ObjectMeta: metav1.ObjectMeta{
  2090  					Name: "test-name",
  2091  				},
  2092  				Spec: networking.ServiceCIDRSpec{
  2093  					CIDRs: []string{"fd00:1234::/64"},
  2094  				},
  2095  			},
  2096  		},
  2097  		"good-iprange-ipv4-ipv6": {
  2098  			expectedErrors: 0,
  2099  			ipRange: &networking.ServiceCIDR{
  2100  				ObjectMeta: metav1.ObjectMeta{
  2101  					Name: "test-name",
  2102  				},
  2103  				Spec: networking.ServiceCIDRSpec{
  2104  					CIDRs: []string{"192.168.0.0/24", "fd00:1234::/64"},
  2105  				},
  2106  			},
  2107  		},
  2108  		"not-iprange-ipv4": {
  2109  			expectedErrors: 1,
  2110  			ipRange: &networking.ServiceCIDR{
  2111  				ObjectMeta: metav1.ObjectMeta{
  2112  					Name: "test-name",
  2113  				},
  2114  				Spec: networking.ServiceCIDRSpec{
  2115  					CIDRs: []string{"asdasdasd"},
  2116  				},
  2117  			},
  2118  		},
  2119  		"iponly-iprange-ipv4": {
  2120  			expectedErrors: 1,
  2121  			ipRange: &networking.ServiceCIDR{
  2122  				ObjectMeta: metav1.ObjectMeta{
  2123  					Name: "test-name",
  2124  				},
  2125  				Spec: networking.ServiceCIDRSpec{
  2126  					CIDRs: []string{"192.168.0.1"},
  2127  				},
  2128  			},
  2129  		},
  2130  		"badip-iprange-ipv4": {
  2131  			expectedErrors: 1,
  2132  			ipRange: &networking.ServiceCIDR{
  2133  				ObjectMeta: metav1.ObjectMeta{
  2134  					Name: "test-name",
  2135  				},
  2136  				Spec: networking.ServiceCIDRSpec{
  2137  					CIDRs: []string{"192.168.0.1/24"},
  2138  				},
  2139  			},
  2140  		},
  2141  		"badip-iprange-ipv6": {
  2142  			expectedErrors: 1,
  2143  			ipRange: &networking.ServiceCIDR{
  2144  				ObjectMeta: metav1.ObjectMeta{
  2145  					Name: "test-name",
  2146  				},
  2147  				Spec: networking.ServiceCIDRSpec{
  2148  					CIDRs: []string{"fd00:1234::2/64"},
  2149  				},
  2150  			},
  2151  		},
  2152  		"badip-iprange-caps-ipv6": {
  2153  			expectedErrors: 2,
  2154  			ipRange: &networking.ServiceCIDR{
  2155  				ObjectMeta: metav1.ObjectMeta{
  2156  					Name: "test-name",
  2157  				},
  2158  				Spec: networking.ServiceCIDRSpec{
  2159  					CIDRs: []string{"FD00:1234::2/64"},
  2160  				},
  2161  			},
  2162  		},
  2163  		"good-iprange-ipv4-bad-ipv6": {
  2164  			expectedErrors: 1,
  2165  			ipRange: &networking.ServiceCIDR{
  2166  				ObjectMeta: metav1.ObjectMeta{
  2167  					Name: "test-name",
  2168  				},
  2169  				Spec: networking.ServiceCIDRSpec{
  2170  					CIDRs: []string{"192.168.0.0/24", "FD00:1234::/64"},
  2171  				},
  2172  			},
  2173  		},
  2174  		"good-iprange-ipv6-bad-ipv4": {
  2175  			expectedErrors: 1,
  2176  			ipRange: &networking.ServiceCIDR{
  2177  				ObjectMeta: metav1.ObjectMeta{
  2178  					Name: "test-name",
  2179  				},
  2180  				Spec: networking.ServiceCIDRSpec{
  2181  					CIDRs: []string{"192.168.007.0/24", "fd00:1234::/64"},
  2182  				},
  2183  			},
  2184  		},
  2185  	}
  2186  
  2187  	for name, testCase := range testCases {
  2188  		t.Run(name, func(t *testing.T) {
  2189  			errs := ValidateServiceCIDR(testCase.ipRange)
  2190  			if len(errs) != testCase.expectedErrors {
  2191  				t.Errorf("Expected %d errors, got %d errors: %v", testCase.expectedErrors, len(errs), errs)
  2192  			}
  2193  		})
  2194  	}
  2195  }
  2196  
  2197  func TestValidateServiceCIDRUpdate(t *testing.T) {
  2198  	oldServiceCIDR := &networking.ServiceCIDR{
  2199  		ObjectMeta: metav1.ObjectMeta{
  2200  			Name:            "mysvc",
  2201  			ResourceVersion: "1",
  2202  		},
  2203  		Spec: networking.ServiceCIDRSpec{
  2204  			CIDRs: []string{"192.168.0.0/24", "fd00:1234::/64"},
  2205  		},
  2206  	}
  2207  
  2208  	testCases := []struct {
  2209  		name      string
  2210  		svc       func(svc *networking.ServiceCIDR) *networking.ServiceCIDR
  2211  		expectErr bool
  2212  	}{
  2213  		{
  2214  			name: "Successful update, no changes",
  2215  			svc: func(svc *networking.ServiceCIDR) *networking.ServiceCIDR {
  2216  				out := svc.DeepCopy()
  2217  				return out
  2218  			},
  2219  			expectErr: false,
  2220  		},
  2221  
  2222  		{
  2223  			name: "Failed update, update spec.CIDRs single stack",
  2224  			svc: func(svc *networking.ServiceCIDR) *networking.ServiceCIDR {
  2225  				out := svc.DeepCopy()
  2226  				out.Spec.CIDRs = []string{"10.0.0.0/16"}
  2227  				return out
  2228  			}, expectErr: true,
  2229  		},
  2230  		{
  2231  			name: "Failed update, update spec.CIDRs dual stack",
  2232  			svc: func(svc *networking.ServiceCIDR) *networking.ServiceCIDR {
  2233  				out := svc.DeepCopy()
  2234  				out.Spec.CIDRs = []string{"10.0.0.0/24", "fd00:1234::/64"}
  2235  				return out
  2236  			}, expectErr: true,
  2237  		},
  2238  	}
  2239  	for _, testCase := range testCases {
  2240  		t.Run(testCase.name, func(t *testing.T) {
  2241  			err := ValidateServiceCIDRUpdate(testCase.svc(oldServiceCIDR), oldServiceCIDR)
  2242  			if !testCase.expectErr && err != nil {
  2243  				t.Errorf("ValidateServiceCIDRUpdate must be successful for test '%s', got %v", testCase.name, err)
  2244  			}
  2245  			if testCase.expectErr && err == nil {
  2246  				t.Errorf("ValidateServiceCIDRUpdate must return error for test: %s, but got nil", testCase.name)
  2247  			}
  2248  		})
  2249  	}
  2250  }
  2251  

View as plain text