github.com/kubewharf/katalyst-core@v0.5.3/pkg/metaserver/kcc/manager_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 kcc 18 19 import ( 20 "context" 21 "os" 22 "reflect" 23 "testing" 24 "time" 25 26 "github.com/stretchr/testify/require" 27 v1 "k8s.io/api/core/v1" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 30 "k8s.io/apimachinery/pkg/runtime/schema" 31 "k8s.io/klog/v2" 32 "k8s.io/kubernetes/pkg/kubelet/checkpointmanager" 33 34 "github.com/kubewharf/katalyst-api/pkg/apis/config/v1alpha1" 35 "github.com/kubewharf/katalyst-core/cmd/katalyst-agent/app/options" 36 pkgconfig "github.com/kubewharf/katalyst-core/pkg/config" 37 "github.com/kubewharf/katalyst-core/pkg/config/agent" 38 "github.com/kubewharf/katalyst-core/pkg/config/agent/dynamic" 39 evictionconfig "github.com/kubewharf/katalyst-core/pkg/config/agent/dynamic/adminqos/eviction" 40 "github.com/kubewharf/katalyst-core/pkg/config/agent/dynamic/crd" 41 "github.com/kubewharf/katalyst-core/pkg/metaserver/agent/cnc" 42 "github.com/kubewharf/katalyst-core/pkg/metrics" 43 "github.com/kubewharf/katalyst-core/pkg/util" 44 "github.com/kubewharf/katalyst-core/pkg/util/native" 45 ) 46 47 var ( 48 defaultEnableNumaLevelDetection = true 49 defaultEnableSystemLevelDetection = true 50 defaultNumaFreeBelowWatermarkTimesThreshold = 4 51 defaultSystemKswapdRateThreshold = 2000 52 defaultSystemKswapdRateExceedDurationThreshold = 120 53 54 nonDefaultEnableNumaLevelEviction = false 55 nonDefaultEnableSystemLevelEviction = false 56 nonDefaultNumaFreeBelowWatermarkTimesThreshold = 5 57 nonDefaultSystemKswapdRateThreshold = 3000 58 nonDefaultSystemKswapdRateExceedDurationThreshold = 130 59 nonDefaultNumaEvictionRankingMetrics = []string{"metric1", "metric2"} 60 nonDefaultSystemEvictionRankingMetrics = []string{"metric3"} 61 nonDefaultGracePeriod int64 = 30 62 63 nonDefaultMemoryEvictionPluginConfig = v1alpha1.MemoryPressureEvictionConfig{ 64 EnableNumaLevelEviction: &nonDefaultEnableNumaLevelEviction, 65 EnableSystemLevelEviction: &nonDefaultEnableSystemLevelEviction, 66 NumaFreeBelowWatermarkTimesThreshold: &nonDefaultNumaFreeBelowWatermarkTimesThreshold, 67 SystemKswapdRateThreshold: &nonDefaultSystemKswapdRateThreshold, 68 SystemKswapdRateExceedDurationThreshold: &nonDefaultSystemKswapdRateExceedDurationThreshold, 69 NumaEvictionRankingMetrics: util.ConvertStringListToNumaEvictionRankingMetrics(nonDefaultNumaEvictionRankingMetrics), 70 SystemEvictionRankingMetrics: util.ConvertStringListToSystemEvictionRankingMetrics(nonDefaultSystemEvictionRankingMetrics), 71 GracePeriod: &nonDefaultGracePeriod, 72 } 73 ) 74 75 func generateTestConfiguration(t *testing.T, nodeName string, dir string) *pkgconfig.Configuration { 76 testConfiguration, err := options.NewOptions().Config() 77 require.NoError(t, err) 78 require.NotNil(t, testConfiguration) 79 80 testConfiguration.NodeName = nodeName 81 testConfiguration.CheckpointManagerDir = dir 82 return testConfiguration 83 } 84 85 func generateTestCNC(nodeName string) *v1alpha1.CustomNodeConfig { 86 return &v1alpha1.CustomNodeConfig{ 87 ObjectMeta: metav1.ObjectMeta{ 88 Name: nodeName, 89 }, 90 Status: v1alpha1.CustomNodeConfigStatus{ 91 KatalystCustomConfigList: []v1alpha1.TargetConfig{ 92 { 93 ConfigName: "default", 94 ConfigNamespace: "test-namespace", 95 ConfigType: testTargetGVR, 96 Hash: "e39c2dd73aac", 97 }, 98 }, 99 }, 100 } 101 } 102 103 func generateTestEvictionConfiguration(evictionThreshold map[v1.ResourceName]float64) *v1alpha1.AdminQoSConfiguration { 104 return &v1alpha1.AdminQoSConfiguration{ 105 ObjectMeta: metav1.ObjectMeta{ 106 Name: "default", 107 Namespace: "test-namespace", 108 }, 109 Spec: v1alpha1.AdminQoSConfigurationSpec{ 110 Config: v1alpha1.AdminQoSConfig{ 111 EvictionConfig: &v1alpha1.EvictionConfig{ 112 ReclaimedResourcesEvictionConfig: &v1alpha1.ReclaimedResourcesEvictionConfig{ 113 EvictionThreshold: evictionThreshold, 114 }, 115 MemoryPressureEvictionConfig: &v1alpha1.MemoryPressureEvictionConfig{ 116 NumaFreeBelowWatermarkTimesThreshold: &defaultNumaFreeBelowWatermarkTimesThreshold, 117 SystemKswapdRateThreshold: &defaultSystemKswapdRateThreshold, 118 SystemKswapdRateExceedDurationThreshold: &defaultSystemKswapdRateExceedDurationThreshold, 119 NumaEvictionRankingMetrics: util.ConvertStringListToNumaEvictionRankingMetrics(evictionconfig.DefaultNumaEvictionRankingMetrics), 120 SystemEvictionRankingMetrics: util.ConvertStringListToSystemEvictionRankingMetrics(evictionconfig.DefaultSystemEvictionRankingMetrics), 121 }, 122 }, 123 }, 124 }, 125 } 126 } 127 128 func constructTestDynamicConfigManager(t *testing.T, nodeName, dir string, evictionConfiguration *v1alpha1.AdminQoSConfiguration) *DynamicConfigManager { 129 clientSet := generateTestGenericClientSet(generateTestCNC(nodeName), evictionConfiguration) 130 conf := generateTestConfiguration(t, nodeName, dir) 131 cncFetcher := cnc.NewCachedCNCFetcher(conf.BaseConfiguration, conf.CNCConfiguration, 132 clientSet.InternalClient.ConfigV1alpha1().CustomNodeConfigs()) 133 134 err := os.MkdirAll(dir, os.FileMode(0o755)) 135 require.NoError(t, err) 136 137 checkpointManager, err := checkpointmanager.NewCheckpointManager(conf.CheckpointManagerDir) 138 require.NoError(t, err) 139 140 configLoader := NewKatalystCustomConfigLoader(clientSet, 1*time.Second, cncFetcher) 141 manager := &DynamicConfigManager{ 142 conf: conf.AgentConfiguration, 143 defaultConfig: deepCopy(conf.GetDynamicConfiguration()), 144 configLoader: configLoader, 145 emitter: &metrics.DummyMetrics{}, 146 resourceGVRMap: make(map[string]metav1.GroupVersionResource), 147 checkpointGraceTime: conf.ConfigCheckpointGraceTime, 148 checkpointManager: checkpointManager, 149 } 150 151 err = manager.AddConfigWatcher(testTargetGVR) 152 require.NoError(t, err) 153 return manager 154 } 155 156 func TestNewDynamicConfigManager(t *testing.T) { 157 t.Parallel() 158 159 nodeName := "test-node" 160 evictionConfiguration := generateTestEvictionConfiguration(map[v1.ResourceName]float64{ 161 v1.ResourceCPU: 1.2, 162 v1.ResourceMemory: 1.3, 163 }) 164 clientSet := generateTestGenericClientSet(generateTestCNC(nodeName), evictionConfiguration) 165 conf := generateTestConfiguration(t, nodeName, "/tmp/metaserver1/TestNewDynamicConfigManager") 166 cncFetcher := cnc.NewCachedCNCFetcher(conf.BaseConfiguration, conf.CNCConfiguration, 167 clientSet.InternalClient.ConfigV1alpha1().CustomNodeConfigs()) 168 defer os.RemoveAll(conf.CheckpointManagerDir) 169 170 err := os.MkdirAll("/tmp/metaserver1/TestNewDynamicConfigManager", os.FileMode(0o755)) 171 require.NoError(t, err) 172 173 manager, err := NewDynamicConfigManager(clientSet, &metrics.DummyMetrics{}, cncFetcher, conf) 174 require.NoError(t, err) 175 require.NotNil(t, manager) 176 177 err = manager.AddConfigWatcher(testTargetGVR) 178 require.NoError(t, err) 179 180 err = manager.InitializeConfig(context.TODO()) 181 require.NoError(t, err) 182 go manager.Run(context.TODO()) 183 } 184 185 func TestDynamicConfigManager_getConfig(t *testing.T) { 186 t.Parallel() 187 188 type fields struct { 189 manager *DynamicConfigManager 190 } 191 type args struct { 192 ctx context.Context 193 } 194 tests := []struct { 195 name string 196 fields fields 197 args args 198 want func(got *agent.AgentConfiguration) bool 199 }{ 200 { 201 name: "test-1", 202 fields: fields{ 203 manager: constructTestDynamicConfigManager(t, "test-node", "/tmp/metaserver1/TestDynamicConfigManager_test-node", 204 generateTestEvictionConfiguration(map[v1.ResourceName]float64{ 205 v1.ResourceCPU: 1.2, 206 v1.ResourceMemory: 1.3, 207 })), 208 }, 209 args: args{ 210 ctx: context.TODO(), 211 }, 212 want: func(got *agent.AgentConfiguration) bool { 213 return got.GetDynamicConfiguration().EvictionThreshold[v1.ResourceCPU] == 1.2 && 214 got.GetDynamicConfiguration().EvictionThreshold[v1.ResourceMemory] == 1.3 215 }, 216 }, 217 { 218 name: "test-no-change", 219 fields: fields{ 220 manager: constructTestDynamicConfigManager(t, "test-node", "/tmp/metaserver1/TestDynamicConfigManager_test-no-change", 221 generateTestEvictionConfiguration(generateTestConfiguration(t, "test-node", "/tmp/metaserver1/TestDynamicConfigManager_test-no-change"). 222 GetDynamicConfiguration().EvictionThreshold)), 223 }, 224 args: args{ 225 ctx: context.TODO(), 226 }, 227 want: func(got *agent.AgentConfiguration) bool { 228 return reflect.DeepEqual(got.GetDynamicConfiguration().EvictionThreshold, 229 generateTestConfiguration(t, "test-node", "/tmp/metaserver1/TestDynamicConfigManager_test-no-change"). 230 GetDynamicConfiguration().EvictionThreshold) 231 }, 232 }, 233 } 234 for _, tt := range tests { 235 tt := tt 236 t.Run(tt.name, func(t *testing.T) { 237 t.Parallel() 238 239 c := tt.fields.manager 240 defer os.RemoveAll(c.conf.CheckpointManagerDir) 241 err := c.updateConfig(tt.args.ctx) 242 require.NoError(t, err) 243 require.True(t, tt.want(c.conf)) 244 }) 245 } 246 } 247 248 func Test_applyDynamicConfig(t *testing.T) { 249 t.Parallel() 250 251 type args struct { 252 currentConfig *dynamic.Configuration 253 dynamicConf *crd.DynamicConfigCRD 254 } 255 tests := []struct { 256 name string 257 args args 258 want func(got *dynamic.Configuration) bool 259 }{ 260 { 261 name: "test-1", 262 args: args{ 263 currentConfig: func() *dynamic.Configuration { 264 d := dynamic.NewConfiguration() 265 d.EvictionThreshold = map[v1.ResourceName]float64{ 266 "cpu": 1.5, 267 } 268 269 d.EnableNumaLevelEviction = defaultEnableNumaLevelDetection 270 d.EnableSystemLevelEviction = defaultEnableSystemLevelDetection 271 d.NumaFreeBelowWatermarkTimesThreshold = defaultNumaFreeBelowWatermarkTimesThreshold 272 d.SystemKswapdRateThreshold = defaultSystemKswapdRateThreshold 273 d.SystemKswapdRateExceedDurationThreshold = defaultSystemKswapdRateExceedDurationThreshold 274 d.NumaEvictionRankingMetrics = evictionconfig.DefaultNumaEvictionRankingMetrics 275 d.SystemEvictionRankingMetrics = evictionconfig.DefaultSystemEvictionRankingMetrics 276 d.MemoryPressureEvictionConfiguration.GracePeriod = evictionconfig.DefaultGracePeriod 277 return d 278 }(), 279 dynamicConf: &crd.DynamicConfigCRD{ 280 AdminQoSConfiguration: &v1alpha1.AdminQoSConfiguration{ 281 Spec: v1alpha1.AdminQoSConfigurationSpec{ 282 Config: v1alpha1.AdminQoSConfig{ 283 EvictionConfig: &v1alpha1.EvictionConfig{ 284 ReclaimedResourcesEvictionConfig: &v1alpha1.ReclaimedResourcesEvictionConfig{ 285 EvictionThreshold: map[v1.ResourceName]float64{ 286 "cpu": 1.3, 287 }, 288 }, 289 MemoryPressureEvictionConfig: &nonDefaultMemoryEvictionPluginConfig, 290 }, 291 }, 292 }, 293 }, 294 }, 295 }, 296 want: func(got *dynamic.Configuration) bool { 297 return got.EvictionThreshold["cpu"] == 1.3 && 298 got.EnableNumaLevelEviction == nonDefaultEnableNumaLevelEviction && 299 got.EnableSystemLevelEviction == nonDefaultEnableSystemLevelEviction && 300 got.NumaFreeBelowWatermarkTimesThreshold == nonDefaultNumaFreeBelowWatermarkTimesThreshold && 301 got.SystemKswapdRateThreshold == nonDefaultSystemKswapdRateThreshold && 302 got.SystemKswapdRateExceedDurationThreshold == nonDefaultSystemKswapdRateExceedDurationThreshold && 303 reflect.DeepEqual(got.NumaEvictionRankingMetrics, nonDefaultNumaEvictionRankingMetrics) && 304 reflect.DeepEqual(got.SystemEvictionRankingMetrics, nonDefaultSystemEvictionRankingMetrics) && 305 got.MemoryPressureEvictionConfiguration.GracePeriod == nonDefaultGracePeriod 306 }, 307 }, 308 { 309 name: "test-2", 310 args: args{ 311 currentConfig: func() *dynamic.Configuration { 312 d := dynamic.NewConfiguration() 313 d.EvictionThreshold = map[v1.ResourceName]float64{ 314 "cpu": 1.3, 315 } 316 317 d.EnableNumaLevelEviction = nonDefaultEnableNumaLevelEviction 318 d.EnableSystemLevelEviction = nonDefaultEnableSystemLevelEviction 319 d.NumaFreeBelowWatermarkTimesThreshold = nonDefaultNumaFreeBelowWatermarkTimesThreshold 320 d.SystemKswapdRateThreshold = nonDefaultSystemKswapdRateThreshold 321 d.SystemKswapdRateExceedDurationThreshold = nonDefaultSystemKswapdRateExceedDurationThreshold 322 d.NumaEvictionRankingMetrics = nonDefaultNumaEvictionRankingMetrics 323 d.SystemEvictionRankingMetrics = nonDefaultSystemEvictionRankingMetrics 324 d.MemoryPressureEvictionConfiguration.GracePeriod = nonDefaultGracePeriod 325 return d 326 }(), 327 dynamicConf: &crd.DynamicConfigCRD{ 328 AdminQoSConfiguration: &v1alpha1.AdminQoSConfiguration{ 329 Spec: v1alpha1.AdminQoSConfigurationSpec{ 330 Config: v1alpha1.AdminQoSConfig{ 331 EvictionConfig: &v1alpha1.EvictionConfig{ 332 ReclaimedResourcesEvictionConfig: &v1alpha1.ReclaimedResourcesEvictionConfig{ 333 EvictionThreshold: map[v1.ResourceName]float64{ 334 "cpu": 1.3, 335 }, 336 }, 337 MemoryPressureEvictionConfig: &nonDefaultMemoryEvictionPluginConfig, 338 }, 339 }, 340 }, 341 }, 342 }, 343 }, 344 want: func(got *dynamic.Configuration) bool { 345 return got.EvictionThreshold["cpu"] == 1.3 && 346 got.EnableNumaLevelEviction == nonDefaultEnableNumaLevelEviction && 347 got.EnableSystemLevelEviction == nonDefaultEnableSystemLevelEviction && 348 got.NumaFreeBelowWatermarkTimesThreshold == nonDefaultNumaFreeBelowWatermarkTimesThreshold && 349 got.SystemKswapdRateThreshold == nonDefaultSystemKswapdRateThreshold && 350 got.SystemKswapdRateExceedDurationThreshold == nonDefaultSystemKswapdRateExceedDurationThreshold && 351 reflect.DeepEqual(got.NumaEvictionRankingMetrics, nonDefaultNumaEvictionRankingMetrics) && 352 reflect.DeepEqual(got.SystemEvictionRankingMetrics, nonDefaultSystemEvictionRankingMetrics) && 353 got.MemoryPressureEvictionConfiguration.GracePeriod == nonDefaultGracePeriod 354 }, 355 }, 356 } 357 for _, tt := range tests { 358 tt := tt 359 t.Run(tt.name, func(t *testing.T) { 360 t.Parallel() 361 applyDynamicConfig(tt.args.currentConfig, tt.args.dynamicConf) 362 got := tt.args.currentConfig 363 require.True(t, tt.want(got)) 364 }) 365 } 366 } 367 368 func Test_getGVRToKindMap(t *testing.T) { 369 t.Parallel() 370 371 tests := []struct { 372 name string 373 wantGVR schema.GroupVersionResource 374 wantGVK schema.GroupVersionKind 375 }{ 376 { 377 name: "aqc", 378 wantGVR: schema.GroupVersionResource(crd.AdminQoSConfigurationGVR), 379 wantGVK: schema.GroupVersionKind{ 380 Group: v1alpha1.SchemeGroupVersion.Group, 381 Version: v1alpha1.SchemeGroupVersion.Version, 382 Kind: crd.ResourceKindAdminQoSConfiguration, 383 }, 384 }, 385 } 386 387 for _, tt := range tests { 388 tt := tt 389 t.Run(tt.name, func(t *testing.T) { 390 t.Parallel() 391 if got := getGVRToGVKMap(); !checkGVRToGVKMap(tt.wantGVR, tt.wantGVK, got) { 392 t.Errorf("getGVRToGVKMap() = %v, wantGVR %v wantGVK %v", got, tt.wantGVR, tt.wantGVK) 393 } 394 }) 395 } 396 } 397 398 func checkGVRToGVKMap(gvr schema.GroupVersionResource, wantGVK schema.GroupVersionKind, 399 m map[schema.GroupVersionResource]schema.GroupVersionKind, 400 ) bool { 401 gvk, ok := m[gvr] 402 if ok && gvk == wantGVK { 403 return true 404 } 405 return false 406 } 407 408 func Test_updateDynamicConf(t *testing.T) { 409 t.Parallel() 410 411 type args struct { 412 resourceGVRMap map[string]metav1.GroupVersionResource 413 gvrToKind map[schema.GroupVersionResource]schema.GroupVersionKind 414 loader func(gvr metav1.GroupVersionResource, conf interface{}) error 415 } 416 tests := []struct { 417 name string 418 args args 419 want *crd.DynamicConfigCRD 420 want1 bool 421 }{ 422 { 423 name: "test-1", 424 args: args{ 425 resourceGVRMap: generateTestResourceGVRMap(), 426 gvrToKind: getGVRToGVKMap(), 427 loader: generateTestLoader(toTestUnstructured(&v1alpha1.AdminQoSConfiguration{ 428 ObjectMeta: metav1.ObjectMeta{ 429 Name: "config-1", 430 }, 431 Spec: v1alpha1.AdminQoSConfigurationSpec{ 432 GenericConfigSpec: v1alpha1.GenericConfigSpec{ 433 NodeLabelSelector: "aa=bb", 434 }, 435 Config: v1alpha1.AdminQoSConfig{ 436 EvictionConfig: &v1alpha1.EvictionConfig{ 437 ReclaimedResourcesEvictionConfig: &v1alpha1.ReclaimedResourcesEvictionConfig{ 438 EvictionThreshold: map[v1.ResourceName]float64{ 439 v1.ResourceCPU: 1.3, 440 v1.ResourceMemory: 1.5, 441 }, 442 }, 443 }, 444 }, 445 }, 446 })), 447 }, 448 want: &crd.DynamicConfigCRD{ 449 AdminQoSConfiguration: &v1alpha1.AdminQoSConfiguration{ 450 ObjectMeta: metav1.ObjectMeta{ 451 Name: "config-1", 452 }, 453 Spec: v1alpha1.AdminQoSConfigurationSpec{ 454 GenericConfigSpec: v1alpha1.GenericConfigSpec{ 455 NodeLabelSelector: "aa=bb", 456 }, 457 Config: v1alpha1.AdminQoSConfig{ 458 EvictionConfig: &v1alpha1.EvictionConfig{ 459 ReclaimedResourcesEvictionConfig: &v1alpha1.ReclaimedResourcesEvictionConfig{ 460 EvictionThreshold: map[v1.ResourceName]float64{ 461 v1.ResourceCPU: 1.3, 462 v1.ResourceMemory: 1.5, 463 }, 464 }, 465 }, 466 }, 467 }, 468 }, 469 }, 470 want1: true, 471 }, 472 } 473 for _, tt := range tests { 474 tt := tt 475 t.Run(tt.name, func(t *testing.T) { 476 t.Parallel() 477 478 ec := generateTestEvictionConfiguration(map[v1.ResourceName]float64{ 479 v1.ResourceCPU: 1.2, 480 v1.ResourceMemory: 1.3, 481 }) 482 manager := constructTestDynamicConfigManager(t, "node-name", "Test_updateDynamicConf", ec) 483 defer os.RemoveAll(manager.conf.CheckpointManagerDir) 484 got, got1, _ := manager.updateDynamicConfig(tt.args.resourceGVRMap, tt.args.gvrToKind, tt.args.loader) 485 if !reflect.DeepEqual(got, tt.want) { 486 t.Errorf("updateDynamicConfig() got = %v, want %v", got, tt.want) 487 } 488 if got1 != tt.want1 { 489 t.Errorf("updateDynamicConfig() got1 = %v, want %v", got1, tt.want1) 490 } 491 }) 492 } 493 } 494 495 func generateTestResourceGVRMap() map[string]metav1.GroupVersionResource { 496 return map[string]metav1.GroupVersionResource{ 497 v1alpha1.ResourceNameAdminQoSConfigurations: crd.AdminQoSConfigurationGVR, 498 } 499 } 500 501 func generateTestLoader(unstructured *unstructured.Unstructured) func(gvr metav1.GroupVersionResource, conf interface{}) error { 502 return func(gvr metav1.GroupVersionResource, conf interface{}) error { 503 return util.ToKCCTargetResource(unstructured).Unmarshal(conf) 504 } 505 } 506 507 func toTestUnstructured(obj interface{}) *unstructured.Unstructured { 508 ret, err := native.ToUnstructured(obj) 509 if err != nil { 510 klog.Error(err) 511 } 512 return ret 513 }