k8s.io/kubernetes@v1.29.3/pkg/api/service/warnings_test.go (about)

     1  /*
     2  Copyright 2022 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 service
    18  
    19  import (
    20  	"reflect"
    21  	"testing"
    22  
    23  	"github.com/google/go-cmp/cmp"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/util/validation/field"
    26  	api "k8s.io/kubernetes/pkg/apis/core"
    27  )
    28  
    29  func TestGetWarningsForService(t *testing.T) {
    30  	testCases := []struct {
    31  		name        string
    32  		tweakSvc    func(svc *api.Service) // Given a basic valid service, each test case can customize it.
    33  		numWarnings int
    34  	}{{
    35  		name: "new topology mode set",
    36  		tweakSvc: func(s *api.Service) {
    37  			s.Annotations = map[string]string{api.AnnotationTopologyMode: "foo"}
    38  		},
    39  		numWarnings: 0,
    40  	}, {
    41  		name: "deprecated hints annotation set",
    42  		tweakSvc: func(s *api.Service) {
    43  			s.Annotations = map[string]string{api.DeprecatedAnnotationTopologyAwareHints: "foo"}
    44  		},
    45  		numWarnings: 1,
    46  	}, {
    47  		name: "externalIPs set when type is ExternalName",
    48  		tweakSvc: func(s *api.Service) {
    49  			s.Spec.Type = api.ServiceTypeExternalName
    50  			s.Spec.ExternalIPs = []string{"1.2.3.4"}
    51  		},
    52  		numWarnings: 1,
    53  	}, {
    54  		name: "externalName set when type is not ExternalName",
    55  		tweakSvc: func(s *api.Service) {
    56  			s.Spec.Type = api.ServiceTypeClusterIP
    57  			s.Spec.ExternalName = "example.com"
    58  		},
    59  		numWarnings: 1,
    60  	}}
    61  
    62  	for _, tc := range testCases {
    63  		t.Run(tc.name, func(t *testing.T) {
    64  			svc := &api.Service{}
    65  			tc.tweakSvc(svc)
    66  			warnings := GetWarningsForService(svc, svc)
    67  			if want, got := tc.numWarnings, len(warnings); got != want {
    68  				t.Errorf("Unexpected warning list: expected %d, got %d\n%q", want, got, warnings)
    69  			}
    70  		})
    71  	}
    72  }
    73  
    74  func TestGetWarningsForServiceClusterIPs(t *testing.T) {
    75  	service := func(clusterIPs []string) *api.Service {
    76  		svc := api.Service{
    77  			ObjectMeta: metav1.ObjectMeta{
    78  				Name:      "svc-test",
    79  				Namespace: "ns",
    80  			},
    81  			Spec: api.ServiceSpec{
    82  				Type: api.ServiceTypeClusterIP,
    83  			},
    84  		}
    85  
    86  		if len(clusterIPs) > 0 {
    87  			svc.Spec.ClusterIP = clusterIPs[0]
    88  			svc.Spec.ClusterIPs = clusterIPs
    89  		}
    90  		return &svc
    91  	}
    92  
    93  	tests := []struct {
    94  		name       string
    95  		service    *api.Service
    96  		oldService *api.Service
    97  		want       []string
    98  	}{
    99  		{
   100  			name:    "IPv4 No failures",
   101  			service: service([]string{"192.12.2.2"}),
   102  		},
   103  		{
   104  			name:    "IPv6 No failures",
   105  			service: service([]string{"2001:db8::2"}),
   106  		},
   107  		{
   108  			name:    "IPv4 with leading zeros",
   109  			service: service([]string{"192.012.2.2"}),
   110  			want: []string{
   111  				`spec.clusterIPs[0]: IP address was accepted, but will be invalid in a future Kubernetes release: ParseAddr("192.012.2.2"): IPv4 field has octet with leading zero`,
   112  			},
   113  		},
   114  		{
   115  			name:    "Dual Stack IPv4-IPv6 and IPv4 with leading zeros",
   116  			service: service([]string{"192.012.2.2", "2001:db8::2"}),
   117  			want: []string{
   118  				`spec.clusterIPs[0]: IP address was accepted, but will be invalid in a future Kubernetes release: ParseAddr("192.012.2.2"): IPv4 field has octet with leading zero`,
   119  			},
   120  		},
   121  		{
   122  			name:    "Dual Stack IPv6-IPv4 and IPv4 with leading zeros",
   123  			service: service([]string{"2001:db8::2", "192.012.2.2"}),
   124  			want: []string{
   125  				`spec.clusterIPs[1]: IP address was accepted, but will be invalid in a future Kubernetes release: ParseAddr("192.012.2.2"): IPv4 field has octet with leading zero`,
   126  			},
   127  		},
   128  		{
   129  			name:    "IPv6 non canonical format",
   130  			service: service([]string{"2001:db8:0:0::2"}),
   131  			want: []string{
   132  				`spec.clusterIPs[0]: IPv6 address "2001:db8:0:0::2" is not in RFC 5952 canonical format ("2001:db8::2"), which may cause controller apply-loops`,
   133  			},
   134  		},
   135  		{
   136  			name:    "Dual Stack IPv4-IPv6 and IPv6 non-canonical format",
   137  			service: service([]string{"192.12.2.2", "2001:db8:0:0::2"}),
   138  			want: []string{
   139  				`spec.clusterIPs[1]: IPv6 address "2001:db8:0:0::2" is not in RFC 5952 canonical format ("2001:db8::2"), which may cause controller apply-loops`,
   140  			},
   141  		},
   142  		{
   143  			name:    "Dual Stack IPv6-IPv4 and IPv6 non-canonical formats",
   144  			service: service([]string{"2001:db8:0:0::2", "192.12.2.2"}),
   145  			want: []string{
   146  				`spec.clusterIPs[0]: IPv6 address "2001:db8:0:0::2" is not in RFC 5952 canonical format ("2001:db8::2"), which may cause controller apply-loops`,
   147  			},
   148  		},
   149  		{
   150  			name:    "Dual Stack IPv4-IPv6 and IPv4 with leading zeros and IPv6 non-canonical format",
   151  			service: service([]string{"192.012.2.2", "2001:db8:0:0::2"}),
   152  			want: []string{
   153  				`spec.clusterIPs[0]: IP address was accepted, but will be invalid in a future Kubernetes release: ParseAddr("192.012.2.2"): IPv4 field has octet with leading zero`,
   154  				`spec.clusterIPs[1]: IPv6 address "2001:db8:0:0::2" is not in RFC 5952 canonical format ("2001:db8::2"), which may cause controller apply-loops`,
   155  			},
   156  		},
   157  		{
   158  			name:    "Dual Stack IPv6-IPv4 and IPv4 with leading zeros and IPv6 non-canonical format",
   159  			service: service([]string{"2001:db8:0:0::2", "192.012.2.2"}),
   160  			want: []string{
   161  				`spec.clusterIPs[0]: IPv6 address "2001:db8:0:0::2" is not in RFC 5952 canonical format ("2001:db8::2"), which may cause controller apply-loops`,
   162  				`spec.clusterIPs[1]: IP address was accepted, but will be invalid in a future Kubernetes release: ParseAddr("192.012.2.2"): IPv4 field has octet with leading zero`,
   163  			},
   164  		},
   165  		{
   166  			name: "Service with all IPs fields with errors",
   167  			service: &api.Service{
   168  				ObjectMeta: metav1.ObjectMeta{
   169  					Name:      "svc-test",
   170  					Namespace: "ns",
   171  				},
   172  				Spec: api.ServiceSpec{
   173  					ClusterIP:                "2001:db8:0:0::2",
   174  					ClusterIPs:               []string{"2001:db8:0:0::2", "192.012.2.2"},
   175  					ExternalIPs:              []string{"2001:db8:1:0::2", "10.012.2.2"},
   176  					LoadBalancerIP:           "10.001.1.1",
   177  					LoadBalancerSourceRanges: []string{"2001:db8:1:0::/64", "10.012.2.0/24"},
   178  					Type:                     api.ServiceTypeClusterIP,
   179  				},
   180  			},
   181  			want: []string{
   182  				`spec.clusterIPs[0]: IPv6 address "2001:db8:0:0::2" is not in RFC 5952 canonical format ("2001:db8::2"), which may cause controller apply-loops`,
   183  				`spec.clusterIPs[1]: IP address was accepted, but will be invalid in a future Kubernetes release: ParseAddr("192.012.2.2"): IPv4 field has octet with leading zero`,
   184  				`spec.externalIPs[0]: IPv6 address "2001:db8:1:0::2" is not in RFC 5952 canonical format ("2001:db8:1::2"), which may cause controller apply-loops`,
   185  				`spec.externalIPs[1]: IP address was accepted, but will be invalid in a future Kubernetes release: ParseAddr("10.012.2.2"): IPv4 field has octet with leading zero`,
   186  				`spec.loadBalancerIP: IP address was accepted, but will be invalid in a future Kubernetes release: ParseAddr("10.001.1.1"): IPv4 field has octet with leading zero`,
   187  				`spec.loadBalancerSourceRanges[0]: IPv6 prefix "2001:db8:1:0::/64" is not in RFC 5952 canonical format ("2001:db8:1::/64"), which may cause controller apply-loops`,
   188  				`spec.loadBalancerSourceRanges[1]: IP prefix was accepted, but will be invalid in a future Kubernetes release: netip.ParsePrefix("10.012.2.0/24"): ParseAddr("10.012.2.0"): IPv4 field has octet with leading zero`,
   189  			},
   190  		},
   191  	}
   192  	for _, tt := range tests {
   193  		t.Run(tt.name, func(t *testing.T) {
   194  			if got := GetWarningsForService(tt.service, tt.oldService); !reflect.DeepEqual(got, tt.want) {
   195  				t.Errorf("GetWarningsForService() = %v", cmp.Diff(got, tt.want))
   196  			}
   197  		})
   198  	}
   199  }
   200  
   201  func Test_getWarningsForIP(t *testing.T) {
   202  	tests := []struct {
   203  		name      string
   204  		fieldPath *field.Path
   205  		address   string
   206  		want      []string
   207  	}{
   208  		{
   209  			name:      "IPv4 No failures",
   210  			address:   "192.12.2.2",
   211  			fieldPath: field.NewPath("spec").Child("clusterIPs").Index(0),
   212  			want:      []string{},
   213  		},
   214  		{
   215  			name:      "IPv6 No failures",
   216  			address:   "2001:db8::2",
   217  			fieldPath: field.NewPath("spec").Child("clusterIPs").Index(0),
   218  			want:      []string{},
   219  		},
   220  		{
   221  			name:      "IPv4 with leading zeros",
   222  			address:   "192.012.2.2",
   223  			fieldPath: field.NewPath("spec").Child("clusterIPs").Index(0),
   224  			want: []string{
   225  				`spec.clusterIPs[0]: IP address was accepted, but will be invalid in a future Kubernetes release: ParseAddr("192.012.2.2"): IPv4 field has octet with leading zero`,
   226  			},
   227  		},
   228  		{
   229  			name:      "IPv6 non-canonical format",
   230  			address:   "2001:db8:0:0::2",
   231  			fieldPath: field.NewPath("spec").Child("loadBalancerIP"),
   232  			want: []string{
   233  				`spec.loadBalancerIP: IPv6 address "2001:db8:0:0::2" is not in RFC 5952 canonical format ("2001:db8::2"), which may cause controller apply-loops`,
   234  			},
   235  		},
   236  	}
   237  	for _, tt := range tests {
   238  		t.Run(tt.name, func(t *testing.T) {
   239  			if got := getWarningsForIP(tt.fieldPath, tt.address); !reflect.DeepEqual(got, tt.want) {
   240  				t.Errorf("getWarningsForIP() = %v, want %v", got, tt.want)
   241  			}
   242  		})
   243  	}
   244  }
   245  
   246  func Test_getWarningsForCIDR(t *testing.T) {
   247  	tests := []struct {
   248  		name      string
   249  		fieldPath *field.Path
   250  		cidr      string
   251  		want      []string
   252  	}{
   253  		{
   254  			name:      "IPv4 No failures",
   255  			cidr:      "192.12.2.0/24",
   256  			fieldPath: field.NewPath("spec").Child("loadBalancerSourceRanges").Index(0),
   257  			want:      []string{},
   258  		},
   259  		{
   260  			name:      "IPv6 No failures",
   261  			cidr:      "2001:db8::/64",
   262  			fieldPath: field.NewPath("spec").Child("loadBalancerSourceRanges").Index(0),
   263  			want:      []string{},
   264  		},
   265  		{
   266  			name:      "IPv4 with leading zeros",
   267  			cidr:      "192.012.2.0/24",
   268  			fieldPath: field.NewPath("spec").Child("loadBalancerSourceRanges").Index(0),
   269  			want: []string{
   270  				`spec.loadBalancerSourceRanges[0]: IP prefix was accepted, but will be invalid in a future Kubernetes release: netip.ParsePrefix("192.012.2.0/24"): ParseAddr("192.012.2.0"): IPv4 field has octet with leading zero`,
   271  			},
   272  		},
   273  		{
   274  			name:      "IPv6 non-canonical format",
   275  			cidr:      "2001:db8:0:0::/64",
   276  			fieldPath: field.NewPath("spec").Child("loadBalancerSourceRanges").Index(0),
   277  			want: []string{
   278  				`spec.loadBalancerSourceRanges[0]: IPv6 prefix "2001:db8:0:0::/64" is not in RFC 5952 canonical format ("2001:db8::/64"), which may cause controller apply-loops`,
   279  			},
   280  		},
   281  	}
   282  	for _, tt := range tests {
   283  		t.Run(tt.name, func(t *testing.T) {
   284  			if got := getWarningsForCIDR(tt.fieldPath, tt.cidr); !reflect.DeepEqual(got, tt.want) {
   285  				t.Errorf("getWarningsForCIDR() = %v, want %v", got, tt.want)
   286  			}
   287  		})
   288  	}
   289  }