diff --git a/Dockerfile b/Dockerfile index b76562e277..da5625bf43 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ # 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. -FROM golang:1.14.4 +FROM golang:1.15.0 WORKDIR /go/src/sigs.k8s.io/descheduler COPY . . diff --git a/README.md b/README.md index 4c0e3a5851..1bc44479c2 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,6 @@ # Descheduler for Kubernetes -## Introduction - Scheduling in Kubernetes is the process of binding pending pods to nodes, and is performed by a component of Kubernetes called kube-scheduler. The scheduler's decisions, whether or where a pod can or can not be scheduled, are guided by its configurable policy which comprises of set of @@ -24,6 +22,33 @@ Descheduler, based on its policy, finds pods that can be moved and evicts them. note, in current implementation, descheduler does not schedule replacement of evicted pods but relies on the default scheduler for that. +Table of Contents +================= + + * [Quick Start](#quick-start) + * [Run As A Job](#run-as-a-job) + * [Run As A CronJob](#run-as-a-cronjob) + * [Install Using Helm](#install-using-helm) + * [User Guide](#user-guide) + * [Policy and Strategies](#policy-and-strategies) + * [RemoveDuplicates](#removeduplicates) + * [LowNodeUtilization](#lownodeutilization) + * [RemovePodsViolatingInterPodAntiAffinity](#removepodsviolatinginterpodantiaffinity) + * [RemovePodsViolatingNodeAffinity](#removepodsviolatingnodeaffinity) + * [RemovePodsViolatingNodeTaints](#removepodsviolatingnodetaints) + * [RemovePodsHavingTooManyRestarts](#removepodshavingtoomanyrestarts) + * [PodLifeTime](#podlifetime) + * [Filter Pods](#filter-pods) + * [Namespace filtering](#namespace-filtering) + * [Priority filtering](#priority-filtering) + * [Pod Evictions](#pod-evictions) + * [Pod Disruption Budget (PDB)](#pod-disruption-budget-pdb) + * [Compatibility Matrix](#compatibility-matrix) + * [Getting Involved and Contributing](#getting-involved-and-contributing) + * [Communicating With Contributors](#communicating-with-contributors) + * [Roadmap](#roadmap) + * [Code of conduct](#code-of-conduct) + ## Quick Start The descheduler can be run as a Job or CronJob inside of a k8s cluster. It has the @@ -64,6 +89,21 @@ Seven strategies `RemoveDuplicates`, `LowNodeUtilization`, `RemovePodsViolatingI are currently implemented. As part of the policy, the parameters associated with the strategies can be configured too. By default, all strategies are enabled. +The policy also includes common configuration for all the strategies: +- `nodeSelector` - limiting the nodes which are processed +- `evictLocalStoragePods` - allowing to evict pods with local storage +- `maxNoOfPodsToEvictPerNode` - maximum number of pods evicted from each node (summed through all strategies) + +``` +apiVersion: "descheduler/v1alpha1" +kind: "DeschedulerPolicy" +nodeSelector: prod=dev +evictLocalStoragePods: true +maxNoOfPodsToEvictPerNode: 40 +strategies: + ... +``` + ### RemoveDuplicates This strategy makes sure that there is only one pod associated with a Replica Set (RS), @@ -346,6 +386,7 @@ packages that it is compiled with. Descheduler | Supported Kubernetes Version -------------|----------------------------- +v0.19 | v1.19 v0.18 | v1.18 v0.10 | v1.17 v0.4-v0.9 | v1.9+ diff --git a/charts/descheduler/Chart.yaml b/charts/descheduler/Chart.yaml index 2fd0156a3d..2d62bf9510 100644 --- a/charts/descheduler/Chart.yaml +++ b/charts/descheduler/Chart.yaml @@ -12,5 +12,5 @@ icon: https://kubernetes.io/images/favicon.png sources: - https://github.com/kubernetes-sigs/descheduler maintainers: -- name: stevehipwell - email: steve.hipwell@github.com +- name: Kubernetes SIG Scheduling + email: kubernetes-sig-scheduling@googlegroups.com diff --git a/cmd/descheduler/app/options/options.go b/cmd/descheduler/app/options/options.go index d398592e00..34f8b84fc5 100644 --- a/cmd/descheduler/app/options/options.go +++ b/cmd/descheduler/app/options/options.go @@ -53,9 +53,9 @@ func (rs *DeschedulerServer) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&rs.PolicyConfigFile, "policy-config-file", rs.PolicyConfigFile, "File with descheduler policy configuration.") fs.BoolVar(&rs.DryRun, "dry-run", rs.DryRun, "execute descheduler in dry run mode.") // node-selector query causes descheduler to run only on nodes that matches the node labels in the query - fs.StringVar(&rs.NodeSelector, "node-selector", rs.NodeSelector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") + fs.StringVar(&rs.NodeSelector, "node-selector", rs.NodeSelector, "DEPRECATED: selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") // max-no-pods-to-evict limits the maximum number of pods to be evicted per node by descheduler. - fs.IntVar(&rs.MaxNoOfPodsToEvictPerNode, "max-pods-to-evict-per-node", rs.MaxNoOfPodsToEvictPerNode, "Limits the maximum number of pods to be evicted per node by descheduler") + fs.IntVar(&rs.MaxNoOfPodsToEvictPerNode, "max-pods-to-evict-per-node", rs.MaxNoOfPodsToEvictPerNode, "DEPRECATED: limits the maximum number of pods to be evicted per node by descheduler") // evict-local-storage-pods allows eviction of pods that are using local storage. This is false by default. - fs.BoolVar(&rs.EvictLocalStoragePods, "evict-local-storage-pods", rs.EvictLocalStoragePods, "Enables evicting pods using local storage by descheduler") + fs.BoolVar(&rs.EvictLocalStoragePods, "evict-local-storage-pods", rs.EvictLocalStoragePods, "DEPRECATED: enables evicting pods using local storage by descheduler") } diff --git a/docs/contributor-guide.md b/docs/contributor-guide.md index 3b9b55c95a..c9df10548c 100644 --- a/docs/contributor-guide.md +++ b/docs/contributor-guide.md @@ -3,7 +3,7 @@ ## Required Tools - [Git](https://git-scm.com/downloads) -- [Go 1.14+](https://golang.org/dl/) +- [Go 1.15+](https://golang.org/dl/) - [Docker](https://docs.docker.com/install/) - [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl) - [kind](https://kind.sigs.k8s.io/) @@ -34,9 +34,10 @@ GOOS=linux make dev-image kind create cluster --config hack/kind_config.yaml kind load docker-image kind get kubeconfig > /tmp/admin.conf +export KUBECONFIG=/tmp/admin.conf make test-unit make test-e2e ``` ### Miscellaneous -See the [hack directory](https://github.com/kubernetes-sigs/descheduler/tree/master/hack) for additional tools and scripts used for developing the descheduler. \ No newline at end of file +See the [hack directory](https://github.com/kubernetes-sigs/descheduler/tree/master/hack) for additional tools and scripts used for developing the descheduler. diff --git a/docs/user-guide.md b/docs/user-guide.md index 65a942a0ee..bce12b5a3b 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -21,11 +21,11 @@ Available Commands: version Version of descheduler Flags: - --add-dir-header If true, adds the file directory to the header + --add-dir-header If true, adds the file directory to the header of the log messages --alsologtostderr log to standard error as well as files --descheduling-interval duration Time interval between two consecutive descheduler executions. Setting this value instructs the descheduler to run in a continuous loop at the interval specified. --dry-run execute descheduler in dry run mode. - --evict-local-storage-pods Enables evicting pods using local storage by descheduler + --evict-local-storage-pods DEPRECATED: enables evicting pods using local storage by descheduler -h, --help help for descheduler --kubeconfig string File with kube configuration. --log-backtrace-at traceLocation when logging hits line file:N, emit a stack trace (default :0) @@ -34,8 +34,8 @@ Flags: --log-file-max-size uint Defines the maximum size a log file can grow to. Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) --log-flush-frequency duration Maximum number of seconds between log flushes (default 5s) --logtostderr log to standard error instead of files (default true) - --max-pods-to-evict-per-node int Limits the maximum number of pods to be evicted per node by descheduler - --node-selector string Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2) + --max-pods-to-evict-per-node int DEPRECATED: limits the maximum number of pods to be evicted per node by descheduler + --node-selector string DEPRECATED: selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2) --policy-config-file string File with descheduler policy configuration. --skip-headers If true, avoid header prefixes in the log messages --skip-log-headers If true, avoid headers when opening log files @@ -88,10 +88,10 @@ strategies: ``` ### Autoheal Node Problems -Descheduler's `RemovePodsViolatingNodeTaints` strategy can be combined with +Descheduler's `RemovePodsViolatingNodeTaints` strategy can be combined with [Node Problem Detector](https://github.com/kubernetes/node-problem-detector/) and [Cluster Autoscaler](https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler) to automatically remove Nodes which have problems. Node Problem Detector can detect specific Node problems and taint any Nodes which have those -problems. The Descheduler will then deschedule workloads from those Nodes. Finally, if the descheduled Node's resource +problems. The Descheduler will then deschedule workloads from those Nodes. Finally, if the descheduled Node's resource allocation falls below the Cluster Autoscaler's scale down threshold, the Node will become a scale down candidate and can be removed by Cluster Autoscaler. These three components form an autohealing cycle for Node problems. diff --git a/go.mod b/go.mod index 12fd1999c7..17da5bd155 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,15 @@ module sigs.k8s.io/descheduler -go 1.14 +go 1.15 require ( github.com/spf13/cobra v0.0.5 github.com/spf13/pflag v1.0.5 - k8s.io/api v0.19.0-rc.4 - k8s.io/apimachinery v0.19.0-rc.4 - k8s.io/apiserver v0.19.0-rc.4 - k8s.io/client-go v0.19.0-rc.4 - k8s.io/code-generator v0.19.0-rc.4 - k8s.io/component-base v0.19.0-rc.4 + k8s.io/api v0.19.0 + k8s.io/apimachinery v0.19.0 + k8s.io/apiserver v0.19.0 + k8s.io/client-go v0.19.0 + k8s.io/code-generator v0.19.0 + k8s.io/component-base v0.19.0 k8s.io/klog/v2 v2.2.0 ) diff --git a/go.sum b/go.sum index dc3a725f11..9d1b749251 100644 --- a/go.sum +++ b/go.sum @@ -87,8 +87,8 @@ github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v0.0.0-20190815234213-e83c0a1c26c8 h1:DM7gHzQfHwIj+St8zaPOI6iQEPAxOwIkskvw6s9rDaM= -github.com/evanphx/json-patch v0.0.0-20190815234213-e83c0a1c26c8/go.mod h1:pmLOTb3x90VhIKxsA9yeQG5yfOkkKnkk1h+Ql8NDYDw= +github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -298,7 +298,7 @@ github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1: github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etcd.io/etcd v0.5.0-alpha.5.0.20200716221620-18dfb9cca345/go.mod h1:skWido08r9w6Lq/w70DO5XYIKMu4QFu1+4VsqLQuJy8= +go.etcd.io/etcd v0.5.0-alpha.5.0.20200819165624-17cef6e3e9d5/go.mod h1:skWido08r9w6Lq/w70DO5XYIKMu4QFu1+4VsqLQuJy8= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -511,18 +511,18 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -k8s.io/api v0.19.0-rc.4 h1:l4LnbkiVjh8estJqbl/kZIfHDi1i8tpCegRzY0dw3XU= -k8s.io/api v0.19.0-rc.4/go.mod h1:1xlMhKahfl3bVAn1T1PhMriUVYwRNJ7D8YMDnUz/yGw= -k8s.io/apimachinery v0.19.0-rc.4 h1:LVJnI5LJCzGHtGiL1ztkYEceAzatGPnnmUwxiLUx77E= -k8s.io/apimachinery v0.19.0-rc.4/go.mod h1:oE8UQU9DqIIc9PyIEYxTj/oJECzZLymCEU9dL0H4F+o= -k8s.io/apiserver v0.19.0-rc.4 h1:KKVgNztYVbz5zOk1yxAxm34XCfkmUxO/LgURStRgBmc= -k8s.io/apiserver v0.19.0-rc.4/go.mod h1:0cfEqMQGjl0GpzK+o9yHgzdcCU8kW/wy+JuSGo3Zl68= -k8s.io/client-go v0.19.0-rc.4 h1:89y1DMsKGLad2OmxFP22IXYigzmq6oXZZIbdk3/0YLs= -k8s.io/client-go v0.19.0-rc.4/go.mod h1:6fKW3B9PgoMpZwAdRpMYyhRFCb4kHIfriPYpiiqhBWw= -k8s.io/code-generator v0.19.0-rc.4 h1:yGFQlO/A4EMc1ROV+ObmHVPXYrNhNaYQ66xXZMWpm/A= -k8s.io/code-generator v0.19.0-rc.4/go.mod h1:l+Q5vASED8GWxNspCbHMV4q9mcQ6DhLzqwoZhvWz7wU= -k8s.io/component-base v0.19.0-rc.4 h1:VVWQNc0f8ykyhdrRHPt87LQRyvgf/Kv+yBN1waAmEQk= -k8s.io/component-base v0.19.0-rc.4/go.mod h1:N91w2PScsJ53fqQDlu7zqBhGaAKz6YUjrybPtS053Vg= +k8s.io/api v0.19.0 h1:XyrFIJqTYZJ2DU7FBE/bSPz7b1HvbVBuBf07oeo6eTc= +k8s.io/api v0.19.0/go.mod h1:I1K45XlvTrDjmj5LoM5LuP/KYrhWbjUKT/SoPG0qTjw= +k8s.io/apimachinery v0.19.0 h1:gjKnAda/HZp5k4xQYjL0K/Yb66IvNqjthCb03QlKpaQ= +k8s.io/apimachinery v0.19.0/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= +k8s.io/apiserver v0.19.0 h1:jLhrL06wGAADbLUUQm8glSLnAGP6c7y5R3p19grkBoY= +k8s.io/apiserver v0.19.0/go.mod h1:XvzqavYj73931x7FLtyagh8WibHpePJ1QwWrSJs2CLk= +k8s.io/client-go v0.19.0 h1:1+0E0zfWFIWeyRhQYWzimJOyAk2UT7TiARaLNwJCf7k= +k8s.io/client-go v0.19.0/go.mod h1:H9E/VT95blcFQnlyShFgnFT9ZnJOAceiUHM3MlRC+mU= +k8s.io/code-generator v0.19.0 h1:r0BxYnttP/r8uyKd4+Njg0B57kKi8wLvwEzaaVy3iZ8= +k8s.io/code-generator v0.19.0/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk= +k8s.io/component-base v0.19.0 h1:OueXf1q3RW7NlLlUCj2Dimwt7E1ys6ZqRnq53l2YuoE= +k8s.io/component-base v0.19.0/go.mod h1:dKsY8BxkA+9dZIAh2aWJLL/UdASFDNtGYTCItL4LM7Y= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14 h1:t4L10Qfx/p7ASH3gXCdIUtPbbIuegCoUJf3TMSFekjw= k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= @@ -530,15 +530,14 @@ k8s.io/klog/v2 v2.0.0 h1:Foj74zO6RbjjP4hBEKjnYtjjAhGg4jNynUdYF6fJrok= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/kube-openapi v0.0.0-20200427153329-656914f816f9 h1:5NC2ITmvg8RoxoH0wgmL4zn4VZqXGsKbxrikjaQx6s4= -k8s.io/kube-openapi v0.0.0-20200427153329-656914f816f9/go.mod h1:bfCVj+qXcEaE5SCvzBaqpOySr6tuCcpPKqF6HD8nyCw= +k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6 h1:+WnxoVtG8TMiudHBSEtrVL1egv36TkkJm+bA8AxicmQ= +k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= k8s.io/utils v0.0.0-20200729134348-d5654de09c73 h1:uJmqzgNWG7XyClnU/mLPBWwfKKF1K8Hf8whTseBgJcg= k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.9/go.mod h1:dzAXnQbTRyDlZPJX2SUPEqvnB+j7AJjtlox7PEwigU0= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= -sigs.k8s.io/structured-merge-diff/v3 v3.0.1-0.20200706213357-43c19bbb7fba h1:AAbnc5KQuTWKuh2QSnyghKIOTFzB0Jayv7/OFDn3Cy4= -sigs.k8s.io/structured-merge-diff/v3 v3.0.1-0.20200706213357-43c19bbb7fba/go.mod h1:V06abazjHneE37ZdSY/UUwPVgcJMKI/jU5XGUjgIKoc= +sigs.k8s.io/structured-merge-diff/v4 v4.0.1 h1:YXTMot5Qz/X1iBRJhAt+vI+HVttY0WkSqqhKxQ0xVbA= +sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/hack/update-gofmt.sh b/hack/update-gofmt.sh index 3b843c28b9..927d76d72e 100755 --- a/hack/update-gofmt.sh +++ b/hack/update-gofmt.sh @@ -23,7 +23,7 @@ DESCHEDULER_ROOT=$(dirname "${BASH_SOURCE}")/.. GO_VERSION=($(go version)) -if [[ -z $(echo "${GO_VERSION[2]}" | grep -E 'go1.2|go1.3|go1.4|go1.5|go1.6|go1.7|go1.8|go1.9|go1.10|go1.11|go1.12|go1.13|go1.14') ]]; then +if [[ -z $(echo "${GO_VERSION[2]}" | grep -E 'go1.13|go1.14|go1.15') ]]; then echo "Unknown go version '${GO_VERSION[2]}', skipping gofmt." exit 1 fi diff --git a/kubernetes/cronjob.yaml b/kubernetes/cronjob.yaml index 31d7ec043a..9544c4db20 100644 --- a/kubernetes/cronjob.yaml +++ b/kubernetes/cronjob.yaml @@ -16,7 +16,7 @@ spec: priorityClassName: system-cluster-critical containers: - name: descheduler - image: k8s.gcr.io/descheduler/descheduler:v0.18.0 + image: k8s.gcr.io/descheduler/descheduler:v0.19.0 volumeMounts: - mountPath: /policy-dir name: policy-volume diff --git a/kubernetes/job.yaml b/kubernetes/job.yaml index f316548ef5..2faa9baff5 100644 --- a/kubernetes/job.yaml +++ b/kubernetes/job.yaml @@ -14,7 +14,7 @@ spec: priorityClassName: system-cluster-critical containers: - name: descheduler - image: k8s.gcr.io/descheduler/descheduler:v0.18.0 + image: k8s.gcr.io/descheduler/descheduler:v0.19.0 volumeMounts: - mountPath: /policy-dir name: policy-volume diff --git a/pkg/api/types.go b/pkg/api/types.go index dd74c94ffa..2044e384a0 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -28,6 +28,15 @@ type DeschedulerPolicy struct { // Strategies Strategies StrategyList + + // NodeSelector for a set of nodes to operate over + NodeSelector *string + + // EvictLocalStoragePods allows pods using local storage to be evicted. + EvictLocalStoragePods *bool + + // MaxNoOfPodsToEvictPerNode restricts maximum of pods to be evicted per node. + MaxNoOfPodsToEvictPerNode *int } type StrategyName string @@ -60,7 +69,7 @@ type StrategyParameters struct { PodsHavingTooManyRestarts *PodsHavingTooManyRestarts MaxPodLifeTimeSeconds *uint RemoveDuplicates *RemoveDuplicates - Namespaces Namespaces + Namespaces *Namespaces ThresholdPriority *int32 ThresholdPriorityClassName string } diff --git a/pkg/api/v1alpha1/types.go b/pkg/api/v1alpha1/types.go index d672d9bea4..eca22681cd 100644 --- a/pkg/api/v1alpha1/types.go +++ b/pkg/api/v1alpha1/types.go @@ -28,6 +28,15 @@ type DeschedulerPolicy struct { // Strategies Strategies StrategyList `json:"strategies,omitempty"` + + // NodeSelector for a set of nodes to operate over + NodeSelector *string `json:"nodeSelector,omitempty"` + + // EvictLocalStoragePods allows pods using local storage to be evicted. + EvictLocalStoragePods *bool `json:"evictLocalStoragePods,omitempty"` + + // MaxNoOfPodsToEvictPerNode restricts maximum of pods to be evicted per node. + MaxNoOfPodsToEvictPerNode *int `json:"maxNoOfPodsToEvictPerNode,omitempty"` } type StrategyName string @@ -58,7 +67,7 @@ type StrategyParameters struct { PodsHavingTooManyRestarts *PodsHavingTooManyRestarts `json:"podsHavingTooManyRestarts,omitempty"` MaxPodLifeTimeSeconds *uint `json:"maxPodLifeTimeSeconds,omitempty"` RemoveDuplicates *RemoveDuplicates `json:"removeDuplicates,omitempty"` - Namespaces Namespaces `json:"namespaces"` + Namespaces *Namespaces `json:"namespaces"` ThresholdPriority *int32 `json:"thresholdPriority"` ThresholdPriorityClassName string `json:"thresholdPriorityClassName"` } diff --git a/pkg/api/v1alpha1/zz_generated.conversion.go b/pkg/api/v1alpha1/zz_generated.conversion.go index e0f0cfc770..7c1d245307 100644 --- a/pkg/api/v1alpha1/zz_generated.conversion.go +++ b/pkg/api/v1alpha1/zz_generated.conversion.go @@ -110,6 +110,9 @@ func RegisterConversions(s *runtime.Scheme) error { func autoConvert_v1alpha1_DeschedulerPolicy_To_api_DeschedulerPolicy(in *DeschedulerPolicy, out *api.DeschedulerPolicy, s conversion.Scope) error { out.Strategies = *(*api.StrategyList)(unsafe.Pointer(&in.Strategies)) + out.NodeSelector = (*string)(unsafe.Pointer(in.NodeSelector)) + out.EvictLocalStoragePods = (*bool)(unsafe.Pointer(in.EvictLocalStoragePods)) + out.MaxNoOfPodsToEvictPerNode = (*int)(unsafe.Pointer(in.MaxNoOfPodsToEvictPerNode)) return nil } @@ -120,6 +123,9 @@ func Convert_v1alpha1_DeschedulerPolicy_To_api_DeschedulerPolicy(in *Descheduler func autoConvert_api_DeschedulerPolicy_To_v1alpha1_DeschedulerPolicy(in *api.DeschedulerPolicy, out *DeschedulerPolicy, s conversion.Scope) error { out.Strategies = *(*StrategyList)(unsafe.Pointer(&in.Strategies)) + out.NodeSelector = (*string)(unsafe.Pointer(in.NodeSelector)) + out.EvictLocalStoragePods = (*bool)(unsafe.Pointer(in.EvictLocalStoragePods)) + out.MaxNoOfPodsToEvictPerNode = (*int)(unsafe.Pointer(in.MaxNoOfPodsToEvictPerNode)) return nil } @@ -246,9 +252,7 @@ func autoConvert_v1alpha1_StrategyParameters_To_api_StrategyParameters(in *Strat out.PodsHavingTooManyRestarts = (*api.PodsHavingTooManyRestarts)(unsafe.Pointer(in.PodsHavingTooManyRestarts)) out.MaxPodLifeTimeSeconds = (*uint)(unsafe.Pointer(in.MaxPodLifeTimeSeconds)) out.RemoveDuplicates = (*api.RemoveDuplicates)(unsafe.Pointer(in.RemoveDuplicates)) - if err := Convert_v1alpha1_Namespaces_To_api_Namespaces(&in.Namespaces, &out.Namespaces, s); err != nil { - return err - } + out.Namespaces = (*api.Namespaces)(unsafe.Pointer(in.Namespaces)) out.ThresholdPriority = (*int32)(unsafe.Pointer(in.ThresholdPriority)) out.ThresholdPriorityClassName = in.ThresholdPriorityClassName return nil @@ -265,9 +269,7 @@ func autoConvert_api_StrategyParameters_To_v1alpha1_StrategyParameters(in *api.S out.PodsHavingTooManyRestarts = (*PodsHavingTooManyRestarts)(unsafe.Pointer(in.PodsHavingTooManyRestarts)) out.MaxPodLifeTimeSeconds = (*uint)(unsafe.Pointer(in.MaxPodLifeTimeSeconds)) out.RemoveDuplicates = (*RemoveDuplicates)(unsafe.Pointer(in.RemoveDuplicates)) - if err := Convert_api_Namespaces_To_v1alpha1_Namespaces(&in.Namespaces, &out.Namespaces, s); err != nil { - return err - } + out.Namespaces = (*Namespaces)(unsafe.Pointer(in.Namespaces)) out.ThresholdPriority = (*int32)(unsafe.Pointer(in.ThresholdPriority)) out.ThresholdPriorityClassName = in.ThresholdPriorityClassName return nil diff --git a/pkg/api/v1alpha1/zz_generated.deepcopy.go b/pkg/api/v1alpha1/zz_generated.deepcopy.go index 33321a5cd7..79ad51ade5 100644 --- a/pkg/api/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/api/v1alpha1/zz_generated.deepcopy.go @@ -35,6 +35,21 @@ func (in *DeschedulerPolicy) DeepCopyInto(out *DeschedulerPolicy) { (*out)[key] = *val.DeepCopy() } } + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = new(string) + **out = **in + } + if in.EvictLocalStoragePods != nil { + in, out := &in.EvictLocalStoragePods, &out.EvictLocalStoragePods + *out = new(bool) + **out = **in + } + if in.MaxNoOfPodsToEvictPerNode != nil { + in, out := &in.MaxNoOfPodsToEvictPerNode, &out.MaxNoOfPodsToEvictPerNode + *out = new(int) + **out = **in + } return } @@ -242,7 +257,11 @@ func (in *StrategyParameters) DeepCopyInto(out *StrategyParameters) { *out = new(RemoveDuplicates) (*in).DeepCopyInto(*out) } - in.Namespaces.DeepCopyInto(&out.Namespaces) + if in.Namespaces != nil { + in, out := &in.Namespaces, &out.Namespaces + *out = new(Namespaces) + (*in).DeepCopyInto(*out) + } if in.ThresholdPriority != nil { in, out := &in.ThresholdPriority, &out.ThresholdPriority *out = new(int32) diff --git a/pkg/api/zz_generated.deepcopy.go b/pkg/api/zz_generated.deepcopy.go index b337e6ae66..17603cac07 100644 --- a/pkg/api/zz_generated.deepcopy.go +++ b/pkg/api/zz_generated.deepcopy.go @@ -35,6 +35,21 @@ func (in *DeschedulerPolicy) DeepCopyInto(out *DeschedulerPolicy) { (*out)[key] = *val.DeepCopy() } } + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = new(string) + **out = **in + } + if in.EvictLocalStoragePods != nil { + in, out := &in.EvictLocalStoragePods, &out.EvictLocalStoragePods + *out = new(bool) + **out = **in + } + if in.MaxNoOfPodsToEvictPerNode != nil { + in, out := &in.MaxNoOfPodsToEvictPerNode, &out.MaxNoOfPodsToEvictPerNode + *out = new(int) + **out = **in + } return } @@ -242,7 +257,11 @@ func (in *StrategyParameters) DeepCopyInto(out *StrategyParameters) { *out = new(RemoveDuplicates) (*in).DeepCopyInto(*out) } - in.Namespaces.DeepCopyInto(&out.Namespaces) + if in.Namespaces != nil { + in, out := &in.Namespaces, &out.Namespaces + *out = new(Namespaces) + (*in).DeepCopyInto(*out) + } if in.ThresholdPriority != nil { in, out := &in.ThresholdPriority, &out.ThresholdPriority *out = new(int32) diff --git a/pkg/descheduler/descheduler.go b/pkg/descheduler/descheduler.go index 768567ed5d..993f3295ea 100644 --- a/pkg/descheduler/descheduler.go +++ b/pkg/descheduler/descheduler.go @@ -20,7 +20,7 @@ import ( "context" "fmt" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" clientset "k8s.io/client-go/kubernetes" "k8s.io/klog/v2" @@ -79,8 +79,23 @@ func RunDeschedulerStrategies(ctx context.Context, rs *options.DeschedulerServer "PodLifeTime": strategies.PodLifeTime, } + nodeSelector := rs.NodeSelector + if deschedulerPolicy.NodeSelector != nil { + nodeSelector = *deschedulerPolicy.NodeSelector + } + + evictLocalStoragePods := rs.EvictLocalStoragePods + if deschedulerPolicy.EvictLocalStoragePods != nil { + evictLocalStoragePods = *deschedulerPolicy.EvictLocalStoragePods + } + + maxNoOfPodsToEvictPerNode := rs.MaxNoOfPodsToEvictPerNode + if deschedulerPolicy.MaxNoOfPodsToEvictPerNode != nil { + maxNoOfPodsToEvictPerNode = *deschedulerPolicy.MaxNoOfPodsToEvictPerNode + } + wait.Until(func() { - nodes, err := nodeutil.ReadyNodes(ctx, rs.Client, nodeInformer, rs.NodeSelector, stopChannel) + nodes, err := nodeutil.ReadyNodes(ctx, rs.Client, nodeInformer, nodeSelector, stopChannel) if err != nil { klog.V(1).Infof("Unable to get ready nodes: %v", err) close(stopChannel) @@ -97,9 +112,9 @@ func RunDeschedulerStrategies(ctx context.Context, rs *options.DeschedulerServer rs.Client, evictionPolicyGroupVersion, rs.DryRun, - rs.MaxNoOfPodsToEvictPerNode, + maxNoOfPodsToEvictPerNode, nodes, - rs.EvictLocalStoragePods, + evictLocalStoragePods, ) for name, f := range strategyFuncs { diff --git a/pkg/descheduler/evictions/evictions.go b/pkg/descheduler/evictions/evictions.go index 7a5c712344..d04d9b5230 100644 --- a/pkg/descheduler/evictions/evictions.go +++ b/pkg/descheduler/evictions/evictions.go @@ -77,41 +77,6 @@ func NewPodEvictor( } } -// IsEvictable checks if a pod is evictable or not. -func (pe *PodEvictor) IsEvictable(pod *v1.Pod, thresholdPriority int32) bool { - checkErrs := []error{} - if IsCriticalPod(pod) { - checkErrs = append(checkErrs, fmt.Errorf("pod is critical")) - } - - if !IsPodEvictableBasedOnPriority(pod, thresholdPriority) { - checkErrs = append(checkErrs, fmt.Errorf("pod is not evictable due to its priority")) - } - - ownerRefList := podutil.OwnerRef(pod) - if IsDaemonsetPod(ownerRefList) { - checkErrs = append(checkErrs, fmt.Errorf("pod is a DaemonSet pod")) - } - - if len(ownerRefList) == 0 { - checkErrs = append(checkErrs, fmt.Errorf("pod does not have any ownerrefs")) - } - - if !pe.evictLocalStoragePods && IsPodWithLocalStorage(pod) { - checkErrs = append(checkErrs, fmt.Errorf("pod has local storage and descheduler is not configured with --evict-local-storage-pods")) - } - - if IsMirrorPod(pod) { - checkErrs = append(checkErrs, fmt.Errorf("pod is a mirror pod")) - } - - if len(checkErrs) > 0 && !HaveEvictAnnotation(pod) { - klog.V(4).Infof("Pod %s in namespace %s is not evictable: Pod lacks an eviction annotation and fails the following checks: %v", pod.Name, pod.Namespace, errors.NewAggregate(checkErrs).Error()) - return false - } - return true -} - // NodeEvicted gives a number of pods evicted for node func (pe *PodEvictor) NodeEvicted(node *v1.Node) int { return pe.nodepodCount[node] @@ -187,6 +152,87 @@ func evictPod(ctx context.Context, client clientset.Interface, pod *v1.Pod, poli return err } +type Options struct { + priority *int32 +} + +// WithPriorityThreshold sets a threshold for pod's priority class. +// Any pod whose priority class is lower is evictable. +func WithPriorityThreshold(priority int32) func(opts *Options) { + return func(opts *Options) { + var p int32 = priority + opts.priority = &p + } +} + +type constraint func(pod *v1.Pod) error + +type evictable struct { + constraints []constraint +} + +// Evictable provides an implementation of IsEvictable(IsEvictable(pod *v1.Pod) bool). +// The method accepts a list of options which allow to extend constraints +// which decides when a pod is considered evictable. +func (pe *PodEvictor) Evictable(opts ...func(opts *Options)) *evictable { + options := &Options{} + for _, opt := range opts { + opt(options) + } + + ev := &evictable{} + if !pe.evictLocalStoragePods { + ev.constraints = append(ev.constraints, func(pod *v1.Pod) error { + if IsPodWithLocalStorage(pod) { + return fmt.Errorf("pod has local storage and descheduler is not configured with --evict-local-storage-pods") + } + return nil + }) + } + if options.priority != nil { + ev.constraints = append(ev.constraints, func(pod *v1.Pod) error { + if IsPodEvictableBasedOnPriority(pod, *options.priority) { + return nil + } + return fmt.Errorf("pod has higher priority than specified priority class threshold") + }) + } + return ev +} + +// IsEvictable decides when a pod is evictable +func (ev *evictable) IsEvictable(pod *v1.Pod) bool { + checkErrs := []error{} + if IsCriticalPod(pod) { + checkErrs = append(checkErrs, fmt.Errorf("pod is critical")) + } + + ownerRefList := podutil.OwnerRef(pod) + if IsDaemonsetPod(ownerRefList) { + checkErrs = append(checkErrs, fmt.Errorf("pod is a DaemonSet pod")) + } + + if len(ownerRefList) == 0 { + checkErrs = append(checkErrs, fmt.Errorf("pod does not have any ownerrefs")) + } + + if IsMirrorPod(pod) { + checkErrs = append(checkErrs, fmt.Errorf("pod is a mirror pod")) + } + + for _, c := range ev.constraints { + if err := c(pod); err != nil { + checkErrs = append(checkErrs, err) + } + } + + if len(checkErrs) > 0 && !HaveEvictAnnotation(pod) { + klog.V(4).Infof("Pod %s in namespace %s is not evictable: Pod lacks an eviction annotation and fails the following checks: %v", pod.Name, pod.Namespace, errors.NewAggregate(checkErrs).Error()) + return false + } + return true +} + func IsCriticalPod(pod *v1.Pod) bool { return utils.IsCriticalPod(pod) } @@ -223,5 +269,5 @@ func IsPodWithLocalStorage(pod *v1.Pod) bool { // IsPodEvictableBasedOnPriority checks if the given pod is evictable based on priority resolved from pod Spec. func IsPodEvictableBasedOnPriority(pod *v1.Pod, priority int32) bool { - return pod.Spec.Priority == nil || (*pod.Spec.Priority < priority && *pod.Spec.Priority < utils.SystemCriticalPriority) + return pod.Spec.Priority == nil || *pod.Spec.Priority < priority } diff --git a/pkg/descheduler/evictions/evictions_test.go b/pkg/descheduler/evictions/evictions_test.go index 81f3155f57..20d770ce8d 100644 --- a/pkg/descheduler/evictions/evictions_test.go +++ b/pkg/descheduler/evictions/evictions_test.go @@ -241,14 +241,17 @@ func TestIsEvictable(t *testing.T) { for _, test := range testCases { test.runBefore(test.pod) + podEvictor := &PodEvictor{ evictLocalStoragePods: test.evictLocalStoragePods, } - testPriorityThreshold := utils.SystemCriticalPriority + + evictable := podEvictor.Evictable() if test.priorityThreshold != nil { - testPriorityThreshold = *test.priorityThreshold + evictable = podEvictor.Evictable(WithPriorityThreshold(*test.priorityThreshold)) } - result := podEvictor.IsEvictable(test.pod, testPriorityThreshold) + + result := evictable.IsEvictable(test.pod) if result != test.result { t.Errorf("IsEvictable should return for pod %s %t, but it returns %t", test.pod.Name, test.result, result) } diff --git a/pkg/descheduler/node/node_test.go b/pkg/descheduler/node/node_test.go index 95af44ad9c..a5f1829e8d 100644 --- a/pkg/descheduler/node/node_test.go +++ b/pkg/descheduler/node/node_test.go @@ -20,7 +20,7 @@ import ( "context" "testing" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" @@ -69,7 +69,7 @@ func TestReadyNodesWithNodeSelector(t *testing.T) { sharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0) nodeInformer := sharedInformerFactory.Core().V1().Nodes() - stopChannel := make(chan struct{}, 0) + stopChannel := make(chan struct{}) sharedInformerFactory.Start(stopChannel) sharedInformerFactory.WaitForCacheSync(stopChannel) defer close(stopChannel) diff --git a/pkg/descheduler/pod/pods.go b/pkg/descheduler/pod/pods.go index cbb4f8c195..01e1cf9167 100644 --- a/pkg/descheduler/pod/pods.go +++ b/pkg/descheduler/pod/pods.go @@ -57,8 +57,8 @@ func WithoutNamespaces(namespaces []string) func(opts *Options) { // ListPodsOnANode lists all of the pods on a node // It also accepts an optional "filter" function which can be used to further limit the pods that are returned. -// (Usually this is podEvictor.IsEvictable, in order to only list the evictable pods on a node, but can -// be used by strategies to extend IsEvictable if there are further restrictions, such as with NodeAffinity). +// (Usually this is podEvictor.Evictable().IsEvictable, in order to only list the evictable pods on a node, but can +// be used by strategies to extend it if there are further restrictions, such as with NodeAffinity). func ListPodsOnANode( ctx context.Context, client clientset.Interface, @@ -117,6 +117,11 @@ func ListPodsOnANode( } for i := range podList.Items { + // fake client does not support field selectors + // so let's filter based on the node name as well (quite cheap) + if podList.Items[i].Spec.NodeName != node.Name { + continue + } if options.filter != nil && !options.filter(&podList.Items[i]) { continue } diff --git a/pkg/descheduler/strategies/duplicates.go b/pkg/descheduler/strategies/duplicates.go index 7aabd42082..53ba26998a 100644 --- a/pkg/descheduler/strategies/duplicates.go +++ b/pkg/descheduler/strategies/duplicates.go @@ -67,11 +67,11 @@ func RemoveDuplicatePods( return } + evictable := podEvictor.Evictable(evictions.WithPriorityThreshold(thresholdPriority)) + for _, node := range nodes { klog.V(1).Infof("Processing node: %#v", node.Name) - pods, err := podutil.ListPodsOnANode(ctx, client, node, podutil.WithFilter(func(pod *v1.Pod) bool { - return podEvictor.IsEvictable(pod, thresholdPriority) - })) + pods, err := podutil.ListPodsOnANode(ctx, client, node, podutil.WithFilter(evictable.IsEvictable)) if err != nil { klog.Errorf("error listing evictable pods on node %s: %+v", node.Name, err) continue diff --git a/pkg/descheduler/strategies/lownodeutilization.go b/pkg/descheduler/strategies/lownodeutilization.go index 2a58101bf8..7e260881cf 100644 --- a/pkg/descheduler/strategies/lownodeutilization.go +++ b/pkg/descheduler/strategies/lownodeutilization.go @@ -126,13 +126,15 @@ func LowNodeUtilization(ctx context.Context, client clientset.Interface, strateg targetThresholds[v1.ResourceCPU], targetThresholds[v1.ResourceMemory], targetThresholds[v1.ResourcePods]) klog.V(1).Infof("Total number of nodes above target utilization: %v", len(targetNodes)) + evictable := podEvictor.Evictable(evictions.WithPriorityThreshold(thresholdPriority)) + evictPodsFromTargetNodes( ctx, targetNodes, lowNodes, targetThresholds, podEvictor, - thresholdPriority) + evictable.IsEvictable) klog.V(1).Infof("Total number of pods evicted: %v", podEvictor.TotalEvicted()) } @@ -212,7 +214,7 @@ func evictPodsFromTargetNodes( targetNodes, lowNodes []NodeUsageMap, targetThresholds api.ResourceThresholds, podEvictor *evictions.PodEvictor, - thresholdPriority int32, + podFilter func(pod *v1.Pod) bool, ) { sortNodesByUsage(targetNodes) @@ -249,7 +251,7 @@ func evictPodsFromTargetNodes( } klog.V(3).Infof("evicting pods from node %#v with usage: %#v", node.node.Name, node.usage) - nonRemovablePods, removablePods := classifyPods(node.allPods, podEvictor, thresholdPriority) + nonRemovablePods, removablePods := classifyPods(node.allPods, podFilter) klog.V(2).Infof("allPods:%v, nonRemovablePods:%v, removablePods:%v", len(node.allPods), len(nonRemovablePods), len(removablePods)) if len(removablePods) == 0 { @@ -410,11 +412,11 @@ func nodeUtilization(node *v1.Node, pods []*v1.Pod) api.ResourceThresholds { } } -func classifyPods(pods []*v1.Pod, evictor *evictions.PodEvictor, thresholdPriority int32) ([]*v1.Pod, []*v1.Pod) { +func classifyPods(pods []*v1.Pod, filter func(pod *v1.Pod) bool) ([]*v1.Pod, []*v1.Pod) { var nonRemovablePods, removablePods []*v1.Pod for _, pod := range pods { - if !evictor.IsEvictable(pod, thresholdPriority) { + if !filter(pod) { nonRemovablePods = append(nonRemovablePods, pod) } else { removablePods = append(removablePods, pod) diff --git a/pkg/descheduler/strategies/lownodeutilization_test.go b/pkg/descheduler/strategies/lownodeutilization_test.go index 076a18825c..b14511d98f 100644 --- a/pkg/descheduler/strategies/lownodeutilization_test.go +++ b/pkg/descheduler/strategies/lownodeutilization_test.go @@ -25,11 +25,7 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/api/policy/v1beta1" "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/kubernetes/fake" core "k8s.io/client-go/testing" "sigs.k8s.io/descheduler/pkg/api" @@ -225,7 +221,7 @@ func TestLowNodeUtilization(t *testing.T) { }, n2NodeName: { Items: []v1.Pod{ - *test.BuildTestPod("p9", 400, 2100, n1NodeName, test.SetRSOwnerRef), + *test.BuildTestPod("p9", 400, 2100, n2NodeName, test.SetRSOwnerRef), }, }, n3NodeName: {}, @@ -650,50 +646,6 @@ func TestValidateThresholds(t *testing.T) { } } -func newFake(objects ...runtime.Object) *core.Fake { - scheme := runtime.NewScheme() - codecs := serializer.NewCodecFactory(scheme) - fake.AddToScheme(scheme) - o := core.NewObjectTracker(scheme, codecs.UniversalDecoder()) - for _, obj := range objects { - if err := o.Add(obj); err != nil { - panic(err) - } - } - - fakePtr := core.Fake{} - fakePtr.AddReactor("list", "pods", func(action core.Action) (bool, runtime.Object, error) { - objs, err := o.List( - schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}, - schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"}, - action.GetNamespace(), - ) - if err != nil { - return true, nil, err - } - - obj := &v1.PodList{ - Items: []v1.Pod{}, - } - for _, pod := range objs.(*v1.PodList).Items { - podFieldSet := fields.Set(map[string]string{ - "spec.nodeName": pod.Spec.NodeName, - "status.phase": string(pod.Status.Phase), - }) - match := action.(core.ListAction).GetListRestrictions().Fields.Matches(podFieldSet) - if !match { - continue - } - obj.Items = append(obj.Items, *pod.DeepCopy()) - } - return true, obj, nil - }) - fakePtr.AddReactor("*", "*", core.ObjectReaction(o)) - fakePtr.AddWatchReactor("*", core.DefaultWatchReactor(watch.NewFake(), nil)) - - return &fakePtr -} - func TestWithTaints(t *testing.T) { ctx := context.Background() strategy := api.DeschedulerStrategy{ @@ -803,18 +755,10 @@ func TestWithTaints(t *testing.T) { objs = append(objs, pod) } - fakePtr := newFake(objs...) - var evictionCounter int - fakePtr.PrependReactor("create", "pods", func(action core.Action) (bool, runtime.Object, error) { - if action.GetSubresource() != "eviction" || action.GetResource().Resource != "pods" { - return false, nil, nil - } - evictionCounter++ - return true, nil, nil - }) + fakeClient := fake.NewSimpleClientset(objs...) podEvictor := evictions.NewPodEvictor( - &fake.Clientset{Fake: *fakePtr}, + fakeClient, "policy/v1", false, item.evictionsExpected, @@ -822,10 +766,10 @@ func TestWithTaints(t *testing.T) { false, ) - LowNodeUtilization(ctx, &fake.Clientset{Fake: *fakePtr}, strategy, item.nodes, podEvictor) + LowNodeUtilization(ctx, fakeClient, strategy, item.nodes, podEvictor) - if item.evictionsExpected != evictionCounter { - t.Errorf("Expected %v evictions, got %v", item.evictionsExpected, evictionCounter) + if item.evictionsExpected != podEvictor.TotalEvicted() { + t.Errorf("Expected %v evictions, got %v", item.evictionsExpected, podEvictor.TotalEvicted()) } }) } diff --git a/pkg/descheduler/strategies/node_affinity.go b/pkg/descheduler/strategies/node_affinity.go index e4d5eac5a5..a17ac191af 100644 --- a/pkg/descheduler/strategies/node_affinity.go +++ b/pkg/descheduler/strategies/node_affinity.go @@ -36,7 +36,7 @@ func validatePodsViolatingNodeAffinityParams(params *api.StrategyParameters) err return fmt.Errorf("NodeAffinityType is empty") } // At most one of include/exclude can be set - if len(params.Namespaces.Include) > 0 && len(params.Namespaces.Exclude) > 0 { + if params.Namespaces != nil && len(params.Namespaces.Include) > 0 && len(params.Namespaces.Exclude) > 0 { return fmt.Errorf("only one of Include/Exclude namespaces can be set") } if params.ThresholdPriority != nil && params.ThresholdPriorityClassName != "" { @@ -58,6 +58,14 @@ func RemovePodsViolatingNodeAffinity(ctx context.Context, client clientset.Inter return } + var includedNamespaces, excludedNamespaces []string + if strategy.Params.Namespaces != nil { + includedNamespaces = strategy.Params.Namespaces.Include + excludedNamespaces = strategy.Params.Namespaces.Exclude + } + + evictable := podEvictor.Evictable(evictions.WithPriorityThreshold(thresholdPriority)) + for _, nodeAffinity := range strategy.Params.NodeAffinityType { klog.V(2).Infof("Executing for nodeAffinityType: %v", nodeAffinity) @@ -71,12 +79,12 @@ func RemovePodsViolatingNodeAffinity(ctx context.Context, client clientset.Inter client, node, podutil.WithFilter(func(pod *v1.Pod) bool { - return podEvictor.IsEvictable(pod, thresholdPriority) && + return evictable.IsEvictable(pod) && !nodeutil.PodFitsCurrentNode(pod, node) && nodeutil.PodFitsAnyNode(pod, nodes) }), - podutil.WithNamespaces(strategy.Params.Namespaces.Include), - podutil.WithoutNamespaces(strategy.Params.Namespaces.Exclude), + podutil.WithNamespaces(includedNamespaces), + podutil.WithoutNamespaces(excludedNamespaces), ) if err != nil { klog.Errorf("failed to get pods from %v: %v", node.Name, err) diff --git a/pkg/descheduler/strategies/node_taint.go b/pkg/descheduler/strategies/node_taint.go index 2a94e0e5e5..3d697864ba 100644 --- a/pkg/descheduler/strategies/node_taint.go +++ b/pkg/descheduler/strategies/node_taint.go @@ -36,7 +36,7 @@ func validateRemovePodsViolatingNodeTaintsParams(params *api.StrategyParameters) } // At most one of include/exclude can be set - if len(params.Namespaces.Include) > 0 && len(params.Namespaces.Exclude) > 0 { + if params.Namespaces != nil && len(params.Namespaces.Include) > 0 && len(params.Namespaces.Exclude) > 0 { return fmt.Errorf("only one of Include/Exclude namespaces can be set") } if params.ThresholdPriority != nil && params.ThresholdPriorityClassName != "" { @@ -52,27 +52,30 @@ func RemovePodsViolatingNodeTaints(ctx context.Context, client clientset.Interfa klog.V(1).Info(err) return } - var namespaces api.Namespaces - if strategy.Params != nil { - namespaces = strategy.Params.Namespaces + + var includedNamespaces, excludedNamespaces []string + if strategy.Params != nil && strategy.Params.Namespaces != nil { + includedNamespaces = strategy.Params.Namespaces.Include + excludedNamespaces = strategy.Params.Namespaces.Exclude } + thresholdPriority, err := utils.GetPriorityFromStrategyParams(ctx, client, strategy.Params) if err != nil { klog.V(1).Infof("failed to get threshold priority from strategy's params: %#v", err) return } + evictable := podEvictor.Evictable(evictions.WithPriorityThreshold(thresholdPriority)) + for _, node := range nodes { klog.V(1).Infof("Processing node: %#v\n", node.Name) pods, err := podutil.ListPodsOnANode( ctx, client, node, - podutil.WithFilter(func(pod *v1.Pod) bool { - return podEvictor.IsEvictable(pod, thresholdPriority) - }), - podutil.WithNamespaces(namespaces.Include), - podutil.WithoutNamespaces(namespaces.Exclude), + podutil.WithFilter(evictable.IsEvictable), + podutil.WithNamespaces(includedNamespaces), + podutil.WithoutNamespaces(excludedNamespaces), ) if err != nil { //no pods evicted as error encountered retrieving evictable Pods diff --git a/pkg/descheduler/strategies/pod_antiaffinity.go b/pkg/descheduler/strategies/pod_antiaffinity.go index ddc7a29112..8a2a3b072b 100644 --- a/pkg/descheduler/strategies/pod_antiaffinity.go +++ b/pkg/descheduler/strategies/pod_antiaffinity.go @@ -25,7 +25,7 @@ import ( podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod" "sigs.k8s.io/descheduler/pkg/utils" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" clientset "k8s.io/client-go/kubernetes" "k8s.io/klog/v2" @@ -37,7 +37,7 @@ func validateRemovePodsViolatingInterPodAntiAffinityParams(params *api.StrategyP } // At most one of include/exclude can be set - if len(params.Namespaces.Include) > 0 && len(params.Namespaces.Exclude) > 0 { + if params.Namespaces != nil && len(params.Namespaces.Include) > 0 && len(params.Namespaces.Exclude) > 0 { return fmt.Errorf("only one of Include/Exclude namespaces can be set") } if params.ThresholdPriority != nil && params.ThresholdPriorityClassName != "" { @@ -53,27 +53,30 @@ func RemovePodsViolatingInterPodAntiAffinity(ctx context.Context, client clients klog.V(1).Info(err) return } - var namespaces api.Namespaces - if strategy.Params != nil { - namespaces = strategy.Params.Namespaces + + var includedNamespaces, excludedNamespaces []string + if strategy.Params != nil && strategy.Params.Namespaces != nil { + includedNamespaces = strategy.Params.Namespaces.Include + excludedNamespaces = strategy.Params.Namespaces.Exclude } + thresholdPriority, err := utils.GetPriorityFromStrategyParams(ctx, client, strategy.Params) if err != nil { klog.V(1).Infof("failed to get threshold priority from strategy's params: %#v", err) return } + evictable := podEvictor.Evictable(evictions.WithPriorityThreshold(thresholdPriority)) + for _, node := range nodes { klog.V(1).Infof("Processing node: %#v\n", node.Name) pods, err := podutil.ListPodsOnANode( ctx, client, node, - podutil.WithFilter(func(pod *v1.Pod) bool { - return podEvictor.IsEvictable(pod, thresholdPriority) - }), - podutil.WithNamespaces(namespaces.Include), - podutil.WithoutNamespaces(namespaces.Exclude), + podutil.WithFilter(evictable.IsEvictable), + podutil.WithNamespaces(includedNamespaces), + podutil.WithoutNamespaces(excludedNamespaces), ) if err != nil { return diff --git a/pkg/descheduler/strategies/pod_lifetime.go b/pkg/descheduler/strategies/pod_lifetime.go index 16cdb09a27..8a984b77d7 100644 --- a/pkg/descheduler/strategies/pod_lifetime.go +++ b/pkg/descheduler/strategies/pod_lifetime.go @@ -37,7 +37,7 @@ func validatePodLifeTimeParams(params *api.StrategyParameters) error { } // At most one of include/exclude can be set - if len(params.Namespaces.Include) > 0 && len(params.Namespaces.Exclude) > 0 { + if params.Namespaces != nil && len(params.Namespaces.Include) > 0 && len(params.Namespaces.Exclude) > 0 { return fmt.Errorf("only one of Include/Exclude namespaces can be set") } if params.ThresholdPriority != nil && params.ThresholdPriorityClassName != "" { @@ -53,16 +53,25 @@ func PodLifeTime(ctx context.Context, client clientset.Interface, strategy api.D klog.V(1).Info(err) return } + thresholdPriority, err := utils.GetPriorityFromStrategyParams(ctx, client, strategy.Params) if err != nil { klog.V(1).Infof("failed to get threshold priority from strategy's params: %#v", err) return } + var includedNamespaces, excludedNamespaces []string + if strategy.Params.Namespaces != nil { + includedNamespaces = strategy.Params.Namespaces.Include + excludedNamespaces = strategy.Params.Namespaces.Exclude + } + + evictable := podEvictor.Evictable(evictions.WithPriorityThreshold(thresholdPriority)) + for _, node := range nodes { klog.V(1).Infof("Processing node: %#v", node.Name) - pods := listOldPodsOnNode(ctx, client, node, strategy.Params, podEvictor, thresholdPriority) + pods := listOldPodsOnNode(ctx, client, node, includedNamespaces, excludedNamespaces, *strategy.Params.MaxPodLifeTimeSeconds, evictable.IsEvictable) for _, pod := range pods { success, err := podEvictor.EvictPod(ctx, pod, node, "PodLifeTime") if success { @@ -78,16 +87,14 @@ func PodLifeTime(ctx context.Context, client clientset.Interface, strategy api.D } } -func listOldPodsOnNode(ctx context.Context, client clientset.Interface, node *v1.Node, params *api.StrategyParameters, podEvictor *evictions.PodEvictor, thresholdPriority int32) []*v1.Pod { +func listOldPodsOnNode(ctx context.Context, client clientset.Interface, node *v1.Node, includedNamespaces, excludedNamespaces []string, maxPodLifeTimeSeconds uint, filter func(pod *v1.Pod) bool) []*v1.Pod { pods, err := podutil.ListPodsOnANode( ctx, client, node, - podutil.WithFilter(func(pod *v1.Pod) bool { - return podEvictor.IsEvictable(pod, thresholdPriority) - }), - podutil.WithNamespaces(params.Namespaces.Include), - podutil.WithoutNamespaces(params.Namespaces.Exclude), + podutil.WithFilter(filter), + podutil.WithNamespaces(includedNamespaces), + podutil.WithoutNamespaces(excludedNamespaces), ) if err != nil { return nil @@ -96,7 +103,7 @@ func listOldPodsOnNode(ctx context.Context, client clientset.Interface, node *v1 var oldPods []*v1.Pod for _, pod := range pods { podAgeSeconds := uint(v1meta.Now().Sub(pod.GetCreationTimestamp().Local()).Seconds()) - if podAgeSeconds > *params.MaxPodLifeTimeSeconds { + if podAgeSeconds > maxPodLifeTimeSeconds { oldPods = append(oldPods, pod) } } diff --git a/pkg/descheduler/strategies/toomanyrestarts.go b/pkg/descheduler/strategies/toomanyrestarts.go index 81caf0b01e..94e620f54b 100644 --- a/pkg/descheduler/strategies/toomanyrestarts.go +++ b/pkg/descheduler/strategies/toomanyrestarts.go @@ -36,7 +36,7 @@ func validateRemovePodsHavingTooManyRestartsParams(params *api.StrategyParameter } // At most one of include/exclude can be set - if len(params.Namespaces.Include) > 0 && len(params.Namespaces.Exclude) > 0 { + if params.Namespaces != nil && len(params.Namespaces.Include) > 0 && len(params.Namespaces.Exclude) > 0 { return fmt.Errorf("only one of Include/Exclude namespaces can be set") } if params.ThresholdPriority != nil && params.ThresholdPriorityClassName != "" { @@ -54,23 +54,30 @@ func RemovePodsHavingTooManyRestarts(ctx context.Context, client clientset.Inter klog.V(1).Info(err) return } + thresholdPriority, err := utils.GetPriorityFromStrategyParams(ctx, client, strategy.Params) if err != nil { klog.V(1).Infof("failed to get threshold priority from strategy's params: %#v", err) return } + var includedNamespaces, excludedNamespaces []string + if strategy.Params.Namespaces != nil { + includedNamespaces = strategy.Params.Namespaces.Include + excludedNamespaces = strategy.Params.Namespaces.Exclude + } + + evictable := podEvictor.Evictable(evictions.WithPriorityThreshold(thresholdPriority)) + for _, node := range nodes { klog.V(1).Infof("Processing node: %s", node.Name) pods, err := podutil.ListPodsOnANode( ctx, client, node, - podutil.WithFilter(func(pod *v1.Pod) bool { - return podEvictor.IsEvictable(pod, thresholdPriority) - }), - podutil.WithNamespaces(strategy.Params.Namespaces.Include), - podutil.WithoutNamespaces(strategy.Params.Namespaces.Exclude), + podutil.WithFilter(evictable.IsEvictable), + podutil.WithNamespaces(includedNamespaces), + podutil.WithoutNamespaces(excludedNamespaces), ) if err != nil { klog.Errorf("Error when list pods at node %s", node.Name) diff --git a/pkg/utils/pod.go b/pkg/utils/pod.go index 0beaa66100..011181ea2c 100644 --- a/pkg/utils/pod.go +++ b/pkg/utils/pod.go @@ -109,9 +109,15 @@ func IsCriticalPod(pod *v1.Pod) bool { if IsStaticPod(pod) { return true } + if IsMirrorPod(pod) { return true } + + if pod.Spec.Priority != nil && *pod.Spec.Priority >= SystemCriticalPriority { + return true + } + return false } diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 4c63ff5466..5d6b979240 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -20,7 +20,6 @@ import ( "context" "math" "os" - "sigs.k8s.io/descheduler/pkg/utils" "sort" "strings" "testing" @@ -145,7 +144,7 @@ func initializeClient(t *testing.T) (clientset.Interface, coreinformers.NodeInfo t.Errorf("Error during client creation with %v", err) } - stopChannel := make(chan struct{}, 0) + stopChannel := make(chan struct{}) sharedInformerFactory := informers.NewSharedInformerFactory(clientSet, 0) sharedInformerFactory.Start(stopChannel) @@ -188,7 +187,7 @@ func TestLowNodeUtilization(t *testing.T) { deleteRC(ctx, t, clientSet, rc) } -func runPodLifetimeStrategy(ctx context.Context, clientset clientset.Interface, nodeInformer coreinformers.NodeInformer, namespaces deschedulerapi.Namespaces, priorityClass string, priority *int32) { +func runPodLifetimeStrategy(ctx context.Context, clientset clientset.Interface, nodeInformer coreinformers.NodeInformer, namespaces *deschedulerapi.Namespaces, priorityClass string, priority *int32) { // Run descheduler. evictionPolicyGroupVersion, err := eutils.SupportEviction(clientset) if err != nil || len(evictionPolicyGroupVersion) == 0 { @@ -286,7 +285,7 @@ func TestNamespaceConstraintsInclude(t *testing.T) { t.Logf("Existing pods: %v", initialPodNames) t.Logf("set the strategy to delete pods from %v namespace", rc.Namespace) - runPodLifetimeStrategy(ctx, clientSet, nodeInformer, deschedulerapi.Namespaces{ + runPodLifetimeStrategy(ctx, clientSet, nodeInformer, &deschedulerapi.Namespaces{ Include: []string{rc.Namespace}, }, "", nil) @@ -357,7 +356,7 @@ func TestNamespaceConstraintsExclude(t *testing.T) { t.Logf("Existing pods: %v", initialPodNames) t.Logf("set the strategy to delete pods from namespaces except the %v namespace", rc.Namespace) - runPodLifetimeStrategy(ctx, clientSet, nodeInformer, deschedulerapi.Namespaces{ + runPodLifetimeStrategy(ctx, clientSet, nodeInformer, &deschedulerapi.Namespaces{ Exclude: []string{rc.Namespace}, }, "", nil) @@ -461,10 +460,10 @@ func testPriority(t *testing.T, isPriorityClass bool) { if isPriorityClass { t.Logf("set the strategy to delete pods with priority lower than priority class %s", highPriorityClass.Name) - runPodLifetimeStrategy(ctx, clientSet, nodeInformer, deschedulerapi.Namespaces{}, highPriorityClass.Name, nil) + runPodLifetimeStrategy(ctx, clientSet, nodeInformer, nil, highPriorityClass.Name, nil) } else { t.Logf("set the strategy to delete pods with priority lower than %d", highPriority) - runPodLifetimeStrategy(ctx, clientSet, nodeInformer, deschedulerapi.Namespaces{}, "", &highPriority) + runPodLifetimeStrategy(ctx, clientSet, nodeInformer, nil, "", &highPriority) } t.Logf("Waiting 10s") @@ -653,10 +652,7 @@ func evictPods(ctx context.Context, t *testing.T, clientSet clientset.Interface, continue } // List all the pods on the current Node - podsOnANode, err := podutil.ListPodsOnANode(ctx, clientSet, node, - podutil.WithFilter(func(pod *v1.Pod) bool { - return podEvictor.IsEvictable(pod, utils.SystemCriticalPriority) - })) + podsOnANode, err := podutil.ListPodsOnANode(ctx, clientSet, node, podutil.WithFilter(podEvictor.Evictable().IsEvictable)) if err != nil { t.Errorf("Error listing pods on a node %v", err) } @@ -668,10 +664,7 @@ func evictPods(ctx context.Context, t *testing.T, clientSet clientset.Interface, } t.Log("Eviction of pods starting") startEndToEndForLowNodeUtilization(ctx, clientSet, nodeInformer, podEvictor) - podsOnleastUtilizedNode, err := podutil.ListPodsOnANode(ctx, clientSet, leastLoadedNode, - podutil.WithFilter(func(pod *v1.Pod) bool { - return podEvictor.IsEvictable(pod, utils.SystemCriticalPriority) - })) + podsOnleastUtilizedNode, err := podutil.ListPodsOnANode(ctx, clientSet, leastLoadedNode, podutil.WithFilter(podEvictor.Evictable().IsEvictable)) if err != nil { t.Errorf("Error listing pods on a node %v", err) } diff --git a/vendor/github.com/evanphx/json-patch/.travis.yml b/vendor/github.com/evanphx/json-patch/.travis.yml index 2092c72c46..50e4afd19a 100644 --- a/vendor/github.com/evanphx/json-patch/.travis.yml +++ b/vendor/github.com/evanphx/json-patch/.travis.yml @@ -1,8 +1,8 @@ language: go go: - - 1.8 - - 1.7 + - 1.14 + - 1.13 install: - if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi @@ -11,6 +11,9 @@ install: script: - go get - go test -cover ./... + - cd ./v5 + - go get + - go test -cover ./... notifications: email: false diff --git a/vendor/github.com/evanphx/json-patch/LICENSE b/vendor/github.com/evanphx/json-patch/LICENSE index 0eb9b72d84..df76d7d771 100644 --- a/vendor/github.com/evanphx/json-patch/LICENSE +++ b/vendor/github.com/evanphx/json-patch/LICENSE @@ -6,7 +6,7 @@ modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright notice +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the Evan Phoenix nor the names of its contributors diff --git a/vendor/github.com/evanphx/json-patch/README.md b/vendor/github.com/evanphx/json-patch/README.md index 9c7f87f7ce..121b039dba 100644 --- a/vendor/github.com/evanphx/json-patch/README.md +++ b/vendor/github.com/evanphx/json-patch/README.md @@ -1,5 +1,5 @@ # JSON-Patch -`jsonpatch` is a library which provides functionallity for both applying +`jsonpatch` is a library which provides functionality for both applying [RFC6902 JSON patches](http://tools.ietf.org/html/rfc6902) against documents, as well as for calculating & applying [RFC7396 JSON merge patches](https://tools.ietf.org/html/rfc7396). @@ -11,10 +11,11 @@ well as for calculating & applying [RFC7396 JSON merge patches](https://tools.ie **Latest and greatest**: ```bash -go get -u github.com/evanphx/json-patch +go get -u github.com/evanphx/json-patch/v5 ``` **Stable Versions**: +* Version 5: `go get -u gopkg.in/evanphx/json-patch.v5` * Version 4: `go get -u gopkg.in/evanphx/json-patch.v4` (previous versions below `v3` are unavailable) @@ -82,7 +83,7 @@ When ran, you get the following output: ```bash $ go run main.go patch document: {"height":null,"name":"Jane"} -updated tina doc: {"age":28,"name":"Jane"} +updated alternative doc: {"age":28,"name":"Jane"} ``` ## Create and apply a JSON Patch @@ -164,7 +165,7 @@ func main() { } if !jsonpatch.Equal(original, different) { - fmt.Println(`"original" is _not_ structurally equal to "similar"`) + fmt.Println(`"original" is _not_ structurally equal to "different"`) } } ``` @@ -173,7 +174,7 @@ When ran, you get the following output: ```bash $ go run main.go "original" is structurally equal to "similar" -"original" is _not_ structurally equal to "similar" +"original" is _not_ structurally equal to "different" ``` ## Combine merge patches diff --git a/vendor/github.com/evanphx/json-patch/go.mod b/vendor/github.com/evanphx/json-patch/go.mod deleted file mode 100644 index a858cab296..0000000000 --- a/vendor/github.com/evanphx/json-patch/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module github.com/evanphx/json-patch - -go 1.12 - -require github.com/pkg/errors v0.8.1 diff --git a/vendor/github.com/evanphx/json-patch/go.sum b/vendor/github.com/evanphx/json-patch/go.sum deleted file mode 100644 index f29ab350a5..0000000000 --- a/vendor/github.com/evanphx/json-patch/go.sum +++ /dev/null @@ -1,2 +0,0 @@ -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/vendor/github.com/evanphx/json-patch/merge.go b/vendor/github.com/evanphx/json-patch/merge.go index 5dc6d344ab..14e8bb5ce3 100644 --- a/vendor/github.com/evanphx/json-patch/merge.go +++ b/vendor/github.com/evanphx/json-patch/merge.go @@ -311,7 +311,12 @@ func matchesValue(av, bv interface{}) bool { return false } for key := range bt { - if !matchesValue(at[key], bt[key]) { + av, aOK := at[key] + bv, bOK := bt[key] + if aOK != bOK { + return false + } + if !matchesValue(av, bv) { return false } } diff --git a/vendor/github.com/evanphx/json-patch/patch.go b/vendor/github.com/evanphx/json-patch/patch.go index 1b5f95e611..f185a45b2c 100644 --- a/vendor/github.com/evanphx/json-patch/patch.go +++ b/vendor/github.com/evanphx/json-patch/patch.go @@ -202,6 +202,10 @@ func (n *lazyNode) equal(o *lazyNode) bool { return false } + if len(n.doc) != len(o.doc) { + return false + } + for k, v := range n.doc { ov, ok := o.doc[k] @@ -209,6 +213,10 @@ func (n *lazyNode) equal(o *lazyNode) bool { return false } + if (v == nil) != (ov == nil) { + return false + } + if v == nil && ov == nil { continue } @@ -429,14 +437,14 @@ func (d *partialArray) add(key string, val *lazyNode) error { return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx) } - if SupportNegativeIndices { - if idx < -len(ary) { + if idx < 0 { + if !SupportNegativeIndices { return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx) } - - if idx < 0 { - idx += len(ary) + if idx < -len(ary) { + return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx) } + idx += len(ary) } copy(ary[0:idx], cur[0:idx]) @@ -473,14 +481,14 @@ func (d *partialArray) remove(key string) error { return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx) } - if SupportNegativeIndices { - if idx < -len(cur) { + if idx < 0 { + if !SupportNegativeIndices { return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx) } - - if idx < 0 { - idx += len(cur) + if idx < -len(cur) { + return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx) } + idx += len(cur) } ary := make([]*lazyNode, len(cur)-1) diff --git a/vendor/k8s.io/apimachinery/pkg/runtime/converter.go b/vendor/k8s.io/apimachinery/pkg/runtime/converter.go index 31f6e00b0f..871e4c8c46 100644 --- a/vendor/k8s.io/apimachinery/pkg/runtime/converter.go +++ b/vendor/k8s.io/apimachinery/pkg/runtime/converter.go @@ -31,7 +31,7 @@ import ( "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/util/json" utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "sigs.k8s.io/structured-merge-diff/v3/value" + "sigs.k8s.io/structured-merge-diff/v4/value" "k8s.io/klog/v2" ) diff --git a/vendor/k8s.io/apimachinery/pkg/util/net/http.go b/vendor/k8s.io/apimachinery/pkg/util/net/http.go index 406df25e0b..945886c438 100644 --- a/vendor/k8s.io/apimachinery/pkg/util/net/http.go +++ b/vendor/k8s.io/apimachinery/pkg/util/net/http.go @@ -62,8 +62,11 @@ func JoinPreservingTrailingSlash(elem ...string) string { // IsTimeout returns true if the given error is a network timeout error func IsTimeout(err error) bool { - neterr, ok := err.(net.Error) - return ok && neterr != nil && neterr.Timeout() + var neterr net.Error + if errors.As(err, &neterr) { + return neterr != nil && neterr.Timeout() + } + return false } // IsProbableEOF returns true if the given error resembles a connection termination @@ -76,7 +79,8 @@ func IsProbableEOF(err error) bool { if err == nil { return false } - if uerr, ok := err.(*url.Error); ok { + var uerr *url.Error + if errors.As(err, &uerr) { err = uerr.Err } msg := err.Error() diff --git a/vendor/k8s.io/apimachinery/pkg/util/net/util.go b/vendor/k8s.io/apimachinery/pkg/util/net/util.go index 2e7cb94994..5950087e02 100644 --- a/vendor/k8s.io/apimachinery/pkg/util/net/util.go +++ b/vendor/k8s.io/apimachinery/pkg/util/net/util.go @@ -17,9 +17,8 @@ limitations under the License. package net import ( + "errors" "net" - "net/url" - "os" "reflect" "syscall" ) @@ -40,34 +39,18 @@ func IPNetEqual(ipnet1, ipnet2 *net.IPNet) bool { // Returns if the given err is "connection reset by peer" error. func IsConnectionReset(err error) bool { - if urlErr, ok := err.(*url.Error); ok { - err = urlErr.Err - } - if opErr, ok := err.(*net.OpError); ok { - err = opErr.Err - } - if osErr, ok := err.(*os.SyscallError); ok { - err = osErr.Err - } - if errno, ok := err.(syscall.Errno); ok && errno == syscall.ECONNRESET { - return true + var errno syscall.Errno + if errors.As(err, &errno) { + return errno == syscall.ECONNRESET } return false } // Returns if the given err is "connection refused" error func IsConnectionRefused(err error) bool { - if urlErr, ok := err.(*url.Error); ok { - err = urlErr.Err - } - if opErr, ok := err.(*net.OpError); ok { - err = opErr.Err - } - if osErr, ok := err.(*os.SyscallError); ok { - err = osErr.Err - } - if errno, ok := err.(syscall.Errno); ok && errno == syscall.ECONNREFUSED { - return true + var errno syscall.Errno + if errors.As(err, &errno) { + return errno == syscall.ECONNREFUSED } return false } diff --git a/vendor/k8s.io/code-generator/go.mod b/vendor/k8s.io/code-generator/go.mod index 3e2b10074f..47424a1018 100644 --- a/vendor/k8s.io/code-generator/go.mod +++ b/vendor/k8s.io/code-generator/go.mod @@ -19,6 +19,6 @@ require ( golang.org/x/tools v0.0.0-20200616133436-c1934b75d054 // indirect k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14 k8s.io/klog/v2 v2.2.0 - k8s.io/kube-openapi v0.0.0-20200427153329-656914f816f9 + k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6 sigs.k8s.io/yaml v1.2.0 // indirect ) diff --git a/vendor/k8s.io/code-generator/go.sum b/vendor/k8s.io/code-generator/go.sum index f061498105..3607705970 100644 --- a/vendor/k8s.io/code-generator/go.sum +++ b/vendor/k8s.io/code-generator/go.sum @@ -132,9 +132,9 @@ k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8 k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/kube-openapi v0.0.0-20200427153329-656914f816f9 h1:5NC2ITmvg8RoxoH0wgmL4zn4VZqXGsKbxrikjaQx6s4= -k8s.io/kube-openapi v0.0.0-20200427153329-656914f816f9/go.mod h1:bfCVj+qXcEaE5SCvzBaqpOySr6tuCcpPKqF6HD8nyCw= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6 h1:+WnxoVtG8TMiudHBSEtrVL1egv36TkkJm+bA8AxicmQ= +k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= +sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/vendor/modules.txt b/vendor/modules.txt index 05bba3dd88..ea8cfdd90d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -22,7 +22,7 @@ github.com/dgrijalva/jwt-go # github.com/emicklei/go-restful v2.9.5+incompatible github.com/emicklei/go-restful github.com/emicklei/go-restful/log -# github.com/evanphx/json-patch v0.0.0-20190815234213-e83c0a1c26c8 +# github.com/evanphx/json-patch v4.9.0+incompatible github.com/evanphx/json-patch # github.com/go-logr/logr v0.2.0 github.com/go-logr/logr @@ -183,7 +183,7 @@ google.golang.org/protobuf/types/known/timestamppb gopkg.in/inf.v0 # gopkg.in/yaml.v2 v2.2.8 gopkg.in/yaml.v2 -# k8s.io/api v0.19.0-rc.4 +# k8s.io/api v0.19.0 ## explicit k8s.io/api/admissionregistration/v1 k8s.io/api/admissionregistration/v1beta1 @@ -226,7 +226,7 @@ k8s.io/api/settings/v1alpha1 k8s.io/api/storage/v1 k8s.io/api/storage/v1alpha1 k8s.io/api/storage/v1beta1 -# k8s.io/apimachinery v0.19.0-rc.4 +# k8s.io/apimachinery v0.19.0 ## explicit k8s.io/apimachinery/pkg/api/errors k8s.io/apimachinery/pkg/api/meta @@ -270,10 +270,10 @@ k8s.io/apimachinery/pkg/version k8s.io/apimachinery/pkg/watch k8s.io/apimachinery/third_party/forked/golang/json k8s.io/apimachinery/third_party/forked/golang/reflect -# k8s.io/apiserver v0.19.0-rc.4 +# k8s.io/apiserver v0.19.0 ## explicit k8s.io/apiserver/pkg/util/feature -# k8s.io/client-go v0.19.0-rc.4 +# k8s.io/client-go v0.19.0 ## explicit k8s.io/client-go/discovery k8s.io/client-go/discovery/fake @@ -490,7 +490,7 @@ k8s.io/client-go/util/homedir k8s.io/client-go/util/jsonpath k8s.io/client-go/util/keyutil k8s.io/client-go/util/workqueue -# k8s.io/code-generator v0.19.0-rc.4 +# k8s.io/code-generator v0.19.0 ## explicit k8s.io/code-generator k8s.io/code-generator/cmd/client-gen @@ -525,7 +525,7 @@ k8s.io/code-generator/cmd/set-gen k8s.io/code-generator/pkg/namer k8s.io/code-generator/pkg/util k8s.io/code-generator/third_party/forked/golang/reflect -# k8s.io/component-base v0.19.0-rc.4 +# k8s.io/component-base v0.19.0 ## explicit k8s.io/component-base/cli/flag k8s.io/component-base/featuregate @@ -545,7 +545,7 @@ k8s.io/gengo/types # k8s.io/klog/v2 v2.2.0 ## explicit k8s.io/klog/v2 -# k8s.io/kube-openapi v0.0.0-20200427153329-656914f816f9 +# k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6 k8s.io/kube-openapi/cmd/openapi-gen/args k8s.io/kube-openapi/pkg/common k8s.io/kube-openapi/pkg/generators @@ -556,7 +556,7 @@ k8s.io/kube-openapi/pkg/util/sets k8s.io/utils/buffer k8s.io/utils/integer k8s.io/utils/trace -# sigs.k8s.io/structured-merge-diff/v3 v3.0.1-0.20200706213357-43c19bbb7fba -sigs.k8s.io/structured-merge-diff/v3/value +# sigs.k8s.io/structured-merge-diff/v4 v4.0.1 +sigs.k8s.io/structured-merge-diff/v4/value # sigs.k8s.io/yaml v1.2.0 sigs.k8s.io/yaml diff --git a/vendor/sigs.k8s.io/structured-merge-diff/v3/LICENSE b/vendor/sigs.k8s.io/structured-merge-diff/v4/LICENSE similarity index 100% rename from vendor/sigs.k8s.io/structured-merge-diff/v3/LICENSE rename to vendor/sigs.k8s.io/structured-merge-diff/v4/LICENSE diff --git a/vendor/sigs.k8s.io/structured-merge-diff/v3/value/allocator.go b/vendor/sigs.k8s.io/structured-merge-diff/v4/value/allocator.go similarity index 100% rename from vendor/sigs.k8s.io/structured-merge-diff/v3/value/allocator.go rename to vendor/sigs.k8s.io/structured-merge-diff/v4/value/allocator.go diff --git a/vendor/sigs.k8s.io/structured-merge-diff/v3/value/doc.go b/vendor/sigs.k8s.io/structured-merge-diff/v4/value/doc.go similarity index 100% rename from vendor/sigs.k8s.io/structured-merge-diff/v3/value/doc.go rename to vendor/sigs.k8s.io/structured-merge-diff/v4/value/doc.go diff --git a/vendor/sigs.k8s.io/structured-merge-diff/v3/value/fields.go b/vendor/sigs.k8s.io/structured-merge-diff/v4/value/fields.go similarity index 100% rename from vendor/sigs.k8s.io/structured-merge-diff/v3/value/fields.go rename to vendor/sigs.k8s.io/structured-merge-diff/v4/value/fields.go diff --git a/vendor/sigs.k8s.io/structured-merge-diff/v3/value/jsontagutil.go b/vendor/sigs.k8s.io/structured-merge-diff/v4/value/jsontagutil.go similarity index 100% rename from vendor/sigs.k8s.io/structured-merge-diff/v3/value/jsontagutil.go rename to vendor/sigs.k8s.io/structured-merge-diff/v4/value/jsontagutil.go diff --git a/vendor/sigs.k8s.io/structured-merge-diff/v3/value/list.go b/vendor/sigs.k8s.io/structured-merge-diff/v4/value/list.go similarity index 100% rename from vendor/sigs.k8s.io/structured-merge-diff/v3/value/list.go rename to vendor/sigs.k8s.io/structured-merge-diff/v4/value/list.go diff --git a/vendor/sigs.k8s.io/structured-merge-diff/v3/value/listreflect.go b/vendor/sigs.k8s.io/structured-merge-diff/v4/value/listreflect.go similarity index 100% rename from vendor/sigs.k8s.io/structured-merge-diff/v3/value/listreflect.go rename to vendor/sigs.k8s.io/structured-merge-diff/v4/value/listreflect.go diff --git a/vendor/sigs.k8s.io/structured-merge-diff/v3/value/listunstructured.go b/vendor/sigs.k8s.io/structured-merge-diff/v4/value/listunstructured.go similarity index 100% rename from vendor/sigs.k8s.io/structured-merge-diff/v3/value/listunstructured.go rename to vendor/sigs.k8s.io/structured-merge-diff/v4/value/listunstructured.go diff --git a/vendor/sigs.k8s.io/structured-merge-diff/v3/value/map.go b/vendor/sigs.k8s.io/structured-merge-diff/v4/value/map.go similarity index 100% rename from vendor/sigs.k8s.io/structured-merge-diff/v3/value/map.go rename to vendor/sigs.k8s.io/structured-merge-diff/v4/value/map.go diff --git a/vendor/sigs.k8s.io/structured-merge-diff/v3/value/mapreflect.go b/vendor/sigs.k8s.io/structured-merge-diff/v4/value/mapreflect.go similarity index 100% rename from vendor/sigs.k8s.io/structured-merge-diff/v3/value/mapreflect.go rename to vendor/sigs.k8s.io/structured-merge-diff/v4/value/mapreflect.go diff --git a/vendor/sigs.k8s.io/structured-merge-diff/v3/value/mapunstructured.go b/vendor/sigs.k8s.io/structured-merge-diff/v4/value/mapunstructured.go similarity index 100% rename from vendor/sigs.k8s.io/structured-merge-diff/v3/value/mapunstructured.go rename to vendor/sigs.k8s.io/structured-merge-diff/v4/value/mapunstructured.go diff --git a/vendor/sigs.k8s.io/structured-merge-diff/v3/value/reflectcache.go b/vendor/sigs.k8s.io/structured-merge-diff/v4/value/reflectcache.go similarity index 100% rename from vendor/sigs.k8s.io/structured-merge-diff/v3/value/reflectcache.go rename to vendor/sigs.k8s.io/structured-merge-diff/v4/value/reflectcache.go diff --git a/vendor/sigs.k8s.io/structured-merge-diff/v3/value/scalar.go b/vendor/sigs.k8s.io/structured-merge-diff/v4/value/scalar.go similarity index 100% rename from vendor/sigs.k8s.io/structured-merge-diff/v3/value/scalar.go rename to vendor/sigs.k8s.io/structured-merge-diff/v4/value/scalar.go diff --git a/vendor/sigs.k8s.io/structured-merge-diff/v3/value/structreflect.go b/vendor/sigs.k8s.io/structured-merge-diff/v4/value/structreflect.go similarity index 100% rename from vendor/sigs.k8s.io/structured-merge-diff/v3/value/structreflect.go rename to vendor/sigs.k8s.io/structured-merge-diff/v4/value/structreflect.go diff --git a/vendor/sigs.k8s.io/structured-merge-diff/v3/value/value.go b/vendor/sigs.k8s.io/structured-merge-diff/v4/value/value.go similarity index 100% rename from vendor/sigs.k8s.io/structured-merge-diff/v3/value/value.go rename to vendor/sigs.k8s.io/structured-merge-diff/v4/value/value.go diff --git a/vendor/sigs.k8s.io/structured-merge-diff/v3/value/valuereflect.go b/vendor/sigs.k8s.io/structured-merge-diff/v4/value/valuereflect.go similarity index 100% rename from vendor/sigs.k8s.io/structured-merge-diff/v3/value/valuereflect.go rename to vendor/sigs.k8s.io/structured-merge-diff/v4/value/valuereflect.go diff --git a/vendor/sigs.k8s.io/structured-merge-diff/v3/value/valueunstructured.go b/vendor/sigs.k8s.io/structured-merge-diff/v4/value/valueunstructured.go similarity index 100% rename from vendor/sigs.k8s.io/structured-merge-diff/v3/value/valueunstructured.go rename to vendor/sigs.k8s.io/structured-merge-diff/v4/value/valueunstructured.go