sigs.k8s.io/kueue@v0.6.2/pkg/util/limitrange/limitrange_test.go (about)

     1  /*
     2  Copyright 2023 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 limitrange
    18  
    19  import (
    20  	"testing"
    21  
    22  	"github.com/google/go-cmp/cmp"
    23  	corev1 "k8s.io/api/core/v1"
    24  	"k8s.io/apimachinery/pkg/api/resource"
    25  	"k8s.io/utils/field"
    26  
    27  	testingutil "sigs.k8s.io/kueue/pkg/util/testing"
    28  )
    29  
    30  var (
    31  	ar1    = resource.MustParse("1")
    32  	ar2    = resource.MustParse("2")
    33  	ar500m = resource.MustParse("500m")
    34  	ar1Gi  = resource.MustParse("1Gi")
    35  	ar2Gi  = resource.MustParse("2Gi")
    36  )
    37  
    38  func TestSummarize(t *testing.T) {
    39  	cases := map[string]struct {
    40  		ranges   []corev1.LimitRange
    41  		expected Summary
    42  	}{
    43  		"empty": {
    44  			ranges:   []corev1.LimitRange{},
    45  			expected: map[corev1.LimitType]corev1.LimitRangeItem{},
    46  		},
    47  		"podDefaults": {
    48  			ranges: []corev1.LimitRange{
    49  				{
    50  					Spec: corev1.LimitRangeSpec{
    51  						Limits: []corev1.LimitRangeItem{
    52  							{
    53  								Type: corev1.LimitTypePod,
    54  								Default: corev1.ResourceList{
    55  									corev1.ResourceCPU: ar2,
    56  								},
    57  								DefaultRequest: corev1.ResourceList{
    58  									corev1.ResourceCPU:    ar500m,
    59  									corev1.ResourceMemory: ar1Gi,
    60  								},
    61  							},
    62  						},
    63  					},
    64  				},
    65  				{
    66  					Spec: corev1.LimitRangeSpec{
    67  						Limits: []corev1.LimitRangeItem{
    68  							{
    69  								Type: corev1.LimitTypePod,
    70  								Default: corev1.ResourceList{
    71  									corev1.ResourceMemory: ar2Gi,
    72  								},
    73  								DefaultRequest: corev1.ResourceList{
    74  									corev1.ResourceCPU: ar1,
    75  								},
    76  							},
    77  						},
    78  					},
    79  				},
    80  			},
    81  			expected: map[corev1.LimitType]corev1.LimitRangeItem{
    82  				corev1.LimitTypePod: {
    83  					Default: corev1.ResourceList{
    84  						corev1.ResourceCPU:    ar2,
    85  						corev1.ResourceMemory: ar2Gi,
    86  					},
    87  					DefaultRequest: corev1.ResourceList{
    88  						corev1.ResourceCPU:    ar500m,
    89  						corev1.ResourceMemory: ar1Gi,
    90  					},
    91  				},
    92  			},
    93  		},
    94  		"limits": {
    95  			ranges: []corev1.LimitRange{
    96  				{
    97  					Spec: corev1.LimitRangeSpec{
    98  						Limits: []corev1.LimitRangeItem{
    99  							{
   100  								Type: corev1.LimitTypePod,
   101  								Max: corev1.ResourceList{
   102  									corev1.ResourceCPU: ar2,
   103  								},
   104  								Min: corev1.ResourceList{
   105  									corev1.ResourceCPU:    ar500m,
   106  									corev1.ResourceMemory: ar1Gi,
   107  								},
   108  								MaxLimitRequestRatio: corev1.ResourceList{
   109  									corev1.ResourceCPU: ar2,
   110  								},
   111  							},
   112  						},
   113  					},
   114  				},
   115  				{
   116  					Spec: corev1.LimitRangeSpec{
   117  						Limits: []corev1.LimitRangeItem{
   118  							{
   119  								Type: corev1.LimitTypePod,
   120  								Max: corev1.ResourceList{
   121  									corev1.ResourceMemory: ar2Gi,
   122  								},
   123  								Min: corev1.ResourceList{
   124  									corev1.ResourceCPU: ar1,
   125  								},
   126  								MaxLimitRequestRatio: corev1.ResourceList{
   127  									corev1.ResourceCPU: ar500m,
   128  								},
   129  							},
   130  						},
   131  					},
   132  				},
   133  			},
   134  			expected: Summary{
   135  				corev1.LimitTypePod: {
   136  					Max: corev1.ResourceList{
   137  						corev1.ResourceCPU:    ar2,
   138  						corev1.ResourceMemory: ar2Gi,
   139  					},
   140  					Min: corev1.ResourceList{
   141  						corev1.ResourceCPU:    ar1,
   142  						corev1.ResourceMemory: ar1Gi,
   143  					},
   144  					MaxLimitRequestRatio: corev1.ResourceList{
   145  						corev1.ResourceCPU: ar500m,
   146  					},
   147  				},
   148  			},
   149  		},
   150  	}
   151  
   152  	for name, tc := range cases {
   153  		t.Run(name, func(t *testing.T) {
   154  			result := Summarize(tc.ranges...)
   155  			if diff := cmp.Diff(tc.expected, result); diff != "" {
   156  				t.Errorf("Unexpected result (-want,+got):\n%s", diff)
   157  			}
   158  		})
   159  	}
   160  }
   161  
   162  func TestTotalRequest(t *testing.T) {
   163  	containers := []corev1.Container{
   164  		{
   165  			Resources: corev1.ResourceRequirements{
   166  				Requests: corev1.ResourceList{
   167  					corev1.ResourceCPU:    resource.MustParse("1"),
   168  					corev1.ResourceMemory: resource.MustParse("2Gi"),
   169  				},
   170  			},
   171  		},
   172  		{
   173  			Resources: corev1.ResourceRequirements{
   174  				Requests: corev1.ResourceList{
   175  					corev1.ResourceCPU: resource.MustParse("1500m"),
   176  					"example.com/gpu":  resource.MustParse("2"),
   177  				},
   178  			},
   179  		},
   180  		{
   181  			Resources: corev1.ResourceRequirements{
   182  				Requests: corev1.ResourceList{
   183  					corev1.ResourceCPU:    resource.MustParse("4"),
   184  					corev1.ResourceMemory: resource.MustParse("2Gi"),
   185  				},
   186  			},
   187  		},
   188  		{
   189  			Resources: corev1.ResourceRequirements{
   190  				Requests: corev1.ResourceList{
   191  					corev1.ResourceCPU: resource.MustParse("1500m"),
   192  					"example.com/gpu":  resource.MustParse("2"),
   193  				},
   194  			},
   195  		},
   196  	}
   197  	cases := map[string]struct {
   198  		podSpec *corev1.PodSpec
   199  		want    corev1.ResourceList
   200  	}{
   201  		"sum up main containers": {
   202  			podSpec: &corev1.PodSpec{
   203  				Containers: containers[:2],
   204  			},
   205  			want: corev1.ResourceList{
   206  				corev1.ResourceCPU:    resource.MustParse("2500m"),
   207  				corev1.ResourceMemory: resource.MustParse("2Gi"),
   208  				"example.com/gpu":     resource.MustParse("2"),
   209  			},
   210  		},
   211  		"one init wants more": {
   212  			podSpec: &corev1.PodSpec{
   213  				InitContainers: containers[2:],
   214  				Containers:     containers[:2],
   215  			},
   216  			want: corev1.ResourceList{
   217  				corev1.ResourceCPU:    resource.MustParse("4000m"),
   218  				corev1.ResourceMemory: resource.MustParse("2Gi"),
   219  				"example.com/gpu":     resource.MustParse("2"),
   220  			},
   221  		},
   222  		"adds overhead": {
   223  			podSpec: &corev1.PodSpec{
   224  				InitContainers: containers[2:],
   225  				Containers:     containers[:2],
   226  				Overhead: corev1.ResourceList{
   227  					corev1.ResourceCPU:    resource.MustParse("1"),
   228  					corev1.ResourceMemory: resource.MustParse("1Gi"),
   229  					"example.com/gpu":     resource.MustParse("1"),
   230  				},
   231  			},
   232  			want: corev1.ResourceList{
   233  				corev1.ResourceCPU:    resource.MustParse("5000m"),
   234  				corev1.ResourceMemory: resource.MustParse("3Gi"),
   235  				"example.com/gpu":     resource.MustParse("3"),
   236  			},
   237  		},
   238  	}
   239  
   240  	for name, tc := range cases {
   241  		t.Run(name, func(t *testing.T) {
   242  			result := TotalRequests(tc.podSpec)
   243  			if diff := cmp.Diff(tc.want, result); diff != "" {
   244  				t.Errorf("Unexpected result (-want,+got):\n%s", diff)
   245  			}
   246  		})
   247  	}
   248  }
   249  func TestValidatePodSpec(t *testing.T) {
   250  	podSpec := &corev1.PodSpec{
   251  		Containers: []corev1.Container{
   252  			{
   253  				Resources: corev1.ResourceRequirements{
   254  					Requests: corev1.ResourceList{
   255  						corev1.ResourceCPU:             resource.MustParse("1"),
   256  						corev1.ResourceMemory:          resource.MustParse("2Gi"),
   257  						"example.com/mainContainerGpu": resource.MustParse("2"),
   258  					},
   259  				},
   260  			},
   261  			{
   262  				Resources: corev1.ResourceRequirements{
   263  					Requests: corev1.ResourceList{
   264  						corev1.ResourceCPU: resource.MustParse("1500m"),
   265  						"example.com/gpu":  resource.MustParse("2"),
   266  					},
   267  				},
   268  			},
   269  		},
   270  		InitContainers: []corev1.Container{
   271  			{
   272  				Resources: corev1.ResourceRequirements{
   273  					Requests: corev1.ResourceList{
   274  						corev1.ResourceCPU:    resource.MustParse("4"),
   275  						corev1.ResourceMemory: resource.MustParse("2Gi"),
   276  					},
   277  				},
   278  			},
   279  			{
   280  				Resources: corev1.ResourceRequirements{
   281  					Requests: corev1.ResourceList{
   282  						corev1.ResourceCPU:             resource.MustParse("1500m"),
   283  						"example.com/gpu":              resource.MustParse("2"),
   284  						"example.com/initContainerGpu": resource.MustParse("2"),
   285  					},
   286  				},
   287  			},
   288  		},
   289  		Overhead: corev1.ResourceList{
   290  			corev1.ResourceCPU:    resource.MustParse("1"),
   291  			corev1.ResourceMemory: resource.MustParse("1Gi"),
   292  			"example.com/gpu":     resource.MustParse("1"),
   293  		},
   294  	}
   295  	cases := map[string]struct {
   296  		summary Summary
   297  		want    []string
   298  	}{
   299  		"empty": {
   300  			summary: Summary{},
   301  			want:    []string{},
   302  		},
   303  		"init container over": {
   304  			summary: Summarize(*testingutil.MakeLimitRange("", "").
   305  				WithType(corev1.LimitTypeContainer).
   306  				WithValue("Max", "example.com/initContainerGpu", "1").
   307  				Obj()),
   308  			want: []string{
   309  				violateMaxMessage(field.NewPath("testPodSet", "initContainers").Index(1), "example.com/initContainerGpu"),
   310  			},
   311  		},
   312  		"init container under": {
   313  			summary: Summarize(*testingutil.MakeLimitRange("", "").
   314  				WithType(corev1.LimitTypeContainer).
   315  				WithValue("Min", "example.com/initContainerGpu", "3").
   316  				Obj()),
   317  			want: []string{
   318  				violateMinMessage(field.NewPath("testPodSet", "initContainers").Index(1), "example.com/initContainerGpu"),
   319  			},
   320  		},
   321  		"container over": {
   322  			summary: Summarize(*testingutil.MakeLimitRange("", "").
   323  				WithType(corev1.LimitTypeContainer).
   324  				WithValue("Max", "example.com/mainContainerGpu", "1").
   325  				Obj()),
   326  			want: []string{
   327  				violateMaxMessage(field.NewPath("testPodSet", "containers").Index(0), "example.com/mainContainerGpu"),
   328  			},
   329  		},
   330  		"container under": {
   331  			summary: Summarize(*testingutil.MakeLimitRange("", "").
   332  				WithType(corev1.LimitTypeContainer).
   333  				WithValue("Min", "example.com/mainContainerGpu", "3").
   334  				Obj()),
   335  			want: []string{
   336  				violateMinMessage(field.NewPath("testPodSet", "containers").Index(0), "example.com/mainContainerGpu"),
   337  			},
   338  		},
   339  		"pod over": {
   340  			summary: Summarize(*testingutil.MakeLimitRange("", "").
   341  				WithType(corev1.LimitTypePod).
   342  				WithValue("Max", corev1.ResourceCPU, "4").
   343  				Obj()),
   344  			want: []string{
   345  				violateMaxMessage(field.NewPath("testPodSet"), string(corev1.ResourceCPU)),
   346  			},
   347  		},
   348  		"pod under": {
   349  			summary: Summarize(*testingutil.MakeLimitRange("", "").
   350  				WithType(corev1.LimitTypePod).
   351  				WithValue("Min", corev1.ResourceCPU, "6").
   352  				Obj()),
   353  			want: []string{
   354  				violateMinMessage(field.NewPath("testPodSet"), string(corev1.ResourceCPU)),
   355  			},
   356  		},
   357  		"multiple": {
   358  			summary: Summarize(
   359  				*testingutil.MakeLimitRange("", "").
   360  					WithType(corev1.LimitTypePod).
   361  					WithValue("Max", corev1.ResourceCPU, "4").
   362  					Obj(),
   363  				*testingutil.MakeLimitRange("", "").
   364  					WithType(corev1.LimitTypeContainer).
   365  					WithValue("Min", "example.com/mainContainerGpu", "3").
   366  					Obj(),
   367  				*testingutil.MakeLimitRange("", "").
   368  					WithType(corev1.LimitTypeContainer).
   369  					WithValue("Max", "example.com/initContainerGpu", "1").
   370  					Obj(),
   371  			),
   372  			want: []string{
   373  				violateMaxMessage(field.NewPath("testPodSet", "initContainers").Index(1), "example.com/initContainerGpu"),
   374  				violateMinMessage(field.NewPath("testPodSet", "containers").Index(0), "example.com/mainContainerGpu"),
   375  				violateMaxMessage(field.NewPath("testPodSet"), string(corev1.ResourceCPU)),
   376  			},
   377  		},
   378  		"multiple valid": {
   379  			summary: Summarize(
   380  				*testingutil.MakeLimitRange("", "").
   381  					WithType(corev1.LimitTypePod).
   382  					WithValue("Max", corev1.ResourceCPU, "5").
   383  					Obj(),
   384  				*testingutil.MakeLimitRange("", "").
   385  					WithType(corev1.LimitTypeContainer).
   386  					WithValue("Min", "example.com/mainContainerGpu", "1").
   387  					Obj(),
   388  				*testingutil.MakeLimitRange("", "").
   389  					WithType(corev1.LimitTypeContainer).
   390  					WithValue("Max", "example.com/initContainerGpu", "2").
   391  					Obj(),
   392  			),
   393  			want: []string{},
   394  		},
   395  	}
   396  	for name, tc := range cases {
   397  		t.Run(name, func(t *testing.T) {
   398  			result := tc.summary.ValidatePodSpec(podSpec, field.NewPath("testPodSet"))
   399  			if diff := cmp.Diff(tc.want, result); diff != "" {
   400  				t.Errorf("Unexpected result (-want,+got):\n%s", diff)
   401  			}
   402  		})
   403  	}
   404  }