From f68d4cf03cfbb0f116a3128a4e3635cda3891ff9 Mon Sep 17 00:00:00 2001 From: Jan Chaloupka Date: Wed, 6 Apr 2022 23:58:21 +0200 Subject: [PATCH] Descheduling framework PoC --- pkg/api/types.go | 5 + .../removeduplicatepods.go | 311 +++++++ .../removeduplicatepods_test.go | 780 ++++++++++++++++++ pkg/framework/types.go | 50 ++ pkg/utils/priority.go | 20 + 5 files changed, 1166 insertions(+) create mode 100644 pkg/framework/plugins/removeduplicatepods/removeduplicatepods.go create mode 100644 pkg/framework/plugins/removeduplicatepods/removeduplicatepods_test.go create mode 100644 pkg/framework/types.go diff --git a/pkg/api/types.go b/pkg/api/types.go index ef4c651232..55bd6ca77a 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -72,6 +72,11 @@ type Namespaces struct { Exclude []string } +type PriorityThreshold struct { + Value *int32 + Name string +} + // Besides Namespaces only one of its members may be specified // TODO(jchaloup): move Namespaces ThresholdPriority and ThresholdPriorityClassName to individual strategies // once the policy version is bumped to v1alpha2 diff --git a/pkg/framework/plugins/removeduplicatepods/removeduplicatepods.go b/pkg/framework/plugins/removeduplicatepods/removeduplicatepods.go new file mode 100644 index 0000000000..39e4ee35db --- /dev/null +++ b/pkg/framework/plugins/removeduplicatepods/removeduplicatepods.go @@ -0,0 +1,311 @@ +/* +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 removeduplicatepods + +import ( + "context" + "fmt" + "math" + "reflect" + "sort" + "strings" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/klog/v2" + + "sigs.k8s.io/descheduler/pkg/descheduler/evictions" + podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod" + "sigs.k8s.io/descheduler/pkg/framework" + "sigs.k8s.io/descheduler/pkg/utils" +) + +const PluginName = "RemoveDuplicatePods" + +// RemoveDuplicatePods removes the duplicate pods on node. This strategy evicts all duplicate pods on node. +// A pod is said to be a duplicate of other if both of them are from same creator, kind and are within the same +// namespace, and have at least one container with the same image. +// As of now, this strategy won't evict daemonsets, mirror pods, critical pods and pods with local storages. +type RemoveDuplicatePods struct { + handle framework.Handle + args *framework.RemoveDuplicatePodsArg + excludeOwnerKinds []string + podFilter podutil.FilterFunc +} + +var _ framework.Plugin = &RemoveDuplicatePods{} +var _ framework.DeschedulePlugin = &RemoveDuplicatePods{} + +func New(args runtime.Object, handle framework.Handle) (framework.Plugin, error) { + duplicatesArg, ok := args.(*framework.RemoveDuplicatePodsArg) + if !ok { + return nil, fmt.Errorf("want args to be of type RemoveDuplicatePodsArg, got %T", args) + } + + // At most one of include/exclude can be set + if duplicatesArg.Namespaces != nil && len(duplicatesArg.Namespaces.Include) > 0 && len(duplicatesArg.Namespaces.Exclude) > 0 { + return nil, fmt.Errorf("only one of Include/Exclude namespaces can be set") + } + if duplicatesArg.PriorityThreshold != nil && duplicatesArg.PriorityThreshold.Value != nil && duplicatesArg.PriorityThreshold.Name != "" { + return nil, fmt.Errorf("only one of priorityThreshold fields can be set") + } + + thresholdPriority, err := utils.GetPriorityValueFromPriorityThreshold(context.TODO(), handle.ClientSet(), duplicatesArg.PriorityThreshold) + if err != nil { + return nil, fmt.Errorf("failed to get priority threshold: %v", err) + } + + evictable := handle.PodEvictor().Evictable( + evictions.WithPriorityThreshold(thresholdPriority), + evictions.WithNodeFit(duplicatesArg.NodeFit), + ) + + var includedNamespaces, excludedNamespaces sets.String + if duplicatesArg.Namespaces != nil { + includedNamespaces = sets.NewString(duplicatesArg.Namespaces.Include...) + excludedNamespaces = sets.NewString(duplicatesArg.Namespaces.Exclude...) + } + + podFilter, err := podutil.NewOptions(). + WithFilter(evictable.IsEvictable). + WithNamespaces(includedNamespaces). + WithoutNamespaces(excludedNamespaces). + BuildFilterFunc() + if err != nil { + return nil, fmt.Errorf("error initializing pod filter function: %v", err) + } + + return &RemoveDuplicatePods{ + handle: handle, + excludeOwnerKinds: duplicatesArg.ExcludeOwnerKinds, + podFilter: podFilter, + }, nil +} + +func (d *RemoveDuplicatePods) Name() string { + return PluginName +} + +type podOwner struct { + namespace, kind, name string + imagesHash string +} + +func (d *RemoveDuplicatePods) Deschedule(ctx context.Context, nodes []*v1.Node) *framework.Status { + duplicatePods := make(map[podOwner]map[string][]*v1.Pod) + ownerKeyOccurence := make(map[podOwner]int32) + nodeCount := 0 + nodeMap := make(map[string]*v1.Node) + + for _, node := range nodes { + klog.V(1).InfoS("Processing node", "node", klog.KObj(node)) + pods, err := podutil.ListPodsOnANode(node.Name, d.handle.GetPodsAssignedToNodeFunc(), d.podFilter) + if err != nil { + klog.ErrorS(err, "Error listing evictable pods on node", "node", klog.KObj(node)) + continue + } + nodeMap[node.Name] = node + nodeCount++ + // Each pod has a list of owners and a list of containers, and each container has 1 image spec. + // For each pod, we go through all the OwnerRef/Image mappings and represent them as a "key" string. + // All of those mappings together makes a list of "key" strings that essentially represent that pod's uniqueness. + // This list of keys representing a single pod is then sorted alphabetically. + // If any other pod has a list that matches that pod's list, those pods are undeniably duplicates for the following reasons: + // - The 2 pods have the exact same ownerrefs + // - The 2 pods have the exact same container images + // + // duplicateKeysMap maps the first Namespace/Kind/Name/Image in a pod's list to a 2D-slice of all the other lists where that is the first key + // (Since we sort each pod's list, we only need to key the map on the first entry in each list. Any pod that doesn't have + // the same first entry is clearly not a duplicate. This makes lookup quick and minimizes storage needed). + // If any of the existing lists for that first key matches the current pod's list, the current pod is a duplicate. + // If not, then we add this pod's list to the list of lists for that key. + duplicateKeysMap := map[string][][]string{} + for _, pod := range pods { + ownerRefList := podutil.OwnerRef(pod) + if len(ownerRefList) == 0 || hasExcludedOwnerRefKind(ownerRefList, d.excludeOwnerKinds) { + continue + } + podContainerKeys := make([]string, 0, len(ownerRefList)*len(pod.Spec.Containers)) + imageList := []string{} + for _, container := range pod.Spec.Containers { + imageList = append(imageList, container.Image) + } + sort.Strings(imageList) + imagesHash := strings.Join(imageList, "#") + for _, ownerRef := range ownerRefList { + ownerKey := podOwner{ + namespace: pod.ObjectMeta.Namespace, + kind: ownerRef.Kind, + name: ownerRef.Name, + imagesHash: imagesHash, + } + ownerKeyOccurence[ownerKey] = ownerKeyOccurence[ownerKey] + 1 + for _, image := range imageList { + // Namespace/Kind/Name should be unique for the cluster. + // We also consider the image, as 2 pods could have the same owner but serve different purposes + // So any non-unique Namespace/Kind/Name/Image pattern is a duplicate pod. + s := strings.Join([]string{pod.ObjectMeta.Namespace, ownerRef.Kind, ownerRef.Name, image}, "/") + podContainerKeys = append(podContainerKeys, s) + } + } + sort.Strings(podContainerKeys) + + // If there have been any other pods with the same first "key", look through all the lists to see if any match + if existing, ok := duplicateKeysMap[podContainerKeys[0]]; ok { + matched := false + for _, keys := range existing { + if reflect.DeepEqual(keys, podContainerKeys) { + matched = true + klog.V(3).InfoS("Duplicate found", "pod", klog.KObj(pod)) + for _, ownerRef := range ownerRefList { + ownerKey := podOwner{ + namespace: pod.ObjectMeta.Namespace, + kind: ownerRef.Kind, + name: ownerRef.Name, + imagesHash: imagesHash, + } + if _, ok := duplicatePods[ownerKey]; !ok { + duplicatePods[ownerKey] = make(map[string][]*v1.Pod) + } + duplicatePods[ownerKey][node.Name] = append(duplicatePods[ownerKey][node.Name], pod) + } + break + } + } + if !matched { + // Found no matches, add this list of keys to the list of lists that have the same first key + duplicateKeysMap[podContainerKeys[0]] = append(duplicateKeysMap[podContainerKeys[0]], podContainerKeys) + } + } else { + // This is the first pod we've seen that has this first "key" entry + duplicateKeysMap[podContainerKeys[0]] = [][]string{podContainerKeys} + } + } + } + + // 1. how many pods can be evicted to respect uniform placement of pods among viable nodes? + for ownerKey, podNodes := range duplicatePods { + targetNodes := getTargetNodes(podNodes, nodes) + + klog.V(2).InfoS("Adjusting feasible nodes", "owner", ownerKey, "from", nodeCount, "to", len(targetNodes)) + if len(targetNodes) < 2 { + klog.V(1).InfoS("Less than two feasible nodes for duplicates to land, skipping eviction", "owner", ownerKey) + continue + } + + upperAvg := int(math.Ceil(float64(ownerKeyOccurence[ownerKey]) / float64(len(targetNodes)))) + for nodeName, pods := range podNodes { + klog.V(2).InfoS("Average occurrence per node", "node", klog.KObj(nodeMap[nodeName]), "ownerKey", ownerKey, "avg", upperAvg) + // list of duplicated pods does not contain the original referential pod + if len(pods)+1 > upperAvg { + // It's assumed all duplicated pods are in the same priority class + // TODO(jchaloup): check if the pod has a different node to lend to + for _, pod := range pods[upperAvg-1:] { + if _, err := d.handle.PodEvictor().EvictPod(ctx, pod, nodeMap[nodeName], "RemoveDuplicatePods"); err != nil { + klog.ErrorS(err, "Error evicting pod", "pod", klog.KObj(pod)) + break + } + } + } + } + } + + return nil +} + +func getNodeAffinityNodeSelector(pod *v1.Pod) *v1.NodeSelector { + if pod.Spec.Affinity == nil { + return nil + } + if pod.Spec.Affinity.NodeAffinity == nil { + return nil + } + return pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution +} + +func getTargetNodes(podNodes map[string][]*v1.Pod, nodes []*v1.Node) []*v1.Node { + // In order to reduce the number of pods processed, identify pods which have + // equal (tolerations, nodeselectors, node affinity) terms and considered them + // as identical. Identical pods wrt. (tolerations, nodeselectors, node affinity) terms + // will produce the same result when checking if a pod is feasible for a node. + // Thus, improving efficiency of processing pods marked for eviction. + + // Collect all distinct pods which differ in at least taints, node affinity or node selector terms + distinctPods := map[*v1.Pod]struct{}{} + for _, pods := range podNodes { + for _, pod := range pods { + duplicated := false + for dp := range distinctPods { + if utils.TolerationsEqual(pod.Spec.Tolerations, dp.Spec.Tolerations) && + utils.NodeSelectorsEqual(getNodeAffinityNodeSelector(pod), getNodeAffinityNodeSelector(dp)) && + reflect.DeepEqual(pod.Spec.NodeSelector, dp.Spec.NodeSelector) { + duplicated = true + continue + } + } + if duplicated { + continue + } + distinctPods[pod] = struct{}{} + } + } + + // For each distinct pod get a list of nodes where it can land + targetNodesMap := map[string]*v1.Node{} + for pod := range distinctPods { + matchingNodes := map[string]*v1.Node{} + for _, node := range nodes { + if !utils.TolerationsTolerateTaintsWithFilter(pod.Spec.Tolerations, node.Spec.Taints, func(taint *v1.Taint) bool { + return taint.Effect == v1.TaintEffectNoSchedule || taint.Effect == v1.TaintEffectNoExecute + }) { + continue + } + if match, err := utils.PodMatchNodeSelector(pod, node); err == nil && !match { + continue + } + matchingNodes[node.Name] = node + } + if len(matchingNodes) > 1 { + for nodeName := range matchingNodes { + targetNodesMap[nodeName] = matchingNodes[nodeName] + } + } + } + + targetNodes := []*v1.Node{} + for _, node := range targetNodesMap { + targetNodes = append(targetNodes, node) + } + + return targetNodes +} + +func hasExcludedOwnerRefKind(ownerRefs []metav1.OwnerReference, ExcludeOwnerKinds []string) bool { + if len(ExcludeOwnerKinds) == 0 { + return false + } + + exclude := sets.NewString(ExcludeOwnerKinds...) + for _, owner := range ownerRefs { + if exclude.Has(owner.Kind) { + return true + } + } + return false +} diff --git a/pkg/framework/plugins/removeduplicatepods/removeduplicatepods_test.go b/pkg/framework/plugins/removeduplicatepods/removeduplicatepods_test.go new file mode 100644 index 0000000000..bb62e631fb --- /dev/null +++ b/pkg/framework/plugins/removeduplicatepods/removeduplicatepods_test.go @@ -0,0 +1,780 @@ +/* +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 removeduplicatepods + +import ( + "context" + "testing" + + v1 "k8s.io/api/core/v1" + policyv1 "k8s.io/api/policy/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/informers" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/fake" + + "sigs.k8s.io/descheduler/pkg/api" + "sigs.k8s.io/descheduler/pkg/descheduler/evictions" + podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod" + "sigs.k8s.io/descheduler/pkg/framework" + "sigs.k8s.io/descheduler/pkg/utils" + "sigs.k8s.io/descheduler/test" +) + +func buildTestPodWithImage(podName, node, image string) *v1.Pod { + pod := test.BuildTestPod(podName, 100, 0, node, test.SetRSOwnerRef) + pod.Spec.Containers = append(pod.Spec.Containers, v1.Container{ + Name: image, + Image: image, + }) + return pod +} + +type frameworkHandle struct { + clientset clientset.Interface + podEvictor *evictions.PodEvictor + getPodsAssignedToNodeFunc podutil.GetPodsAssignedToNodeFunc +} + +func (f frameworkHandle) ClientSet() clientset.Interface { + return f.clientset +} +func (f frameworkHandle) PodEvictor() *evictions.PodEvictor { + return f.podEvictor +} +func (f frameworkHandle) GetPodsAssignedToNodeFunc() podutil.GetPodsAssignedToNodeFunc { + return f.getPodsAssignedToNodeFunc +} + +func TestFindDuplicatePods(t *testing.T) { + // first setup pods + node1 := test.BuildTestNode("n1", 2000, 3000, 10, nil) + node2 := test.BuildTestNode("n2", 2000, 3000, 10, nil) + node3 := test.BuildTestNode("n3", 2000, 3000, 10, func(node *v1.Node) { + node.Spec.Taints = []v1.Taint{ + { + Key: "hardware", + Value: "gpu", + Effect: v1.TaintEffectNoSchedule, + }, + } + }) + node4 := test.BuildTestNode("n4", 2000, 3000, 10, func(node *v1.Node) { + node.ObjectMeta.Labels = map[string]string{ + "datacenter": "east", + } + }) + node5 := test.BuildTestNode("n5", 2000, 3000, 10, func(node *v1.Node) { + node.Spec = v1.NodeSpec{ + Unschedulable: true, + } + }) + + p1 := test.BuildTestPod("p1", 100, 0, node1.Name, nil) + p1.Namespace = "dev" + p2 := test.BuildTestPod("p2", 100, 0, node1.Name, nil) + p2.Namespace = "dev" + p3 := test.BuildTestPod("p3", 100, 0, node1.Name, nil) + p3.Namespace = "dev" + p4 := test.BuildTestPod("p4", 100, 0, node1.Name, nil) + p5 := test.BuildTestPod("p5", 100, 0, node1.Name, nil) + p6 := test.BuildTestPod("p6", 100, 0, node1.Name, nil) + p7 := test.BuildTestPod("p7", 100, 0, node1.Name, nil) + p7.Namespace = "kube-system" + p8 := test.BuildTestPod("p8", 100, 0, node1.Name, nil) + p8.Namespace = "test" + p9 := test.BuildTestPod("p9", 100, 0, node1.Name, nil) + p9.Namespace = "test" + p10 := test.BuildTestPod("p10", 100, 0, node1.Name, nil) + p10.Namespace = "test" + p11 := test.BuildTestPod("p11", 100, 0, node1.Name, nil) + p11.Namespace = "different-images" + p12 := test.BuildTestPod("p12", 100, 0, node1.Name, nil) + p12.Namespace = "different-images" + p13 := test.BuildTestPod("p13", 100, 0, node1.Name, nil) + p13.Namespace = "different-images" + p14 := test.BuildTestPod("p14", 100, 0, node1.Name, nil) + p14.Namespace = "different-images" + p15 := test.BuildTestPod("p15", 100, 0, node1.Name, nil) + p15.Namespace = "node-fit" + p16 := test.BuildTestPod("NOT1", 100, 0, node1.Name, nil) + p16.Namespace = "node-fit" + p17 := test.BuildTestPod("NOT2", 100, 0, node1.Name, nil) + p17.Namespace = "node-fit" + p18 := test.BuildTestPod("TARGET", 100, 0, node1.Name, nil) + p18.Namespace = "node-fit" + + // ### Evictable Pods ### + + // Three Pods in the "default" Namespace, bound to same ReplicaSet. 2 should be evicted. + ownerRef1 := test.GetReplicaSetOwnerRefList() + p1.ObjectMeta.OwnerReferences = ownerRef1 + p2.ObjectMeta.OwnerReferences = ownerRef1 + p3.ObjectMeta.OwnerReferences = ownerRef1 + + // Three Pods in the "test" Namespace, bound to same ReplicaSet. 2 should be evicted. + ownerRef2 := test.GetReplicaSetOwnerRefList() + p8.ObjectMeta.OwnerReferences = ownerRef2 + p9.ObjectMeta.OwnerReferences = ownerRef2 + p10.ObjectMeta.OwnerReferences = ownerRef2 + + // ### Non-evictable Pods ### + + // A DaemonSet. + p4.ObjectMeta.OwnerReferences = test.GetDaemonSetOwnerRefList() + + // A Pod with local storage. + p5.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList() + p5.Spec.Volumes = []v1.Volume{ + { + Name: "sample", + VolumeSource: v1.VolumeSource{ + HostPath: &v1.HostPathVolumeSource{Path: "somePath"}, + EmptyDir: &v1.EmptyDirVolumeSource{ + SizeLimit: resource.NewQuantity(int64(10), resource.BinarySI)}, + }, + }, + } + + // A Mirror Pod. + p6.Annotations = test.GetMirrorPodAnnotation() + + // A Critical Pod. + priority := utils.SystemCriticalPriority + p7.Spec.Priority = &priority + + // Same owners, but different images + p11.Spec.Containers[0].Image = "foo" + p11.ObjectMeta.OwnerReferences = ownerRef1 + p12.Spec.Containers[0].Image = "bar" + p12.ObjectMeta.OwnerReferences = ownerRef1 + + // Multiple containers + p13.ObjectMeta.OwnerReferences = ownerRef1 + p13.Spec.Containers = append(p13.Spec.Containers, v1.Container{ + Name: "foo", + Image: "foo", + }) + + // ### Pods Evictable Based On Node Fit ### + + ownerRef3 := test.GetReplicaSetOwnerRefList() + p15.ObjectMeta.OwnerReferences = ownerRef3 + p16.ObjectMeta.OwnerReferences = ownerRef3 + p17.ObjectMeta.OwnerReferences = ownerRef3 + p18.ObjectMeta.OwnerReferences = ownerRef3 + + p15.Spec.NodeSelector = map[string]string{ + "datacenter": "west", + } + p16.Spec.NodeSelector = map[string]string{ + "datacenter": "west", + } + p17.Spec.NodeSelector = map[string]string{ + "datacenter": "west", + } + + testCases := []struct { + description string + pods []*v1.Pod + nodes []*v1.Node + expectedEvictedPodCount uint + + nodeFit bool + excludeOwnerKinds []string + }{ + { + description: "Three pods in the `dev` Namespace, bound to same ReplicaSet. 1 should be evicted.", + pods: []*v1.Pod{p1, p2, p3}, + nodes: []*v1.Node{node1, node2}, + expectedEvictedPodCount: 1, + }, + { + description: "Three pods in the `dev` Namespace, bound to same ReplicaSet, but ReplicaSet kind is excluded. 0 should be evicted.", + pods: []*v1.Pod{p1, p2, p3}, + nodes: []*v1.Node{node1, node2}, + expectedEvictedPodCount: 0, + excludeOwnerKinds: []string{"ReplicaSet"}, + }, + { + description: "Three Pods in the `test` Namespace, bound to same ReplicaSet. 1 should be evicted.", + pods: []*v1.Pod{p8, p9, p10}, + nodes: []*v1.Node{node1, node2}, + expectedEvictedPodCount: 1, + }, + { + description: "Three Pods in the `dev` Namespace, three Pods in the `test` Namespace. Bound to ReplicaSet with same name. 4 should be evicted.", + pods: []*v1.Pod{p1, p2, p3, p8, p9, p10}, + nodes: []*v1.Node{node1, node2}, + expectedEvictedPodCount: 2, + }, + { + description: "Pods are: part of DaemonSet, with local storage, mirror pod annotation, critical pod annotation - none should be evicted.", + pods: []*v1.Pod{p4, p5, p6, p7}, + nodes: []*v1.Node{node1, node2}, + expectedEvictedPodCount: 0, + }, + { + description: "Test all Pods: 4 should be evicted.", + pods: []*v1.Pod{p1, p2, p3, p4, p5, p6, p7, p8, p9, p10}, + nodes: []*v1.Node{node1, node2}, + expectedEvictedPodCount: 2, + }, + { + description: "Pods with the same owner but different images should not be evicted", + pods: []*v1.Pod{p11, p12}, + nodes: []*v1.Node{node1, node2}, + expectedEvictedPodCount: 0, + }, + { + description: "Pods with multiple containers should not match themselves", + pods: []*v1.Pod{p13}, + nodes: []*v1.Node{node1, node2}, + expectedEvictedPodCount: 0, + }, + { + description: "Pods with matching ownerrefs and at not all matching image should not trigger an eviction", + pods: []*v1.Pod{p11, p13}, + nodes: []*v1.Node{node1, node2}, + expectedEvictedPodCount: 0, + }, + { + description: "Three pods in the `dev` Namespace, bound to same ReplicaSet. Only node available has a taint, and nodeFit set to true. 0 should be evicted.", + pods: []*v1.Pod{p1, p2, p3}, + nodes: []*v1.Node{node1, node3}, + expectedEvictedPodCount: 0, + nodeFit: true, + }, + { + description: "Three pods in the `node-fit` Namespace, bound to same ReplicaSet, all with a nodeSelector. Only node available has an incorrect node label, and nodeFit set to true. 0 should be evicted.", + pods: []*v1.Pod{p15, p16, p17}, + nodes: []*v1.Node{node1, node4}, + expectedEvictedPodCount: 0, + nodeFit: true, + }, + { + description: "Three pods in the `node-fit` Namespace, bound to same ReplicaSet. Only node available is not schedulable, and nodeFit set to true. 0 should be evicted.", + pods: []*v1.Pod{p1, p2, p3}, + nodes: []*v1.Node{node1, node5}, + expectedEvictedPodCount: 0, + nodeFit: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.description, func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var objs []runtime.Object + for _, node := range testCase.nodes { + objs = append(objs, node) + } + for _, pod := range testCase.pods { + objs = append(objs, pod) + } + fakeClient := fake.NewSimpleClientset(objs...) + + sharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0) + podInformer := sharedInformerFactory.Core().V1().Pods() + + getPodsAssignedToNode, err := podutil.BuildGetPodsAssignedToNodeFunc(podInformer) + if err != nil { + t.Errorf("Build get pods assigned to node function error: %v", err) + } + + sharedInformerFactory.Start(ctx.Done()) + sharedInformerFactory.WaitForCacheSync(ctx.Done()) + + podEvictor := evictions.NewPodEvictor( + fakeClient, + "v1", + false, + nil, + nil, + testCase.nodes, + false, + false, + false, + false, + false, + ) + + plugin, err := New(&framework.RemoveDuplicatePodsArg{ + // Namespaces *api.Namespaces + // PriorityThreshold *api.PriorityThreshold + NodeFit: testCase.nodeFit, + ExcludeOwnerKinds: testCase.excludeOwnerKinds, + }, + frameworkHandle{ + clientset: fakeClient, + podEvictor: podEvictor, + getPodsAssignedToNodeFunc: getPodsAssignedToNode, + }, + ) + if err != nil { + t.Fatalf("Unable to initialize the plugin: %v", err) + } + + plugin.(interface{}).(framework.DeschedulePlugin).Deschedule(ctx, testCase.nodes) + podsEvicted := podEvictor.TotalEvicted() + if podsEvicted != testCase.expectedEvictedPodCount { + t.Errorf("Test error for description: %s. Expected evicted pods count %v, got %v", testCase.description, testCase.expectedEvictedPodCount, podsEvicted) + } + }) + } + +} + +func TestRemoveDuplicatesUniformly(t *testing.T) { + setRSOwnerRef2 := func(pod *v1.Pod) { + pod.ObjectMeta.OwnerReferences = []metav1.OwnerReference{ + {Kind: "ReplicaSet", APIVersion: "v1", Name: "replicaset-2"}, + } + } + setTwoRSOwnerRef := func(pod *v1.Pod) { + pod.ObjectMeta.OwnerReferences = []metav1.OwnerReference{ + {Kind: "ReplicaSet", APIVersion: "v1", Name: "replicaset-1"}, + {Kind: "ReplicaSet", APIVersion: "v1", Name: "replicaset-2"}, + } + } + + setTolerationsK1 := func(pod *v1.Pod) { + test.SetRSOwnerRef(pod) + pod.Spec.Tolerations = []v1.Toleration{ + { + Key: "k1", + Value: "v1", + Operator: v1.TolerationOpEqual, + Effect: v1.TaintEffectNoSchedule, + }, + } + } + setTolerationsK2 := func(pod *v1.Pod) { + test.SetRSOwnerRef(pod) + pod.Spec.Tolerations = []v1.Toleration{ + { + Key: "k2", + Value: "v2", + Operator: v1.TolerationOpEqual, + Effect: v1.TaintEffectNoSchedule, + }, + } + } + + setMasterNoScheduleTaint := func(node *v1.Node) { + node.Spec.Taints = []v1.Taint{ + { + Effect: v1.TaintEffectNoSchedule, + Key: "node-role.kubernetes.io/master", + }, + } + } + + setMasterNoScheduleLabel := func(node *v1.Node) { + if node.ObjectMeta.Labels == nil { + node.ObjectMeta.Labels = map[string]string{} + } + node.ObjectMeta.Labels["node-role.kubernetes.io/master"] = "" + } + + setWorkerLabel := func(node *v1.Node) { + if node.ObjectMeta.Labels == nil { + node.ObjectMeta.Labels = map[string]string{} + } + node.ObjectMeta.Labels["node-role.kubernetes.io/worker"] = "k1" + node.ObjectMeta.Labels["node-role.kubernetes.io/worker"] = "k2" + } + + setNotMasterNodeSelectorK1 := func(pod *v1.Pod) { + test.SetRSOwnerRef(pod) + pod.Spec.Affinity = &v1.Affinity{ + NodeAffinity: &v1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "node-role.kubernetes.io/master", + Operator: v1.NodeSelectorOpDoesNotExist, + }, + { + Key: "k1", + Operator: v1.NodeSelectorOpDoesNotExist, + }, + }, + }, + }, + }, + }, + } + } + + setNotMasterNodeSelectorK2 := func(pod *v1.Pod) { + test.SetRSOwnerRef(pod) + pod.Spec.Affinity = &v1.Affinity{ + NodeAffinity: &v1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "node-role.kubernetes.io/master", + Operator: v1.NodeSelectorOpDoesNotExist, + }, + { + Key: "k2", + Operator: v1.NodeSelectorOpDoesNotExist, + }, + }, + }, + }, + }, + }, + } + } + + setWorkerLabelSelectorK1 := func(pod *v1.Pod) { + test.SetRSOwnerRef(pod) + if pod.Spec.NodeSelector == nil { + pod.Spec.NodeSelector = map[string]string{} + } + pod.Spec.NodeSelector["node-role.kubernetes.io/worker"] = "k1" + } + + setWorkerLabelSelectorK2 := func(pod *v1.Pod) { + test.SetRSOwnerRef(pod) + if pod.Spec.NodeSelector == nil { + pod.Spec.NodeSelector = map[string]string{} + } + pod.Spec.NodeSelector["node-role.kubernetes.io/worker"] = "k2" + } + + testCases := []struct { + description string + pods []*v1.Pod + nodes []*v1.Node + expectedEvictedPodCount uint + strategy api.DeschedulerStrategy + }{ + { + description: "Evict pods uniformly", + pods: []*v1.Pod{ + // (5,3,1) -> (3,3,3) -> 2 evictions + test.BuildTestPod("p1", 100, 0, "n1", test.SetRSOwnerRef), + test.BuildTestPod("p2", 100, 0, "n1", test.SetRSOwnerRef), + test.BuildTestPod("p3", 100, 0, "n1", test.SetRSOwnerRef), + test.BuildTestPod("p4", 100, 0, "n1", test.SetRSOwnerRef), + test.BuildTestPod("p5", 100, 0, "n1", test.SetRSOwnerRef), + test.BuildTestPod("p6", 100, 0, "n2", test.SetRSOwnerRef), + test.BuildTestPod("p7", 100, 0, "n2", test.SetRSOwnerRef), + test.BuildTestPod("p8", 100, 0, "n2", test.SetRSOwnerRef), + test.BuildTestPod("p9", 100, 0, "n3", test.SetRSOwnerRef), + }, + expectedEvictedPodCount: 2, + nodes: []*v1.Node{ + test.BuildTestNode("n1", 2000, 3000, 10, nil), + test.BuildTestNode("n2", 2000, 3000, 10, nil), + test.BuildTestNode("n3", 2000, 3000, 10, nil), + }, + strategy: api.DeschedulerStrategy{}, + }, + { + description: "Evict pods uniformly with one node left out", + pods: []*v1.Pod{ + // (5,3,1) -> (4,4,1) -> 1 eviction + test.BuildTestPod("p1", 100, 0, "n1", test.SetRSOwnerRef), + test.BuildTestPod("p2", 100, 0, "n1", test.SetRSOwnerRef), + test.BuildTestPod("p3", 100, 0, "n1", test.SetRSOwnerRef), + test.BuildTestPod("p4", 100, 0, "n1", test.SetRSOwnerRef), + test.BuildTestPod("p5", 100, 0, "n1", test.SetRSOwnerRef), + test.BuildTestPod("p6", 100, 0, "n2", test.SetRSOwnerRef), + test.BuildTestPod("p7", 100, 0, "n2", test.SetRSOwnerRef), + test.BuildTestPod("p8", 100, 0, "n2", test.SetRSOwnerRef), + test.BuildTestPod("p9", 100, 0, "n3", test.SetRSOwnerRef), + }, + expectedEvictedPodCount: 1, + nodes: []*v1.Node{ + test.BuildTestNode("n1", 2000, 3000, 10, nil), + test.BuildTestNode("n2", 2000, 3000, 10, nil), + }, + strategy: api.DeschedulerStrategy{}, + }, + { + description: "Evict pods uniformly with two replica sets", + pods: []*v1.Pod{ + // (5,3,1) -> (3,3,3) -> 2 evictions + test.BuildTestPod("p11", 100, 0, "n1", setTwoRSOwnerRef), + test.BuildTestPod("p12", 100, 0, "n1", setTwoRSOwnerRef), + test.BuildTestPod("p13", 100, 0, "n1", setTwoRSOwnerRef), + test.BuildTestPod("p14", 100, 0, "n1", setTwoRSOwnerRef), + test.BuildTestPod("p15", 100, 0, "n1", setTwoRSOwnerRef), + test.BuildTestPod("p16", 100, 0, "n2", setTwoRSOwnerRef), + test.BuildTestPod("p17", 100, 0, "n2", setTwoRSOwnerRef), + test.BuildTestPod("p18", 100, 0, "n2", setTwoRSOwnerRef), + test.BuildTestPod("p19", 100, 0, "n3", setTwoRSOwnerRef), + }, + expectedEvictedPodCount: 4, + nodes: []*v1.Node{ + test.BuildTestNode("n1", 2000, 3000, 10, nil), + test.BuildTestNode("n2", 2000, 3000, 10, nil), + test.BuildTestNode("n3", 2000, 3000, 10, nil), + }, + strategy: api.DeschedulerStrategy{}, + }, + { + description: "Evict pods uniformly with two owner references", + pods: []*v1.Pod{ + // (5,3,1) -> (3,3,3) -> 2 evictions + test.BuildTestPod("p11", 100, 0, "n1", test.SetRSOwnerRef), + test.BuildTestPod("p12", 100, 0, "n1", test.SetRSOwnerRef), + test.BuildTestPod("p13", 100, 0, "n1", test.SetRSOwnerRef), + test.BuildTestPod("p14", 100, 0, "n1", test.SetRSOwnerRef), + test.BuildTestPod("p15", 100, 0, "n1", test.SetRSOwnerRef), + test.BuildTestPod("p16", 100, 0, "n2", test.SetRSOwnerRef), + test.BuildTestPod("p17", 100, 0, "n2", test.SetRSOwnerRef), + test.BuildTestPod("p18", 100, 0, "n2", test.SetRSOwnerRef), + test.BuildTestPod("p19", 100, 0, "n3", test.SetRSOwnerRef), + // (1,3,5) -> (3,3,3) -> 2 evictions + test.BuildTestPod("p21", 100, 0, "n1", setRSOwnerRef2), + test.BuildTestPod("p22", 100, 0, "n2", setRSOwnerRef2), + test.BuildTestPod("p23", 100, 0, "n2", setRSOwnerRef2), + test.BuildTestPod("p24", 100, 0, "n2", setRSOwnerRef2), + test.BuildTestPod("p25", 100, 0, "n3", setRSOwnerRef2), + test.BuildTestPod("p26", 100, 0, "n3", setRSOwnerRef2), + test.BuildTestPod("p27", 100, 0, "n3", setRSOwnerRef2), + test.BuildTestPod("p28", 100, 0, "n3", setRSOwnerRef2), + test.BuildTestPod("p29", 100, 0, "n3", setRSOwnerRef2), + }, + expectedEvictedPodCount: 4, + nodes: []*v1.Node{ + test.BuildTestNode("n1", 2000, 3000, 10, nil), + test.BuildTestNode("n2", 2000, 3000, 10, nil), + test.BuildTestNode("n3", 2000, 3000, 10, nil), + }, + strategy: api.DeschedulerStrategy{}, + }, + { + description: "Evict pods with number of pods less than nodes", + pods: []*v1.Pod{ + // (2,0,0) -> (1,1,0) -> 1 eviction + test.BuildTestPod("p1", 100, 0, "n1", test.SetRSOwnerRef), + test.BuildTestPod("p2", 100, 0, "n1", test.SetRSOwnerRef), + }, + expectedEvictedPodCount: 1, + nodes: []*v1.Node{ + test.BuildTestNode("n1", 2000, 3000, 10, nil), + test.BuildTestNode("n2", 2000, 3000, 10, nil), + test.BuildTestNode("n3", 2000, 3000, 10, nil), + }, + strategy: api.DeschedulerStrategy{}, + }, + { + description: "Evict pods with number of pods less than nodes, but ignore different pods with the same ownerref", + pods: []*v1.Pod{ + // (1, 0, 0) for "bar","baz" images -> no eviction, even with a matching ownerKey + // (2, 0, 0) for "foo" image -> (1,1,0) - 1 eviction + // In this case the only "real" duplicates are p1 and p4, so one of those should be evicted + buildTestPodWithImage("p1", "n1", "foo"), + buildTestPodWithImage("p2", "n1", "bar"), + buildTestPodWithImage("p3", "n1", "baz"), + buildTestPodWithImage("p4", "n1", "foo"), + }, + expectedEvictedPodCount: 1, + nodes: []*v1.Node{ + test.BuildTestNode("n1", 2000, 3000, 10, nil), + test.BuildTestNode("n2", 2000, 3000, 10, nil), + test.BuildTestNode("n3", 2000, 3000, 10, nil), + }, + strategy: api.DeschedulerStrategy{}, + }, + { + description: "Evict pods with a single pod with three nodes", + pods: []*v1.Pod{ + // (2,0,0) -> (1,1,0) -> 1 eviction + test.BuildTestPod("p1", 100, 0, "n1", test.SetRSOwnerRef), + }, + expectedEvictedPodCount: 0, + nodes: []*v1.Node{ + test.BuildTestNode("n1", 2000, 3000, 10, nil), + test.BuildTestNode("n2", 2000, 3000, 10, nil), + test.BuildTestNode("n3", 2000, 3000, 10, nil), + }, + strategy: api.DeschedulerStrategy{}, + }, + { + description: "Evict pods uniformly respecting taints", + pods: []*v1.Pod{ + // (5,3,1,0,0,0) -> (3,3,3,0,0,0) -> 2 evictions + test.BuildTestPod("p1", 100, 0, "worker1", setTolerationsK1), + test.BuildTestPod("p2", 100, 0, "worker1", setTolerationsK2), + test.BuildTestPod("p3", 100, 0, "worker1", setTolerationsK1), + test.BuildTestPod("p4", 100, 0, "worker1", setTolerationsK2), + test.BuildTestPod("p5", 100, 0, "worker1", setTolerationsK1), + test.BuildTestPod("p6", 100, 0, "worker2", setTolerationsK2), + test.BuildTestPod("p7", 100, 0, "worker2", setTolerationsK1), + test.BuildTestPod("p8", 100, 0, "worker2", setTolerationsK2), + test.BuildTestPod("p9", 100, 0, "worker3", setTolerationsK1), + }, + expectedEvictedPodCount: 2, + nodes: []*v1.Node{ + test.BuildTestNode("worker1", 2000, 3000, 10, nil), + test.BuildTestNode("worker2", 2000, 3000, 10, nil), + test.BuildTestNode("worker3", 2000, 3000, 10, nil), + test.BuildTestNode("master1", 2000, 3000, 10, setMasterNoScheduleTaint), + test.BuildTestNode("master2", 2000, 3000, 10, setMasterNoScheduleTaint), + test.BuildTestNode("master3", 2000, 3000, 10, setMasterNoScheduleTaint), + }, + strategy: api.DeschedulerStrategy{}, + }, + { + description: "Evict pods uniformly respecting RequiredDuringSchedulingIgnoredDuringExecution node affinity", + pods: []*v1.Pod{ + // (5,3,1,0,0,0) -> (3,3,3,0,0,0) -> 2 evictions + test.BuildTestPod("p1", 100, 0, "worker1", setNotMasterNodeSelectorK1), + test.BuildTestPod("p2", 100, 0, "worker1", setNotMasterNodeSelectorK2), + test.BuildTestPod("p3", 100, 0, "worker1", setNotMasterNodeSelectorK1), + test.BuildTestPod("p4", 100, 0, "worker1", setNotMasterNodeSelectorK2), + test.BuildTestPod("p5", 100, 0, "worker1", setNotMasterNodeSelectorK1), + test.BuildTestPod("p6", 100, 0, "worker2", setNotMasterNodeSelectorK2), + test.BuildTestPod("p7", 100, 0, "worker2", setNotMasterNodeSelectorK1), + test.BuildTestPod("p8", 100, 0, "worker2", setNotMasterNodeSelectorK2), + test.BuildTestPod("p9", 100, 0, "worker3", setNotMasterNodeSelectorK1), + }, + expectedEvictedPodCount: 2, + nodes: []*v1.Node{ + test.BuildTestNode("worker1", 2000, 3000, 10, nil), + test.BuildTestNode("worker2", 2000, 3000, 10, nil), + test.BuildTestNode("worker3", 2000, 3000, 10, nil), + test.BuildTestNode("master1", 2000, 3000, 10, setMasterNoScheduleLabel), + test.BuildTestNode("master2", 2000, 3000, 10, setMasterNoScheduleLabel), + test.BuildTestNode("master3", 2000, 3000, 10, setMasterNoScheduleLabel), + }, + strategy: api.DeschedulerStrategy{}, + }, + { + description: "Evict pods uniformly respecting node selector", + pods: []*v1.Pod{ + // (5,3,1,0,0,0) -> (3,3,3,0,0,0) -> 2 evictions + test.BuildTestPod("p1", 100, 0, "worker1", setWorkerLabelSelectorK1), + test.BuildTestPod("p2", 100, 0, "worker1", setWorkerLabelSelectorK2), + test.BuildTestPod("p3", 100, 0, "worker1", setWorkerLabelSelectorK1), + test.BuildTestPod("p4", 100, 0, "worker1", setWorkerLabelSelectorK2), + test.BuildTestPod("p5", 100, 0, "worker1", setWorkerLabelSelectorK1), + test.BuildTestPod("p6", 100, 0, "worker2", setWorkerLabelSelectorK2), + test.BuildTestPod("p7", 100, 0, "worker2", setWorkerLabelSelectorK1), + test.BuildTestPod("p8", 100, 0, "worker2", setWorkerLabelSelectorK2), + test.BuildTestPod("p9", 100, 0, "worker3", setWorkerLabelSelectorK1), + }, + expectedEvictedPodCount: 2, + nodes: []*v1.Node{ + test.BuildTestNode("worker1", 2000, 3000, 10, setWorkerLabel), + test.BuildTestNode("worker2", 2000, 3000, 10, setWorkerLabel), + test.BuildTestNode("worker3", 2000, 3000, 10, setWorkerLabel), + test.BuildTestNode("master1", 2000, 3000, 10, nil), + test.BuildTestNode("master2", 2000, 3000, 10, nil), + test.BuildTestNode("master3", 2000, 3000, 10, nil), + }, + strategy: api.DeschedulerStrategy{}, + }, + { + description: "Evict pods uniformly respecting node selector with zero target nodes", + pods: []*v1.Pod{ + // (5,3,1,0,0,0) -> (3,3,3,0,0,0) -> 2 evictions + test.BuildTestPod("p1", 100, 0, "worker1", setWorkerLabelSelectorK1), + test.BuildTestPod("p2", 100, 0, "worker1", setWorkerLabelSelectorK2), + test.BuildTestPod("p3", 100, 0, "worker1", setWorkerLabelSelectorK1), + test.BuildTestPod("p4", 100, 0, "worker1", setWorkerLabelSelectorK2), + test.BuildTestPod("p5", 100, 0, "worker1", setWorkerLabelSelectorK1), + test.BuildTestPod("p6", 100, 0, "worker2", setWorkerLabelSelectorK2), + test.BuildTestPod("p7", 100, 0, "worker2", setWorkerLabelSelectorK1), + test.BuildTestPod("p8", 100, 0, "worker2", setWorkerLabelSelectorK2), + test.BuildTestPod("p9", 100, 0, "worker3", setWorkerLabelSelectorK1), + }, + expectedEvictedPodCount: 0, + nodes: []*v1.Node{ + test.BuildTestNode("worker1", 2000, 3000, 10, nil), + test.BuildTestNode("worker2", 2000, 3000, 10, nil), + test.BuildTestNode("worker3", 2000, 3000, 10, nil), + test.BuildTestNode("master1", 2000, 3000, 10, nil), + test.BuildTestNode("master2", 2000, 3000, 10, nil), + test.BuildTestNode("master3", 2000, 3000, 10, nil), + }, + strategy: api.DeschedulerStrategy{}, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.description, func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var objs []runtime.Object + for _, node := range testCase.nodes { + objs = append(objs, node) + } + for _, pod := range testCase.pods { + objs = append(objs, pod) + } + fakeClient := fake.NewSimpleClientset(objs...) + + sharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0) + podInformer := sharedInformerFactory.Core().V1().Pods() + + getPodsAssignedToNode, err := podutil.BuildGetPodsAssignedToNodeFunc(podInformer) + if err != nil { + t.Errorf("Build get pods assigned to node function error: %v", err) + } + + sharedInformerFactory.Start(ctx.Done()) + sharedInformerFactory.WaitForCacheSync(ctx.Done()) + + podEvictor := evictions.NewPodEvictor( + fakeClient, + policyv1.SchemeGroupVersion.String(), + false, + nil, + nil, + testCase.nodes, + false, + false, + false, + false, + false, + ) + + plugin, err := New(&framework.RemoveDuplicatePodsArg{}, + frameworkHandle{ + clientset: fakeClient, + podEvictor: podEvictor, + getPodsAssignedToNodeFunc: getPodsAssignedToNode, + }, + ) + if err != nil { + t.Fatalf("Unable to initialize the plugin: %v", err) + } + + plugin.(interface{}).(framework.DeschedulePlugin).Deschedule(ctx, testCase.nodes) + podsEvicted := podEvictor.TotalEvicted() + if podsEvicted != testCase.expectedEvictedPodCount { + t.Errorf("Test error for description: %s. Expected evicted pods count %v, got %v", testCase.description, testCase.expectedEvictedPodCount, podsEvicted) + } + }) + } +} diff --git a/pkg/framework/types.go b/pkg/framework/types.go new file mode 100644 index 0000000000..c3aedd6915 --- /dev/null +++ b/pkg/framework/types.go @@ -0,0 +1,50 @@ +package framework + +import ( + "context" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + clientset "k8s.io/client-go/kubernetes" + + "sigs.k8s.io/descheduler/pkg/api" + "sigs.k8s.io/descheduler/pkg/descheduler/evictions" + podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod" +) + +type Handle interface { + // ClientSet returns a kubernetes clientSet. + ClientSet() clientset.Interface + + PodEvictor() *evictions.PodEvictor + GetPodsAssignedToNodeFunc() podutil.GetPodsAssignedToNodeFunc +} + +type Status struct { + err error +} + +// Plugin is the parent type for all the descheduling framework plugins. +type Plugin interface { + Name() string +} + +type DeschedulePlugin interface { + Deschedule(ctx context.Context, nodes []*v1.Node) *Status +} + +// RemoveDuplicatePodsArg holds arguments used to configure the RemoveDuplicatePods plugin. +type RemoveDuplicatePodsArg struct { + metav1.TypeMeta + + Namespaces *api.Namespaces + PriorityThreshold *api.PriorityThreshold + NodeFit bool + ExcludeOwnerKinds []string +} + +// TODO(jchaloup): have this go generated +func (in *RemoveDuplicatePodsArg) DeepCopyObject() runtime.Object { + return nil +} diff --git a/pkg/utils/priority.go b/pkg/utils/priority.go index 709273dfcf..058de5ea87 100644 --- a/pkg/utils/priority.go +++ b/pkg/utils/priority.go @@ -72,3 +72,23 @@ func GetPriorityFromStrategyParams(ctx context.Context, client clientset.Interfa } return } + +// GetPriorityValueFromPriorityThreshold gets priority from the given PriorityThreshold. +// It will return SystemCriticalPriority by default. +func GetPriorityValueFromPriorityThreshold(ctx context.Context, client clientset.Interface, priorityThreshold *api.PriorityThreshold) (priority int32, err error) { + if priorityThreshold == nil { + return SystemCriticalPriority, nil + } + if priorityThreshold.Value != nil { + priority = *priorityThreshold.Value + } else { + priority, err = GetPriorityFromPriorityClass(ctx, client, priorityThreshold.Name) + if err != nil { + return 0, fmt.Errorf("unable to get priority value from the priority class: %v", err) + } + } + if priority > SystemCriticalPriority { + return 0, fmt.Errorf("priority threshold can't be greater than %d", SystemCriticalPriority) + } + return +}