k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/apis/discovery/validation/validation_test.go (about)

     1  /*
     2  Copyright 2019 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  	corev1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	api "k8s.io/kubernetes/pkg/apis/core"
    27  	"k8s.io/kubernetes/pkg/apis/discovery"
    28  	utilpointer "k8s.io/utils/pointer"
    29  )
    30  
    31  func TestValidateEndpointSlice(t *testing.T) {
    32  	standardMeta := metav1.ObjectMeta{
    33  		Name:      "hello",
    34  		Namespace: "world",
    35  	}
    36  
    37  	testCases := map[string]struct {
    38  		expectedErrors int
    39  		endpointSlice  *discovery.EndpointSlice
    40  	}{
    41  		"good-slice": {
    42  			expectedErrors: 0,
    43  			endpointSlice: &discovery.EndpointSlice{
    44  				ObjectMeta:  standardMeta,
    45  				AddressType: discovery.AddressTypeIPv4,
    46  				Ports: []discovery.EndpointPort{{
    47  					Name:     utilpointer.String("http"),
    48  					Protocol: protocolPtr(api.ProtocolTCP),
    49  				}},
    50  				Endpoints: []discovery.Endpoint{{
    51  					Addresses: generateIPAddresses(1),
    52  					Hostname:  utilpointer.String("valid-123"),
    53  				}},
    54  			},
    55  		},
    56  		"good-ipv6": {
    57  			expectedErrors: 0,
    58  			endpointSlice: &discovery.EndpointSlice{
    59  				ObjectMeta:  standardMeta,
    60  				AddressType: discovery.AddressTypeIPv6,
    61  				Ports: []discovery.EndpointPort{{
    62  					Name:     utilpointer.String("http"),
    63  					Protocol: protocolPtr(api.ProtocolTCP),
    64  				}},
    65  				Endpoints: []discovery.Endpoint{{
    66  					Addresses: []string{"a00:100::4"},
    67  					Hostname:  utilpointer.String("valid-123"),
    68  				}},
    69  			},
    70  		},
    71  		"good-fqdns": {
    72  			expectedErrors: 0,
    73  			endpointSlice: &discovery.EndpointSlice{
    74  				ObjectMeta:  standardMeta,
    75  				AddressType: discovery.AddressTypeFQDN,
    76  				Ports: []discovery.EndpointPort{{
    77  					Name:     utilpointer.String("http"),
    78  					Protocol: protocolPtr(api.ProtocolTCP),
    79  				}},
    80  				Endpoints: []discovery.Endpoint{{
    81  					Addresses: []string{"foo.example.com", "example.com", "example.com.", "hyphens-are-good.example.com"},
    82  					Hostname:  utilpointer.String("valid-123"),
    83  				}},
    84  			},
    85  		},
    86  		"all-protocols": {
    87  			expectedErrors: 0,
    88  			endpointSlice: &discovery.EndpointSlice{
    89  				ObjectMeta:  standardMeta,
    90  				AddressType: discovery.AddressTypeIPv4,
    91  				Ports: []discovery.EndpointPort{{
    92  					Name:     utilpointer.String("tcp"),
    93  					Protocol: protocolPtr(api.ProtocolTCP),
    94  				}, {
    95  					Name:     utilpointer.String("udp"),
    96  					Protocol: protocolPtr(api.ProtocolUDP),
    97  				}, {
    98  					Name:     utilpointer.String("sctp"),
    99  					Protocol: protocolPtr(api.ProtocolSCTP),
   100  				}},
   101  				Endpoints: []discovery.Endpoint{{
   102  					Addresses: generateIPAddresses(1),
   103  					Hostname:  utilpointer.String("valid-123"),
   104  				}},
   105  			},
   106  		},
   107  		"app-protocols": {
   108  			expectedErrors: 0,
   109  			endpointSlice: &discovery.EndpointSlice{
   110  				ObjectMeta:  standardMeta,
   111  				AddressType: discovery.AddressTypeIPv4,
   112  				Ports: []discovery.EndpointPort{{
   113  					Name:        utilpointer.String("one"),
   114  					Protocol:    protocolPtr(api.ProtocolTCP),
   115  					AppProtocol: utilpointer.String("HTTP"),
   116  				}, {
   117  					Name:        utilpointer.String("two"),
   118  					Protocol:    protocolPtr(api.ProtocolTCP),
   119  					AppProtocol: utilpointer.String("https"),
   120  				}, {
   121  					Name:        utilpointer.String("three"),
   122  					Protocol:    protocolPtr(api.ProtocolTCP),
   123  					AppProtocol: utilpointer.String("my-protocol"),
   124  				}, {
   125  					Name:        utilpointer.String("four"),
   126  					Protocol:    protocolPtr(api.ProtocolTCP),
   127  					AppProtocol: utilpointer.String("example.com/custom-protocol"),
   128  				}},
   129  				Endpoints: []discovery.Endpoint{{
   130  					Addresses: generateIPAddresses(1),
   131  					Hostname:  utilpointer.String("valid-123"),
   132  				}},
   133  			},
   134  		},
   135  		"empty-port-name": {
   136  			expectedErrors: 0,
   137  			endpointSlice: &discovery.EndpointSlice{
   138  				ObjectMeta:  standardMeta,
   139  				AddressType: discovery.AddressTypeIPv4,
   140  				Ports: []discovery.EndpointPort{{
   141  					Name:     utilpointer.String(""),
   142  					Protocol: protocolPtr(api.ProtocolTCP),
   143  				}, {
   144  					Name:     utilpointer.String("http"),
   145  					Protocol: protocolPtr(api.ProtocolTCP),
   146  				}},
   147  				Endpoints: []discovery.Endpoint{{
   148  					Addresses: generateIPAddresses(1),
   149  				}},
   150  			},
   151  		},
   152  		"long-port-name": {
   153  			expectedErrors: 0,
   154  			endpointSlice: &discovery.EndpointSlice{
   155  				ObjectMeta:  standardMeta,
   156  				AddressType: discovery.AddressTypeIPv4,
   157  				Ports: []discovery.EndpointPort{{
   158  					Name:     utilpointer.String(strings.Repeat("a", 63)),
   159  					Protocol: protocolPtr(api.ProtocolTCP),
   160  				}},
   161  				Endpoints: []discovery.Endpoint{{
   162  					Addresses: generateIPAddresses(1),
   163  				}},
   164  			},
   165  		},
   166  		"empty-ports-and-endpoints": {
   167  			expectedErrors: 0,
   168  			endpointSlice: &discovery.EndpointSlice{
   169  				ObjectMeta:  standardMeta,
   170  				AddressType: discovery.AddressTypeIPv4,
   171  				Ports:       []discovery.EndpointPort{},
   172  				Endpoints:   []discovery.Endpoint{},
   173  			},
   174  		},
   175  		"max-endpoints": {
   176  			expectedErrors: 0,
   177  			endpointSlice: &discovery.EndpointSlice{
   178  				ObjectMeta:  standardMeta,
   179  				AddressType: discovery.AddressTypeIPv4,
   180  				Ports:       generatePorts(1),
   181  				Endpoints:   generateEndpoints(maxEndpoints),
   182  			},
   183  		},
   184  		"max-ports": {
   185  			expectedErrors: 0,
   186  			endpointSlice: &discovery.EndpointSlice{
   187  				ObjectMeta:  standardMeta,
   188  				AddressType: discovery.AddressTypeIPv4,
   189  				Ports:       generatePorts(maxPorts),
   190  			},
   191  		},
   192  		"max-addresses": {
   193  			expectedErrors: 0,
   194  			endpointSlice: &discovery.EndpointSlice{
   195  				ObjectMeta:  standardMeta,
   196  				AddressType: discovery.AddressTypeIPv4,
   197  				Ports: []discovery.EndpointPort{{
   198  					Name:     utilpointer.String("http"),
   199  					Protocol: protocolPtr(api.ProtocolTCP),
   200  				}},
   201  				Endpoints: []discovery.Endpoint{{
   202  					Addresses: generateIPAddresses(maxAddresses),
   203  				}},
   204  			},
   205  		},
   206  		"max-topology-keys": {
   207  			expectedErrors: 0,
   208  			endpointSlice: &discovery.EndpointSlice{
   209  				ObjectMeta:  standardMeta,
   210  				AddressType: discovery.AddressTypeIPv4,
   211  				Ports: []discovery.EndpointPort{{
   212  					Name:     utilpointer.String("http"),
   213  					Protocol: protocolPtr(api.ProtocolTCP),
   214  				}},
   215  				Endpoints: []discovery.Endpoint{{
   216  					Addresses:          generateIPAddresses(1),
   217  					DeprecatedTopology: generateTopology(maxTopologyLabels),
   218  				}},
   219  			},
   220  		},
   221  		"valid-hints": {
   222  			expectedErrors: 0,
   223  			endpointSlice: &discovery.EndpointSlice{
   224  				ObjectMeta:  standardMeta,
   225  				AddressType: discovery.AddressTypeIPv4,
   226  				Ports: []discovery.EndpointPort{{
   227  					Name:     utilpointer.String("http"),
   228  					Protocol: protocolPtr(api.ProtocolTCP),
   229  				}},
   230  				Endpoints: []discovery.Endpoint{{
   231  					Addresses: generateIPAddresses(1),
   232  					Hints: &discovery.EndpointHints{
   233  						ForZones: []discovery.ForZone{{Name: "zone-a"}},
   234  					},
   235  				}},
   236  			},
   237  		},
   238  
   239  		// expected failures
   240  		"duplicate-port-name": {
   241  			expectedErrors: 1,
   242  			endpointSlice: &discovery.EndpointSlice{
   243  				ObjectMeta:  standardMeta,
   244  				AddressType: discovery.AddressTypeIPv4,
   245  				Ports: []discovery.EndpointPort{{
   246  					Name:     utilpointer.String(""),
   247  					Protocol: protocolPtr(api.ProtocolTCP),
   248  				}, {
   249  					Name:     utilpointer.String(""),
   250  					Protocol: protocolPtr(api.ProtocolTCP),
   251  				}},
   252  				Endpoints: []discovery.Endpoint{},
   253  			},
   254  		},
   255  		"bad-port-name-caps": {
   256  			expectedErrors: 1,
   257  			endpointSlice: &discovery.EndpointSlice{
   258  				ObjectMeta:  standardMeta,
   259  				AddressType: discovery.AddressTypeIPv4,
   260  				Ports: []discovery.EndpointPort{{
   261  					Name:     utilpointer.String("aCapital"),
   262  					Protocol: protocolPtr(api.ProtocolTCP),
   263  				}},
   264  				Endpoints: []discovery.Endpoint{},
   265  			},
   266  		},
   267  		"bad-port-name-chars": {
   268  			expectedErrors: 1,
   269  			endpointSlice: &discovery.EndpointSlice{
   270  				ObjectMeta:  standardMeta,
   271  				AddressType: discovery.AddressTypeIPv4,
   272  				Ports: []discovery.EndpointPort{{
   273  					Name:     utilpointer.String("almost_valid"),
   274  					Protocol: protocolPtr(api.ProtocolTCP),
   275  				}},
   276  				Endpoints: []discovery.Endpoint{},
   277  			},
   278  		},
   279  		"bad-port-name-length": {
   280  			expectedErrors: 1,
   281  			endpointSlice: &discovery.EndpointSlice{
   282  				ObjectMeta:  standardMeta,
   283  				AddressType: discovery.AddressTypeIPv4,
   284  				Ports: []discovery.EndpointPort{{
   285  					Name:     utilpointer.String(strings.Repeat("a", 64)),
   286  					Protocol: protocolPtr(api.ProtocolTCP),
   287  				}},
   288  				Endpoints: []discovery.Endpoint{},
   289  			},
   290  		},
   291  		"invalid-port-protocol": {
   292  			expectedErrors: 1,
   293  			endpointSlice: &discovery.EndpointSlice{
   294  				ObjectMeta:  standardMeta,
   295  				AddressType: discovery.AddressTypeIPv4,
   296  				Ports: []discovery.EndpointPort{{
   297  					Name:     utilpointer.String("http"),
   298  					Protocol: protocolPtr(api.Protocol("foo")),
   299  				}},
   300  			},
   301  		},
   302  		"too-many-ports": {
   303  			expectedErrors: 1,
   304  			endpointSlice: &discovery.EndpointSlice{
   305  				ObjectMeta:  standardMeta,
   306  				AddressType: discovery.AddressTypeIPv4,
   307  				Ports:       generatePorts(maxPorts + 1),
   308  			},
   309  		},
   310  		"too-many-endpoints": {
   311  			expectedErrors: 1,
   312  			endpointSlice: &discovery.EndpointSlice{
   313  				ObjectMeta:  standardMeta,
   314  				AddressType: discovery.AddressTypeIPv4,
   315  				Ports:       generatePorts(1),
   316  				Endpoints:   generateEndpoints(maxEndpoints + 1),
   317  			},
   318  		},
   319  		"no-endpoint-addresses": {
   320  			expectedErrors: 1,
   321  			endpointSlice: &discovery.EndpointSlice{
   322  				ObjectMeta:  standardMeta,
   323  				AddressType: discovery.AddressTypeIPv4,
   324  				Ports: []discovery.EndpointPort{{
   325  					Name:     utilpointer.String("http"),
   326  					Protocol: protocolPtr(api.ProtocolTCP),
   327  				}},
   328  				Endpoints: []discovery.Endpoint{{
   329  					Addresses: generateIPAddresses(0),
   330  				}},
   331  			},
   332  		},
   333  		"too-many-addresses": {
   334  			expectedErrors: 1,
   335  			endpointSlice: &discovery.EndpointSlice{
   336  				ObjectMeta:  standardMeta,
   337  				AddressType: discovery.AddressTypeIPv4,
   338  				Ports: []discovery.EndpointPort{{
   339  					Name:     utilpointer.String("http"),
   340  					Protocol: protocolPtr(api.ProtocolTCP),
   341  				}},
   342  				Endpoints: []discovery.Endpoint{{
   343  					Addresses: generateIPAddresses(maxAddresses + 1),
   344  				}},
   345  			},
   346  		},
   347  		"bad-topology-key": {
   348  			expectedErrors: 1,
   349  			endpointSlice: &discovery.EndpointSlice{
   350  				ObjectMeta:  standardMeta,
   351  				AddressType: discovery.AddressTypeIPv4,
   352  				Ports: []discovery.EndpointPort{{
   353  					Name:     utilpointer.String("http"),
   354  					Protocol: protocolPtr(api.ProtocolTCP),
   355  				}},
   356  				Endpoints: []discovery.Endpoint{{
   357  					Addresses:          generateIPAddresses(1),
   358  					DeprecatedTopology: map[string]string{"--INVALID": "example"},
   359  				}},
   360  			},
   361  		},
   362  		"too-many-topology-keys": {
   363  			expectedErrors: 1,
   364  			endpointSlice: &discovery.EndpointSlice{
   365  				ObjectMeta:  standardMeta,
   366  				AddressType: discovery.AddressTypeIPv4,
   367  				Ports: []discovery.EndpointPort{{
   368  					Name:     utilpointer.String("http"),
   369  					Protocol: protocolPtr(api.ProtocolTCP),
   370  				}},
   371  				Endpoints: []discovery.Endpoint{{
   372  					Addresses:          generateIPAddresses(1),
   373  					DeprecatedTopology: generateTopology(maxTopologyLabels + 1),
   374  				}},
   375  			},
   376  		},
   377  		"bad-hostname": {
   378  			expectedErrors: 1,
   379  			endpointSlice: &discovery.EndpointSlice{
   380  				ObjectMeta:  standardMeta,
   381  				AddressType: discovery.AddressTypeIPv4,
   382  				Ports: []discovery.EndpointPort{{
   383  					Name:     utilpointer.String("http"),
   384  					Protocol: protocolPtr(api.ProtocolTCP),
   385  				}},
   386  				Endpoints: []discovery.Endpoint{{
   387  					Addresses: generateIPAddresses(1),
   388  					Hostname:  utilpointer.String("--INVALID"),
   389  				}},
   390  			},
   391  		},
   392  		"bad-meta": {
   393  			expectedErrors: 1,
   394  			endpointSlice: &discovery.EndpointSlice{
   395  				ObjectMeta: metav1.ObjectMeta{
   396  					Name:      "*&^",
   397  					Namespace: "foo",
   398  				},
   399  				AddressType: discovery.AddressTypeIPv4,
   400  				Ports: []discovery.EndpointPort{{
   401  					Name:     utilpointer.String("http"),
   402  					Protocol: protocolPtr(api.ProtocolTCP),
   403  				}},
   404  				Endpoints: []discovery.Endpoint{{
   405  					Addresses: generateIPAddresses(1),
   406  					Hostname:  utilpointer.String("valid-123"),
   407  				}},
   408  			},
   409  		},
   410  		"bad-ip": {
   411  			expectedErrors: 2,
   412  			endpointSlice: &discovery.EndpointSlice{
   413  				ObjectMeta:  standardMeta,
   414  				AddressType: discovery.AddressTypeIPv4,
   415  				Ports: []discovery.EndpointPort{{
   416  					Name:     utilpointer.String("http"),
   417  					Protocol: protocolPtr(api.ProtocolTCP),
   418  				}},
   419  				Endpoints: []discovery.Endpoint{{
   420  					Addresses: []string{"123.456.789.012"},
   421  					Hostname:  utilpointer.String("valid-123"),
   422  				}},
   423  			},
   424  		},
   425  		"bad-ipv4": {
   426  			expectedErrors: 3,
   427  			endpointSlice: &discovery.EndpointSlice{
   428  				ObjectMeta:  standardMeta,
   429  				AddressType: discovery.AddressTypeIPv4,
   430  				Ports: []discovery.EndpointPort{{
   431  					Name:     utilpointer.String("http"),
   432  					Protocol: protocolPtr(api.ProtocolTCP),
   433  				}},
   434  				Endpoints: []discovery.Endpoint{{
   435  					Addresses: []string{"123.456.789.012", "2001:4860:4860::8888"},
   436  					Hostname:  utilpointer.String("valid-123"),
   437  				}},
   438  			},
   439  		},
   440  		"bad-ipv6": {
   441  			expectedErrors: 4,
   442  			endpointSlice: &discovery.EndpointSlice{
   443  				ObjectMeta:  standardMeta,
   444  				AddressType: discovery.AddressTypeIPv6,
   445  				Ports: []discovery.EndpointPort{{
   446  					Name:     utilpointer.String("http"),
   447  					Protocol: protocolPtr(api.ProtocolTCP),
   448  				}},
   449  				Endpoints: []discovery.Endpoint{{
   450  					Addresses: []string{"123.456.789.012", "2001:4860:4860:defg"},
   451  					Hostname:  utilpointer.String("valid-123"),
   452  				}},
   453  			},
   454  		},
   455  		"bad-fqdns": {
   456  			expectedErrors: 4,
   457  			endpointSlice: &discovery.EndpointSlice{
   458  				ObjectMeta:  standardMeta,
   459  				AddressType: discovery.AddressTypeFQDN,
   460  				Ports: []discovery.EndpointPort{{
   461  					Name:     utilpointer.String("http"),
   462  					Protocol: protocolPtr(api.ProtocolTCP),
   463  				}},
   464  				Endpoints: []discovery.Endpoint{{
   465  					Addresses: []string{"foo.*", "FOO.example.com", "underscores_are_bad.example.com", "*.example.com"},
   466  					Hostname:  utilpointer.String("valid-123"),
   467  				}},
   468  			},
   469  		},
   470  		"bad-app-protocol": {
   471  			expectedErrors: 1,
   472  			endpointSlice: &discovery.EndpointSlice{
   473  				ObjectMeta:  standardMeta,
   474  				AddressType: discovery.AddressTypeIPv4,
   475  				Ports: []discovery.EndpointPort{{
   476  					Name:        utilpointer.String("http"),
   477  					Protocol:    protocolPtr(api.ProtocolTCP),
   478  					AppProtocol: utilpointer.String("--"),
   479  				}},
   480  				Endpoints: []discovery.Endpoint{{
   481  					Addresses: generateIPAddresses(1),
   482  					Hostname:  utilpointer.String("valid-123"),
   483  				}},
   484  			},
   485  		},
   486  		"invalid-hints": {
   487  			expectedErrors: 1,
   488  			endpointSlice: &discovery.EndpointSlice{
   489  				ObjectMeta:  standardMeta,
   490  				AddressType: discovery.AddressTypeIPv4,
   491  				Ports: []discovery.EndpointPort{{
   492  					Name:     utilpointer.String("http"),
   493  					Protocol: protocolPtr(api.ProtocolTCP),
   494  				}},
   495  				Endpoints: []discovery.Endpoint{{
   496  					Addresses: generateIPAddresses(1),
   497  					Hints: &discovery.EndpointHints{
   498  						ForZones: []discovery.ForZone{{Name: "inv@lid"}},
   499  					},
   500  				}},
   501  			},
   502  		},
   503  		"overlapping-hints": {
   504  			expectedErrors: 1,
   505  			endpointSlice: &discovery.EndpointSlice{
   506  				ObjectMeta:  standardMeta,
   507  				AddressType: discovery.AddressTypeIPv4,
   508  				Ports: []discovery.EndpointPort{{
   509  					Name:     utilpointer.String("http"),
   510  					Protocol: protocolPtr(api.ProtocolTCP),
   511  				}},
   512  				Endpoints: []discovery.Endpoint{{
   513  					Addresses: generateIPAddresses(1),
   514  					Hints: &discovery.EndpointHints{
   515  						ForZones: []discovery.ForZone{
   516  							{Name: "zone-a"},
   517  							{Name: "zone-b"},
   518  							{Name: "zone-a"},
   519  						},
   520  					},
   521  				}},
   522  			},
   523  		},
   524  		"too-many-hints": {
   525  			expectedErrors: 1,
   526  			endpointSlice: &discovery.EndpointSlice{
   527  				ObjectMeta:  standardMeta,
   528  				AddressType: discovery.AddressTypeIPv4,
   529  				Ports: []discovery.EndpointPort{{
   530  					Name:     utilpointer.String("http"),
   531  					Protocol: protocolPtr(api.ProtocolTCP),
   532  				}},
   533  				Endpoints: []discovery.Endpoint{{
   534  					Addresses: generateIPAddresses(1),
   535  					Hints: &discovery.EndpointHints{
   536  						ForZones: []discovery.ForZone{
   537  							{Name: "zone-a"},
   538  							{Name: "zone-b"},
   539  							{Name: "zone-c"},
   540  							{Name: "zone-d"},
   541  							{Name: "zone-e"},
   542  							{Name: "zone-f"},
   543  							{Name: "zone-g"},
   544  							{Name: "zone-h"},
   545  							{Name: "zone-i"},
   546  						},
   547  					},
   548  				}},
   549  			},
   550  		},
   551  		"empty-everything": {
   552  			expectedErrors: 3,
   553  			endpointSlice:  &discovery.EndpointSlice{},
   554  		},
   555  		"zone-key-topology": {
   556  			expectedErrors: 1,
   557  			endpointSlice: &discovery.EndpointSlice{
   558  				ObjectMeta:  standardMeta,
   559  				AddressType: discovery.AddressTypeIPv4,
   560  				Ports: []discovery.EndpointPort{{
   561  					Name:     utilpointer.String("http"),
   562  					Protocol: protocolPtr(api.ProtocolTCP),
   563  				}},
   564  				Endpoints: []discovery.Endpoint{{
   565  					Addresses:          generateIPAddresses(1),
   566  					DeprecatedTopology: map[string]string{corev1.LabelTopologyZone: "zone1"},
   567  				}},
   568  			},
   569  		},
   570  		"special-ipv4": {
   571  			expectedErrors: 1,
   572  			endpointSlice: &discovery.EndpointSlice{
   573  				ObjectMeta:  standardMeta,
   574  				AddressType: discovery.AddressTypeIPv4,
   575  				Ports: []discovery.EndpointPort{{
   576  					Name:     utilpointer.String("http"),
   577  					Protocol: protocolPtr(api.ProtocolTCP),
   578  				}},
   579  				Endpoints: []discovery.Endpoint{{
   580  					Addresses: []string{"127.0.0.1"},
   581  					Hostname:  utilpointer.String("valid-123"),
   582  				}},
   583  			},
   584  		},
   585  		"special-ipv6": {
   586  			expectedErrors: 1,
   587  			endpointSlice: &discovery.EndpointSlice{
   588  				ObjectMeta:  standardMeta,
   589  				AddressType: discovery.AddressTypeIPv6,
   590  				Ports: []discovery.EndpointPort{{
   591  					Name:     utilpointer.String("http"),
   592  					Protocol: protocolPtr(api.ProtocolTCP),
   593  				}},
   594  				Endpoints: []discovery.Endpoint{{
   595  					Addresses: []string{"fe80::9656:d028:8652:66b6"},
   596  					Hostname:  utilpointer.String("valid-123"),
   597  				}},
   598  			},
   599  		},
   600  	}
   601  
   602  	for name, testCase := range testCases {
   603  		t.Run(name, func(t *testing.T) {
   604  			errs := ValidateEndpointSlice(testCase.endpointSlice)
   605  			if len(errs) != testCase.expectedErrors {
   606  				t.Errorf("Expected %d errors, got %d errors: %v", testCase.expectedErrors, len(errs), errs)
   607  			}
   608  		})
   609  	}
   610  }
   611  
   612  func TestValidateEndpointSliceCreate(t *testing.T) {
   613  	standardMeta := metav1.ObjectMeta{
   614  		Name:      "hello",
   615  		Namespace: "world",
   616  	}
   617  
   618  	testCases := map[string]struct {
   619  		expectedErrors      int
   620  		endpointSlice       *discovery.EndpointSlice
   621  		nodeNameGateEnabled bool
   622  	}{
   623  		"good-slice": {
   624  			expectedErrors: 0,
   625  			endpointSlice: &discovery.EndpointSlice{
   626  				ObjectMeta:  standardMeta,
   627  				AddressType: discovery.AddressTypeIPv4,
   628  				Ports: []discovery.EndpointPort{{
   629  					Name:     utilpointer.String("http"),
   630  					Protocol: protocolPtr(api.ProtocolTCP),
   631  				}},
   632  				Endpoints: []discovery.Endpoint{{
   633  					Addresses: generateIPAddresses(1),
   634  					Hostname:  utilpointer.String("valid-123"),
   635  				}},
   636  			},
   637  		},
   638  		"good-slice-node-name": {
   639  			expectedErrors: 0,
   640  			endpointSlice: &discovery.EndpointSlice{
   641  				ObjectMeta:  standardMeta,
   642  				AddressType: discovery.AddressTypeIPv4,
   643  				Ports: []discovery.EndpointPort{{
   644  					Name:     utilpointer.String("http"),
   645  					Protocol: protocolPtr(api.ProtocolTCP),
   646  				}},
   647  				Endpoints: []discovery.Endpoint{{
   648  					Addresses: generateIPAddresses(1),
   649  					Hostname:  utilpointer.String("valid-123"),
   650  					NodeName:  utilpointer.String("valid-node-name"),
   651  				}},
   652  			},
   653  		},
   654  
   655  		// expected failures
   656  		"bad-node-name": {
   657  			expectedErrors: 1,
   658  			endpointSlice: &discovery.EndpointSlice{
   659  				ObjectMeta:  standardMeta,
   660  				AddressType: discovery.AddressTypeIPv4,
   661  				Ports: []discovery.EndpointPort{{
   662  					Name:     utilpointer.String("http"),
   663  					Protocol: protocolPtr(api.ProtocolTCP),
   664  				}},
   665  				Endpoints: []discovery.Endpoint{{
   666  					Addresses: generateIPAddresses(1),
   667  					Hostname:  utilpointer.String("valid-123"),
   668  					NodeName:  utilpointer.String("INvalid-node-name"),
   669  				}},
   670  			},
   671  		},
   672  		"deprecated-address-type": {
   673  			expectedErrors: 1,
   674  			endpointSlice: &discovery.EndpointSlice{
   675  				ObjectMeta:  standardMeta,
   676  				AddressType: discovery.AddressType("IP"),
   677  				Ports: []discovery.EndpointPort{{
   678  					Name:     utilpointer.String("http"),
   679  					Protocol: protocolPtr(api.ProtocolTCP),
   680  				}},
   681  				Endpoints: []discovery.Endpoint{{
   682  					Addresses: generateIPAddresses(1),
   683  				}},
   684  			},
   685  		},
   686  		"bad-address-type": {
   687  			expectedErrors: 1,
   688  			endpointSlice: &discovery.EndpointSlice{
   689  				ObjectMeta:  standardMeta,
   690  				AddressType: discovery.AddressType("other"),
   691  				Ports: []discovery.EndpointPort{{
   692  					Name:     utilpointer.String("http"),
   693  					Protocol: protocolPtr(api.ProtocolTCP),
   694  				}},
   695  				Endpoints: []discovery.Endpoint{{
   696  					Addresses: generateIPAddresses(1),
   697  				}},
   698  			},
   699  		},
   700  	}
   701  
   702  	for name, testCase := range testCases {
   703  		t.Run(name, func(t *testing.T) {
   704  			errs := ValidateEndpointSliceCreate(testCase.endpointSlice)
   705  			if len(errs) != testCase.expectedErrors {
   706  				t.Errorf("Expected %d errors, got %d errors: %v", testCase.expectedErrors, len(errs), errs)
   707  			}
   708  		})
   709  	}
   710  }
   711  
   712  func TestValidateEndpointSliceUpdate(t *testing.T) {
   713  	standardMeta := metav1.ObjectMeta{Name: "es1", Namespace: "test"}
   714  
   715  	testCases := map[string]struct {
   716  		expectedErrors      int
   717  		nodeNameGateEnabled bool
   718  		oldEndpointSlice    *discovery.EndpointSlice
   719  		newEndpointSlice    *discovery.EndpointSlice
   720  	}{
   721  		"valid and identical slices": {
   722  			oldEndpointSlice: &discovery.EndpointSlice{
   723  				ObjectMeta:  standardMeta,
   724  				AddressType: discovery.AddressTypeIPv6,
   725  			},
   726  			newEndpointSlice: &discovery.EndpointSlice{
   727  				ObjectMeta:  standardMeta,
   728  				AddressType: discovery.AddressTypeIPv6,
   729  			},
   730  			expectedErrors: 0,
   731  		},
   732  
   733  		// expected errors
   734  		"invalid node name set": {
   735  			oldEndpointSlice: &discovery.EndpointSlice{
   736  				ObjectMeta:  standardMeta,
   737  				AddressType: discovery.AddressTypeIPv4,
   738  				Endpoints: []discovery.Endpoint{{
   739  					Addresses: []string{"10.1.2.3"},
   740  				}},
   741  			},
   742  			newEndpointSlice: &discovery.EndpointSlice{
   743  				ObjectMeta:  standardMeta,
   744  				AddressType: discovery.AddressTypeIPv4,
   745  				Endpoints: []discovery.Endpoint{{
   746  					Addresses: []string{"10.1.2.3"},
   747  					NodeName:  utilpointer.String("INVALID foo"),
   748  				}},
   749  			},
   750  			expectedErrors: 1,
   751  		},
   752  
   753  		"deprecated address type": {
   754  			expectedErrors: 1,
   755  			oldEndpointSlice: &discovery.EndpointSlice{
   756  				ObjectMeta:  standardMeta,
   757  				AddressType: discovery.AddressType("IP"),
   758  			},
   759  			newEndpointSlice: &discovery.EndpointSlice{
   760  				ObjectMeta:  standardMeta,
   761  				AddressType: discovery.AddressType("IP"),
   762  			},
   763  		},
   764  		"valid and identical slices with different address types": {
   765  			oldEndpointSlice: &discovery.EndpointSlice{
   766  				ObjectMeta:  standardMeta,
   767  				AddressType: discovery.AddressType("other"),
   768  			},
   769  			newEndpointSlice: &discovery.EndpointSlice{
   770  				ObjectMeta:  standardMeta,
   771  				AddressType: discovery.AddressTypeIPv4,
   772  			},
   773  			expectedErrors: 1,
   774  		},
   775  		"invalid slices with valid address types": {
   776  			oldEndpointSlice: &discovery.EndpointSlice{
   777  				ObjectMeta:  standardMeta,
   778  				AddressType: discovery.AddressTypeIPv4,
   779  			},
   780  			newEndpointSlice: &discovery.EndpointSlice{
   781  				ObjectMeta:  standardMeta,
   782  				AddressType: discovery.AddressTypeIPv4,
   783  				Ports: []discovery.EndpointPort{{
   784  					Name:     utilpointer.String(""),
   785  					Protocol: protocolPtr(api.Protocol("invalid")),
   786  				}},
   787  			},
   788  			expectedErrors: 1,
   789  		},
   790  	}
   791  
   792  	for name, testCase := range testCases {
   793  		t.Run(name, func(t *testing.T) {
   794  			errs := ValidateEndpointSliceUpdate(testCase.newEndpointSlice, testCase.oldEndpointSlice)
   795  			if len(errs) != testCase.expectedErrors {
   796  				t.Errorf("Expected %d errors, got %d errors: %v", testCase.expectedErrors, len(errs), errs)
   797  			}
   798  		})
   799  	}
   800  }
   801  
   802  // Test helpers
   803  
   804  func protocolPtr(protocol api.Protocol) *api.Protocol {
   805  	return &protocol
   806  }
   807  
   808  func generatePorts(n int) []discovery.EndpointPort {
   809  	ports := []discovery.EndpointPort{}
   810  	for i := 0; i < n; i++ {
   811  		ports = append(ports, discovery.EndpointPort{
   812  			Name:     utilpointer.String(fmt.Sprintf("http-%d", i)),
   813  			Protocol: protocolPtr(api.ProtocolTCP),
   814  		})
   815  	}
   816  	return ports
   817  }
   818  
   819  func generateEndpoints(n int) []discovery.Endpoint {
   820  	endpoints := []discovery.Endpoint{}
   821  	for i := 0; i < n; i++ {
   822  		endpoints = append(endpoints, discovery.Endpoint{
   823  			Addresses: []string{fmt.Sprintf("10.1.2.%d", i%255)},
   824  		})
   825  	}
   826  	return endpoints
   827  }
   828  
   829  func generateIPAddresses(n int) []string {
   830  	addresses := []string{}
   831  	for i := 0; i < n; i++ {
   832  		addresses = append(addresses, fmt.Sprintf("10.1.2.%d", i%255))
   833  	}
   834  	return addresses
   835  }
   836  
   837  func generateTopology(n int) map[string]string {
   838  	topology := map[string]string{}
   839  	for i := 0; i < n; i++ {
   840  		topology[fmt.Sprintf("topology-%d", i)] = "example"
   841  	}
   842  	return topology
   843  }