mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 09:22:44 +00:00
Update equivalence cache to use predicate as key
Remove Invalid field from host predicate
This commit is contained in:
parent
c888069a3b
commit
819554f514
@ -247,22 +247,21 @@ func copyAndReplace(set sets.String, replaceWhat, replaceWith string) sets.Strin
|
|||||||
|
|
||||||
// GetEquivalencePod returns a EquivalencePod which contains a group of pod attributes which can be reused.
|
// GetEquivalencePod returns a EquivalencePod which contains a group of pod attributes which can be reused.
|
||||||
func GetEquivalencePod(pod *v1.Pod) interface{} {
|
func GetEquivalencePod(pod *v1.Pod) interface{} {
|
||||||
equivalencePod := EquivalencePod{}
|
|
||||||
// For now we only consider pods:
|
// For now we only consider pods:
|
||||||
// 1. OwnerReferences is Controller
|
// 1. OwnerReferences is Controller
|
||||||
// 2. OwnerReferences kind is in valid controller kinds
|
// 2. with same OwnerReferences
|
||||||
// 3. with same OwnerReferences
|
|
||||||
// to be equivalent
|
// to be equivalent
|
||||||
if len(pod.OwnerReferences) != 0 {
|
if len(pod.OwnerReferences) != 0 {
|
||||||
for _, ref := range pod.OwnerReferences {
|
for _, ref := range pod.OwnerReferences {
|
||||||
if *ref.Controller {
|
if *ref.Controller {
|
||||||
equivalencePod.ControllerRef = ref
|
|
||||||
// a pod can only belongs to one controller
|
// a pod can only belongs to one controller
|
||||||
break
|
return &EquivalencePod{
|
||||||
|
ControllerRef: ref,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &equivalencePod
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EquivalencePod is a group of pod attributes which can be reused as equivalence to schedule other pods.
|
// EquivalencePod is a group of pod attributes which can be reused as equivalence to schedule other pods.
|
||||||
|
@ -18,18 +18,19 @@ package core
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
|
|
||||||
"github.com/golang/groupcache/lru"
|
|
||||||
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/kubernetes/pkg/api/v1"
|
"k8s.io/kubernetes/pkg/api/v1"
|
||||||
hashutil "k8s.io/kubernetes/pkg/util/hash"
|
hashutil "k8s.io/kubernetes/pkg/util/hash"
|
||||||
"k8s.io/kubernetes/plugin/pkg/scheduler/algorithm"
|
"k8s.io/kubernetes/plugin/pkg/scheduler/algorithm"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"github.com/golang/groupcache/lru"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO(harryz) figure out the right number for this, 4096 may be too big
|
// we use predicate names as cache's key, its count is limited
|
||||||
const maxCacheEntries = 4096
|
const maxCacheEntries = 100
|
||||||
|
|
||||||
type HostPredicate struct {
|
type HostPredicate struct {
|
||||||
Fit bool
|
Fit bool
|
||||||
@ -41,6 +42,9 @@ type AlgorithmCache struct {
|
|||||||
predicatesCache *lru.Cache
|
predicatesCache *lru.Cache
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PredicateMap use equivalence hash as key
|
||||||
|
type PredicateMap map[uint64]HostPredicate
|
||||||
|
|
||||||
func newAlgorithmCache() AlgorithmCache {
|
func newAlgorithmCache() AlgorithmCache {
|
||||||
return AlgorithmCache{
|
return AlgorithmCache{
|
||||||
predicatesCache: lru.New(maxCacheEntries),
|
predicatesCache: lru.New(maxCacheEntries),
|
||||||
@ -61,74 +65,151 @@ func NewEquivalenceCache(getEquivalencePodFunc algorithm.GetEquivalencePodFunc)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// addPodPredicate adds pod predicate for equivalence class
|
// UpdateCachedPredicateItem updates pod predicate for equivalence class
|
||||||
func (ec *EquivalenceCache) addPodPredicate(podKey uint64, nodeName string, fit bool, failReasons []algorithm.PredicateFailureReason) {
|
func (ec *EquivalenceCache) UpdateCachedPredicateItem(pod *v1.Pod, nodeName, predicateKey string, fit bool, reasons []algorithm.PredicateFailureReason, equivalenceHash uint64) {
|
||||||
|
ec.Lock()
|
||||||
|
defer ec.Unlock()
|
||||||
if _, exist := ec.algorithmCache[nodeName]; !exist {
|
if _, exist := ec.algorithmCache[nodeName]; !exist {
|
||||||
ec.algorithmCache[nodeName] = newAlgorithmCache()
|
ec.algorithmCache[nodeName] = newAlgorithmCache()
|
||||||
}
|
}
|
||||||
ec.algorithmCache[nodeName].predicatesCache.Add(podKey, HostPredicate{Fit: fit, FailReasons: failReasons})
|
predicateItem := HostPredicate{
|
||||||
|
Fit: fit,
|
||||||
|
FailReasons: reasons,
|
||||||
|
}
|
||||||
|
// if cached predicate map already exists, just update the predicate by key
|
||||||
|
if v, ok := ec.algorithmCache[nodeName].predicatesCache.Get(predicateKey); ok {
|
||||||
|
predicateMap := v.(PredicateMap)
|
||||||
|
// maps in golang are references, no need to add them back
|
||||||
|
predicateMap[equivalenceHash] = predicateItem
|
||||||
|
} else {
|
||||||
|
ec.algorithmCache[nodeName].predicatesCache.Add(predicateKey,
|
||||||
|
PredicateMap{
|
||||||
|
equivalenceHash: predicateItem,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
glog.V(5).Infof("Updated cached predicate: %v for pod: %v on node: %s, with item %v", predicateKey, pod.GetName(), nodeName, predicateItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddPodPredicatesCache cache pod predicate for equivalence class
|
// PredicateWithECache returns:
|
||||||
func (ec *EquivalenceCache) AddPodPredicatesCache(pod *v1.Pod, fitNodeList []*v1.Node, failedPredicates *FailedPredicateMap) {
|
// 1. if fit
|
||||||
equivalenceHash := ec.hashEquivalencePod(pod)
|
// 2. reasons if not fit
|
||||||
|
// 3. if this cache is invalid
|
||||||
for _, fitNode := range fitNodeList {
|
// based on cached predicate results
|
||||||
ec.addPodPredicate(equivalenceHash, fitNode.Name, true, nil)
|
func (ec *EquivalenceCache) PredicateWithECache(pod *v1.Pod, nodeName, predicateKey string, equivalenceHash uint64) (bool, []algorithm.PredicateFailureReason, bool) {
|
||||||
}
|
ec.RLock()
|
||||||
for failNodeName, failReasons := range *failedPredicates {
|
defer ec.RUnlock()
|
||||||
ec.addPodPredicate(equivalenceHash, failNodeName, false, failReasons)
|
glog.V(5).Infof("Begin to calculate predicate: %v for pod: %s on node: %s based on equivalence cache", predicateKey, pod.GetName(), nodeName)
|
||||||
}
|
if algorithmCache, exist := ec.algorithmCache[nodeName]; exist {
|
||||||
}
|
if cachePredicate, exist := algorithmCache.predicatesCache.Get(predicateKey); exist {
|
||||||
|
predicateMap := cachePredicate.(PredicateMap)
|
||||||
// GetCachedPredicates gets cached predicates for equivalence class
|
// TODO(resouer) Is it possible a race that cache failed to update immediately?
|
||||||
func (ec *EquivalenceCache) GetCachedPredicates(pod *v1.Pod, nodes []*v1.Node) ([]*v1.Node, FailedPredicateMap, []*v1.Node) {
|
if hostPredicate, ok := predicateMap[equivalenceHash]; ok {
|
||||||
fitNodeList := []*v1.Node{}
|
|
||||||
failedPredicates := FailedPredicateMap{}
|
|
||||||
noCacheNodeList := []*v1.Node{}
|
|
||||||
equivalenceHash := ec.hashEquivalencePod(pod)
|
|
||||||
for _, node := range nodes {
|
|
||||||
findCache := false
|
|
||||||
if algorithmCache, exist := ec.algorithmCache[node.Name]; exist {
|
|
||||||
if cachePredicate, exist := algorithmCache.predicatesCache.Get(equivalenceHash); exist {
|
|
||||||
hostPredicate := cachePredicate.(HostPredicate)
|
|
||||||
if hostPredicate.Fit {
|
if hostPredicate.Fit {
|
||||||
fitNodeList = append(fitNodeList, node)
|
return true, []algorithm.PredicateFailureReason{}, false
|
||||||
} else {
|
} else {
|
||||||
failedPredicates[node.Name] = hostPredicate.FailReasons
|
return false, hostPredicate.FailReasons, false
|
||||||
}
|
}
|
||||||
findCache = true
|
} else {
|
||||||
|
// is invalid
|
||||||
|
return false, []algorithm.PredicateFailureReason{}, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !findCache {
|
}
|
||||||
noCacheNodeList = append(noCacheNodeList, node)
|
return false, []algorithm.PredicateFailureReason{}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvalidateCachedPredicateItem marks all items of given predicateKeys, of all pods, on the given node as invalid
|
||||||
|
func (ec *EquivalenceCache) InvalidateCachedPredicateItem(nodeName string, predicateKeys sets.String) {
|
||||||
|
if len(predicateKeys) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ec.Lock()
|
||||||
|
defer ec.Unlock()
|
||||||
|
if algorithmCache, exist := ec.algorithmCache[nodeName]; exist {
|
||||||
|
for predicateKey := range predicateKeys {
|
||||||
|
algorithmCache.predicatesCache.Remove(predicateKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fitNodeList, failedPredicates, noCacheNodeList
|
glog.V(5).Infof("Done invalidating cached predicates: %v on node: %s", predicateKeys, nodeName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendInvalidAlgorithmCacheReq marks AlgorithmCache item as invalid
|
// InvalidateCachedPredicateItemOfAllNodes marks all items of given predicateKeys, of all pods, on all node as invalid
|
||||||
func (ec *EquivalenceCache) SendInvalidAlgorithmCacheReq(nodeName string) {
|
func (ec *EquivalenceCache) InvalidateCachedPredicateItemOfAllNodes(predicateKeys sets.String) {
|
||||||
ec.Lock()
|
if len(predicateKeys) == 0 {
|
||||||
defer ec.Unlock()
|
return
|
||||||
// clear the cache of this node
|
|
||||||
delete(ec.algorithmCache, nodeName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendClearAllCacheReq marks all cached item as invalid
|
|
||||||
func (ec *EquivalenceCache) SendClearAllCacheReq() {
|
|
||||||
ec.Lock()
|
|
||||||
defer ec.Unlock()
|
|
||||||
// clear cache of all nodes
|
|
||||||
for nodeName := range ec.algorithmCache {
|
|
||||||
delete(ec.algorithmCache, nodeName)
|
|
||||||
}
|
}
|
||||||
|
ec.Lock()
|
||||||
|
defer ec.Unlock()
|
||||||
|
// algorithmCache uses nodeName as key, so we just iterate it and invalid given predicates
|
||||||
|
for _, algorithmCache := range ec.algorithmCache {
|
||||||
|
for predicateKey := range predicateKeys {
|
||||||
|
// just use keys is enough
|
||||||
|
algorithmCache.predicatesCache.Remove(predicateKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
glog.V(5).Infof("Done invalidating cached predicates: %v on all node", predicateKeys)
|
||||||
}
|
}
|
||||||
|
|
||||||
// hashEquivalencePod returns the hash of equivalence pod.
|
// InvalidateAllCachedPredicateItemOfNode marks all cached items on given node as invalid
|
||||||
func (ec *EquivalenceCache) hashEquivalencePod(pod *v1.Pod) uint64 {
|
func (ec *EquivalenceCache) InvalidateAllCachedPredicateItemOfNode(nodeName string) {
|
||||||
equivalencePod := ec.getEquivalencePod(pod)
|
ec.Lock()
|
||||||
hash := fnv.New32a()
|
defer ec.Unlock()
|
||||||
hashutil.DeepHashObject(hash, equivalencePod)
|
delete(ec.algorithmCache, nodeName)
|
||||||
return uint64(hash.Sum32())
|
glog.V(5).Infof("Done invalidating all cached predicates on node: %s", nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvalidateCachedPredicateItemForPod marks item of given predicateKeys, of given pod, on the given node as invalid
|
||||||
|
func (ec *EquivalenceCache) InvalidateCachedPredicateItemForPod(nodeName string, predicateKeys sets.String, pod *v1.Pod) {
|
||||||
|
if len(predicateKeys) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
equivalenceHash := ec.getHashEquivalencePod(pod)
|
||||||
|
if equivalenceHash == 0 {
|
||||||
|
// no equivalence pod found, just return
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ec.Lock()
|
||||||
|
defer ec.Unlock()
|
||||||
|
if algorithmCache, exist := ec.algorithmCache[nodeName]; exist {
|
||||||
|
for predicateKey := range predicateKeys {
|
||||||
|
if cachePredicate, exist := algorithmCache.predicatesCache.Get(predicateKey); exist {
|
||||||
|
// got the cached item of by predicateKey & pod
|
||||||
|
predicateMap := cachePredicate.(PredicateMap)
|
||||||
|
delete(predicateMap, equivalenceHash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
glog.V(5).Infof("Done invalidating cached predicates %v on node %s, for pod %v", predicateKeys, nodeName, pod.GetName())
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvalidateCachedPredicateItemForPodAdd is a wrapper of InvalidateCachedPredicateItem for pod add case
|
||||||
|
func (ec *EquivalenceCache) InvalidateCachedPredicateItemForPodAdd(pod *v1.Pod, nodeName string) {
|
||||||
|
// MatchInterPodAffinity: we assume scheduler can make sure newly binded pod
|
||||||
|
// will not break the existing inter pod affinity. So we does not need to invalidate
|
||||||
|
// MatchInterPodAffinity when pod added.
|
||||||
|
//
|
||||||
|
// But when a pod is deleted, existing inter pod affinity may become invalid.
|
||||||
|
// (e.g. this pod was preferred by some else, or vice versa)
|
||||||
|
//
|
||||||
|
// NOTE: assumptions above will not stand when we implemented features like
|
||||||
|
// RequiredDuringSchedulingRequiredDuringExecution.
|
||||||
|
|
||||||
|
// NoDiskConflict: the newly scheduled pod fits to existing pods on this node,
|
||||||
|
// it will also fits to equivalence class of existing pods
|
||||||
|
|
||||||
|
// GeneralPredicates: will always be affected by adding a new pod
|
||||||
|
invalidPredicates := sets.NewString("GeneralPredicates")
|
||||||
|
ec.InvalidateCachedPredicateItem(nodeName, invalidPredicates)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getHashEquivalencePod returns the hash of equivalence pod.
|
||||||
|
// if no equivalence pod found, return 0
|
||||||
|
func (ec *EquivalenceCache) getHashEquivalencePod(pod *v1.Pod) uint64 {
|
||||||
|
equivalencePod := ec.getEquivalencePod(pod)
|
||||||
|
if equivalencePod != nil {
|
||||||
|
hash := fnv.New32a()
|
||||||
|
hashutil.DeepHashObject(hash, equivalencePod)
|
||||||
|
return uint64(hash.Sum32())
|
||||||
|
}
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
131
plugin/pkg/scheduler/core/equivalence_cache_test.go
Normal file
131
plugin/pkg/scheduler/core/equivalence_cache_test.go
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
"k8s.io/kubernetes/pkg/api/v1"
|
||||||
|
"k8s.io/kubernetes/plugin/pkg/scheduler/algorithm"
|
||||||
|
"k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUpdateCachedPredicateItem(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pod *v1.Pod
|
||||||
|
predicateKey string
|
||||||
|
nodeName string
|
||||||
|
fit bool
|
||||||
|
reasons []algorithm.PredicateFailureReason
|
||||||
|
equivalenceHash uint64
|
||||||
|
expectCacheItem HostPredicate
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "test 1",
|
||||||
|
pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "testPod"}},
|
||||||
|
predicateKey: "GeneralPredicates",
|
||||||
|
nodeName: "node1",
|
||||||
|
fit: true,
|
||||||
|
equivalenceHash: 123,
|
||||||
|
expectCacheItem: HostPredicate{
|
||||||
|
Fit: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
// this case does not need to calculate equivalence hash, just pass an empty function
|
||||||
|
fakeGetEquivalencePodFunc := func(pod *v1.Pod) interface{} { return nil }
|
||||||
|
ecache := NewEquivalenceCache(fakeGetEquivalencePodFunc)
|
||||||
|
ecache.UpdateCachedPredicateItem(test.pod, test.nodeName, test.predicateKey, test.fit, test.reasons, test.equivalenceHash)
|
||||||
|
|
||||||
|
value, ok := ecache.algorithmCache[test.nodeName].predicatesCache.Get(test.predicateKey)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("Failed : %s, can't find expected cache item: %v", test.name, test.expectCacheItem)
|
||||||
|
} else {
|
||||||
|
cachedMapItem := value.(PredicateMap)
|
||||||
|
if !reflect.DeepEqual(cachedMapItem[test.equivalenceHash], test.expectCacheItem) {
|
||||||
|
t.Errorf("Failed : %s, expected cached item: %v, but got: %v", test.name, test.expectCacheItem, cachedMapItem[test.equivalenceHash])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type predicateItemType struct {
|
||||||
|
fit bool
|
||||||
|
reasons []algorithm.PredicateFailureReason
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvalidateCachedPredicateItem(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pod *v1.Pod
|
||||||
|
nodeName string
|
||||||
|
predicateKey string
|
||||||
|
equivalenceHash uint64
|
||||||
|
cachedItem predicateItemType
|
||||||
|
expectedInvalid bool
|
||||||
|
expectedPredicateItem predicateItemType
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "test 1",
|
||||||
|
pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "testPod"}},
|
||||||
|
nodeName: "node1",
|
||||||
|
equivalenceHash: 123,
|
||||||
|
predicateKey: "GeneralPredicates",
|
||||||
|
cachedItem: predicateItemType{
|
||||||
|
fit: false,
|
||||||
|
reasons: []algorithm.PredicateFailureReason{predicates.ErrPodNotFitsHostPorts},
|
||||||
|
},
|
||||||
|
expectedInvalid: true,
|
||||||
|
expectedPredicateItem: predicateItemType{
|
||||||
|
fit: false,
|
||||||
|
reasons: []algorithm.PredicateFailureReason{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
// this case does not need to calculate equivalence hash, just pass an empty function
|
||||||
|
fakeGetEquivalencePodFunc := func(pod *v1.Pod) interface{} { return nil }
|
||||||
|
ecache := NewEquivalenceCache(fakeGetEquivalencePodFunc)
|
||||||
|
// set cached item to equivalence cache
|
||||||
|
ecache.UpdateCachedPredicateItem(test.pod, test.nodeName, test.predicateKey, test.cachedItem.fit, test.cachedItem.reasons, test.equivalenceHash)
|
||||||
|
// if we want to do invalid, invalid the cached item
|
||||||
|
if test.expectedInvalid {
|
||||||
|
predicateKeys := sets.NewString()
|
||||||
|
predicateKeys.Insert(test.predicateKey)
|
||||||
|
ecache.InvalidateCachedPredicateItem(test.nodeName, predicateKeys)
|
||||||
|
}
|
||||||
|
// calculate predicate with equivalence cache
|
||||||
|
fit, reasons, invalid := ecache.PredicateWithECache(test.pod, test.nodeName, test.predicateKey, test.equivalenceHash)
|
||||||
|
// returned invalid should match expectedInvalid
|
||||||
|
if invalid != test.expectedInvalid {
|
||||||
|
t.Errorf("Failed : %s, expected invalid: %v, but got: %v", test.name, test.expectedInvalid, invalid)
|
||||||
|
}
|
||||||
|
// returned predicate result should match expected predicate item
|
||||||
|
if fit != test.expectedPredicateItem.fit {
|
||||||
|
t.Errorf("Failed : %s, expected fit: %v, but got: %v", test.name, test.cachedItem.fit, fit)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(reasons, test.expectedPredicateItem.reasons) {
|
||||||
|
t.Errorf("Failed : %s, expected reasons: %v, but got: %v", test.name, test.cachedItem.reasons, reasons)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user