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  }