k8s.io/kubernetes@v1.29.3/pkg/kubelet/cm/topologymanager/topology_manager_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 topologymanager 18 19 import ( 20 "fmt" 21 "strings" 22 "testing" 23 24 "k8s.io/api/core/v1" 25 26 cadvisorapi "github.com/google/cadvisor/info/v1" 27 28 "k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/bitmask" 29 "k8s.io/kubernetes/pkg/kubelet/lifecycle" 30 ) 31 32 func NewTestBitMask(sockets ...int) bitmask.BitMask { 33 s, _ := bitmask.NewBitMask(sockets...) 34 return s 35 } 36 37 func TestNewManager(t *testing.T) { 38 tcases := []struct { 39 description string 40 policyName string 41 expectedPolicy string 42 expectedError error 43 topologyError error 44 policyOptions map[string]string 45 topology []cadvisorapi.Node 46 }{ 47 { 48 description: "Policy is set to none", 49 policyName: "none", 50 expectedPolicy: "none", 51 }, 52 { 53 description: "Policy is set to best-effort", 54 policyName: "best-effort", 55 expectedPolicy: "best-effort", 56 }, 57 { 58 description: "Policy is set to restricted", 59 policyName: "restricted", 60 expectedPolicy: "restricted", 61 }, 62 { 63 description: "Policy is set to single-numa-node", 64 policyName: "single-numa-node", 65 expectedPolicy: "single-numa-node", 66 }, 67 { 68 description: "Policy is set to unknown", 69 policyName: "unknown", 70 expectedError: fmt.Errorf("unknown policy: \"unknown\""), 71 }, 72 { 73 description: "Unknown policy name best-effort policy", 74 policyName: "best-effort", 75 expectedPolicy: "best-effort", 76 expectedError: fmt.Errorf("unknown Topology Manager Policy option:"), 77 policyOptions: map[string]string{ 78 "unknown-option": "true", 79 }, 80 }, 81 { 82 description: "Unknown policy name restricted policy", 83 policyName: "restricted", 84 expectedPolicy: "restricted", 85 expectedError: fmt.Errorf("unknown Topology Manager Policy option:"), 86 policyOptions: map[string]string{ 87 "unknown-option": "true", 88 }, 89 }, 90 { 91 description: "can't get NUMA distances", 92 policyName: "best-effort", 93 expectedPolicy: "best-effort", 94 policyOptions: map[string]string{ 95 PreferClosestNUMANodes: "true", 96 }, 97 expectedError: fmt.Errorf("error getting NUMA distances from cadvisor"), 98 topology: []cadvisorapi.Node{ 99 { 100 Id: 0, 101 }, 102 }, 103 }, 104 { 105 description: "more than 8 NUMA nodes", 106 policyName: "best-effort", 107 expectedPolicy: "best-effort", 108 expectedError: fmt.Errorf("unsupported on machines with more than %v NUMA Nodes", maxAllowableNUMANodes), 109 topology: []cadvisorapi.Node{ 110 { 111 Id: 0, 112 }, 113 { 114 Id: 1, 115 }, 116 { 117 Id: 2, 118 }, 119 { 120 Id: 3, 121 }, 122 { 123 Id: 4, 124 }, 125 { 126 Id: 5, 127 }, 128 { 129 Id: 6, 130 }, 131 { 132 Id: 7, 133 }, 134 { 135 Id: 8, 136 }, 137 }, 138 }, 139 } 140 141 for _, tc := range tcases { 142 topology := tc.topology 143 144 mngr, err := NewManager(topology, tc.policyName, "container", tc.policyOptions) 145 if tc.expectedError != nil { 146 if !strings.Contains(err.Error(), tc.expectedError.Error()) { 147 t.Errorf("Unexpected error message. Have: %s wants %s", err.Error(), tc.expectedError.Error()) 148 } 149 } else { 150 rawMgr := mngr.(*manager) 151 var policyName string 152 if rawScope, ok := rawMgr.scope.(*containerScope); ok { 153 policyName = rawScope.policy.Name() 154 } else if rawScope, ok := rawMgr.scope.(*noneScope); ok { 155 policyName = rawScope.policy.Name() 156 } 157 if policyName != tc.expectedPolicy { 158 t.Errorf("Unexpected policy name. Have: %q wants %q", policyName, tc.expectedPolicy) 159 } 160 } 161 } 162 } 163 164 func TestManagerScope(t *testing.T) { 165 tcases := []struct { 166 description string 167 scopeName string 168 expectedScope string 169 expectedError error 170 }{ 171 { 172 description: "Topology Manager Scope is set to container", 173 scopeName: "container", 174 expectedScope: "container", 175 }, 176 { 177 description: "Topology Manager Scope is set to pod", 178 scopeName: "pod", 179 expectedScope: "pod", 180 }, 181 { 182 description: "Topology Manager Scope is set to unknown", 183 scopeName: "unknown", 184 expectedError: fmt.Errorf("unknown scope: \"unknown\""), 185 }, 186 } 187 188 for _, tc := range tcases { 189 mngr, err := NewManager(nil, "best-effort", tc.scopeName, nil) 190 191 if tc.expectedError != nil { 192 if !strings.Contains(err.Error(), tc.expectedError.Error()) { 193 t.Errorf("Unexpected error message. Have: %s wants %s", err.Error(), tc.expectedError.Error()) 194 } 195 } else { 196 rawMgr := mngr.(*manager) 197 if rawMgr.scope.Name() != tc.expectedScope { 198 t.Errorf("Unexpected scope name. Have: %q wants %q", rawMgr.scope, tc.expectedScope) 199 } 200 } 201 } 202 } 203 204 type mockHintProvider struct { 205 th map[string][]TopologyHint 206 //TODO: Add this field and add some tests to make sure things error out 207 //appropriately on allocation errors. 208 //allocateError error 209 } 210 211 func (m *mockHintProvider) GetTopologyHints(pod *v1.Pod, container *v1.Container) map[string][]TopologyHint { 212 return m.th 213 } 214 215 func (m *mockHintProvider) GetPodTopologyHints(pod *v1.Pod) map[string][]TopologyHint { 216 return m.th 217 } 218 219 func (m *mockHintProvider) Allocate(pod *v1.Pod, container *v1.Container) error { 220 //return allocateError 221 return nil 222 } 223 224 type mockPolicy struct { 225 nonePolicy 226 ph []map[string][]TopologyHint 227 } 228 229 func (p *mockPolicy) Merge(providersHints []map[string][]TopologyHint) (TopologyHint, bool) { 230 p.ph = providersHints 231 return TopologyHint{}, true 232 } 233 234 func TestAddHintProvider(t *testing.T) { 235 tcases := []struct { 236 name string 237 hp []HintProvider 238 }{ 239 { 240 name: "Add HintProvider", 241 hp: []HintProvider{ 242 &mockHintProvider{}, 243 &mockHintProvider{}, 244 &mockHintProvider{}, 245 }, 246 }, 247 } 248 mngr := manager{} 249 mngr.scope = NewContainerScope(NewNonePolicy()) 250 for _, tc := range tcases { 251 for _, hp := range tc.hp { 252 mngr.AddHintProvider(hp) 253 } 254 if len(tc.hp) != len(mngr.scope.(*containerScope).hintProviders) { 255 t.Errorf("error") 256 } 257 } 258 } 259 260 func TestAdmit(t *testing.T) { 261 numaInfo := &NUMAInfo{ 262 Nodes: []int{0, 1}, 263 NUMADistances: NUMADistances{ 264 0: {10, 11}, 265 1: {11, 10}, 266 }, 267 } 268 269 opts := PolicyOptions{} 270 bePolicy := NewBestEffortPolicy(numaInfo, opts) 271 restrictedPolicy := NewRestrictedPolicy(numaInfo, opts) 272 singleNumaPolicy := NewSingleNumaNodePolicy(numaInfo, opts) 273 274 tcases := []struct { 275 name string 276 result lifecycle.PodAdmitResult 277 qosClass v1.PodQOSClass 278 policy Policy 279 hp []HintProvider 280 expected bool 281 }{ 282 { 283 name: "QOSClass set as BestEffort. None Policy. No Hints.", 284 qosClass: v1.PodQOSBestEffort, 285 policy: NewNonePolicy(), 286 hp: []HintProvider{}, 287 expected: true, 288 }, 289 { 290 name: "QOSClass set as Guaranteed. None Policy. No Hints.", 291 qosClass: v1.PodQOSGuaranteed, 292 policy: NewNonePolicy(), 293 hp: []HintProvider{}, 294 expected: true, 295 }, 296 { 297 name: "QOSClass set as BestEffort. single-numa-node Policy. No Hints.", 298 qosClass: v1.PodQOSBestEffort, 299 policy: singleNumaPolicy, 300 hp: []HintProvider{ 301 &mockHintProvider{}, 302 }, 303 expected: true, 304 }, 305 { 306 name: "QOSClass set as BestEffort. Restricted Policy. No Hints.", 307 qosClass: v1.PodQOSBestEffort, 308 policy: restrictedPolicy, 309 hp: []HintProvider{ 310 &mockHintProvider{}, 311 }, 312 expected: true, 313 }, 314 { 315 name: "QOSClass set as Guaranteed. BestEffort Policy. Preferred Affinity.", 316 qosClass: v1.PodQOSGuaranteed, 317 policy: bePolicy, 318 hp: []HintProvider{ 319 &mockHintProvider{ 320 map[string][]TopologyHint{ 321 "resource": { 322 { 323 NUMANodeAffinity: NewTestBitMask(0), 324 Preferred: true, 325 }, 326 { 327 NUMANodeAffinity: NewTestBitMask(0, 1), 328 Preferred: false, 329 }, 330 }, 331 }, 332 }, 333 }, 334 expected: true, 335 }, 336 { 337 name: "QOSClass set as Guaranteed. BestEffort Policy. More than one Preferred Affinity.", 338 qosClass: v1.PodQOSGuaranteed, 339 policy: bePolicy, 340 hp: []HintProvider{ 341 &mockHintProvider{ 342 map[string][]TopologyHint{ 343 "resource": { 344 { 345 NUMANodeAffinity: NewTestBitMask(0), 346 Preferred: true, 347 }, 348 { 349 NUMANodeAffinity: NewTestBitMask(1), 350 Preferred: true, 351 }, 352 { 353 NUMANodeAffinity: NewTestBitMask(0, 1), 354 Preferred: false, 355 }, 356 }, 357 }, 358 }, 359 }, 360 expected: true, 361 }, 362 { 363 name: "QOSClass set as Burstable. BestEffort Policy. More than one Preferred Affinity.", 364 qosClass: v1.PodQOSBurstable, 365 policy: bePolicy, 366 hp: []HintProvider{ 367 &mockHintProvider{ 368 map[string][]TopologyHint{ 369 "resource": { 370 { 371 NUMANodeAffinity: NewTestBitMask(0), 372 Preferred: true, 373 }, 374 { 375 NUMANodeAffinity: NewTestBitMask(1), 376 Preferred: true, 377 }, 378 { 379 NUMANodeAffinity: NewTestBitMask(0, 1), 380 Preferred: false, 381 }, 382 }, 383 }, 384 }, 385 }, 386 expected: true, 387 }, 388 { 389 name: "QOSClass set as Guaranteed. BestEffort Policy. No Preferred Affinity.", 390 qosClass: v1.PodQOSGuaranteed, 391 policy: bePolicy, 392 hp: []HintProvider{ 393 &mockHintProvider{ 394 map[string][]TopologyHint{ 395 "resource": { 396 { 397 NUMANodeAffinity: NewTestBitMask(0, 1), 398 Preferred: false, 399 }, 400 }, 401 }, 402 }, 403 }, 404 expected: true, 405 }, 406 { 407 name: "QOSClass set as Guaranteed. Restricted Policy. Preferred Affinity.", 408 qosClass: v1.PodQOSGuaranteed, 409 policy: restrictedPolicy, 410 hp: []HintProvider{ 411 &mockHintProvider{ 412 map[string][]TopologyHint{ 413 "resource": { 414 { 415 NUMANodeAffinity: NewTestBitMask(0), 416 Preferred: true, 417 }, 418 { 419 NUMANodeAffinity: NewTestBitMask(0, 1), 420 Preferred: false, 421 }, 422 }, 423 }, 424 }, 425 }, 426 expected: true, 427 }, 428 { 429 name: "QOSClass set as Burstable. Restricted Policy. Preferred Affinity.", 430 qosClass: v1.PodQOSBurstable, 431 policy: restrictedPolicy, 432 hp: []HintProvider{ 433 &mockHintProvider{ 434 map[string][]TopologyHint{ 435 "resource": { 436 { 437 NUMANodeAffinity: NewTestBitMask(0), 438 Preferred: true, 439 }, 440 { 441 NUMANodeAffinity: NewTestBitMask(0, 1), 442 Preferred: false, 443 }, 444 }, 445 }, 446 }, 447 }, 448 expected: true, 449 }, 450 { 451 name: "QOSClass set as Guaranteed. Restricted Policy. More than one Preferred affinity.", 452 qosClass: v1.PodQOSGuaranteed, 453 policy: restrictedPolicy, 454 hp: []HintProvider{ 455 &mockHintProvider{ 456 map[string][]TopologyHint{ 457 "resource": { 458 { 459 NUMANodeAffinity: NewTestBitMask(0), 460 Preferred: true, 461 }, 462 { 463 NUMANodeAffinity: NewTestBitMask(1), 464 Preferred: true, 465 }, 466 { 467 NUMANodeAffinity: NewTestBitMask(0, 1), 468 Preferred: false, 469 }, 470 }, 471 }, 472 }, 473 }, 474 expected: true, 475 }, 476 { 477 name: "QOSClass set as Burstable. Restricted Policy. More than one Preferred affinity.", 478 qosClass: v1.PodQOSBurstable, 479 policy: restrictedPolicy, 480 hp: []HintProvider{ 481 &mockHintProvider{ 482 map[string][]TopologyHint{ 483 "resource": { 484 { 485 NUMANodeAffinity: NewTestBitMask(0), 486 Preferred: true, 487 }, 488 { 489 NUMANodeAffinity: NewTestBitMask(1), 490 Preferred: true, 491 }, 492 { 493 NUMANodeAffinity: NewTestBitMask(0, 1), 494 Preferred: false, 495 }, 496 }, 497 }, 498 }, 499 }, 500 expected: true, 501 }, 502 { 503 name: "QOSClass set as Guaranteed. Restricted Policy. No Preferred affinity.", 504 qosClass: v1.PodQOSGuaranteed, 505 policy: restrictedPolicy, 506 hp: []HintProvider{ 507 &mockHintProvider{ 508 map[string][]TopologyHint{ 509 "resource": { 510 { 511 NUMANodeAffinity: NewTestBitMask(0, 1), 512 Preferred: false, 513 }, 514 }, 515 }, 516 }, 517 }, 518 expected: false, 519 }, 520 { 521 name: "QOSClass set as Burstable. Restricted Policy. No Preferred affinity.", 522 qosClass: v1.PodQOSBurstable, 523 policy: restrictedPolicy, 524 hp: []HintProvider{ 525 &mockHintProvider{ 526 map[string][]TopologyHint{ 527 "resource": { 528 { 529 NUMANodeAffinity: NewTestBitMask(0, 1), 530 Preferred: false, 531 }, 532 }, 533 }, 534 }, 535 }, 536 expected: false, 537 }, 538 } 539 for _, tc := range tcases { 540 ctnScopeManager := manager{} 541 ctnScopeManager.scope = NewContainerScope(tc.policy) 542 ctnScopeManager.scope.(*containerScope).hintProviders = tc.hp 543 544 podScopeManager := manager{} 545 podScopeManager.scope = NewPodScope(tc.policy) 546 podScopeManager.scope.(*podScope).hintProviders = tc.hp 547 548 pod := &v1.Pod{ 549 Spec: v1.PodSpec{ 550 Containers: []v1.Container{ 551 { 552 Resources: v1.ResourceRequirements{}, 553 }, 554 }, 555 }, 556 Status: v1.PodStatus{ 557 QOSClass: tc.qosClass, 558 }, 559 } 560 561 podAttr := lifecycle.PodAdmitAttributes{ 562 Pod: pod, 563 } 564 565 // Container scope Admit 566 ctnActual := ctnScopeManager.Admit(&podAttr) 567 if ctnActual.Admit != tc.expected { 568 t.Errorf("Error occurred, expected Admit in result to be %v got %v", tc.expected, ctnActual.Admit) 569 } 570 if !ctnActual.Admit && ctnActual.Reason != ErrorTopologyAffinity { 571 t.Errorf("Error occurred, expected Reason in result to be %v got %v", ErrorTopologyAffinity, ctnActual.Reason) 572 } 573 574 // Pod scope Admit 575 podActual := podScopeManager.Admit(&podAttr) 576 if podActual.Admit != tc.expected { 577 t.Errorf("Error occurred, expected Admit in result to be %v got %v", tc.expected, podActual.Admit) 578 } 579 if !ctnActual.Admit && ctnActual.Reason != ErrorTopologyAffinity { 580 t.Errorf("Error occurred, expected Reason in result to be %v got %v", ErrorTopologyAffinity, ctnActual.Reason) 581 } 582 } 583 }