github.com/kubewharf/katalyst-core@v0.5.3/pkg/config/generic/qos.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 generic 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "sync" 23 24 v1 "k8s.io/api/core/v1" 25 "k8s.io/apimachinery/pkg/util/sets" 26 27 apiconsts "github.com/kubewharf/katalyst-api/pkg/consts" 28 "github.com/kubewharf/katalyst-core/pkg/agent/qrm-plugins/cpu/dynamicpolicy/state" 29 "github.com/kubewharf/katalyst-core/pkg/util/general" 30 ) 31 32 // defaultQoSLevel willed b e use as default QoS Level if nothing in annotation 33 const defaultQoSLevel = apiconsts.PodAnnotationQoSLevelSharedCores 34 35 type qosValidationFunc func(pod *v1.Pod, annotation map[string]string) (bool, error) 36 37 // validQosKey contains all the qos-level that Katalyst supports 38 var validQosKey = sets.NewString( 39 apiconsts.PodAnnotationQoSLevelSharedCores, 40 apiconsts.PodAnnotationQoSLevelDedicatedCores, 41 apiconsts.PodAnnotationQoSLevelReclaimedCores, 42 apiconsts.PodAnnotationQoSLevelSystemCores, 43 ) 44 45 // validQosEnhancementKey contains all the enhancement that Katalyst supports 46 var validQosEnhancementKey = sets.NewString( 47 apiconsts.PodAnnotationCPUEnhancementKey, 48 apiconsts.PodAnnotationMemoryEnhancementKey, 49 apiconsts.PodAnnotationNetworkEnhancementKey, 50 ) 51 52 // QoSConfiguration stores the qos configurations needed by core katalyst components. 53 // since we may have legacy QoS judgement ways, we should map those legacy configs 54 // into standard katalyst QoS Level. 55 type QoSConfiguration struct { 56 sync.RWMutex 57 58 // QoSClassAnnotationSelector is used as an expanded way to match legacy specified 59 // QoS annotations into standard katalyst QoS level 60 // - if no expended selector is configured 61 // --- only use the default key-value instead 62 // - if multiple expended selectors are defined 63 // --- returns true if anyone matches 64 // - we should also do validation 65 QoSClassAnnotationSelector map[string]map[string]string 66 67 // QoSEnhancementAnnotationKey is used as an expanded way to match legacy specified 68 // QoS annotations into standard katalyst QoS enhancement 69 QoSEnhancementAnnotationKey map[string]string 70 71 // for different situation, there may be different default values for enhancement keys 72 // we use options to control those different values 73 // the key here is specific enhancement key such as "numa_binding", "numa_exclusive" 74 // the value is the default value of the key 75 QoSEnhancementDefaultValues map[string]string 76 77 // qosCheckFunc is used as a syntactic sugar to easily walk through 78 // all QoS Level validation functions 79 qosCheckFuncMap map[string]qosValidationFunc 80 } 81 82 // NewQoSConfiguration creates a new qos configuration. 83 func NewQoSConfiguration() *QoSConfiguration { 84 c := &QoSConfiguration{ 85 QoSClassAnnotationSelector: map[string]map[string]string{ 86 apiconsts.PodAnnotationQoSLevelSharedCores: { 87 apiconsts.PodAnnotationQoSLevelKey: apiconsts.PodAnnotationQoSLevelSharedCores, 88 }, 89 apiconsts.PodAnnotationQoSLevelDedicatedCores: { 90 apiconsts.PodAnnotationQoSLevelKey: apiconsts.PodAnnotationQoSLevelDedicatedCores, 91 }, 92 apiconsts.PodAnnotationQoSLevelReclaimedCores: { 93 apiconsts.PodAnnotationQoSLevelKey: apiconsts.PodAnnotationQoSLevelReclaimedCores, 94 }, 95 apiconsts.PodAnnotationQoSLevelSystemCores: { 96 apiconsts.PodAnnotationQoSLevelKey: apiconsts.PodAnnotationQoSLevelSystemCores, 97 }, 98 }, 99 QoSEnhancementAnnotationKey: make(map[string]string), 100 QoSEnhancementDefaultValues: make(map[string]string), 101 } 102 103 c.qosCheckFuncMap = map[string]qosValidationFunc{ 104 apiconsts.PodAnnotationQoSLevelSharedCores: c.CheckSharedQoS, 105 apiconsts.PodAnnotationQoSLevelDedicatedCores: c.CheckDedicatedQoS, 106 apiconsts.PodAnnotationQoSLevelReclaimedCores: c.CheckReclaimedQoS, 107 apiconsts.PodAnnotationQoSLevelSystemCores: c.CheckSystemQoS, 108 } 109 return c 110 } 111 112 func (c *QoSConfiguration) SetExpandQoSLevelSelector(qosLevel string, selectorMap map[string]string) { 113 if _, ok := c.qosCheckFuncMap[qosLevel]; !ok { 114 return 115 } 116 117 c.Lock() 118 defer c.Unlock() 119 c.QoSClassAnnotationSelector[qosLevel] = general.MergeMap(c.QoSClassAnnotationSelector[qosLevel], selectorMap) 120 } 121 122 func (c *QoSConfiguration) SetExpandQoSEnhancementKey(enhancementKeys map[string]string) { 123 c.Lock() 124 defer c.Unlock() 125 126 for defaultKey, expandedKey := range enhancementKeys { 127 if validQosEnhancementKey.Has(defaultKey) { 128 c.QoSEnhancementAnnotationKey[expandedKey] = defaultKey 129 } 130 } 131 } 132 133 // SetEnhancementDefaultValues set default values for enhancement keys 134 // because sometimes we need different default values for enhancement keys in different types of clusters 135 func (c *QoSConfiguration) SetEnhancementDefaultValues(enhancementDefaultValues map[string]string) { 136 c.Lock() 137 defer c.Unlock() 138 139 c.QoSEnhancementDefaultValues = general.MergeMap(c.QoSEnhancementDefaultValues, enhancementDefaultValues) 140 } 141 142 // FilterQoSMap filter map that are related to katalyst QoS. 143 // it works both for default katalyst QoS keys and expanded QoS keys 144 func (c *QoSConfiguration) FilterQoSMap(annotations map[string]string) map[string]string { 145 c.RLock() 146 defer c.RUnlock() 147 148 filteredAnnotations := make(map[string]string) 149 for qos := range c.QoSClassAnnotationSelector { 150 for qosExpand := range c.QoSClassAnnotationSelector[qos] { 151 if val, ok := annotations[qosExpand]; ok { 152 filteredAnnotations[qosExpand] = val 153 } 154 } 155 } 156 return filteredAnnotations 157 } 158 159 // FilterQoSEnhancementMap filter map that are related to katalyst Enhancement. 160 // for enhancements,we should unmarshal and store the unmarshal key-value. 161 // it works both for default katalyst QoS keys and expanded QoS keys. 162 func (c *QoSConfiguration) FilterQoSEnhancementMap(annotations map[string]string) map[string]string { 163 c.RLock() 164 defer c.RUnlock() 165 166 filteredAnnotations := make(map[string]string) 167 168 for _, enhancementKey := range validQosEnhancementKey.List() { 169 enhancementKVs := c.GetQoSEnhancementKVs(nil, annotations, enhancementKey) 170 for key, val := range enhancementKVs { 171 if filteredAnnotations[key] != "" { 172 general.Warningf("get enhancements %s:%s from %s, but the kv already exists: %s:%s", 173 key, val, enhancementKey, key, filteredAnnotations[key]) 174 } 175 filteredAnnotations[key] = val 176 } 177 } 178 179 for enhancementKey, defaultValue := range c.QoSEnhancementDefaultValues { 180 if _, found := filteredAnnotations[enhancementKey]; !found { 181 general.Infof("enhancementKey: %s isn't declared, set its value to defaultValue: %s", 182 enhancementKey, defaultValue) 183 filteredAnnotations[enhancementKey] = defaultValue 184 } 185 } 186 187 return filteredAnnotations 188 } 189 190 func (c *QoSConfiguration) FilterQoSAndEnhancementMap(annotations map[string]string) map[string]string { 191 return general.MergeMap(c.FilterQoSMap(annotations), c.FilterQoSEnhancementMap(annotations)) 192 } 193 194 func (c *QoSConfiguration) GetQoSLevelForPod(pod *v1.Pod) (string, error) { 195 return c.GetQoSLevel(pod, map[string]string{}) 196 } 197 198 // GetQoSLevel returns the standard katalyst QoS Level for given annotations; 199 // - returns error if there is conflict in qos level annotations or can't get valid qos level. 200 // - returns defaultQoSLevel if nothing matches and isNotDefaultQoSLevel is false. 201 func (c *QoSConfiguration) GetQoSLevel(pod *v1.Pod, expandedAnnotations map[string]string) (qosLevel string, retErr error) { 202 annotations := MergeAnnotations(pod, expandedAnnotations) 203 204 defer func() { 205 if retErr != nil { 206 return 207 } 208 // redirect qos-level according to user-specified qos judgement function 209 overrideQoSLevel, ok := getQoSLevelExpander().Override(qosLevel, pod, annotations) 210 if ok { 211 general.Infof("update qosLevel from %s to %s", qosLevel, overrideQoSLevel) 212 qosLevel = overrideQoSLevel 213 } 214 }() 215 216 isNotDefaultQoSLevel := false 217 for qos := range validQosKey { 218 identified, matched, err := c.checkQosMatched(annotations, qos) 219 if err != nil { 220 general.Errorf("check qos level %v for annotation failed: %v", qos, err) 221 return "", err 222 } else if identified { 223 if matched { 224 return qos, nil 225 } else if qos == defaultQoSLevel { 226 isNotDefaultQoSLevel = true 227 } 228 } 229 } 230 231 if isNotDefaultQoSLevel { 232 return "", fmt.Errorf("can't get valid qos level") 233 } 234 return defaultQoSLevel, nil 235 } 236 237 func (c *QoSConfiguration) CheckReclaimedQoSForPod(pod *v1.Pod) (bool, error) { 238 return c.CheckReclaimedQoS(pod, map[string]string{}) 239 } 240 241 // CheckReclaimedQoS returns true if the annotation indicates for ReclaimedCores; 242 // - returns error if different QoS configurations conflict with each other. 243 func (c *QoSConfiguration) CheckReclaimedQoS(pod *v1.Pod, expandedAnnotations map[string]string) (bool, error) { 244 if qosLevel, err := c.GetQoSLevel(pod, expandedAnnotations); err != nil { 245 return false, err 246 } else { 247 return qosLevel == apiconsts.PodAnnotationQoSLevelReclaimedCores, nil 248 } 249 } 250 251 func (c *QoSConfiguration) CheckSharedQoSForPod(pod *v1.Pod) (bool, error) { 252 return c.CheckSharedQoS(pod, map[string]string{}) 253 } 254 255 // CheckSharedQoS returns true if the annotation indicates for SharedCores; 256 // - returns error if different QoS configurations conflict with each other. 257 func (c *QoSConfiguration) CheckSharedQoS(pod *v1.Pod, expandedAnnotations map[string]string) (bool, error) { 258 if qosLevel, err := c.GetQoSLevel(pod, expandedAnnotations); err != nil { 259 return false, err 260 } else { 261 return qosLevel == apiconsts.PodAnnotationQoSLevelSharedCores, nil 262 } 263 } 264 265 func (c *QoSConfiguration) CheckDedicatedQoSForPod(pod *v1.Pod) (bool, error) { 266 return c.CheckDedicatedQoS(pod, map[string]string{}) 267 } 268 269 // CheckDedicatedQoS returns true if the annotation indicates for DedicatedCores; 270 // - returns error if different QoS configurations conflict with each other. 271 func (c *QoSConfiguration) CheckDedicatedQoS(pod *v1.Pod, expandedAnnotations map[string]string) (bool, error) { 272 if qosLevel, err := c.GetQoSLevel(pod, expandedAnnotations); err != nil { 273 return false, err 274 } else { 275 return qosLevel == apiconsts.PodAnnotationQoSLevelDedicatedCores, nil 276 } 277 } 278 279 func (c *QoSConfiguration) CheckSystemQoSForPod(pod *v1.Pod) (bool, error) { 280 return c.CheckSystemQoS(pod, map[string]string{}) 281 } 282 283 // CheckSystemQoS returns true if the annotation indicates for SystemCores; 284 // - returns error if different QoS configurations conflict with each other. 285 func (c *QoSConfiguration) CheckSystemQoS(pod *v1.Pod, expandedAnnotations map[string]string) (bool, error) { 286 if qosLevel, err := c.GetQoSLevel(pod, expandedAnnotations); err != nil { 287 return false, err 288 } else { 289 return qosLevel == apiconsts.PodAnnotationQoSLevelSystemCores, nil 290 } 291 } 292 293 // checkQosMatched is a unified helper function to judge whether annotation 294 // matches with the given QoS Level; 295 // return 296 // - identified: returning true if the function identifies the qos level matches according to QoSClassAnnotationSelector, else false. 297 // - matched: returning true if annotations match with qosValue, else false. 298 // - error: return err != nil if different QoS configurations conflict with each other 299 func (c *QoSConfiguration) checkQosMatched(annotations map[string]string, qosValue string) (identified bool, matched bool, err error) { 300 c.RLock() 301 defer c.RUnlock() 302 303 valueNotEqualCnt, valueEqualCnt := 0, 0 304 for key, value := range c.QoSClassAnnotationSelector[qosValue] { 305 _, valueNotEqual, valueEqual := checkKeyValueMatched(annotations, key, value) 306 valueNotEqualCnt, valueEqualCnt = valueNotEqualCnt+valueNotEqual, valueEqualCnt+valueEqual 307 } 308 309 if valueEqualCnt > 0 { 310 // some key-value list match while others don't 311 if valueNotEqualCnt > 0 { 312 return false, false, 313 fmt.Errorf("qos %v conflicts, matched count %v, mis matched count %v", qosValue, valueEqualCnt, valueNotEqualCnt) 314 } 315 // some key-value list match and some key may not exist 316 return true, true, nil 317 } else if valueNotEqualCnt == 0 { 318 return false, false, nil 319 } 320 321 return true, false, nil 322 } 323 324 func (c *QoSConfiguration) GetSpecifiedPoolNameForPod(pod *v1.Pod) (string, error) { 325 return c.GetSpecifiedPoolName(pod, map[string]string{}) 326 } 327 328 // GetSpecifiedPoolName returns the specified cpuset pool name for given enhancements and annotations; 329 func (c *QoSConfiguration) GetSpecifiedPoolName(pod *v1.Pod, expandedAnnotations map[string]string) (string, error) { 330 qosLevel, err := c.GetQoSLevel(pod, expandedAnnotations) 331 if err != nil { 332 return "", fmt.Errorf("GetQoSLevel failed with error: %v", err) 333 } 334 335 enhancementKVs := c.GetQoSEnhancementKVs(pod, expandedAnnotations, apiconsts.PodAnnotationCPUEnhancementKey) 336 return state.GetSpecifiedPoolName(qosLevel, enhancementKVs[apiconsts.PodAnnotationCPUEnhancementCPUSet]), nil 337 } 338 339 // GetQoSEnhancementKVs parses enhancements from annotations by given key, 340 // since enhancement values are stored as k-v, so we should unmarshal it into maps. 341 func (c *QoSConfiguration) GetQoSEnhancementKVs(pod *v1.Pod, expandedAnnotations map[string]string, enhancementKey string) (flattenedEnhancements map[string]string) { 342 annotations := c.getQoSEnhancements(MergeAnnotations(pod, expandedAnnotations)) 343 344 defer func() { 345 overrideFlattenedEnhancements, ok := getQoSEnhancementExpander().Override(flattenedEnhancements, pod, annotations) 346 if ok { 347 general.Infof("update enhancements from %+v to %+v", flattenedEnhancements, overrideFlattenedEnhancements) 348 flattenedEnhancements = overrideFlattenedEnhancements 349 } 350 }() 351 352 flattenedEnhancements = map[string]string{} 353 enhancementValue, ok := annotations[enhancementKey] 354 if !ok { 355 return 356 } 357 358 err := json.Unmarshal([]byte(enhancementValue), &flattenedEnhancements) 359 if err != nil { 360 general.Errorf("parse enhancement %s failed: %v", enhancementKey, err) 361 return 362 } 363 return flattenedEnhancements 364 } 365 366 // GetQoSEnhancements returns the standard katalyst QoS Enhancement Map for given annotations; 367 // - ignore conflict cases: default enhancement key always prior to expand enhancement key 368 func (c *QoSConfiguration) getQoSEnhancements(annotations map[string]string) map[string]string { 369 res := make(map[string]string) 370 371 c.RLock() 372 defer c.RUnlock() 373 for k, v := range annotations { 374 if validQosEnhancementKey.Has(k) { 375 res[k] = v 376 } else if defaultK, ok := c.QoSEnhancementAnnotationKey[k]; ok { 377 if _, exist := res[defaultK]; !exist { 378 res[defaultK] = v 379 } 380 } 381 } 382 383 return res 384 } 385 386 func MergeAnnotations(pod *v1.Pod, expandAnnotations map[string]string) map[string]string { 387 if pod == nil { 388 if expandAnnotations == nil { 389 return map[string]string{} 390 } 391 return expandAnnotations 392 } else { 393 return general.MergeMap(pod.Annotations, expandAnnotations) 394 } 395 } 396 397 // checkKeyValueMatched checks whether the given key-value pair exists in the map 398 // if the returns value equals 1, it represents 399 // - key not exists 400 // - key exists, but value not equals 401 // - key exists, and value not equal 402 // returns 0 otherwise 403 func checkKeyValueMatched(m map[string]string, key, value string) (keyNotExist int, valueNotEqual int, valueEqual int) { 404 v, ok := m[key] 405 if !ok { 406 keyNotExist = 1 407 } else if v != value { 408 valueNotEqual = 1 409 } else { 410 valueEqual = 1 411 } 412 return 413 }