...

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

Documentation: k8s.io/kubernetes/pkg/apis/core/v1/validation

     1  /*
     2  Copyright 2017 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  	"strings"
    21  	"testing"
    22  
    23  	v1 "k8s.io/api/core/v1"
    24  	"k8s.io/apimachinery/pkg/api/resource"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/util/sets"
    27  	"k8s.io/apimachinery/pkg/util/validation/field"
    28  	"k8s.io/kubernetes/pkg/apis/core"
    29  )
    30  
    31  func TestValidateResourceRequirements(t *testing.T) {
    32  	successCase := []struct {
    33  		name         string
    34  		requirements v1.ResourceRequirements
    35  	}{{
    36  		name: "Resources with Requests equal to Limits",
    37  		requirements: v1.ResourceRequirements{
    38  			Requests: v1.ResourceList{
    39  				v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
    40  				v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
    41  			},
    42  			Limits: v1.ResourceList{
    43  				v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
    44  				v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
    45  			},
    46  		},
    47  	}, {
    48  		name: "Resources with only Limits",
    49  		requirements: v1.ResourceRequirements{
    50  			Limits: v1.ResourceList{
    51  				v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
    52  				v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
    53  				v1.ResourceName("my.org/resource"): resource.MustParse("10"),
    54  			},
    55  		},
    56  	}, {
    57  		name: "Resources with only Requests",
    58  		requirements: v1.ResourceRequirements{
    59  			Requests: v1.ResourceList{
    60  				v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
    61  				v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
    62  				v1.ResourceName("my.org/resource"): resource.MustParse("10"),
    63  			},
    64  		},
    65  	}, {
    66  		name: "Resources with Requests Less Than Limits",
    67  		requirements: v1.ResourceRequirements{
    68  			Requests: v1.ResourceList{
    69  				v1.ResourceName(v1.ResourceCPU):    resource.MustParse("9"),
    70  				v1.ResourceName(v1.ResourceMemory): resource.MustParse("9G"),
    71  				v1.ResourceName("my.org/resource"): resource.MustParse("9"),
    72  			},
    73  			Limits: v1.ResourceList{
    74  				v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
    75  				v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
    76  				v1.ResourceName("my.org/resource"): resource.MustParse("9"),
    77  			},
    78  		},
    79  	}}
    80  	for _, tc := range successCase {
    81  		t.Run(tc.name, func(t *testing.T) {
    82  			if errs := ValidateResourceRequirements(&tc.requirements, field.NewPath("resources")); len(errs) != 0 {
    83  				t.Errorf("unexpected error: %v", errs)
    84  			}
    85  		})
    86  	}
    87  
    88  	errorCase := []struct {
    89  		name                  string
    90  		requirements          v1.ResourceRequirements
    91  		skipLimitValueCheck   bool
    92  		skipRequestValueCheck bool
    93  	}{{
    94  		name: "Resources with Requests Larger Than Limits",
    95  		requirements: v1.ResourceRequirements{
    96  			Requests: v1.ResourceList{
    97  				v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
    98  				v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
    99  				v1.ResourceName("my.org/resource"): resource.MustParse("10m"),
   100  			},
   101  			Limits: v1.ResourceList{
   102  				v1.ResourceName(v1.ResourceCPU):    resource.MustParse("9"),
   103  				v1.ResourceName(v1.ResourceMemory): resource.MustParse("9G"),
   104  				v1.ResourceName("my.org/resource"): resource.MustParse("9m"),
   105  			},
   106  		},
   107  	}, {
   108  		name: "Invalid Resources with Requests",
   109  		requirements: v1.ResourceRequirements{
   110  			Requests: v1.ResourceList{
   111  				v1.ResourceName("my.org"): resource.MustParse("10m"),
   112  			},
   113  		},
   114  		skipRequestValueCheck: true,
   115  	}, {
   116  		name: "Invalid Resources with Limits",
   117  		requirements: v1.ResourceRequirements{
   118  			Limits: v1.ResourceList{
   119  				v1.ResourceName("my.org"): resource.MustParse("9m"),
   120  			},
   121  		},
   122  		skipLimitValueCheck: true,
   123  	}}
   124  	for _, tc := range errorCase {
   125  		t.Run(tc.name, func(t *testing.T) {
   126  			errs := ValidateResourceRequirements(&tc.requirements, field.NewPath("resources"))
   127  			if len(errs) == 0 {
   128  				t.Errorf("expected error")
   129  			}
   130  			validateNamesAndValuesInDescription(t, tc.requirements.Limits, errs, tc.skipLimitValueCheck, "limit")
   131  			validateNamesAndValuesInDescription(t, tc.requirements.Requests, errs, tc.skipRequestValueCheck, "request")
   132  		})
   133  	}
   134  }
   135  
   136  func validateNamesAndValuesInDescription(t *testing.T, r v1.ResourceList, errs field.ErrorList, skipValueTest bool, rl string) {
   137  	for name, value := range r {
   138  		containsName := false
   139  		containsValue := false
   140  
   141  		for _, e := range errs {
   142  			if strings.Contains(e.Error(), name.String()) {
   143  				containsName = true
   144  			}
   145  
   146  			if strings.Contains(e.Error(), value.String()) {
   147  				containsValue = true
   148  			}
   149  		}
   150  		if !containsName {
   151  			t.Errorf("error must contain %s name", rl)
   152  		}
   153  		if !containsValue && !skipValueTest {
   154  			t.Errorf("error must contain %s value", rl)
   155  		}
   156  	}
   157  }
   158  
   159  func TestValidateContainerResourceName(t *testing.T) {
   160  	successCase := []struct {
   161  		name         string
   162  		ResourceName core.ResourceName
   163  	}{{
   164  		name:         "CPU resource",
   165  		ResourceName: "cpu",
   166  	}, {
   167  		name:         "Memory resource",
   168  		ResourceName: "memory",
   169  	}, {
   170  		name:         "Hugepages resource",
   171  		ResourceName: "hugepages-2Mi",
   172  	}, {
   173  		name:         "Namespaced resource",
   174  		ResourceName: "kubernetes.io/resource-foo",
   175  	}, {
   176  		name:         "Extended Resource",
   177  		ResourceName: "my.org/resource-bar",
   178  	}}
   179  	for _, tc := range successCase {
   180  		t.Run(tc.name, func(t *testing.T) {
   181  			if errs := ValidateContainerResourceName(tc.ResourceName, field.NewPath(string(tc.ResourceName))); len(errs) != 0 {
   182  				t.Errorf("unexpected error: %v", errs)
   183  			}
   184  		})
   185  	}
   186  
   187  	errorCase := []struct {
   188  		name         string
   189  		ResourceName core.ResourceName
   190  	}{{
   191  		name:         "Invalid standard resource",
   192  		ResourceName: "cpu-core",
   193  	}, {
   194  		name:         "Invalid namespaced resource",
   195  		ResourceName: "kubernetes.io/",
   196  	}, {
   197  		name:         "Invalid extended resource",
   198  		ResourceName: "my.org-foo-resource",
   199  	}}
   200  	for _, tc := range errorCase {
   201  		t.Run(tc.name, func(t *testing.T) {
   202  			if errs := ValidateContainerResourceName(tc.ResourceName, field.NewPath(string(tc.ResourceName))); len(errs) == 0 {
   203  				t.Errorf("expected error")
   204  			}
   205  		})
   206  	}
   207  }
   208  
   209  func TestValidatePodLogOptions(t *testing.T) {
   210  
   211  	var (
   212  		positiveLine             = int64(8)
   213  		negativeLine             = int64(-8)
   214  		limitBytesGreaterThan1   = int64(12)
   215  		limitBytesLessThan1      = int64(0)
   216  		sinceSecondsGreaterThan1 = int64(10)
   217  		sinceSecondsLessThan1    = int64(0)
   218  		timestamp                = metav1.Now()
   219  	)
   220  
   221  	successCase := []struct {
   222  		name          string
   223  		podLogOptions v1.PodLogOptions
   224  	}{{
   225  		name:          "Empty PodLogOptions",
   226  		podLogOptions: v1.PodLogOptions{},
   227  	}, {
   228  		name: "PodLogOptions with TailLines",
   229  		podLogOptions: v1.PodLogOptions{
   230  			TailLines: &positiveLine,
   231  		},
   232  	}, {
   233  		name: "PodLogOptions with LimitBytes",
   234  		podLogOptions: v1.PodLogOptions{
   235  			LimitBytes: &limitBytesGreaterThan1,
   236  		},
   237  	}, {
   238  		name: "PodLogOptions with only sinceSeconds",
   239  		podLogOptions: v1.PodLogOptions{
   240  			SinceSeconds: &sinceSecondsGreaterThan1,
   241  		},
   242  	}, {
   243  		name: "PodLogOptions with LimitBytes with TailLines",
   244  		podLogOptions: v1.PodLogOptions{
   245  			LimitBytes: &limitBytesGreaterThan1,
   246  			TailLines:  &positiveLine,
   247  		},
   248  	}, {
   249  		name: "PodLogOptions with LimitBytes with TailLines with SinceSeconds",
   250  		podLogOptions: v1.PodLogOptions{
   251  			LimitBytes:   &limitBytesGreaterThan1,
   252  			TailLines:    &positiveLine,
   253  			SinceSeconds: &sinceSecondsGreaterThan1,
   254  		},
   255  	}}
   256  	for _, tc := range successCase {
   257  		t.Run(tc.name, func(t *testing.T) {
   258  			if errs := ValidatePodLogOptions(&tc.podLogOptions); len(errs) != 0 {
   259  				t.Errorf("unexpected error: %v", errs)
   260  			}
   261  		})
   262  	}
   263  
   264  	errorCase := []struct {
   265  		name          string
   266  		podLogOptions v1.PodLogOptions
   267  	}{{
   268  		name: "Invalid podLogOptions with Negative TailLines",
   269  		podLogOptions: v1.PodLogOptions{
   270  			TailLines:    &negativeLine,
   271  			LimitBytes:   &limitBytesGreaterThan1,
   272  			SinceSeconds: &sinceSecondsGreaterThan1,
   273  		},
   274  	}, {
   275  		name: "Invalid podLogOptions with zero or negative LimitBytes",
   276  		podLogOptions: v1.PodLogOptions{
   277  			TailLines:    &positiveLine,
   278  			LimitBytes:   &limitBytesLessThan1,
   279  			SinceSeconds: &sinceSecondsGreaterThan1,
   280  		},
   281  	}, {
   282  		name: "Invalid podLogOptions with zero or negative SinceSeconds",
   283  		podLogOptions: v1.PodLogOptions{
   284  			TailLines:    &negativeLine,
   285  			LimitBytes:   &limitBytesGreaterThan1,
   286  			SinceSeconds: &sinceSecondsLessThan1,
   287  		},
   288  	}, {
   289  		name: "Invalid podLogOptions with both SinceSeconds and SinceTime set",
   290  		podLogOptions: v1.PodLogOptions{
   291  			TailLines:    &negativeLine,
   292  			LimitBytes:   &limitBytesGreaterThan1,
   293  			SinceSeconds: &sinceSecondsGreaterThan1,
   294  			SinceTime:    &timestamp,
   295  		},
   296  	}}
   297  	for _, tc := range errorCase {
   298  		t.Run(tc.name, func(t *testing.T) {
   299  			if errs := ValidatePodLogOptions(&tc.podLogOptions); len(errs) == 0 {
   300  				t.Errorf("expected error")
   301  			}
   302  		})
   303  	}
   304  }
   305  
   306  func TestAccumulateUniqueHostPorts(t *testing.T) {
   307  	successCase := []struct {
   308  		name        string
   309  		containers  []v1.Container
   310  		accumulator *sets.String
   311  		fldPath     *field.Path
   312  	}{{
   313  		name: "HostPort is not allocated while containers use the same port with different protocol",
   314  		containers: []v1.Container{{
   315  			Ports: []v1.ContainerPort{{
   316  				HostPort: 8080,
   317  				Protocol: v1.ProtocolUDP,
   318  			}},
   319  		}, {
   320  			Ports: []v1.ContainerPort{{
   321  				HostPort: 8080,
   322  				Protocol: v1.ProtocolTCP,
   323  			}},
   324  		}},
   325  		accumulator: &sets.String{},
   326  		fldPath:     field.NewPath("spec", "containers"),
   327  	}, {
   328  		name: "HostPort is not allocated while containers use different ports",
   329  		containers: []v1.Container{{
   330  			Ports: []v1.ContainerPort{{
   331  				HostPort: 8080,
   332  				Protocol: v1.ProtocolUDP,
   333  			}},
   334  		}, {
   335  			Ports: []v1.ContainerPort{{
   336  				HostPort: 8081,
   337  				Protocol: v1.ProtocolUDP,
   338  			}},
   339  		}},
   340  		accumulator: &sets.String{},
   341  		fldPath:     field.NewPath("spec", "containers"),
   342  	}}
   343  	for _, tc := range successCase {
   344  		t.Run(tc.name, func(t *testing.T) {
   345  			if errs := AccumulateUniqueHostPorts(tc.containers, tc.accumulator, tc.fldPath); len(errs) != 0 {
   346  				t.Errorf("unexpected error: %v", errs)
   347  			}
   348  		})
   349  	}
   350  	errorCase := []struct {
   351  		name        string
   352  		containers  []v1.Container
   353  		accumulator *sets.String
   354  		fldPath     *field.Path
   355  	}{{
   356  		name: "HostPort is already allocated while containers use the same port with UDP",
   357  		containers: []v1.Container{{
   358  			Ports: []v1.ContainerPort{{
   359  				HostPort: 8080,
   360  				Protocol: v1.ProtocolUDP,
   361  			}},
   362  		}, {
   363  			Ports: []v1.ContainerPort{{
   364  				HostPort: 8080,
   365  				Protocol: v1.ProtocolUDP,
   366  			}},
   367  		}},
   368  		accumulator: &sets.String{},
   369  		fldPath:     field.NewPath("spec", "containers"),
   370  	}, {
   371  		name: "HostPort is already allocated",
   372  		containers: []v1.Container{{
   373  			Ports: []v1.ContainerPort{{
   374  				HostPort: 8080,
   375  				Protocol: v1.ProtocolUDP,
   376  			}},
   377  		}, {
   378  			Ports: []v1.ContainerPort{{
   379  				HostPort: 8081,
   380  				Protocol: v1.ProtocolUDP,
   381  			}},
   382  		}},
   383  		accumulator: &sets.String{"8080/UDP": sets.Empty{}},
   384  		fldPath:     field.NewPath("spec", "containers"),
   385  	}}
   386  	for _, tc := range errorCase {
   387  		t.Run(tc.name, func(t *testing.T) {
   388  			if errs := AccumulateUniqueHostPorts(tc.containers, tc.accumulator, tc.fldPath); len(errs) == 0 {
   389  				t.Errorf("expected error, but get nil")
   390  			}
   391  		})
   392  	}
   393  }
   394  

View as plain text