sigs.k8s.io/kueue@v0.6.2/pkg/podset/podset_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 package podset 17 18 import ( 19 "context" 20 "testing" 21 22 "github.com/google/go-cmp/cmp" 23 "github.com/google/go-cmp/cmp/cmpopts" 24 corev1 "k8s.io/api/core/v1" 25 apierrors "k8s.io/apimachinery/pkg/api/errors" 26 "k8s.io/apimachinery/pkg/runtime/schema" 27 "k8s.io/utils/ptr" 28 29 kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1" 30 utiltesting "sigs.k8s.io/kueue/pkg/util/testing" 31 ) 32 33 func TestFromAssignment(t *testing.T) { 34 toleration1 := corev1.Toleration{ 35 Key: "t1k", 36 Operator: corev1.TolerationOpEqual, 37 Value: "t1v", 38 Effect: corev1.TaintEffectNoExecute, 39 } 40 toleration2 := corev1.Toleration{ 41 Key: "t2k", 42 Operator: corev1.TolerationOpExists, 43 Effect: corev1.TaintEffectNoSchedule, 44 } 45 toleration3 := corev1.Toleration{ 46 Key: "t3k", 47 Operator: corev1.TolerationOpEqual, 48 Value: "t3v", 49 Effect: corev1.TaintEffectPreferNoSchedule, 50 } 51 52 flavor1 := utiltesting.MakeResourceFlavor("flavor1"). 53 Label("f1l1", "f1v1"). 54 Label("f1l2", "f1v2"). 55 Toleration(*toleration1.DeepCopy()). 56 Toleration(*toleration2.DeepCopy()). 57 Obj() 58 59 flavor2 := utiltesting.MakeResourceFlavor("flavor2"). 60 Label("f2l1", "f2v1"). 61 Label("f2l2", "f2v2"). 62 Toleration(*toleration3.DeepCopy()). 63 Obj() 64 65 cases := map[string]struct { 66 assignment *kueue.PodSetAssignment 67 defaultCount int32 68 flavors []kueue.ResourceFlavor 69 wantError error 70 wantInfo PodSetInfo 71 }{ 72 "single flavor": { 73 assignment: &kueue.PodSetAssignment{ 74 Name: "name", 75 Flavors: map[corev1.ResourceName]kueue.ResourceFlavorReference{ 76 corev1.ResourceCPU: kueue.ResourceFlavorReference(flavor1.Name), 77 }, 78 Count: ptr.To[int32](2), 79 }, 80 defaultCount: 4, 81 flavors: []kueue.ResourceFlavor{*flavor1.DeepCopy()}, 82 wantInfo: PodSetInfo{ 83 Name: "name", 84 Count: 2, 85 NodeSelector: map[string]string{ 86 "f1l1": "f1v1", 87 "f1l2": "f1v2", 88 }, 89 Tolerations: []corev1.Toleration{*toleration1.DeepCopy(), *toleration2.DeepCopy()}, 90 }, 91 }, 92 "multiple flavors": { 93 assignment: &kueue.PodSetAssignment{ 94 Name: "name", 95 Flavors: map[corev1.ResourceName]kueue.ResourceFlavorReference{ 96 corev1.ResourceCPU: kueue.ResourceFlavorReference(flavor1.Name), 97 corev1.ResourceMemory: kueue.ResourceFlavorReference(flavor2.Name), 98 }, 99 Count: ptr.To[int32](2), 100 }, 101 defaultCount: 4, 102 flavors: []kueue.ResourceFlavor{*flavor1.DeepCopy(), *flavor2.DeepCopy()}, 103 wantInfo: PodSetInfo{ 104 Name: "name", 105 Count: 2, 106 NodeSelector: map[string]string{ 107 "f1l1": "f1v1", 108 "f1l2": "f1v2", 109 "f2l1": "f2v1", 110 "f2l2": "f2v2", 111 }, 112 Tolerations: []corev1.Toleration{*toleration1.DeepCopy(), *toleration2.DeepCopy(), *toleration3.DeepCopy()}, 113 }, 114 }, 115 "duplicate flavor": { 116 assignment: &kueue.PodSetAssignment{ 117 Name: "name", 118 Flavors: map[corev1.ResourceName]kueue.ResourceFlavorReference{ 119 corev1.ResourceCPU: kueue.ResourceFlavorReference(flavor1.Name), 120 corev1.ResourceMemory: kueue.ResourceFlavorReference(flavor1.Name), 121 }, 122 Count: ptr.To[int32](2), 123 }, 124 defaultCount: 4, 125 flavors: []kueue.ResourceFlavor{*flavor1.DeepCopy(), *flavor2.DeepCopy()}, 126 wantInfo: PodSetInfo{ 127 Name: "name", 128 Count: 2, 129 NodeSelector: map[string]string{ 130 "f1l1": "f1v1", 131 "f1l2": "f1v2", 132 }, 133 Tolerations: []corev1.Toleration{*toleration1.DeepCopy(), *toleration2.DeepCopy()}, 134 }, 135 }, 136 "flavor not found": { 137 assignment: &kueue.PodSetAssignment{ 138 Name: "name", 139 Flavors: map[corev1.ResourceName]kueue.ResourceFlavorReference{ 140 corev1.ResourceCPU: kueue.ResourceFlavorReference(flavor1.Name), 141 }, 142 Count: ptr.To[int32](2), 143 }, 144 defaultCount: 4, 145 wantError: apierrors.NewNotFound(schema.GroupResource{Group: kueue.GroupVersion.Group, Resource: "resourceflavors"}, "flavor1"), 146 }, 147 "default count": { 148 assignment: &kueue.PodSetAssignment{ 149 Name: "name", 150 Flavors: map[corev1.ResourceName]kueue.ResourceFlavorReference{ 151 corev1.ResourceCPU: kueue.ResourceFlavorReference(flavor1.Name), 152 }, 153 }, 154 defaultCount: 4, 155 flavors: []kueue.ResourceFlavor{*flavor1.DeepCopy()}, 156 wantInfo: PodSetInfo{ 157 Name: "name", 158 Count: 4, 159 NodeSelector: map[string]string{ 160 "f1l1": "f1v1", 161 "f1l2": "f1v2", 162 }, 163 Tolerations: []corev1.Toleration{*toleration1.DeepCopy(), *toleration2.DeepCopy()}, 164 }, 165 }, 166 } 167 for name, tc := range cases { 168 t.Run(name, func(t *testing.T) { 169 ctx := context.TODO() 170 client := utiltesting.NewClientBuilder().WithLists(&kueue.ResourceFlavorList{Items: tc.flavors}).Build() 171 172 gotInfo, gotError := FromAssignment(ctx, client, tc.assignment, tc.defaultCount) 173 174 if diff := cmp.Diff(tc.wantError, gotError); diff != "" { 175 t.Errorf("Unexpected error (-want/+got):\n%s", diff) 176 } 177 178 if tc.wantError == nil { 179 if diff := cmp.Diff(tc.wantInfo, gotInfo, cmpopts.EquateEmpty(), cmpopts.SortSlices(func(a, b corev1.Toleration) bool { return a.Key < b.Key })); diff != "" { 180 t.Errorf("Unexpected info (-want/+got):\n%s", diff) 181 } 182 } 183 }) 184 } 185 } 186 187 func TestMergeRestore(t *testing.T) { 188 189 basePodSet := utiltesting.MakePodSet("", 1). 190 NodeSelector(map[string]string{"ns0": "ns0v"}). 191 Labels(map[string]string{"l0": "l0v"}). 192 Annotations(map[string]string{"a0": "a0v"}). 193 Toleration(corev1.Toleration{ 194 Key: "t0", 195 Operator: corev1.TolerationOpEqual, 196 Value: "t0v", 197 Effect: corev1.TaintEffectNoSchedule, 198 }). 199 Obj() 200 201 cases := map[string]struct { 202 podSet *kueue.PodSet 203 info PodSetInfo 204 wantError bool 205 wantPodSet *kueue.PodSet 206 wantRestoreChanges bool 207 }{ 208 "empty info": { 209 podSet: basePodSet.DeepCopy(), 210 wantPodSet: basePodSet.DeepCopy(), 211 }, 212 "no conflicts": { 213 podSet: basePodSet.DeepCopy(), 214 info: PodSetInfo{ 215 Annotations: map[string]string{ 216 "a1": "a1v", 217 }, 218 Labels: map[string]string{ 219 "l1": "l1v", 220 }, 221 NodeSelector: map[string]string{ 222 "ns1": "ns1v", 223 }, 224 Tolerations: []corev1.Toleration{ 225 { 226 Key: "t1", 227 Operator: corev1.TolerationOpEqual, 228 Value: "t1v", 229 Effect: corev1.TaintEffectNoSchedule, 230 }, 231 }, 232 }, 233 wantPodSet: utiltesting.MakePodSet("", 1). 234 NodeSelector(map[string]string{"ns0": "ns0v", "ns1": "ns1v"}). 235 Labels(map[string]string{"l0": "l0v", "l1": "l1v"}). 236 Annotations(map[string]string{"a0": "a0v", "a1": "a1v"}). 237 Toleration(corev1.Toleration{ 238 Key: "t0", 239 Operator: corev1.TolerationOpEqual, 240 Value: "t0v", 241 Effect: corev1.TaintEffectNoSchedule, 242 }). 243 Toleration(corev1.Toleration{ 244 Key: "t1", 245 Operator: corev1.TolerationOpEqual, 246 Value: "t1v", 247 Effect: corev1.TaintEffectNoSchedule, 248 }). 249 Obj(), 250 wantRestoreChanges: true, 251 }, 252 "conflicting label": { 253 podSet: basePodSet.DeepCopy(), 254 info: PodSetInfo{ 255 Labels: map[string]string{ 256 "l0": "l0v1", 257 }, 258 }, 259 wantError: true, 260 }, 261 "conflicting annotation": { 262 podSet: basePodSet.DeepCopy(), 263 info: PodSetInfo{ 264 Annotations: map[string]string{ 265 "a0": "a0v1", 266 }, 267 }, 268 wantError: true, 269 }, 270 "conflicting node selector": { 271 podSet: basePodSet.DeepCopy(), 272 info: PodSetInfo{ 273 NodeSelector: map[string]string{ 274 "ns0": "ns0v1", 275 }, 276 }, 277 wantError: true, 278 }, 279 } 280 281 for name, tc := range cases { 282 t.Run(name, func(t *testing.T) { 283 orig := tc.podSet.DeepCopy() 284 285 gotError := Merge(&tc.podSet.Template.ObjectMeta, &tc.podSet.Template.Spec, tc.info) 286 287 if tc.wantError != (gotError != nil) { 288 t.Errorf("Unexpected error status want: %v", tc.wantError) 289 } 290 291 if !tc.wantError { 292 if diff := cmp.Diff(tc.wantPodSet.Template, tc.podSet.Template, cmpopts.EquateEmpty()); diff != "" { 293 t.Errorf("Unexpected template (-want/+got):\n%s", diff) 294 } 295 296 restoreInfo := FromPodSet(orig) 297 gotRestoreChage := RestorePodSpec(&tc.podSet.Template.ObjectMeta, &tc.podSet.Template.Spec, restoreInfo) 298 if gotRestoreChage != tc.wantRestoreChanges { 299 t.Errorf("Unexpected restore change status want:%v", tc.wantRestoreChanges) 300 } 301 if diff := cmp.Diff(orig.Template, tc.podSet.Template, cmpopts.EquateEmpty()); diff != "" { 302 t.Errorf("Unexpected template (-want/+got):\n%s", diff) 303 } 304 305 } 306 }) 307 } 308 } 309 310 func TestAddOrUpdateLabel(t *testing.T) { 311 cases := map[string]struct { 312 info PodSetInfo 313 k, v string 314 wantInfo PodSetInfo 315 }{ 316 "add to nil labels": { 317 info: PodSetInfo{}, 318 k: "key", 319 v: "value", 320 wantInfo: PodSetInfo{ 321 Labels: map[string]string{"key": "value"}, 322 }, 323 }, 324 "add": { 325 info: PodSetInfo{ 326 Labels: map[string]string{"other-key": "other-value"}, 327 }, 328 k: "key", 329 v: "value", 330 wantInfo: PodSetInfo{ 331 Labels: map[string]string{"other-key": "other-value", "key": "value"}, 332 }, 333 }, 334 "update": { 335 info: PodSetInfo{ 336 Labels: map[string]string{"key": "value"}, 337 }, 338 k: "key", 339 v: "updated-value", 340 wantInfo: PodSetInfo{ 341 Labels: map[string]string{"key": "updated-value"}, 342 }, 343 }, 344 } 345 for name, tc := range cases { 346 t.Run(name, func(t *testing.T) { 347 tc.info.AddOrUpdateLabel(tc.k, tc.v) 348 if diff := cmp.Diff(tc.wantInfo, tc.info, cmpopts.EquateEmpty()); diff != "" { 349 t.Errorf("Unexpected info (-want/+got):\n%s", diff) 350 } 351 }) 352 } 353 }