1/* 2Copyright 2019 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package topologymanager 18 19import ( 20 "fmt" 21 "strings" 22 "testing" 23 24 "k8s.io/api/core/v1" 25 "k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/bitmask" 26 "k8s.io/kubernetes/pkg/kubelet/lifecycle" 27) 28 29func NewTestBitMask(sockets ...int) bitmask.BitMask { 30 s, _ := bitmask.NewBitMask(sockets...) 31 return s 32} 33 34func TestNewManager(t *testing.T) { 35 tcases := []struct { 36 description string 37 policyName string 38 expectedPolicy string 39 expectedError error 40 }{ 41 { 42 description: "Policy is set to none", 43 policyName: "none", 44 expectedPolicy: "none", 45 }, 46 { 47 description: "Policy is set to best-effort", 48 policyName: "best-effort", 49 expectedPolicy: "best-effort", 50 }, 51 { 52 description: "Policy is set to restricted", 53 policyName: "restricted", 54 expectedPolicy: "restricted", 55 }, 56 { 57 description: "Policy is set to single-numa-node", 58 policyName: "single-numa-node", 59 expectedPolicy: "single-numa-node", 60 }, 61 { 62 description: "Policy is set to unknown", 63 policyName: "unknown", 64 expectedError: fmt.Errorf("unknown policy: \"unknown\""), 65 }, 66 } 67 68 for _, tc := range tcases { 69 mngr, err := NewManager(nil, tc.policyName, "container") 70 71 if tc.expectedError != nil { 72 if !strings.Contains(err.Error(), tc.expectedError.Error()) { 73 t.Errorf("Unexpected error message. Have: %s wants %s", err.Error(), tc.expectedError.Error()) 74 } 75 } else { 76 rawMgr := mngr.(*manager) 77 rawScope := rawMgr.scope.(*containerScope) 78 if rawScope.policy.Name() != tc.expectedPolicy { 79 t.Errorf("Unexpected policy name. Have: %q wants %q", rawScope.policy.Name(), tc.expectedPolicy) 80 } 81 } 82 } 83} 84 85func TestManagerScope(t *testing.T) { 86 tcases := []struct { 87 description string 88 scopeName string 89 expectedScope string 90 expectedError error 91 }{ 92 { 93 description: "Topology Manager Scope is set to container", 94 scopeName: "container", 95 expectedScope: "container", 96 }, 97 { 98 description: "Topology Manager Scope is set to pod", 99 scopeName: "pod", 100 expectedScope: "pod", 101 }, 102 { 103 description: "Topology Manager Scope is set to unknown", 104 scopeName: "unknown", 105 expectedError: fmt.Errorf("unknown scope: \"unknown\""), 106 }, 107 } 108 109 for _, tc := range tcases { 110 mngr, err := NewManager(nil, "best-effort", tc.scopeName) 111 112 if tc.expectedError != nil { 113 if !strings.Contains(err.Error(), tc.expectedError.Error()) { 114 t.Errorf("Unexpected error message. Have: %s wants %s", err.Error(), tc.expectedError.Error()) 115 } 116 } else { 117 rawMgr := mngr.(*manager) 118 if rawMgr.scope.Name() != tc.expectedScope { 119 t.Errorf("Unexpected scope name. Have: %q wants %q", rawMgr.scope, tc.expectedScope) 120 } 121 } 122 } 123} 124 125type mockHintProvider struct { 126 th map[string][]TopologyHint 127 //TODO: Add this field and add some tests to make sure things error out 128 //appropriately on allocation errors. 129 //allocateError error 130} 131 132func (m *mockHintProvider) GetTopologyHints(pod *v1.Pod, container *v1.Container) map[string][]TopologyHint { 133 return m.th 134} 135 136func (m *mockHintProvider) GetPodTopologyHints(pod *v1.Pod) map[string][]TopologyHint { 137 return m.th 138} 139 140func (m *mockHintProvider) Allocate(pod *v1.Pod, container *v1.Container) error { 141 //return allocateError 142 return nil 143} 144 145type mockPolicy struct { 146 nonePolicy 147 ph []map[string][]TopologyHint 148} 149 150func (p *mockPolicy) Merge(providersHints []map[string][]TopologyHint) (TopologyHint, bool) { 151 p.ph = providersHints 152 return TopologyHint{}, true 153} 154 155func TestAddHintProvider(t *testing.T) { 156 tcases := []struct { 157 name string 158 hp []HintProvider 159 }{ 160 { 161 name: "Add HintProvider", 162 hp: []HintProvider{ 163 &mockHintProvider{}, 164 &mockHintProvider{}, 165 &mockHintProvider{}, 166 }, 167 }, 168 } 169 mngr := manager{} 170 mngr.scope = NewContainerScope(NewNonePolicy()) 171 for _, tc := range tcases { 172 for _, hp := range tc.hp { 173 mngr.AddHintProvider(hp) 174 } 175 if len(tc.hp) != len(mngr.scope.(*containerScope).hintProviders) { 176 t.Errorf("error") 177 } 178 } 179} 180 181func TestAdmit(t *testing.T) { 182 numaNodes := []int{0, 1} 183 184 tcases := []struct { 185 name string 186 result lifecycle.PodAdmitResult 187 qosClass v1.PodQOSClass 188 policy Policy 189 hp []HintProvider 190 expected bool 191 }{ 192 { 193 name: "QOSClass set as BestEffort. None Policy. No Hints.", 194 qosClass: v1.PodQOSBestEffort, 195 policy: NewNonePolicy(), 196 hp: []HintProvider{}, 197 expected: true, 198 }, 199 { 200 name: "QOSClass set as Guaranteed. None Policy. No Hints.", 201 qosClass: v1.PodQOSGuaranteed, 202 policy: NewNonePolicy(), 203 hp: []HintProvider{}, 204 expected: true, 205 }, 206 { 207 name: "QOSClass set as BestEffort. single-numa-node Policy. No Hints.", 208 qosClass: v1.PodQOSBestEffort, 209 policy: NewRestrictedPolicy(numaNodes), 210 hp: []HintProvider{ 211 &mockHintProvider{}, 212 }, 213 expected: true, 214 }, 215 { 216 name: "QOSClass set as BestEffort. Restricted Policy. No Hints.", 217 qosClass: v1.PodQOSBestEffort, 218 policy: NewRestrictedPolicy(numaNodes), 219 hp: []HintProvider{ 220 &mockHintProvider{}, 221 }, 222 expected: true, 223 }, 224 { 225 name: "QOSClass set as Guaranteed. BestEffort Policy. Preferred Affinity.", 226 qosClass: v1.PodQOSGuaranteed, 227 policy: NewBestEffortPolicy(numaNodes), 228 hp: []HintProvider{ 229 &mockHintProvider{ 230 map[string][]TopologyHint{ 231 "resource": { 232 { 233 NUMANodeAffinity: NewTestBitMask(0), 234 Preferred: true, 235 }, 236 { 237 NUMANodeAffinity: NewTestBitMask(0, 1), 238 Preferred: false, 239 }, 240 }, 241 }, 242 }, 243 }, 244 expected: true, 245 }, 246 { 247 name: "QOSClass set as Guaranteed. BestEffort Policy. More than one Preferred Affinity.", 248 qosClass: v1.PodQOSGuaranteed, 249 policy: NewBestEffortPolicy(numaNodes), 250 hp: []HintProvider{ 251 &mockHintProvider{ 252 map[string][]TopologyHint{ 253 "resource": { 254 { 255 NUMANodeAffinity: NewTestBitMask(0), 256 Preferred: true, 257 }, 258 { 259 NUMANodeAffinity: NewTestBitMask(1), 260 Preferred: true, 261 }, 262 { 263 NUMANodeAffinity: NewTestBitMask(0, 1), 264 Preferred: false, 265 }, 266 }, 267 }, 268 }, 269 }, 270 expected: true, 271 }, 272 { 273 name: "QOSClass set as Burstable. BestEffort Policy. More than one Preferred Affinity.", 274 qosClass: v1.PodQOSBurstable, 275 policy: NewBestEffortPolicy(numaNodes), 276 hp: []HintProvider{ 277 &mockHintProvider{ 278 map[string][]TopologyHint{ 279 "resource": { 280 { 281 NUMANodeAffinity: NewTestBitMask(0), 282 Preferred: true, 283 }, 284 { 285 NUMANodeAffinity: NewTestBitMask(1), 286 Preferred: true, 287 }, 288 { 289 NUMANodeAffinity: NewTestBitMask(0, 1), 290 Preferred: false, 291 }, 292 }, 293 }, 294 }, 295 }, 296 expected: true, 297 }, 298 { 299 name: "QOSClass set as Guaranteed. BestEffort Policy. No Preferred Affinity.", 300 qosClass: v1.PodQOSGuaranteed, 301 policy: NewBestEffortPolicy(numaNodes), 302 hp: []HintProvider{ 303 &mockHintProvider{ 304 map[string][]TopologyHint{ 305 "resource": { 306 { 307 NUMANodeAffinity: NewTestBitMask(0, 1), 308 Preferred: false, 309 }, 310 }, 311 }, 312 }, 313 }, 314 expected: true, 315 }, 316 { 317 name: "QOSClass set as Guaranteed. Restricted Policy. Preferred Affinity.", 318 qosClass: v1.PodQOSGuaranteed, 319 policy: NewRestrictedPolicy(numaNodes), 320 hp: []HintProvider{ 321 &mockHintProvider{ 322 map[string][]TopologyHint{ 323 "resource": { 324 { 325 NUMANodeAffinity: NewTestBitMask(0), 326 Preferred: true, 327 }, 328 { 329 NUMANodeAffinity: NewTestBitMask(0, 1), 330 Preferred: false, 331 }, 332 }, 333 }, 334 }, 335 }, 336 expected: true, 337 }, 338 { 339 name: "QOSClass set as Burstable. Restricted Policy. Preferred Affinity.", 340 qosClass: v1.PodQOSBurstable, 341 policy: NewRestrictedPolicy(numaNodes), 342 hp: []HintProvider{ 343 &mockHintProvider{ 344 map[string][]TopologyHint{ 345 "resource": { 346 { 347 NUMANodeAffinity: NewTestBitMask(0), 348 Preferred: true, 349 }, 350 { 351 NUMANodeAffinity: NewTestBitMask(0, 1), 352 Preferred: false, 353 }, 354 }, 355 }, 356 }, 357 }, 358 expected: true, 359 }, 360 { 361 name: "QOSClass set as Guaranteed. Restricted Policy. More than one Preferred affinity.", 362 qosClass: v1.PodQOSGuaranteed, 363 policy: NewRestrictedPolicy(numaNodes), 364 hp: []HintProvider{ 365 &mockHintProvider{ 366 map[string][]TopologyHint{ 367 "resource": { 368 { 369 NUMANodeAffinity: NewTestBitMask(0), 370 Preferred: true, 371 }, 372 { 373 NUMANodeAffinity: NewTestBitMask(1), 374 Preferred: true, 375 }, 376 { 377 NUMANodeAffinity: NewTestBitMask(0, 1), 378 Preferred: false, 379 }, 380 }, 381 }, 382 }, 383 }, 384 expected: true, 385 }, 386 { 387 name: "QOSClass set as Burstable. Restricted Policy. More than one Preferred affinity.", 388 qosClass: v1.PodQOSBurstable, 389 policy: NewRestrictedPolicy(numaNodes), 390 hp: []HintProvider{ 391 &mockHintProvider{ 392 map[string][]TopologyHint{ 393 "resource": { 394 { 395 NUMANodeAffinity: NewTestBitMask(0), 396 Preferred: true, 397 }, 398 { 399 NUMANodeAffinity: NewTestBitMask(1), 400 Preferred: true, 401 }, 402 { 403 NUMANodeAffinity: NewTestBitMask(0, 1), 404 Preferred: false, 405 }, 406 }, 407 }, 408 }, 409 }, 410 expected: true, 411 }, 412 { 413 name: "QOSClass set as Guaranteed. Restricted Policy. No Preferred affinity.", 414 qosClass: v1.PodQOSGuaranteed, 415 policy: NewRestrictedPolicy(numaNodes), 416 hp: []HintProvider{ 417 &mockHintProvider{ 418 map[string][]TopologyHint{ 419 "resource": { 420 { 421 NUMANodeAffinity: NewTestBitMask(0, 1), 422 Preferred: false, 423 }, 424 }, 425 }, 426 }, 427 }, 428 expected: false, 429 }, 430 { 431 name: "QOSClass set as Burstable. Restricted Policy. No Preferred affinity.", 432 qosClass: v1.PodQOSBurstable, 433 policy: NewRestrictedPolicy(numaNodes), 434 hp: []HintProvider{ 435 &mockHintProvider{ 436 map[string][]TopologyHint{ 437 "resource": { 438 { 439 NUMANodeAffinity: NewTestBitMask(0, 1), 440 Preferred: false, 441 }, 442 }, 443 }, 444 }, 445 }, 446 expected: false, 447 }, 448 } 449 for _, tc := range tcases { 450 ctnScopeManager := manager{} 451 ctnScopeManager.scope = NewContainerScope(tc.policy) 452 ctnScopeManager.scope.(*containerScope).hintProviders = tc.hp 453 454 podScopeManager := manager{} 455 podScopeManager.scope = NewPodScope(tc.policy) 456 podScopeManager.scope.(*podScope).hintProviders = tc.hp 457 458 pod := &v1.Pod{ 459 Spec: v1.PodSpec{ 460 Containers: []v1.Container{ 461 { 462 Resources: v1.ResourceRequirements{}, 463 }, 464 }, 465 }, 466 Status: v1.PodStatus{ 467 QOSClass: tc.qosClass, 468 }, 469 } 470 471 podAttr := lifecycle.PodAdmitAttributes{ 472 Pod: pod, 473 } 474 475 // Container scope Admit 476 ctnActual := ctnScopeManager.Admit(&podAttr) 477 if ctnActual.Admit != tc.expected { 478 t.Errorf("Error occurred, expected Admit in result to be %v got %v", tc.expected, ctnActual.Admit) 479 } 480 if !ctnActual.Admit && ctnActual.Reason != ErrorTopologyAffinity { 481 t.Errorf("Error occurred, expected Reason in result to be %v got %v", ErrorTopologyAffinity, ctnActual.Reason) 482 } 483 484 // Pod scope Admit 485 podActual := podScopeManager.Admit(&podAttr) 486 if podActual.Admit != tc.expected { 487 t.Errorf("Error occurred, expected Admit in result to be %v got %v", tc.expected, podActual.Admit) 488 } 489 if !ctnActual.Admit && ctnActual.Reason != ErrorTopologyAffinity { 490 t.Errorf("Error occurred, expected Reason in result to be %v got %v", ErrorTopologyAffinity, ctnActual.Reason) 491 } 492 } 493} 494