2016-04-25 19:48:47 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2016 The Kubernetes Authors .
2016-04-25 19:48:47 +00:00
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 eviction
import (
"fmt"
2016-05-13 03:35:18 +00:00
"sort"
2016-08-10 04:09:27 +00:00
"strconv"
2016-04-25 19:48:47 +00:00
"strings"
"time"
2016-05-13 03:35:18 +00:00
"github.com/golang/glog"
2017-01-25 13:13:07 +00:00
"k8s.io/apimachinery/pkg/api/resource"
2017-01-11 14:09:48 +00:00
"k8s.io/apimachinery/pkg/util/sets"
2016-04-25 19:48:47 +00:00
"k8s.io/kubernetes/pkg/api"
2016-11-18 20:50:58 +00:00
"k8s.io/kubernetes/pkg/api/v1"
2016-05-13 03:35:18 +00:00
statsapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/stats"
2017-03-02 01:56:24 +00:00
"k8s.io/kubernetes/pkg/kubelet/cm"
2017-02-10 05:14:10 +00:00
evictionapi "k8s.io/kubernetes/pkg/kubelet/eviction/api"
2016-06-21 01:28:42 +00:00
"k8s.io/kubernetes/pkg/kubelet/qos"
2016-05-13 03:35:18 +00:00
"k8s.io/kubernetes/pkg/kubelet/server/stats"
"k8s.io/kubernetes/pkg/quota/evaluator/core"
2016-04-25 19:48:47 +00:00
)
const (
unsupportedEvictionSignal = "unsupported eviction signal %v"
2016-05-13 03:35:18 +00:00
// the reason reported back in status.
reason = "Evicted"
2016-08-27 03:06:15 +00:00
// the message associated with the reason.
2016-11-04 16:30:02 +00:00
message = "The node was low on resource: %v."
2016-05-13 03:35:18 +00:00
// disk, in bytes. internal to this module, used to account for local disk usage.
2016-11-18 20:50:58 +00:00
resourceDisk v1 . ResourceName = "disk"
2016-08-09 21:34:11 +00:00
// inodes, number. internal to this module, used to account for local disk inode consumption.
2016-11-18 20:50:58 +00:00
resourceInodes v1 . ResourceName = "inodes"
2016-07-22 19:23:09 +00:00
// imagefs, in bytes. internal to this module, used to account for local image filesystem usage.
2016-11-18 20:50:58 +00:00
resourceImageFs v1 . ResourceName = "imagefs"
2016-08-09 21:34:11 +00:00
// imagefs inodes, number. internal to this module, used to account for local image filesystem inodes.
2016-11-18 20:50:58 +00:00
resourceImageFsInodes v1 . ResourceName = "imagefsInodes"
2016-07-22 19:23:09 +00:00
// nodefs, in bytes. internal to this module, used to account for local node root filesystem usage.
2016-11-18 20:50:58 +00:00
resourceNodeFs v1 . ResourceName = "nodefs"
2016-08-09 21:34:11 +00:00
// nodefs inodes, number. internal to this module, used to account for local node root filesystem inodes.
2016-11-18 20:50:58 +00:00
resourceNodeFsInodes v1 . ResourceName = "nodefsInodes"
2016-04-25 19:48:47 +00:00
)
2016-08-03 20:55:52 +00:00
var (
// signalToNodeCondition maps a signal to the node condition to report if threshold is met.
2017-02-10 05:14:10 +00:00
signalToNodeCondition map [ evictionapi . Signal ] v1 . NodeConditionType
2016-08-03 20:55:52 +00:00
// signalToResource maps a Signal to its associated Resource.
2017-02-10 05:14:10 +00:00
signalToResource map [ evictionapi . Signal ] v1 . ResourceName
2016-08-03 20:55:52 +00:00
// resourceToSignal maps a Resource to its associated Signal
2017-02-10 05:14:10 +00:00
resourceToSignal map [ v1 . ResourceName ] evictionapi . Signal
2016-08-03 20:55:52 +00:00
)
func init ( ) {
// map eviction signals to node conditions
2017-02-10 05:14:10 +00:00
signalToNodeCondition = map [ evictionapi . Signal ] v1 . NodeConditionType { }
signalToNodeCondition [ evictionapi . SignalMemoryAvailable ] = v1 . NodeMemoryPressure
2017-03-02 01:56:24 +00:00
signalToNodeCondition [ evictionapi . SignalAllocatableMemoryAvailable ] = v1 . NodeMemoryPressure
2017-02-10 05:14:10 +00:00
signalToNodeCondition [ evictionapi . SignalImageFsAvailable ] = v1 . NodeDiskPressure
signalToNodeCondition [ evictionapi . SignalNodeFsAvailable ] = v1 . NodeDiskPressure
signalToNodeCondition [ evictionapi . SignalImageFsInodesFree ] = v1 . NodeDiskPressure
signalToNodeCondition [ evictionapi . SignalNodeFsInodesFree ] = v1 . NodeDiskPressure
2016-05-13 03:35:18 +00:00
2016-08-03 20:55:52 +00:00
// map signals to resources (and vice-versa)
2017-02-10 05:14:10 +00:00
signalToResource = map [ evictionapi . Signal ] v1 . ResourceName { }
signalToResource [ evictionapi . SignalMemoryAvailable ] = v1 . ResourceMemory
2017-03-02 01:56:24 +00:00
signalToResource [ evictionapi . SignalAllocatableMemoryAvailable ] = v1 . ResourceMemory
2017-02-10 05:14:10 +00:00
signalToResource [ evictionapi . SignalImageFsAvailable ] = resourceImageFs
signalToResource [ evictionapi . SignalImageFsInodesFree ] = resourceImageFsInodes
signalToResource [ evictionapi . SignalNodeFsAvailable ] = resourceNodeFs
signalToResource [ evictionapi . SignalNodeFsInodesFree ] = resourceNodeFsInodes
resourceToSignal = map [ v1 . ResourceName ] evictionapi . Signal { }
2016-08-03 20:55:52 +00:00
for key , value := range signalToResource {
resourceToSignal [ value ] = key
}
2016-04-25 19:48:47 +00:00
}
// validSignal returns true if the signal is supported.
2017-02-10 05:14:10 +00:00
func validSignal ( signal evictionapi . Signal ) bool {
2016-04-25 19:48:47 +00:00
_ , found := signalToResource [ signal ]
return found
}
// ParseThresholdConfig parses the flags for thresholds.
2017-03-02 01:56:24 +00:00
func ParseThresholdConfig ( allocatableConfig [ ] string , evictionHard , evictionSoft , evictionSoftGracePeriod , evictionMinimumReclaim string ) ( [ ] evictionapi . Threshold , error ) {
2017-02-10 05:14:10 +00:00
results := [ ] evictionapi . Threshold { }
2017-03-02 01:56:24 +00:00
allocatableThresholds := getAllocatableThreshold ( allocatableConfig )
results = append ( results , allocatableThresholds ... )
2016-04-25 19:48:47 +00:00
hardThresholds , err := parseThresholdStatements ( evictionHard )
if err != nil {
return nil , err
}
results = append ( results , hardThresholds ... )
softThresholds , err := parseThresholdStatements ( evictionSoft )
if err != nil {
return nil , err
}
gracePeriods , err := parseGracePeriods ( evictionSoftGracePeriod )
if err != nil {
return nil , err
}
2016-07-21 20:45:57 +00:00
minReclaims , err := parseMinimumReclaims ( evictionMinimumReclaim )
if err != nil {
return nil , err
}
2016-04-25 19:48:47 +00:00
for i := range softThresholds {
signal := softThresholds [ i ] . Signal
period , found := gracePeriods [ signal ]
if ! found {
return nil , fmt . Errorf ( "grace period must be specified for the soft eviction threshold %v" , signal )
}
softThresholds [ i ] . GracePeriod = period
}
results = append ( results , softThresholds ... )
2016-07-21 20:45:57 +00:00
for i := range results {
for signal , minReclaim := range minReclaims {
if results [ i ] . Signal == signal {
results [ i ] . MinReclaim = & minReclaim
break
}
}
}
2016-04-25 19:48:47 +00:00
return results , nil
}
// parseThresholdStatements parses the input statements into a list of Threshold objects.
2017-02-10 05:14:10 +00:00
func parseThresholdStatements ( expr string ) ( [ ] evictionapi . Threshold , error ) {
2016-04-25 19:48:47 +00:00
if len ( expr ) == 0 {
return nil , nil
}
2017-02-10 05:14:10 +00:00
results := [ ] evictionapi . Threshold { }
2016-04-25 19:48:47 +00:00
statements := strings . Split ( expr , "," )
signalsFound := sets . NewString ( )
for _ , statement := range statements {
result , err := parseThresholdStatement ( statement )
if err != nil {
return nil , err
}
if signalsFound . Has ( string ( result . Signal ) ) {
return nil , fmt . Errorf ( "found duplicate eviction threshold for signal %v" , result . Signal )
}
signalsFound . Insert ( string ( result . Signal ) )
results = append ( results , result )
}
return results , nil
}
// parseThresholdStatement parses a threshold statement.
2017-02-10 05:14:10 +00:00
func parseThresholdStatement ( statement string ) ( evictionapi . Threshold , error ) {
tokens2Operator := map [ string ] evictionapi . ThresholdOperator {
"<" : evictionapi . OpLessThan ,
2016-04-25 19:48:47 +00:00
}
var (
2017-02-10 05:14:10 +00:00
operator evictionapi . ThresholdOperator
2016-04-25 19:48:47 +00:00
parts [ ] string
)
for token := range tokens2Operator {
parts = strings . Split ( statement , token )
// if we got a token, we know this was the operator...
if len ( parts ) > 1 {
operator = tokens2Operator [ token ]
break
}
}
if len ( operator ) == 0 || len ( parts ) != 2 {
2017-02-10 05:14:10 +00:00
return evictionapi . Threshold { } , fmt . Errorf ( "invalid eviction threshold syntax %v, expected <signal><operator><value>" , statement )
2016-04-25 19:48:47 +00:00
}
2017-02-10 05:14:10 +00:00
signal := evictionapi . Signal ( parts [ 0 ] )
2016-04-25 19:48:47 +00:00
if ! validSignal ( signal ) {
2017-02-10 05:14:10 +00:00
return evictionapi . Threshold { } , fmt . Errorf ( unsupportedEvictionSignal , signal )
2016-04-25 19:48:47 +00:00
}
2016-08-10 04:09:27 +00:00
quantityValue := parts [ 1 ]
if strings . HasSuffix ( quantityValue , "%" ) {
percentage , err := parsePercentage ( quantityValue )
if err != nil {
2017-02-10 05:14:10 +00:00
return evictionapi . Threshold { } , err
2016-08-10 04:09:27 +00:00
}
if percentage <= 0 {
2017-02-10 05:14:10 +00:00
return evictionapi . Threshold { } , fmt . Errorf ( "eviction percentage threshold %v must be positive: %s" , signal , quantityValue )
2016-08-10 04:09:27 +00:00
}
2017-02-10 05:14:10 +00:00
return evictionapi . Threshold {
2016-08-10 04:09:27 +00:00
Signal : signal ,
Operator : operator ,
2017-02-10 05:14:10 +00:00
Value : evictionapi . ThresholdValue {
2016-08-10 04:09:27 +00:00
Percentage : percentage ,
} ,
} , nil
2016-04-25 19:48:47 +00:00
}
2016-08-09 21:34:11 +00:00
quantity , err := resource . ParseQuantity ( quantityValue )
if err != nil {
2017-02-10 05:14:10 +00:00
return evictionapi . Threshold { } , err
2016-08-09 21:34:11 +00:00
}
if quantity . Sign ( ) < 0 || quantity . IsZero ( ) {
2017-02-10 05:14:10 +00:00
return evictionapi . Threshold { } , fmt . Errorf ( "eviction threshold %v must be positive: %s" , signal , & quantity )
2016-08-09 21:34:11 +00:00
}
2017-02-10 05:14:10 +00:00
return evictionapi . Threshold {
2016-08-09 21:34:11 +00:00
Signal : signal ,
Operator : operator ,
2017-02-10 05:14:10 +00:00
Value : evictionapi . ThresholdValue {
2016-08-09 21:34:11 +00:00
Quantity : & quantity ,
} ,
} , nil
2016-08-10 04:09:27 +00:00
}
2017-03-02 01:56:24 +00:00
// getAllocatableThreshold returns the thresholds applicable for the allocatable configuration
func getAllocatableThreshold ( allocatableConfig [ ] string ) [ ] evictionapi . Threshold {
for _ , key := range allocatableConfig {
if key == cm . NodeAllocatableEnforcementKey {
return [ ] evictionapi . Threshold {
{
Signal : evictionapi . SignalAllocatableMemoryAvailable ,
Operator : evictionapi . OpLessThan ,
Value : evictionapi . ThresholdValue {
Quantity : resource . NewQuantity ( int64 ( 0 ) , resource . BinarySI ) ,
} ,
MinReclaim : & evictionapi . ThresholdValue {
Quantity : resource . NewQuantity ( int64 ( 0 ) , resource . BinarySI ) ,
} ,
} ,
}
}
}
return [ ] evictionapi . Threshold { }
}
2016-08-10 04:09:27 +00:00
// parsePercentage parses a string representing a percentage value
func parsePercentage ( input string ) ( float32 , error ) {
value , err := strconv . ParseFloat ( strings . TrimRight ( input , "%" ) , 32 )
if err != nil {
return 0 , err
2016-07-26 15:39:30 +00:00
}
2016-08-10 04:09:27 +00:00
return float32 ( value ) / 100 , nil
2016-04-25 19:48:47 +00:00
}
// parseGracePeriods parses the grace period statements
2017-02-10 05:14:10 +00:00
func parseGracePeriods ( expr string ) ( map [ evictionapi . Signal ] time . Duration , error ) {
2016-04-25 19:48:47 +00:00
if len ( expr ) == 0 {
return nil , nil
}
2017-02-10 05:14:10 +00:00
results := map [ evictionapi . Signal ] time . Duration { }
2016-04-25 19:48:47 +00:00
statements := strings . Split ( expr , "," )
for _ , statement := range statements {
parts := strings . Split ( statement , "=" )
if len ( parts ) != 2 {
return nil , fmt . Errorf ( "invalid eviction grace period syntax %v, expected <signal>=<duration>" , statement )
}
2017-02-10 05:14:10 +00:00
signal := evictionapi . Signal ( parts [ 0 ] )
2016-04-25 19:48:47 +00:00
if ! validSignal ( signal ) {
return nil , fmt . Errorf ( unsupportedEvictionSignal , signal )
}
gracePeriod , err := time . ParseDuration ( parts [ 1 ] )
if err != nil {
return nil , err
}
if gracePeriod < 0 {
return nil , fmt . Errorf ( "invalid eviction grace period specified: %v, must be a positive value" , parts [ 1 ] )
}
// check against duplicate statements
if _ , found := results [ signal ] ; found {
return nil , fmt . Errorf ( "duplicate eviction grace period specified for %v" , signal )
}
results [ signal ] = gracePeriod
}
return results , nil
}
2016-05-13 03:35:18 +00:00
2016-07-21 20:45:57 +00:00
// parseMinimumReclaims parses the minimum reclaim statements
2017-02-10 05:14:10 +00:00
func parseMinimumReclaims ( expr string ) ( map [ evictionapi . Signal ] evictionapi . ThresholdValue , error ) {
2016-07-21 20:45:57 +00:00
if len ( expr ) == 0 {
return nil , nil
}
2017-02-10 05:14:10 +00:00
results := map [ evictionapi . Signal ] evictionapi . ThresholdValue { }
2016-07-21 20:45:57 +00:00
statements := strings . Split ( expr , "," )
for _ , statement := range statements {
parts := strings . Split ( statement , "=" )
if len ( parts ) != 2 {
2016-09-23 20:36:03 +00:00
return nil , fmt . Errorf ( "invalid eviction minimum reclaim syntax: %v, expected <signal>=<value>" , statement )
2016-07-21 20:45:57 +00:00
}
2017-02-10 05:14:10 +00:00
signal := evictionapi . Signal ( parts [ 0 ] )
2016-07-21 20:45:57 +00:00
if ! validSignal ( signal ) {
return nil , fmt . Errorf ( unsupportedEvictionSignal , signal )
}
2016-09-23 20:36:03 +00:00
quantityValue := parts [ 1 ]
if strings . HasSuffix ( quantityValue , "%" ) {
percentage , err := parsePercentage ( quantityValue )
if err != nil {
return nil , err
}
if percentage <= 0 {
return nil , fmt . Errorf ( "eviction percentage minimum reclaim %v must be positive: %s" , signal , quantityValue )
}
// check against duplicate statements
if _ , found := results [ signal ] ; found {
return nil , fmt . Errorf ( "duplicate eviction minimum reclaim specified for %v" , signal )
}
2017-02-10 05:14:10 +00:00
results [ signal ] = evictionapi . ThresholdValue {
2016-09-23 20:36:03 +00:00
Percentage : percentage ,
}
continue
}
2016-07-21 20:45:57 +00:00
// check against duplicate statements
if _ , found := results [ signal ] ; found {
return nil , fmt . Errorf ( "duplicate eviction minimum reclaim specified for %v" , signal )
}
quantity , err := resource . ParseQuantity ( parts [ 1 ] )
if quantity . Sign ( ) < 0 {
return nil , fmt . Errorf ( "negative eviction minimum reclaim specified for %v" , signal )
}
if err != nil {
return nil , err
}
2017-02-10 05:14:10 +00:00
results [ signal ] = evictionapi . ThresholdValue {
2016-09-23 20:36:03 +00:00
Quantity : & quantity ,
}
2016-07-21 20:45:57 +00:00
}
return results , nil
}
2016-05-13 03:35:18 +00:00
// diskUsage converts used bytes into a resource quantity.
func diskUsage ( fsStats * statsapi . FsStats ) * resource . Quantity {
if fsStats == nil || fsStats . UsedBytes == nil {
return & resource . Quantity { Format : resource . BinarySI }
}
usage := int64 ( * fsStats . UsedBytes )
return resource . NewQuantity ( usage , resource . BinarySI )
}
2016-08-09 21:34:11 +00:00
// inodeUsage converts inodes consumed into a resource quantity.
func inodeUsage ( fsStats * statsapi . FsStats ) * resource . Quantity {
2016-10-27 20:56:55 +00:00
if fsStats == nil || fsStats . InodesUsed == nil {
return & resource . Quantity { Format : resource . BinarySI }
}
usage := int64 ( * fsStats . InodesUsed )
return resource . NewQuantity ( usage , resource . BinarySI )
2016-08-09 21:34:11 +00:00
}
2016-05-13 03:35:18 +00:00
// memoryUsage converts working set into a resource quantity.
func memoryUsage ( memStats * statsapi . MemoryStats ) * resource . Quantity {
if memStats == nil || memStats . WorkingSetBytes == nil {
return & resource . Quantity { Format : resource . BinarySI }
}
usage := int64 ( * memStats . WorkingSetBytes )
return resource . NewQuantity ( usage , resource . BinarySI )
}
2016-07-22 19:23:09 +00:00
// localVolumeNames returns the set of volumes for the pod that are local
2016-07-26 02:40:22 +00:00
// TODO: sumamry API should report what volumes consume local storage rather than hard-code here.
2016-11-18 20:50:58 +00:00
func localVolumeNames ( pod * v1 . Pod ) [ ] string {
2016-07-22 19:23:09 +00:00
result := [ ] string { }
for _ , volume := range pod . Spec . Volumes {
if volume . HostPath != nil ||
2016-11-18 20:50:58 +00:00
( volume . EmptyDir != nil && volume . EmptyDir . Medium != v1 . StorageMediumMemory ) ||
2016-07-22 19:23:09 +00:00
volume . ConfigMap != nil ||
volume . GitRepo != nil {
result = append ( result , volume . Name )
}
}
return result
}
2016-08-09 21:34:11 +00:00
// podDiskUsage aggregates pod disk usage and inode consumption for the specified stats to measure.
2016-11-18 20:50:58 +00:00
func podDiskUsage ( podStats statsapi . PodStats , pod * v1 . Pod , statsToMeasure [ ] fsStatsType ) ( v1 . ResourceList , error ) {
2016-07-22 19:23:09 +00:00
disk := resource . Quantity { Format : resource . BinarySI }
2016-08-09 21:34:11 +00:00
inodes := resource . Quantity { Format : resource . BinarySI }
2016-07-22 19:23:09 +00:00
for _ , container := range podStats . Containers {
2016-07-26 02:40:22 +00:00
if hasFsStatsType ( statsToMeasure , fsStatsRoot ) {
2016-07-22 19:23:09 +00:00
disk . Add ( * diskUsage ( container . Rootfs ) )
2016-08-09 21:34:11 +00:00
inodes . Add ( * inodeUsage ( container . Rootfs ) )
2016-07-22 19:23:09 +00:00
}
2016-07-26 02:40:22 +00:00
if hasFsStatsType ( statsToMeasure , fsStatsLogs ) {
2016-07-22 19:23:09 +00:00
disk . Add ( * diskUsage ( container . Logs ) )
2016-08-09 21:34:11 +00:00
inodes . Add ( * inodeUsage ( container . Logs ) )
2016-07-22 19:23:09 +00:00
}
}
2016-07-26 02:40:22 +00:00
if hasFsStatsType ( statsToMeasure , fsStatsLocalVolumeSource ) {
2016-07-22 19:23:09 +00:00
volumeNames := localVolumeNames ( pod )
for _ , volumeName := range volumeNames {
for _ , volumeStats := range podStats . VolumeStats {
if volumeStats . Name == volumeName {
disk . Add ( * diskUsage ( & volumeStats . FsStats ) )
2016-08-09 21:34:11 +00:00
inodes . Add ( * inodeUsage ( & volumeStats . FsStats ) )
2016-07-26 02:40:22 +00:00
break
2016-07-22 19:23:09 +00:00
}
}
}
}
2016-11-18 20:50:58 +00:00
return v1 . ResourceList {
2016-08-09 21:34:11 +00:00
resourceDisk : disk ,
resourceInodes : inodes ,
2016-07-22 19:23:09 +00:00
} , nil
}
// podMemoryUsage aggregates pod memory usage.
2016-11-18 20:50:58 +00:00
func podMemoryUsage ( podStats statsapi . PodStats ) ( v1 . ResourceList , error ) {
2016-05-13 03:35:18 +00:00
disk := resource . Quantity { Format : resource . BinarySI }
memory := resource . Quantity { Format : resource . BinarySI }
for _ , container := range podStats . Containers {
// disk usage (if known)
for _ , fsStats := range [ ] * statsapi . FsStats { container . Rootfs , container . Logs } {
2016-05-18 14:27:59 +00:00
disk . Add ( * diskUsage ( fsStats ) )
2016-05-13 03:35:18 +00:00
}
// memory usage (if known)
2016-05-18 14:27:59 +00:00
memory . Add ( * memoryUsage ( container . Memory ) )
2016-05-13 03:35:18 +00:00
}
2016-11-18 20:50:58 +00:00
return v1 . ResourceList {
v1 . ResourceMemory : memory ,
resourceDisk : disk ,
2016-05-13 03:35:18 +00:00
} , nil
}
// formatThreshold formats a threshold for logging.
2017-02-10 05:14:10 +00:00
func formatThreshold ( threshold evictionapi . Threshold ) string {
return fmt . Sprintf ( "threshold(signal=%v, operator=%v, value=%v, gracePeriod=%v)" , threshold . Signal , threshold . Operator , evictionapi . ThresholdValue ( threshold . Value ) , threshold . GracePeriod )
2016-08-10 04:09:27 +00:00
}
2017-02-10 05:14:10 +00:00
// formatevictionapi.ThresholdValue formats a thresholdValue for logging.
func formatThresholdValue ( value evictionapi . ThresholdValue ) string {
2016-08-10 04:09:27 +00:00
if value . Quantity != nil {
return value . Quantity . String ( )
}
return fmt . Sprintf ( "%f%%" , value . Percentage * float32 ( 100 ) )
2016-05-13 03:35:18 +00:00
}
// cachedStatsFunc returns a statsFunc based on the provided pod stats.
func cachedStatsFunc ( podStats [ ] statsapi . PodStats ) statsFunc {
uid2PodStats := map [ string ] statsapi . PodStats { }
for i := range podStats {
uid2PodStats [ podStats [ i ] . PodRef . UID ] = podStats [ i ]
}
2016-11-18 20:50:58 +00:00
return func ( pod * v1 . Pod ) ( statsapi . PodStats , bool ) {
2016-05-13 03:35:18 +00:00
stats , found := uid2PodStats [ string ( pod . UID ) ]
return stats , found
}
}
// Cmp compares p1 and p2 and returns:
//
// -1 if p1 < p2
// 0 if p1 == p2
// +1 if p1 > p2
//
2016-11-18 20:50:58 +00:00
type cmpFunc func ( p1 , p2 * v1 . Pod ) int
2016-05-13 03:35:18 +00:00
// multiSorter implements the Sort interface, sorting changes within.
type multiSorter struct {
2016-11-18 20:50:58 +00:00
pods [ ] * v1 . Pod
2016-05-13 03:35:18 +00:00
cmp [ ] cmpFunc
}
// Sort sorts the argument slice according to the less functions passed to OrderedBy.
2016-11-18 20:50:58 +00:00
func ( ms * multiSorter ) Sort ( pods [ ] * v1 . Pod ) {
2016-05-13 03:35:18 +00:00
ms . pods = pods
sort . Sort ( ms )
}
// OrderedBy returns a Sorter that sorts using the cmp functions, in order.
// Call its Sort method to sort the data.
func orderedBy ( cmp ... cmpFunc ) * multiSorter {
return & multiSorter {
cmp : cmp ,
}
}
// Len is part of sort.Interface.
func ( ms * multiSorter ) Len ( ) int {
return len ( ms . pods )
}
// Swap is part of sort.Interface.
func ( ms * multiSorter ) Swap ( i , j int ) {
ms . pods [ i ] , ms . pods [ j ] = ms . pods [ j ] , ms . pods [ i ]
}
// Less is part of sort.Interface.
func ( ms * multiSorter ) Less ( i , j int ) bool {
p1 , p2 := ms . pods [ i ] , ms . pods [ j ]
var k int
for k = 0 ; k < len ( ms . cmp ) - 1 ; k ++ {
cmpResult := ms . cmp [ k ] ( p1 , p2 )
// p1 is less than p2
if cmpResult < 0 {
return true
}
// p1 is greater than p2
if cmpResult > 0 {
return false
}
// we don't know yet
}
// the last cmp func is the final decider
return ms . cmp [ k ] ( p1 , p2 ) < 0
}
2016-06-26 23:08:18 +00:00
// qosComparator compares pods by QoS (BestEffort < Burstable < Guaranteed)
2016-11-18 20:50:58 +00:00
func qosComparator ( p1 , p2 * v1 . Pod ) int {
2016-06-26 23:08:18 +00:00
qosP1 := qos . GetPodQOS ( p1 )
qosP2 := qos . GetPodQOS ( p2 )
2016-05-13 03:35:18 +00:00
// its a tie
if qosP1 == qosP2 {
return 0
}
// if p1 is best effort, we know p2 is burstable or guaranteed
2016-12-19 21:02:01 +00:00
if qosP1 == v1 . PodQOSBestEffort {
2016-05-13 03:35:18 +00:00
return - 1
}
// we know p1 and p2 are not besteffort, so if p1 is burstable, p2 must be guaranteed
2016-12-19 21:02:01 +00:00
if qosP1 == v1 . PodQOSBurstable {
if qosP2 == v1 . PodQOSGuaranteed {
2016-05-13 03:35:18 +00:00
return - 1
}
return 1
}
// ok, p1 must be guaranteed.
return 1
}
// memory compares pods by largest consumer of memory relative to request.
func memory ( stats statsFunc ) cmpFunc {
2016-11-18 20:50:58 +00:00
return func ( p1 , p2 * v1 . Pod ) int {
2016-05-13 03:35:18 +00:00
p1Stats , found := stats ( p1 )
// if we have no usage stats for p1, we want p2 first
if ! found {
return - 1
}
// if we have no usage stats for p2, but p1 has usage, we want p1 first.
p2Stats , found := stats ( p2 )
if ! found {
return 1
}
// if we cant get usage for p1 measured, we want p2 first
2016-07-22 19:23:09 +00:00
p1Usage , err := podMemoryUsage ( p1Stats )
2016-05-13 03:35:18 +00:00
if err != nil {
return - 1
}
// if we cant get usage for p2 measured, we want p1 first
2016-07-22 19:23:09 +00:00
p2Usage , err := podMemoryUsage ( p2Stats )
2016-05-13 03:35:18 +00:00
if err != nil {
return 1
}
// adjust p1, p2 usage relative to the request (if any)
2016-11-18 20:50:58 +00:00
p1Memory := p1Usage [ v1 . ResourceMemory ]
2016-12-07 20:44:16 +00:00
p1Spec , err := core . PodUsageFunc ( p1 )
if err != nil {
return - 1
}
2016-05-13 03:35:18 +00:00
p1Request := p1Spec [ api . ResourceRequestsMemory ]
p1Memory . Sub ( p1Request )
2016-11-18 20:50:58 +00:00
p2Memory := p2Usage [ v1 . ResourceMemory ]
2016-12-07 20:44:16 +00:00
p2Spec , err := core . PodUsageFunc ( p2 )
if err != nil {
return 1
}
2016-05-13 03:35:18 +00:00
p2Request := p2Spec [ api . ResourceRequestsMemory ]
p2Memory . Sub ( p2Request )
// if p2 is using more than p1, we want p2 first
return p2Memory . Cmp ( p1Memory )
}
}
2016-08-09 21:34:11 +00:00
// disk compares pods by largest consumer of disk relative to request for the specified disk resource.
2016-11-18 20:50:58 +00:00
func disk ( stats statsFunc , fsStatsToMeasure [ ] fsStatsType , diskResource v1 . ResourceName ) cmpFunc {
return func ( p1 , p2 * v1 . Pod ) int {
2016-05-13 03:35:18 +00:00
p1Stats , found := stats ( p1 )
// if we have no usage stats for p1, we want p2 first
if ! found {
return - 1
}
// if we have no usage stats for p2, but p1 has usage, we want p1 first.
p2Stats , found := stats ( p2 )
if ! found {
return 1
}
// if we cant get usage for p1 measured, we want p2 first
2016-07-22 19:23:09 +00:00
p1Usage , err := podDiskUsage ( p1Stats , p1 , fsStatsToMeasure )
2016-05-13 03:35:18 +00:00
if err != nil {
return - 1
}
// if we cant get usage for p2 measured, we want p1 first
2016-07-22 19:23:09 +00:00
p2Usage , err := podDiskUsage ( p2Stats , p2 , fsStatsToMeasure )
2016-05-13 03:35:18 +00:00
if err != nil {
return 1
}
// disk is best effort, so we don't measure relative to a request.
// TODO: add disk as a guaranteed resource
2016-08-09 21:34:11 +00:00
p1Disk := p1Usage [ diskResource ]
p2Disk := p2Usage [ diskResource ]
2016-05-13 03:35:18 +00:00
// if p2 is using more than p1, we want p2 first
return p2Disk . Cmp ( p1Disk )
}
}
// rankMemoryPressure orders the input pods for eviction in response to memory pressure.
2016-11-18 20:50:58 +00:00
func rankMemoryPressure ( pods [ ] * v1 . Pod , stats statsFunc ) {
2016-06-21 01:28:42 +00:00
orderedBy ( qosComparator , memory ( stats ) ) . Sort ( pods )
2016-05-13 03:35:18 +00:00
}
2016-07-22 19:23:09 +00:00
// rankDiskPressureFunc returns a rankFunc that measures the specified fs stats.
2016-11-18 20:50:58 +00:00
func rankDiskPressureFunc ( fsStatsToMeasure [ ] fsStatsType , diskResource v1 . ResourceName ) rankFunc {
return func ( pods [ ] * v1 . Pod , stats statsFunc ) {
2016-08-09 21:34:11 +00:00
orderedBy ( qosComparator , disk ( stats , fsStatsToMeasure , diskResource ) ) . Sort ( pods )
2016-07-22 19:23:09 +00:00
}
2016-05-13 03:35:18 +00:00
}
2016-11-18 20:50:58 +00:00
// byEvictionPriority implements sort.Interface for []v1.ResourceName.
type byEvictionPriority [ ] v1 . ResourceName
2016-05-13 03:35:18 +00:00
func ( a byEvictionPriority ) Len ( ) int { return len ( a ) }
func ( a byEvictionPriority ) Swap ( i , j int ) { a [ i ] , a [ j ] = a [ j ] , a [ i ] }
// Less ranks memory before all other resources.
func ( a byEvictionPriority ) Less ( i , j int ) bool {
2016-11-18 20:50:58 +00:00
return a [ i ] == v1 . ResourceMemory
2016-05-13 03:35:18 +00:00
}
// makeSignalObservations derives observations using the specified summary provider.
2017-03-02 01:56:24 +00:00
func makeSignalObservations ( summaryProvider stats . SummaryProvider , nodeProvider NodeProvider ) ( signalObservations , statsFunc , error ) {
2016-05-13 03:35:18 +00:00
summary , err := summaryProvider . Get ( )
if err != nil {
return nil , nil , err
}
2017-03-02 01:56:24 +00:00
node , err := nodeProvider . GetNode ( )
if err != nil {
return nil , nil , err
}
2016-08-09 21:34:11 +00:00
2016-05-13 03:35:18 +00:00
// build the function to work against for pod stats
statsFunc := cachedStatsFunc ( summary . Pods )
// build an evaluation context for current eviction signals
result := signalObservations { }
2016-07-22 19:23:09 +00:00
2016-08-10 04:09:27 +00:00
if memory := summary . Node . Memory ; memory != nil && memory . AvailableBytes != nil && memory . WorkingSetBytes != nil {
2017-02-10 05:14:10 +00:00
result [ evictionapi . SignalMemoryAvailable ] = signalObservation {
2016-08-10 04:09:27 +00:00
available : resource . NewQuantity ( int64 ( * memory . AvailableBytes ) , resource . BinarySI ) ,
capacity : resource . NewQuantity ( int64 ( * memory . AvailableBytes + * memory . WorkingSetBytes ) , resource . BinarySI ) ,
2016-09-14 20:46:20 +00:00
time : memory . Time ,
2016-08-10 04:09:27 +00:00
}
2016-07-22 19:23:09 +00:00
}
2016-08-09 21:34:11 +00:00
if nodeFs := summary . Node . Fs ; nodeFs != nil {
if nodeFs . AvailableBytes != nil && nodeFs . CapacityBytes != nil {
2017-02-10 05:14:10 +00:00
result [ evictionapi . SignalNodeFsAvailable ] = signalObservation {
2016-08-09 21:34:11 +00:00
available : resource . NewQuantity ( int64 ( * nodeFs . AvailableBytes ) , resource . BinarySI ) ,
capacity : resource . NewQuantity ( int64 ( * nodeFs . CapacityBytes ) , resource . BinarySI ) ,
2016-09-20 19:47:04 +00:00
time : nodeFs . Time ,
2016-08-09 21:34:11 +00:00
}
}
if nodeFs . InodesFree != nil && nodeFs . Inodes != nil {
2017-02-10 05:14:10 +00:00
result [ evictionapi . SignalNodeFsInodesFree ] = signalObservation {
2016-08-09 21:34:11 +00:00
available : resource . NewQuantity ( int64 ( * nodeFs . InodesFree ) , resource . BinarySI ) ,
capacity : resource . NewQuantity ( int64 ( * nodeFs . Inodes ) , resource . BinarySI ) ,
2016-09-20 19:47:04 +00:00
time : nodeFs . Time ,
2016-08-09 21:34:11 +00:00
}
2016-08-10 04:09:27 +00:00
}
2016-07-22 19:23:09 +00:00
}
if summary . Node . Runtime != nil {
2016-08-09 21:34:11 +00:00
if imageFs := summary . Node . Runtime . ImageFs ; imageFs != nil {
if imageFs . AvailableBytes != nil && imageFs . CapacityBytes != nil {
2017-02-10 05:14:10 +00:00
result [ evictionapi . SignalImageFsAvailable ] = signalObservation {
2016-08-09 21:34:11 +00:00
available : resource . NewQuantity ( int64 ( * imageFs . AvailableBytes ) , resource . BinarySI ) ,
capacity : resource . NewQuantity ( int64 ( * imageFs . CapacityBytes ) , resource . BinarySI ) ,
2016-09-20 19:47:04 +00:00
time : imageFs . Time ,
2016-08-09 21:34:11 +00:00
}
if imageFs . InodesFree != nil && imageFs . Inodes != nil {
2017-02-10 05:14:10 +00:00
result [ evictionapi . SignalImageFsInodesFree ] = signalObservation {
2016-08-09 21:34:11 +00:00
available : resource . NewQuantity ( int64 ( * imageFs . InodesFree ) , resource . BinarySI ) ,
capacity : resource . NewQuantity ( int64 ( * imageFs . Inodes ) , resource . BinarySI ) ,
2016-09-20 19:47:04 +00:00
time : imageFs . Time ,
2016-08-09 21:34:11 +00:00
}
}
2016-08-10 04:09:27 +00:00
}
2016-07-22 19:23:09 +00:00
}
}
2017-03-02 01:56:24 +00:00
if memoryAllocatableCapacity , ok := node . Status . Allocatable [ v1 . ResourceMemory ] ; ok {
memoryAllocatableAvailable := memoryAllocatableCapacity . Copy ( )
for _ , pod := range summary . Pods {
mu , err := podMemoryUsage ( pod )
if err == nil {
memoryAllocatableAvailable . Sub ( mu [ v1 . ResourceMemory ] )
}
}
result [ evictionapi . SignalAllocatableMemoryAvailable ] = signalObservation {
available : memoryAllocatableAvailable ,
capacity : memoryAllocatableCapacity . Copy ( ) ,
}
}
2016-05-13 03:35:18 +00:00
return result , statsFunc , nil
}
// thresholdsMet returns the set of thresholds that were met independent of grace period
2017-02-10 05:14:10 +00:00
func thresholdsMet ( thresholds [ ] evictionapi . Threshold , observations signalObservations , enforceMinReclaim bool ) [ ] evictionapi . Threshold {
results := [ ] evictionapi . Threshold { }
2016-05-13 03:35:18 +00:00
for i := range thresholds {
threshold := thresholds [ i ]
observed , found := observations [ threshold . Signal ]
if ! found {
glog . Warningf ( "eviction manager: no observation found for eviction signal %v" , threshold . Signal )
continue
}
// determine if we have met the specified threshold
thresholdMet := false
2017-02-10 05:14:10 +00:00
quantity := evictionapi . GetThresholdQuantity ( threshold . Value , observed . capacity )
2016-08-03 02:58:48 +00:00
// if enforceMinReclaim is specified, we compare relative to value - minreclaim
if enforceMinReclaim && threshold . MinReclaim != nil {
2017-02-10 05:14:10 +00:00
quantity . Add ( * evictionapi . GetThresholdQuantity ( * threshold . MinReclaim , observed . capacity ) )
2016-08-03 02:58:48 +00:00
}
2016-08-10 04:09:27 +00:00
thresholdResult := quantity . Cmp ( * observed . available )
2016-05-13 03:35:18 +00:00
switch threshold . Operator {
2017-02-10 05:14:10 +00:00
case evictionapi . OpLessThan :
2016-05-13 03:35:18 +00:00
thresholdMet = thresholdResult > 0
}
if thresholdMet {
results = append ( results , threshold )
}
}
return results
}
2017-02-08 17:58:02 +00:00
func debugLogObservations ( logPrefix string , observations signalObservations ) {
2017-03-09 01:19:52 +00:00
if ! glog . V ( 3 ) {
return
}
2017-02-08 17:58:02 +00:00
for k , v := range observations {
if ! v . time . IsZero ( ) {
2017-03-09 01:19:52 +00:00
glog . Infof ( "eviction manager: %v: signal=%v, available: %v, capacity: %v, time: %v" , logPrefix , k , v . available , v . capacity , v . time )
2017-02-08 17:58:02 +00:00
} else {
2017-03-09 01:19:52 +00:00
glog . Infof ( "eviction manager: %v: signal=%v, available: %v, capacity: %v" , logPrefix , k , v . available , v . capacity )
2017-02-08 17:58:02 +00:00
}
}
}
2017-02-10 05:14:10 +00:00
func debugLogThresholdsWithObservation ( logPrefix string , thresholds [ ] evictionapi . Threshold , observations signalObservations ) {
2017-03-09 01:19:52 +00:00
if ! glog . V ( 3 ) {
return
}
2017-02-08 17:58:02 +00:00
for i := range thresholds {
threshold := thresholds [ i ]
observed , found := observations [ threshold . Signal ]
if found {
2017-02-10 05:14:10 +00:00
quantity := evictionapi . GetThresholdQuantity ( threshold . Value , observed . capacity )
2017-03-09 01:19:52 +00:00
glog . Infof ( "eviction manager: %v: threshold [signal=%v, quantity=%v] observed %v" , logPrefix , threshold . Signal , quantity , observed . available )
2017-02-08 17:58:02 +00:00
} else {
2017-03-09 01:19:52 +00:00
glog . Infof ( "eviction manager: %v: threshold [signal=%v] had no observation" , logPrefix , threshold . Signal )
2017-02-08 17:58:02 +00:00
}
}
}
2017-02-10 05:14:10 +00:00
func thresholdsUpdatedStats ( thresholds [ ] evictionapi . Threshold , observations , lastObservations signalObservations ) [ ] evictionapi . Threshold {
results := [ ] evictionapi . Threshold { }
2016-09-14 20:46:20 +00:00
for i := range thresholds {
threshold := thresholds [ i ]
observed , found := observations [ threshold . Signal ]
if ! found {
glog . Warningf ( "eviction manager: no observation found for eviction signal %v" , threshold . Signal )
continue
}
last , found := lastObservations [ threshold . Signal ]
if ! found || observed . time . IsZero ( ) || observed . time . After ( last . time . Time ) {
results = append ( results , threshold )
}
}
return results
}
2016-05-13 03:35:18 +00:00
// thresholdsFirstObservedAt merges the input set of thresholds with the previous observation to determine when active set of thresholds were initially met.
2017-02-10 05:14:10 +00:00
func thresholdsFirstObservedAt ( thresholds [ ] evictionapi . Threshold , lastObservedAt thresholdsObservedAt , now time . Time ) thresholdsObservedAt {
2016-05-13 03:35:18 +00:00
results := thresholdsObservedAt { }
for i := range thresholds {
observedAt , found := lastObservedAt [ thresholds [ i ] ]
if ! found {
observedAt = now
}
results [ thresholds [ i ] ] = observedAt
}
return results
}
// thresholdsMetGracePeriod returns the set of thresholds that have satisfied associated grace period
2017-02-10 05:14:10 +00:00
func thresholdsMetGracePeriod ( observedAt thresholdsObservedAt , now time . Time ) [ ] evictionapi . Threshold {
results := [ ] evictionapi . Threshold { }
2016-05-13 03:35:18 +00:00
for threshold , at := range observedAt {
duration := now . Sub ( at )
if duration < threshold . GracePeriod {
glog . V ( 2 ) . Infof ( "eviction manager: eviction criteria not yet met for %v, duration: %v" , formatThreshold ( threshold ) , duration )
continue
}
results = append ( results , threshold )
}
return results
}
// nodeConditions returns the set of node conditions associated with a threshold
2017-02-10 05:14:10 +00:00
func nodeConditions ( thresholds [ ] evictionapi . Threshold ) [ ] v1 . NodeConditionType {
2016-11-18 20:50:58 +00:00
results := [ ] v1 . NodeConditionType { }
2016-05-13 03:35:18 +00:00
for _ , threshold := range thresholds {
if nodeCondition , found := signalToNodeCondition [ threshold . Signal ] ; found {
2016-08-03 02:58:48 +00:00
if ! hasNodeCondition ( results , nodeCondition ) {
results = append ( results , nodeCondition )
}
2016-05-13 03:35:18 +00:00
}
}
return results
}
// nodeConditionsLastObservedAt merges the input with the previous observation to determine when a condition was most recently met.
2016-11-18 20:50:58 +00:00
func nodeConditionsLastObservedAt ( nodeConditions [ ] v1 . NodeConditionType , lastObservedAt nodeConditionsObservedAt , now time . Time ) nodeConditionsObservedAt {
2016-05-13 03:35:18 +00:00
results := nodeConditionsObservedAt { }
// the input conditions were observed "now"
for i := range nodeConditions {
results [ nodeConditions [ i ] ] = now
}
// the conditions that were not observed now are merged in with their old time
for key , value := range lastObservedAt {
_ , found := results [ key ]
if ! found {
results [ key ] = value
}
}
return results
}
// nodeConditionsObservedSince returns the set of conditions that have been observed within the specified period
2016-11-18 20:50:58 +00:00
func nodeConditionsObservedSince ( observedAt nodeConditionsObservedAt , period time . Duration , now time . Time ) [ ] v1 . NodeConditionType {
results := [ ] v1 . NodeConditionType { }
2016-05-13 03:35:18 +00:00
for nodeCondition , at := range observedAt {
duration := now . Sub ( at )
if duration < period {
results = append ( results , nodeCondition )
}
}
return results
}
2016-07-26 02:40:22 +00:00
// hasFsStatsType returns true if the fsStat is in the input list
func hasFsStatsType ( inputs [ ] fsStatsType , item fsStatsType ) bool {
2016-07-22 19:23:09 +00:00
for _ , input := range inputs {
if input == item {
return true
}
}
return false
}
2016-05-13 03:35:18 +00:00
// hasNodeCondition returns true if the node condition is in the input list
2016-11-18 20:50:58 +00:00
func hasNodeCondition ( inputs [ ] v1 . NodeConditionType , item v1 . NodeConditionType ) bool {
2016-05-13 03:35:18 +00:00
for _ , input := range inputs {
if input == item {
return true
}
}
return false
}
2016-08-03 02:58:48 +00:00
// mergeThresholds will merge both threshold lists eliminating duplicates.
2017-02-10 05:14:10 +00:00
func mergeThresholds ( inputsA [ ] evictionapi . Threshold , inputsB [ ] evictionapi . Threshold ) [ ] evictionapi . Threshold {
2016-08-03 02:58:48 +00:00
results := inputsA
for _ , threshold := range inputsB {
if ! hasThreshold ( results , threshold ) {
results = append ( results , threshold )
}
}
return results
}
// hasThreshold returns true if the threshold is in the input list
2017-02-10 05:14:10 +00:00
func hasThreshold ( inputs [ ] evictionapi . Threshold , item evictionapi . Threshold ) bool {
2016-05-13 03:35:18 +00:00
for _ , input := range inputs {
2016-08-10 04:09:27 +00:00
if input . GracePeriod == item . GracePeriod && input . Operator == item . Operator && input . Signal == item . Signal && compareThresholdValue ( input . Value , item . Value ) {
2016-05-13 03:35:18 +00:00
return true
}
}
return false
}
2017-02-27 23:32:41 +00:00
// compareThresholdValue returns true if the two thresholdValue objects are logically the same
2017-02-10 05:14:10 +00:00
func compareThresholdValue ( a evictionapi . ThresholdValue , b evictionapi . ThresholdValue ) bool {
2016-08-10 04:09:27 +00:00
if a . Quantity != nil {
if b . Quantity == nil {
return false
}
return a . Quantity . Cmp ( * b . Quantity ) == 0
}
if b . Quantity != nil {
return false
}
return a . Percentage == b . Percentage
}
2016-08-03 20:55:52 +00:00
// getStarvedResources returns the set of resources that are starved based on thresholds met.
2017-02-10 05:14:10 +00:00
func getStarvedResources ( thresholds [ ] evictionapi . Threshold ) [ ] v1 . ResourceName {
2016-11-18 20:50:58 +00:00
results := [ ] v1 . ResourceName { }
2016-05-13 03:35:18 +00:00
for _ , threshold := range thresholds {
if starvedResource , found := signalToResource [ threshold . Signal ] ; found {
results = append ( results , starvedResource )
}
}
return results
}
2016-05-17 21:11:08 +00:00
// isSoftEviction returns true if the thresholds met for the starved resource are only soft thresholds
2017-02-10 05:14:10 +00:00
func isSoftEvictionThresholds ( thresholds [ ] evictionapi . Threshold , starvedResource v1 . ResourceName ) bool {
2016-05-17 21:11:08 +00:00
for _ , threshold := range thresholds {
if resourceToCheck := signalToResource [ threshold . Signal ] ; resourceToCheck != starvedResource {
continue
}
2016-09-09 15:16:47 +00:00
if isHardEvictionThreshold ( threshold ) {
2016-05-17 21:11:08 +00:00
return false
}
}
return true
}
2016-07-22 19:23:09 +00:00
2016-09-09 15:16:47 +00:00
// isSoftEviction returns true if the thresholds met for the starved resource are only soft thresholds
2017-02-10 05:14:10 +00:00
func isHardEvictionThreshold ( threshold evictionapi . Threshold ) bool {
2016-09-09 15:16:47 +00:00
return threshold . GracePeriod == time . Duration ( 0 )
}
2016-07-26 02:40:22 +00:00
// buildResourceToRankFunc returns ranking functions associated with resources
2016-11-18 20:50:58 +00:00
func buildResourceToRankFunc ( withImageFs bool ) map [ v1 . ResourceName ] rankFunc {
resourceToRankFunc := map [ v1 . ResourceName ] rankFunc {
v1 . ResourceMemory : rankMemoryPressure ,
2016-07-22 19:23:09 +00:00
}
// usage of an imagefs is optional
if withImageFs {
// with an imagefs, nodefs pod rank func for eviction only includes logs and local volumes
2016-08-09 21:34:11 +00:00
resourceToRankFunc [ resourceNodeFs ] = rankDiskPressureFunc ( [ ] fsStatsType { fsStatsLogs , fsStatsLocalVolumeSource } , resourceDisk )
resourceToRankFunc [ resourceNodeFsInodes ] = rankDiskPressureFunc ( [ ] fsStatsType { fsStatsLogs , fsStatsLocalVolumeSource } , resourceInodes )
2016-07-22 19:23:09 +00:00
// with an imagefs, imagefs pod rank func for eviction only includes rootfs
2016-08-09 21:34:11 +00:00
resourceToRankFunc [ resourceImageFs ] = rankDiskPressureFunc ( [ ] fsStatsType { fsStatsRoot } , resourceDisk )
resourceToRankFunc [ resourceImageFsInodes ] = rankDiskPressureFunc ( [ ] fsStatsType { fsStatsRoot } , resourceInodes )
2016-07-22 19:23:09 +00:00
} else {
2016-08-26 15:08:43 +00:00
// without an imagefs, nodefs pod rank func for eviction looks at all fs stats.
// since imagefs and nodefs share a common device, they share common ranking functions.
2016-08-09 21:34:11 +00:00
resourceToRankFunc [ resourceNodeFs ] = rankDiskPressureFunc ( [ ] fsStatsType { fsStatsRoot , fsStatsLogs , fsStatsLocalVolumeSource } , resourceDisk )
resourceToRankFunc [ resourceNodeFsInodes ] = rankDiskPressureFunc ( [ ] fsStatsType { fsStatsRoot , fsStatsLogs , fsStatsLocalVolumeSource } , resourceInodes )
2016-08-26 15:08:43 +00:00
resourceToRankFunc [ resourceImageFs ] = rankDiskPressureFunc ( [ ] fsStatsType { fsStatsRoot , fsStatsLogs , fsStatsLocalVolumeSource } , resourceDisk )
resourceToRankFunc [ resourceImageFsInodes ] = rankDiskPressureFunc ( [ ] fsStatsType { fsStatsRoot , fsStatsLogs , fsStatsLocalVolumeSource } , resourceInodes )
2016-07-22 19:23:09 +00:00
}
return resourceToRankFunc
}
2016-08-02 01:14:52 +00:00
2016-08-09 21:34:11 +00:00
// PodIsEvicted returns true if the reported pod status is due to an eviction.
2016-11-18 20:50:58 +00:00
func PodIsEvicted ( podStatus v1 . PodStatus ) bool {
return podStatus . Phase == v1 . PodFailed && podStatus . Reason == reason
2016-08-02 01:14:52 +00:00
}
2016-08-03 20:55:52 +00:00
// buildResourceToNodeReclaimFuncs returns reclaim functions associated with resources.
2016-11-18 20:50:58 +00:00
func buildResourceToNodeReclaimFuncs ( imageGC ImageGC , withImageFs bool ) map [ v1 . ResourceName ] nodeReclaimFuncs {
resourceToReclaimFunc := map [ v1 . ResourceName ] nodeReclaimFuncs { }
2016-08-03 20:55:52 +00:00
// usage of an imagefs is optional
if withImageFs {
// with an imagefs, nodefs pressure should just delete logs
resourceToReclaimFunc [ resourceNodeFs ] = nodeReclaimFuncs { deleteLogs ( ) }
2016-08-09 21:34:11 +00:00
resourceToReclaimFunc [ resourceNodeFsInodes ] = nodeReclaimFuncs { deleteLogs ( ) }
2016-08-03 20:55:52 +00:00
// with an imagefs, imagefs pressure should delete unused images
2016-08-09 21:34:11 +00:00
resourceToReclaimFunc [ resourceImageFs ] = nodeReclaimFuncs { deleteImages ( imageGC , true ) }
resourceToReclaimFunc [ resourceImageFsInodes ] = nodeReclaimFuncs { deleteImages ( imageGC , false ) }
2016-08-03 20:55:52 +00:00
} else {
// without an imagefs, nodefs pressure should delete logs, and unused images
2016-08-26 15:08:43 +00:00
// since imagefs and nodefs share a common device, they share common reclaim functions
2016-08-09 21:34:11 +00:00
resourceToReclaimFunc [ resourceNodeFs ] = nodeReclaimFuncs { deleteLogs ( ) , deleteImages ( imageGC , true ) }
resourceToReclaimFunc [ resourceNodeFsInodes ] = nodeReclaimFuncs { deleteLogs ( ) , deleteImages ( imageGC , false ) }
2016-08-26 15:08:43 +00:00
resourceToReclaimFunc [ resourceImageFs ] = nodeReclaimFuncs { deleteLogs ( ) , deleteImages ( imageGC , true ) }
resourceToReclaimFunc [ resourceImageFsInodes ] = nodeReclaimFuncs { deleteLogs ( ) , deleteImages ( imageGC , false ) }
2016-08-03 20:55:52 +00:00
}
return resourceToReclaimFunc
}
// deleteLogs will delete logs to free up disk pressure.
func deleteLogs ( ) nodeReclaimFunc {
return func ( ) ( * resource . Quantity , error ) {
// TODO: not yet supported.
return resource . NewQuantity ( int64 ( 0 ) , resource . BinarySI ) , nil
}
}
// deleteImages will delete unused images to free up disk pressure.
2016-08-09 21:34:11 +00:00
func deleteImages ( imageGC ImageGC , reportBytesFreed bool ) nodeReclaimFunc {
2016-08-03 20:55:52 +00:00
return func ( ) ( * resource . Quantity , error ) {
glog . Infof ( "eviction manager: attempting to delete unused images" )
2016-08-09 21:34:11 +00:00
bytesFreed , err := imageGC . DeleteUnusedImages ( )
2016-08-03 20:55:52 +00:00
if err != nil {
return nil , err
}
2016-08-09 21:34:11 +00:00
reclaimed := int64 ( 0 )
if reportBytesFreed {
reclaimed = bytesFreed
}
2016-08-03 20:55:52 +00:00
return resource . NewQuantity ( reclaimed , resource . BinarySI ) , nil
}
}