github.com/kubewharf/katalyst-core@v0.5.3/pkg/controller/spd/spd_baseline_test.go (about) 1 /* 2 Copyright 2022 The Katalyst 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 spd 18 19 import ( 20 "context" 21 "fmt" 22 "testing" 23 "time" 24 25 "github.com/stretchr/testify/assert" 26 appsv1 "k8s.io/api/apps/v1" 27 v1 "k8s.io/api/core/v1" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/runtime" 30 "k8s.io/client-go/tools/cache" 31 "k8s.io/utils/pointer" 32 33 apis "github.com/kubewharf/katalyst-api/pkg/apis/autoscaling/v1alpha1" 34 apiworkload "github.com/kubewharf/katalyst-api/pkg/apis/workload/v1alpha1" 35 "github.com/kubewharf/katalyst-api/pkg/consts" 36 katalystbase "github.com/kubewharf/katalyst-core/cmd/base" 37 "github.com/kubewharf/katalyst-core/pkg/config/controller" 38 "github.com/kubewharf/katalyst-core/pkg/config/generic" 39 "github.com/kubewharf/katalyst-core/pkg/util" 40 ) 41 42 func TestSPDController_updateBaselinePercentile(t *testing.T) { 43 t.Parallel() 44 45 type fields struct { 46 podList []runtime.Object 47 workload *appsv1.StatefulSet 48 spd *apiworkload.ServiceProfileDescriptor 49 } 50 tests := []struct { 51 name string 52 fields fields 53 wantSPD *apiworkload.ServiceProfileDescriptor 54 wantErr assert.ErrorAssertionFunc 55 }{ 56 { 57 name: "one pod", 58 fields: fields{ 59 podList: []runtime.Object{ 60 &v1.Pod{ 61 ObjectMeta: metav1.ObjectMeta{ 62 Name: "pod1", 63 Namespace: "default", 64 OwnerReferences: []metav1.OwnerReference{ 65 { 66 APIVersion: "apps/v1", 67 Kind: "StatefulSet", 68 Name: "sts1", 69 }, 70 }, 71 Annotations: map[string]string{ 72 consts.PodAnnotationSPDNameKey: "spd1", 73 }, 74 Labels: map[string]string{ 75 "workload": "sts1", 76 }, 77 CreationTimestamp: metav1.NewTime(time.Date(2023, time.August, 1, 0, 0, 0, 0, time.UTC)), 78 }, 79 }, 80 }, 81 workload: &appsv1.StatefulSet{ 82 TypeMeta: metav1.TypeMeta{ 83 Kind: "StatefulSet", 84 APIVersion: "apps/v1", 85 }, 86 ObjectMeta: metav1.ObjectMeta{ 87 Name: "sts1", 88 Namespace: "default", 89 Annotations: map[string]string{}, 90 }, 91 Spec: appsv1.StatefulSetSpec{ 92 Selector: &metav1.LabelSelector{ 93 MatchLabels: map[string]string{ 94 "workload": "sts1", 95 }, 96 }, 97 }, 98 }, 99 spd: &apiworkload.ServiceProfileDescriptor{ 100 ObjectMeta: metav1.ObjectMeta{ 101 Namespace: "default", 102 Name: "spd1", 103 }, 104 Spec: apiworkload.ServiceProfileDescriptorSpec{ 105 TargetRef: apis.CrossVersionObjectReference{ 106 Kind: stsGVK.Kind, 107 Name: "sts1", 108 APIVersion: stsGVK.GroupVersion().String(), 109 }, 110 BaselinePercent: pointer.Int32(50), 111 }, 112 Status: apiworkload.ServiceProfileDescriptorStatus{}, 113 }, 114 }, 115 wantSPD: &apiworkload.ServiceProfileDescriptor{ 116 ObjectMeta: metav1.ObjectMeta{ 117 Namespace: "default", 118 Name: "spd1", 119 Annotations: map[string]string{ 120 consts.SPDAnnotationBaselineSentinelKey: util.SPDBaselinePodMeta{ 121 TimeStamp: metav1.NewTime(time.Date(2023, time.August, 1, 0, 0, 0, 0, time.UTC)), 122 PodName: "pod1", 123 }.String(), 124 }, 125 }, 126 Spec: apiworkload.ServiceProfileDescriptorSpec{ 127 TargetRef: apis.CrossVersionObjectReference{ 128 Kind: stsGVK.Kind, 129 Name: "sts1", 130 APIVersion: stsGVK.GroupVersion().String(), 131 }, 132 BaselinePercent: pointer.Int32(50), 133 }, 134 Status: apiworkload.ServiceProfileDescriptorStatus{}, 135 }, 136 wantErr: assert.NoError, 137 }, 138 { 139 name: "none pod", 140 fields: fields{ 141 podList: []runtime.Object{}, 142 workload: &appsv1.StatefulSet{ 143 TypeMeta: metav1.TypeMeta{ 144 Kind: "StatefulSet", 145 APIVersion: "apps/v1", 146 }, 147 ObjectMeta: metav1.ObjectMeta{ 148 Name: "sts1", 149 Namespace: "default", 150 Annotations: map[string]string{}, 151 }, 152 Spec: appsv1.StatefulSetSpec{ 153 Selector: &metav1.LabelSelector{ 154 MatchLabels: map[string]string{ 155 "workload": "sts1", 156 }, 157 }, 158 }, 159 }, 160 spd: &apiworkload.ServiceProfileDescriptor{ 161 ObjectMeta: metav1.ObjectMeta{ 162 Namespace: "default", 163 Name: "spd1", 164 }, 165 Spec: apiworkload.ServiceProfileDescriptorSpec{ 166 TargetRef: apis.CrossVersionObjectReference{ 167 Kind: stsGVK.Kind, 168 Name: "sts1", 169 APIVersion: stsGVK.GroupVersion().String(), 170 }, 171 BaselinePercent: pointer.Int32(50), 172 }, 173 Status: apiworkload.ServiceProfileDescriptorStatus{}, 174 }, 175 }, 176 wantSPD: &apiworkload.ServiceProfileDescriptor{ 177 ObjectMeta: metav1.ObjectMeta{ 178 Namespace: "default", 179 Name: "spd1", 180 }, 181 Spec: apiworkload.ServiceProfileDescriptorSpec{ 182 TargetRef: apis.CrossVersionObjectReference{ 183 Kind: stsGVK.Kind, 184 Name: "sts1", 185 APIVersion: stsGVK.GroupVersion().String(), 186 }, 187 BaselinePercent: pointer.Int32(50), 188 }, 189 Status: apiworkload.ServiceProfileDescriptorStatus{}, 190 }, 191 wantErr: assert.NoError, 192 }, 193 { 194 name: "three pod for 50% baseline percent", 195 fields: fields{ 196 podList: []runtime.Object{ 197 &v1.Pod{ 198 ObjectMeta: metav1.ObjectMeta{ 199 Name: "pod1", 200 Namespace: "default", 201 OwnerReferences: []metav1.OwnerReference{ 202 { 203 APIVersion: "apps/v1", 204 Kind: "StatefulSet", 205 Name: "sts1", 206 }, 207 }, 208 Annotations: map[string]string{ 209 consts.PodAnnotationSPDNameKey: "spd1", 210 }, 211 Labels: map[string]string{ 212 "workload": "sts1", 213 }, 214 CreationTimestamp: metav1.NewTime(time.Date(2023, time.August, 1, 0, 0, 0, 0, time.UTC)), 215 }, 216 }, 217 &v1.Pod{ 218 ObjectMeta: metav1.ObjectMeta{ 219 Name: "pod2", 220 Namespace: "default", 221 OwnerReferences: []metav1.OwnerReference{ 222 { 223 APIVersion: "apps/v1", 224 Kind: "StatefulSet", 225 Name: "sts1", 226 }, 227 }, 228 Annotations: map[string]string{ 229 consts.PodAnnotationSPDNameKey: "spd1", 230 }, 231 Labels: map[string]string{ 232 "workload": "sts1", 233 }, 234 CreationTimestamp: metav1.NewTime(time.Date(2023, time.August, 1, 0, 0, 1, 0, time.UTC)), 235 }, 236 }, 237 &v1.Pod{ 238 ObjectMeta: metav1.ObjectMeta{ 239 Name: "pod3", 240 Namespace: "default", 241 OwnerReferences: []metav1.OwnerReference{ 242 { 243 APIVersion: "apps/v1", 244 Kind: "StatefulSet", 245 Name: "sts1", 246 }, 247 }, 248 Annotations: map[string]string{ 249 consts.PodAnnotationSPDNameKey: "spd1", 250 }, 251 Labels: map[string]string{ 252 "workload": "sts1", 253 }, 254 CreationTimestamp: metav1.NewTime(time.Date(2023, time.August, 1, 0, 0, 2, 0, time.UTC)), 255 }, 256 }, 257 }, 258 workload: &appsv1.StatefulSet{ 259 TypeMeta: metav1.TypeMeta{ 260 Kind: "StatefulSet", 261 APIVersion: "apps/v1", 262 }, 263 ObjectMeta: metav1.ObjectMeta{ 264 Name: "sts1", 265 Namespace: "default", 266 Annotations: map[string]string{}, 267 }, 268 Spec: appsv1.StatefulSetSpec{ 269 Selector: &metav1.LabelSelector{ 270 MatchLabels: map[string]string{ 271 "workload": "sts1", 272 }, 273 }, 274 }, 275 }, 276 spd: &apiworkload.ServiceProfileDescriptor{ 277 ObjectMeta: metav1.ObjectMeta{ 278 Namespace: "default", 279 Name: "spd1", 280 }, 281 Spec: apiworkload.ServiceProfileDescriptorSpec{ 282 TargetRef: apis.CrossVersionObjectReference{ 283 Kind: stsGVK.Kind, 284 Name: "sts1", 285 APIVersion: stsGVK.GroupVersion().String(), 286 }, 287 BaselinePercent: pointer.Int32(50), 288 ExtendedIndicator: []apiworkload.ServiceExtendedIndicatorSpec{ 289 { 290 Name: "TestExtended", 291 BaselinePercent: pointer.Int32(50), 292 Indicators: runtime.RawExtension{ 293 Object: &apiworkload.TestExtendedIndicators{}, 294 }, 295 }, 296 }, 297 }, 298 Status: apiworkload.ServiceProfileDescriptorStatus{}, 299 }, 300 }, 301 wantSPD: &apiworkload.ServiceProfileDescriptor{ 302 ObjectMeta: metav1.ObjectMeta{ 303 Namespace: "default", 304 Name: "spd1", 305 Annotations: map[string]string{ 306 consts.SPDAnnotationBaselineSentinelKey: "{\"timeStamp\":\"2023-08-01T00:00:01Z\",\"podName\":\"pod2\"}", 307 consts.SPDAnnotationExtendedBaselineSentinelKey: "{\"TestExtended\":{\"timeStamp\":\"2023-08-01T00:00:01Z\",\"podName\":\"pod2\"}}", 308 }, 309 }, 310 Spec: apiworkload.ServiceProfileDescriptorSpec{ 311 TargetRef: apis.CrossVersionObjectReference{ 312 Kind: stsGVK.Kind, 313 Name: "sts1", 314 APIVersion: stsGVK.GroupVersion().String(), 315 }, 316 BaselinePercent: pointer.Int32(50), 317 ExtendedIndicator: []apiworkload.ServiceExtendedIndicatorSpec{ 318 { 319 Name: "TestExtended", 320 BaselinePercent: pointer.Int32(50), 321 Indicators: runtime.RawExtension{ 322 Object: &apiworkload.TestExtendedIndicators{}, 323 }, 324 }, 325 }, 326 }, 327 Status: apiworkload.ServiceProfileDescriptorStatus{}, 328 }, 329 wantErr: assert.NoError, 330 }, 331 { 332 name: "three pod for 100% baseline percent", 333 fields: fields{ 334 podList: []runtime.Object{ 335 &v1.Pod{ 336 ObjectMeta: metav1.ObjectMeta{ 337 Name: "pod1", 338 Namespace: "default", 339 OwnerReferences: []metav1.OwnerReference{ 340 { 341 APIVersion: "apps/v1", 342 Kind: "StatefulSet", 343 Name: "sts1", 344 }, 345 }, 346 Annotations: map[string]string{ 347 consts.PodAnnotationSPDNameKey: "spd1", 348 }, 349 Labels: map[string]string{ 350 "workload": "sts1", 351 }, 352 CreationTimestamp: metav1.NewTime(time.Date(2023, time.August, 1, 0, 0, 0, 0, time.UTC)), 353 }, 354 }, 355 &v1.Pod{ 356 ObjectMeta: metav1.ObjectMeta{ 357 Name: "pod2", 358 Namespace: "default", 359 OwnerReferences: []metav1.OwnerReference{ 360 { 361 APIVersion: "apps/v1", 362 Kind: "StatefulSet", 363 Name: "sts1", 364 }, 365 }, 366 Annotations: map[string]string{ 367 consts.PodAnnotationSPDNameKey: "spd1", 368 }, 369 Labels: map[string]string{ 370 "workload": "sts1", 371 }, 372 CreationTimestamp: metav1.NewTime(time.Date(2023, time.August, 1, 0, 0, 1, 0, time.UTC)), 373 }, 374 }, 375 &v1.Pod{ 376 ObjectMeta: metav1.ObjectMeta{ 377 Name: "pod3", 378 Namespace: "default", 379 OwnerReferences: []metav1.OwnerReference{ 380 { 381 APIVersion: "apps/v1", 382 Kind: "StatefulSet", 383 Name: "sts1", 384 }, 385 }, 386 Annotations: map[string]string{ 387 consts.PodAnnotationSPDNameKey: "spd1", 388 }, 389 Labels: map[string]string{ 390 "workload": "sts1", 391 }, 392 CreationTimestamp: metav1.NewTime(time.Date(2023, time.August, 1, 0, 0, 2, 0, time.UTC)), 393 }, 394 }, 395 }, 396 workload: &appsv1.StatefulSet{ 397 TypeMeta: metav1.TypeMeta{ 398 Kind: "StatefulSet", 399 APIVersion: "apps/v1", 400 }, 401 ObjectMeta: metav1.ObjectMeta{ 402 Name: "sts1", 403 Namespace: "default", 404 Annotations: map[string]string{}, 405 }, 406 Spec: appsv1.StatefulSetSpec{ 407 Selector: &metav1.LabelSelector{ 408 MatchLabels: map[string]string{ 409 "workload": "sts1", 410 }, 411 }, 412 }, 413 }, 414 spd: &apiworkload.ServiceProfileDescriptor{ 415 ObjectMeta: metav1.ObjectMeta{ 416 Namespace: "default", 417 Name: "spd1", 418 }, 419 Spec: apiworkload.ServiceProfileDescriptorSpec{ 420 TargetRef: apis.CrossVersionObjectReference{ 421 Kind: stsGVK.Kind, 422 Name: "sts1", 423 APIVersion: stsGVK.GroupVersion().String(), 424 }, 425 BaselinePercent: pointer.Int32(100), 426 }, 427 Status: apiworkload.ServiceProfileDescriptorStatus{}, 428 }, 429 }, 430 wantSPD: &apiworkload.ServiceProfileDescriptor{ 431 ObjectMeta: metav1.ObjectMeta{ 432 Namespace: "default", 433 Name: "spd1", 434 }, 435 Spec: apiworkload.ServiceProfileDescriptorSpec{ 436 TargetRef: apis.CrossVersionObjectReference{ 437 Kind: stsGVK.Kind, 438 Name: "sts1", 439 APIVersion: stsGVK.GroupVersion().String(), 440 }, 441 BaselinePercent: pointer.Int32(100), 442 }, 443 Status: apiworkload.ServiceProfileDescriptorStatus{}, 444 }, 445 wantErr: assert.NoError, 446 }, 447 { 448 name: "three pod for 0% baseline percent", 449 fields: fields{ 450 podList: []runtime.Object{ 451 &v1.Pod{ 452 ObjectMeta: metav1.ObjectMeta{ 453 Name: "pod1", 454 Namespace: "default", 455 OwnerReferences: []metav1.OwnerReference{ 456 { 457 APIVersion: "apps/v1", 458 Kind: "StatefulSet", 459 Name: "sts1", 460 }, 461 }, 462 Annotations: map[string]string{ 463 consts.PodAnnotationSPDNameKey: "spd1", 464 }, 465 Labels: map[string]string{ 466 "workload": "sts1", 467 }, 468 CreationTimestamp: metav1.NewTime(time.Date(2023, time.August, 1, 0, 0, 0, 0, time.UTC)), 469 }, 470 }, 471 &v1.Pod{ 472 ObjectMeta: metav1.ObjectMeta{ 473 Name: "pod2", 474 Namespace: "default", 475 OwnerReferences: []metav1.OwnerReference{ 476 { 477 APIVersion: "apps/v1", 478 Kind: "StatefulSet", 479 Name: "sts1", 480 }, 481 }, 482 Annotations: map[string]string{ 483 consts.PodAnnotationSPDNameKey: "spd1", 484 }, 485 Labels: map[string]string{ 486 "workload": "sts1", 487 }, 488 CreationTimestamp: metav1.NewTime(time.Date(2023, time.August, 1, 0, 0, 1, 0, time.UTC)), 489 }, 490 }, 491 &v1.Pod{ 492 ObjectMeta: metav1.ObjectMeta{ 493 Name: "pod3", 494 Namespace: "default", 495 OwnerReferences: []metav1.OwnerReference{ 496 { 497 APIVersion: "apps/v1", 498 Kind: "StatefulSet", 499 Name: "sts1", 500 }, 501 }, 502 Annotations: map[string]string{ 503 consts.PodAnnotationSPDNameKey: "spd1", 504 }, 505 Labels: map[string]string{ 506 "workload": "sts1", 507 }, 508 CreationTimestamp: metav1.NewTime(time.Date(2023, time.August, 1, 0, 0, 2, 0, time.UTC)), 509 }, 510 }, 511 }, 512 workload: &appsv1.StatefulSet{ 513 TypeMeta: metav1.TypeMeta{ 514 Kind: "StatefulSet", 515 APIVersion: "apps/v1", 516 }, 517 ObjectMeta: metav1.ObjectMeta{ 518 Name: "sts1", 519 Namespace: "default", 520 Annotations: map[string]string{}, 521 }, 522 Spec: appsv1.StatefulSetSpec{ 523 Selector: &metav1.LabelSelector{ 524 MatchLabels: map[string]string{ 525 "workload": "sts1", 526 }, 527 }, 528 }, 529 }, 530 spd: &apiworkload.ServiceProfileDescriptor{ 531 ObjectMeta: metav1.ObjectMeta{ 532 Namespace: "default", 533 Name: "spd1", 534 }, 535 Spec: apiworkload.ServiceProfileDescriptorSpec{ 536 TargetRef: apis.CrossVersionObjectReference{ 537 Kind: stsGVK.Kind, 538 Name: "sts1", 539 APIVersion: stsGVK.GroupVersion().String(), 540 }, 541 BaselinePercent: pointer.Int32(0), 542 }, 543 Status: apiworkload.ServiceProfileDescriptorStatus{}, 544 }, 545 }, 546 wantSPD: &apiworkload.ServiceProfileDescriptor{ 547 ObjectMeta: metav1.ObjectMeta{ 548 Namespace: "default", 549 Name: "spd1", 550 }, 551 Spec: apiworkload.ServiceProfileDescriptorSpec{ 552 TargetRef: apis.CrossVersionObjectReference{ 553 Kind: stsGVK.Kind, 554 Name: "sts1", 555 APIVersion: stsGVK.GroupVersion().String(), 556 }, 557 BaselinePercent: pointer.Int32(0), 558 }, 559 Status: apiworkload.ServiceProfileDescriptorStatus{}, 560 }, 561 wantErr: assert.NoError, 562 }, 563 } 564 for _, tt := range tests { 565 tt := tt 566 t.Run(tt.name, func(t *testing.T) { 567 t.Parallel() 568 569 spdConfig := &controller.SPDConfig{ 570 SPDWorkloadGVResources: []string{"statefulsets.v1.apps"}, 571 } 572 genericConfig := &generic.GenericConfiguration{} 573 controllerConf := &controller.GenericControllerConfiguration{ 574 DynamicGVResources: []string{"statefulsets.v1.apps"}, 575 } 576 577 ctx := context.TODO() 578 controlCtx, err := katalystbase.GenerateFakeGenericContext(tt.fields.podList, 579 []runtime.Object{tt.fields.spd}, []runtime.Object{tt.fields.workload}) 580 assert.NoError(t, err) 581 582 spdController, err := NewSPDController(ctx, controlCtx, genericConfig, controllerConf, spdConfig, nil, struct{}{}) 583 assert.NoError(t, err) 584 585 controlCtx.StartInformer(ctx) 586 go spdController.Run() 587 synced := cache.WaitForCacheSync(ctx.Done(), spdController.syncedFunc...) 588 assert.True(t, synced) 589 time.Sleep(1 * time.Second) 590 591 tt.wantErr(t, spdController.updateBaselineSentinel(tt.fields.spd), fmt.Sprintf("updateBaselineSentinel(%v)", tt.fields.spd)) 592 assert.Equal(t, tt.wantSPD, tt.fields.spd) 593 }) 594 } 595 }