2015-01-23 01:31:31 +00:00
/ *
2015-05-01 16:19:44 +00:00
Copyright 2014 The Kubernetes Authors All rights reserved .
2015-01-23 01:31:31 +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 limitranger
import (
"fmt"
"io"
2015-09-15 21:13:05 +00:00
"sort"
"strings"
2015-01-23 01:31:31 +00:00
2016-02-03 21:04:58 +00:00
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2"
2016-02-01 22:30:47 +00:00
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/admission"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/resource"
2015-09-03 21:40:58 +00:00
"k8s.io/kubernetes/pkg/client/cache"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/runtime"
2015-10-14 05:18:37 +00:00
utilerrors "k8s.io/kubernetes/pkg/util/errors"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/watch"
2015-01-23 01:31:31 +00:00
)
2015-09-15 21:13:05 +00:00
const (
limitRangerAnnotation = "kubernetes.io/limit-ranger"
)
2015-01-23 01:31:31 +00:00
func init ( ) {
2016-02-01 22:30:47 +00:00
admission . RegisterPlugin ( "LimitRanger" , func ( client clientset . Interface , config io . Reader ) ( admission . Interface , error ) {
2015-03-31 14:12:57 +00:00
return NewLimitRanger ( client , Limit ) , nil
2015-01-23 01:31:31 +00:00
} )
}
// limitRanger enforces usage limits on a per resource basis in the namespace
type limitRanger struct {
2015-05-15 14:48:33 +00:00
* admission . Handler
2016-02-01 22:30:47 +00:00
client clientset . Interface
2015-01-23 01:31:31 +00:00
limitFunc LimitFunc
2015-02-16 15:54:29 +00:00
indexer cache . Indexer
2015-01-23 01:31:31 +00:00
}
// Admit admits resources into cluster that do not violate any defined LimitRange in the namespace
func ( l * limitRanger ) Admit ( a admission . Attributes ) ( err error ) {
2015-06-18 19:43:48 +00:00
// Ignore all calls to subresources
if a . GetSubresource ( ) != "" {
return nil
}
2015-02-16 15:54:29 +00:00
obj := a . GetObject ( )
name := "Unknown"
if obj != nil {
name , _ = meta . NewAccessor ( ) . Name ( obj )
2015-04-14 22:03:26 +00:00
if len ( name ) == 0 {
name , _ = meta . NewAccessor ( ) . GenerateName ( obj )
}
2015-02-16 15:54:29 +00:00
}
key := & api . LimitRange {
ObjectMeta : api . ObjectMeta {
Namespace : a . GetNamespace ( ) ,
Name : "" ,
} ,
}
items , err := l . indexer . Index ( "namespace" , key )
2015-01-23 01:31:31 +00:00
if err != nil {
2015-11-30 17:02:04 +00:00
return admission . NewForbidden ( a , fmt . Errorf ( "Unable to %s %v at this time because there was an error enforcing limit ranges" , a . GetOperation ( ) , a . GetResource ( ) ) )
2015-02-16 15:54:29 +00:00
}
if len ( items ) == 0 {
return nil
2015-01-23 01:31:31 +00:00
}
// ensure it meets each prescribed min/max
2015-02-16 15:54:29 +00:00
for i := range items {
limitRange := items [ i ] . ( * api . LimitRange )
2015-11-30 17:02:04 +00:00
err = l . limitFunc ( limitRange , a . GetResource ( ) . Resource , a . GetObject ( ) )
2015-01-23 01:31:31 +00:00
if err != nil {
2015-04-14 22:03:26 +00:00
return admission . NewForbidden ( a , err )
2015-01-23 01:31:31 +00:00
}
}
return nil
}
// NewLimitRanger returns an object that enforces limits based on the supplied limit function
2016-02-01 22:30:47 +00:00
func NewLimitRanger ( client clientset . Interface , limitFunc LimitFunc ) admission . Interface {
2015-02-16 15:54:29 +00:00
lw := & cache . ListWatch {
2015-12-10 09:39:03 +00:00
ListFunc : func ( options api . ListOptions ) ( runtime . Object , error ) {
2016-02-01 22:30:47 +00:00
return client . Legacy ( ) . LimitRanges ( api . NamespaceAll ) . List ( options )
2015-02-16 15:54:29 +00:00
} ,
2015-12-10 09:39:03 +00:00
WatchFunc : func ( options api . ListOptions ) ( watch . Interface , error ) {
2016-02-01 22:30:47 +00:00
return client . Legacy ( ) . LimitRanges ( api . NamespaceAll ) . Watch ( options )
2015-02-16 15:54:29 +00:00
} ,
}
indexer , reflector := cache . NewNamespaceKeyedIndexerAndReflector ( lw , & api . LimitRange { } , 0 )
reflector . Run ( )
2015-05-15 14:48:33 +00:00
return & limitRanger {
Handler : admission . NewHandler ( admission . Create , admission . Update ) ,
client : client ,
limitFunc : limitFunc ,
indexer : indexer ,
}
2015-01-23 01:31:31 +00:00
}
2015-05-15 14:48:33 +00:00
// Min returns the lesser of its 2 arguments
2015-01-23 01:31:31 +00:00
func Min ( a int64 , b int64 ) int64 {
if a < b {
return a
}
return b
}
2015-05-15 14:48:33 +00:00
// Max returns the greater of its 2 arguments
2015-01-23 01:31:31 +00:00
func Max ( a int64 , b int64 ) int64 {
if a > b {
return a
}
return b
}
2015-03-31 14:12:57 +00:00
// Limit enforces resource requirements of incoming resources against enumerated constraints
// on the LimitRange. It may modify the incoming object to apply default resource requirements
// if not specified, and enumerated on the LimitRange
func Limit ( limitRange * api . LimitRange , resourceName string , obj runtime . Object ) error {
switch resourceName {
case "pods" :
return PodLimitFunc ( limitRange , obj . ( * api . Pod ) )
}
return nil
}
// defaultContainerResourceRequirements returns the default requirements for a container
// the requirement.Limits are taken from the LimitRange defaults (if specified)
2015-08-28 16:26:36 +00:00
// the requirement.Requests are taken from the LimitRange default request (if specified)
2015-03-31 14:12:57 +00:00
func defaultContainerResourceRequirements ( limitRange * api . LimitRange ) api . ResourceRequirements {
requirements := api . ResourceRequirements { }
2015-08-27 08:50:50 +00:00
requirements . Requests = api . ResourceList { }
2015-08-28 16:26:36 +00:00
requirements . Limits = api . ResourceList { }
2015-03-31 14:12:57 +00:00
for i := range limitRange . Spec . Limits {
limit := limitRange . Spec . Limits [ i ]
if limit . Type == api . LimitTypeContainer {
2015-08-28 16:26:36 +00:00
for k , v := range limit . DefaultRequest {
value := v . Copy ( )
requirements . Requests [ k ] = * value
}
2015-03-31 14:12:57 +00:00
for k , v := range limit . Default {
value := v . Copy ( )
requirements . Limits [ k ] = * value
}
}
2015-01-23 01:31:31 +00:00
}
2015-03-31 14:12:57 +00:00
return requirements
}
// mergePodResourceRequirements merges enumerated requirements with default requirements
2015-09-15 21:13:05 +00:00
// it annotates the pod with information about what requirements were modified
2015-03-31 14:12:57 +00:00
func mergePodResourceRequirements ( pod * api . Pod , defaultRequirements * api . ResourceRequirements ) {
2015-09-15 21:13:05 +00:00
annotations := [ ] string { }
2015-03-31 14:12:57 +00:00
for i := range pod . Spec . Containers {
2015-04-14 20:12:24 +00:00
container := & pod . Spec . Containers [ i ]
2015-09-15 21:13:05 +00:00
setRequests := [ ] string { }
setLimits := [ ] string { }
2015-04-14 20:12:24 +00:00
if container . Resources . Limits == nil {
container . Resources . Limits = api . ResourceList { }
}
if container . Resources . Requests == nil {
container . Resources . Requests = api . ResourceList { }
}
2015-03-31 14:12:57 +00:00
for k , v := range defaultRequirements . Limits {
_ , found := container . Resources . Limits [ k ]
if ! found {
container . Resources . Limits [ k ] = * v . Copy ( )
2015-09-15 21:13:05 +00:00
setLimits = append ( setLimits , string ( k ) )
2015-03-31 14:12:57 +00:00
}
}
for k , v := range defaultRequirements . Requests {
_ , found := container . Resources . Requests [ k ]
if ! found {
container . Resources . Requests [ k ] = * v . Copy ( )
2015-09-15 21:13:05 +00:00
setRequests = append ( setRequests , string ( k ) )
2015-03-31 14:12:57 +00:00
}
}
2015-09-15 21:13:05 +00:00
if len ( setRequests ) > 0 {
sort . Strings ( setRequests )
a := strings . Join ( setRequests , ", " ) + " request for container " + container . Name
annotations = append ( annotations , a )
}
if len ( setLimits ) > 0 {
sort . Strings ( setLimits )
a := strings . Join ( setLimits , ", " ) + " limit for container " + container . Name
annotations = append ( annotations , a )
}
}
if len ( annotations ) > 0 {
if pod . ObjectMeta . Annotations == nil {
pod . ObjectMeta . Annotations = make ( map [ string ] string )
}
val := "LimitRanger plugin set: " + strings . Join ( annotations , "; " )
pod . ObjectMeta . Annotations [ limitRangerAnnotation ] = val
2015-03-31 14:12:57 +00:00
}
}
2015-08-28 16:26:36 +00:00
// requestLimitEnforcedValues returns the specified values at a common precision to support comparability
func requestLimitEnforcedValues ( requestQuantity , limitQuantity , enforcedQuantity resource . Quantity ) ( request , limit , enforced int64 ) {
request = requestQuantity . Value ( )
limit = limitQuantity . Value ( )
enforced = enforcedQuantity . Value ( )
// do a more precise comparison if possible (if the value won't overflow)
if request <= resource . MaxMilliValue && limit <= resource . MaxMilliValue && enforced <= resource . MaxMilliValue {
request = requestQuantity . MilliValue ( )
limit = limitQuantity . MilliValue ( )
enforced = enforcedQuantity . MilliValue ( )
}
return
}
2015-08-24 19:20:10 +00:00
2015-08-28 16:26:36 +00:00
// minConstraint enforces the min constraint over the specified resource
func minConstraint ( limitType api . LimitType , resourceName api . ResourceName , enforced resource . Quantity , request api . ResourceList , limit api . ResourceList ) error {
req , reqExists := request [ resourceName ]
lim , limExists := limit [ resourceName ]
observedReqValue , observedLimValue , enforcedValue := requestLimitEnforcedValues ( req , lim , enforced )
if ! reqExists {
return fmt . Errorf ( "Minimum %s usage per %s is %s. No request is specified." , resourceName , limitType , enforced . String ( ) )
}
if observedReqValue < enforcedValue {
return fmt . Errorf ( "Minimum %s usage per %s is %s, but request is %s." , resourceName , limitType , enforced . String ( ) , req . String ( ) )
}
if limExists && ( observedLimValue < enforcedValue ) {
return fmt . Errorf ( "Minimum %s usage per %s is %s, but limit is %s." , resourceName , limitType , enforced . String ( ) , lim . String ( ) )
}
return nil
}
2015-08-24 19:20:10 +00:00
2015-08-28 16:26:36 +00:00
// maxConstraint enforces the max constraint over the specified resource
func maxConstraint ( limitType api . LimitType , resourceName api . ResourceName , enforced resource . Quantity , request api . ResourceList , limit api . ResourceList ) error {
req , reqExists := request [ resourceName ]
lim , limExists := limit [ resourceName ]
observedReqValue , observedLimValue , enforcedValue := requestLimitEnforcedValues ( req , lim , enforced )
2015-08-24 19:20:10 +00:00
2015-08-28 16:26:36 +00:00
if ! limExists {
return fmt . Errorf ( "Maximum %s usage per %s is %s. No limit is specified." , resourceName , limitType , enforced . String ( ) )
}
if observedLimValue > enforcedValue {
return fmt . Errorf ( "Maximum %s usage per %s is %s, but limit is %s." , resourceName , limitType , enforced . String ( ) , lim . String ( ) )
}
if reqExists && ( observedReqValue > enforcedValue ) {
return fmt . Errorf ( "Maximum %s usage per %s is %s, but request is %s." , resourceName , limitType , enforced . String ( ) , req . String ( ) )
}
return nil
}
2015-08-24 19:20:10 +00:00
2015-08-28 16:26:36 +00:00
// limitRequestRatioConstraint enforces the limit to request ratio over the specified resource
func limitRequestRatioConstraint ( limitType api . LimitType , resourceName api . ResourceName , enforced resource . Quantity , request api . ResourceList , limit api . ResourceList ) error {
req , reqExists := request [ resourceName ]
lim , limExists := limit [ resourceName ]
2015-09-10 13:38:45 +00:00
observedReqValue , observedLimValue , _ := requestLimitEnforcedValues ( req , lim , enforced )
2015-08-28 16:26:36 +00:00
if ! reqExists || ( observedReqValue == int64 ( 0 ) ) {
return fmt . Errorf ( "%s max limit to request ratio per %s is %s, but no request is specified or request is 0." , resourceName , limitType , enforced . String ( ) )
}
if ! limExists || ( observedLimValue == int64 ( 0 ) ) {
return fmt . Errorf ( "%s max limit to request ratio per %s is %s, but no limit is specified or limit is 0." , resourceName , limitType , enforced . String ( ) )
}
2015-09-10 13:38:45 +00:00
observedRatio := float64 ( observedLimValue ) / float64 ( observedReqValue )
displayObservedRatio := observedRatio
maxLimitRequestRatio := float64 ( enforced . Value ( ) )
if enforced . Value ( ) <= resource . MaxMilliValue {
observedRatio = observedRatio * 1000
maxLimitRequestRatio = float64 ( enforced . MilliValue ( ) )
}
2015-08-28 16:26:36 +00:00
2015-09-10 13:38:45 +00:00
if observedRatio > maxLimitRequestRatio {
return fmt . Errorf ( "%s max limit to request ratio per %s is %s, but provided ratio is %f." , resourceName , limitType , enforced . String ( ) , displayObservedRatio )
2015-08-28 16:26:36 +00:00
}
return nil
}
// sum takes the total of each named resource across all inputs
// if a key is not in each input, then the output resource list will omit the key
func sum ( inputs [ ] api . ResourceList ) api . ResourceList {
result := api . ResourceList { }
keys := [ ] api . ResourceName { }
for i := range inputs {
for k := range inputs [ i ] {
keys = append ( keys , k )
}
}
for _ , key := range keys {
total , isSet := int64 ( 0 ) , true
for i := range inputs {
input := inputs [ i ]
v , exists := input [ key ]
if exists {
if key == api . ResourceCPU {
total = total + v . MilliValue ( )
} else {
total = total + v . Value ( )
}
} else {
isSet = false
}
2015-01-23 01:31:31 +00:00
}
2015-08-28 16:26:36 +00:00
if isSet {
if key == api . ResourceCPU {
result [ key ] = * ( resource . NewMilliQuantity ( total , resource . DecimalSI ) )
} else {
result [ key ] = * ( resource . NewQuantity ( total , resource . DecimalSI ) )
}
2015-01-23 01:31:31 +00:00
2015-08-28 16:26:36 +00:00
}
2015-01-23 01:31:31 +00:00
}
2015-08-28 16:26:36 +00:00
return result
}
// PodLimitFunc enforces resource requirements enumerated by the pod against
// the specified LimitRange. The pod may be modified to apply default resource
// requirements if not specified, and enumerated on the LimitRange
func PodLimitFunc ( limitRange * api . LimitRange , pod * api . Pod ) error {
2015-09-09 07:02:01 +00:00
var errs [ ] error
2015-08-28 16:26:36 +00:00
defaultResources := defaultContainerResourceRequirements ( limitRange )
mergePodResourceRequirements ( pod , & defaultResources )
2015-01-23 01:31:31 +00:00
for i := range limitRange . Spec . Limits {
limit := limitRange . Spec . Limits [ i ]
2015-08-28 16:26:36 +00:00
limitType := limit . Type
// enforce container limits
if limitType == api . LimitTypeContainer {
for j := range pod . Spec . Containers {
container := & pod . Spec . Containers [ j ]
for k , v := range limit . Min {
if err := minConstraint ( limitType , k , v , container . Resources . Requests , container . Resources . Limits ) ; err != nil {
2015-09-09 07:02:01 +00:00
errs = append ( errs , err )
2015-01-27 21:54:50 +00:00
}
2015-01-23 01:31:31 +00:00
}
2015-08-28 16:26:36 +00:00
for k , v := range limit . Max {
if err := maxConstraint ( limitType , k , v , container . Resources . Requests , container . Resources . Limits ) ; err != nil {
2015-09-09 07:02:01 +00:00
errs = append ( errs , err )
2015-01-27 21:54:50 +00:00
}
2015-08-28 16:26:36 +00:00
}
for k , v := range limit . MaxLimitRequestRatio {
if err := limitRequestRatioConstraint ( limitType , k , v , container . Resources . Requests , container . Resources . Limits ) ; err != nil {
2015-09-09 07:02:01 +00:00
errs = append ( errs , err )
2015-01-27 21:54:50 +00:00
}
2015-01-23 01:31:31 +00:00
}
}
}
2015-08-28 16:26:36 +00:00
// enforce pod limits
if limitType == api . LimitTypePod {
containerRequests , containerLimits := [ ] api . ResourceList { } , [ ] api . ResourceList { }
for j := range pod . Spec . Containers {
container := & pod . Spec . Containers [ j ]
containerRequests = append ( containerRequests , container . Resources . Requests )
containerLimits = append ( containerLimits , container . Resources . Limits )
}
podRequests := sum ( containerRequests )
podLimits := sum ( containerLimits )
for k , v := range limit . Min {
if err := minConstraint ( limitType , k , v , podRequests , podLimits ) ; err != nil {
2015-09-09 07:02:01 +00:00
errs = append ( errs , err )
2015-08-28 16:26:36 +00:00
}
}
for k , v := range limit . Max {
if err := maxConstraint ( limitType , k , v , podRequests , podLimits ) ; err != nil {
2015-09-09 07:02:01 +00:00
errs = append ( errs , err )
2015-08-28 16:26:36 +00:00
}
}
for k , v := range limit . MaxLimitRequestRatio {
if err := limitRequestRatioConstraint ( limitType , k , v , podRequests , podLimits ) ; err != nil {
2015-09-09 07:02:01 +00:00
errs = append ( errs , err )
2015-08-28 16:26:36 +00:00
}
}
}
2015-01-23 01:31:31 +00:00
}
2015-10-14 05:18:37 +00:00
return utilerrors . NewAggregate ( errs )
2015-01-23 01:31:31 +00:00
}