Commit 6f8a951f authored by k8s-merge-robot's avatar k8s-merge-robot

Merge pull request #20446 from derekwaynecarr/quota_scopes

Auto commit by PR queue bot
parents c768d477 95df07aa
...@@ -17515,9 +17515,20 @@ ...@@ -17515,9 +17515,20 @@
"hard": { "hard": {
"type": "any", "type": "any",
"description": "Hard is the set of desired hard limits for each named resource. More info: http://releases.k8s.io/HEAD/docs/design/admission_control_resource_quota.md#admissioncontrol-plugin-resourcequota" "description": "Hard is the set of desired hard limits for each named resource. More info: http://releases.k8s.io/HEAD/docs/design/admission_control_resource_quota.md#admissioncontrol-plugin-resourcequota"
},
"scopes": {
"type": "array",
"items": {
"$ref": "v1.ResourceQuotaScope"
},
"description": "A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects."
} }
} }
}, },
"v1.ResourceQuotaScope": {
"id": "v1.ResourceQuotaScope",
"properties": {}
},
"v1.ResourceQuotaStatus": { "v1.ResourceQuotaStatus": {
"id": "v1.ResourceQuotaStatus", "id": "v1.ResourceQuotaStatus",
"description": "ResourceQuotaStatus defines the enforced hard limits and observed use.", "description": "ResourceQuotaStatus defines the enforced hard limits and observed use.",
......
...@@ -60,6 +60,7 @@ import ( ...@@ -60,6 +60,7 @@ import (
servicecontroller "k8s.io/kubernetes/pkg/controller/service" servicecontroller "k8s.io/kubernetes/pkg/controller/service"
serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount" serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
"k8s.io/kubernetes/pkg/healthz" "k8s.io/kubernetes/pkg/healthz"
quotainstall "k8s.io/kubernetes/pkg/quota/install"
"k8s.io/kubernetes/pkg/serviceaccount" "k8s.io/kubernetes/pkg/serviceaccount"
"k8s.io/kubernetes/pkg/util" "k8s.io/kubernetes/pkg/util"
"k8s.io/kubernetes/pkg/util/configz" "k8s.io/kubernetes/pkg/util/configz"
...@@ -226,9 +227,23 @@ func StartControllers(s *options.CMServer, kubeClient *client.Client, kubeconfig ...@@ -226,9 +227,23 @@ func StartControllers(s *options.CMServer, kubeClient *client.Client, kubeconfig
glog.Infof("allocate-node-cidrs set to %v, node controller not creating routes", s.AllocateNodeCIDRs) glog.Infof("allocate-node-cidrs set to %v, node controller not creating routes", s.AllocateNodeCIDRs)
} }
go resourcequotacontroller.NewResourceQuotaController( resourceQuotaControllerClient := clientset.NewForConfigOrDie(client.AddUserAgent(kubeconfig, "resourcequota-controller"))
clientset.NewForConfigOrDie(client.AddUserAgent(kubeconfig, "resourcequota-controller")), resourceQuotaRegistry := quotainstall.NewRegistry(resourceQuotaControllerClient)
controller.StaticResyncPeriodFunc(s.ResourceQuotaSyncPeriod.Duration)).Run(s.ConcurrentResourceQuotaSyncs, wait.NeverStop) groupKindsToReplenish := []unversioned.GroupKind{
api.Kind("Pod"),
api.Kind("Service"),
api.Kind("ReplicationController"),
api.Kind("PersistentVolumeClaim"),
api.Kind("Secret"),
}
resourceQuotaControllerOptions := &resourcequotacontroller.ResourceQuotaControllerOptions{
KubeClient: resourceQuotaControllerClient,
ResyncPeriod: controller.StaticResyncPeriodFunc(s.ResourceQuotaSyncPeriod.Duration),
Registry: resourceQuotaRegistry,
GroupKindsToReplenish: groupKindsToReplenish,
ControllerFactory: resourcequotacontroller.NewReplenishmentControllerFactory(resourceQuotaControllerClient),
}
go resourcequotacontroller.NewResourceQuotaController(resourceQuotaControllerOptions).Run(s.ConcurrentResourceQuotaSyncs, wait.NeverStop)
// If apiserver is not running we should wait for some time and fail only then. This is particularly // If apiserver is not running we should wait for some time and fail only then. This is particularly
// important when we start apiserver and controller manager at the same time. // important when we start apiserver and controller manager at the same time.
......
...@@ -28,6 +28,7 @@ import ( ...@@ -28,6 +28,7 @@ import (
kubecontrollermanager "k8s.io/kubernetes/cmd/kube-controller-manager/app" kubecontrollermanager "k8s.io/kubernetes/cmd/kube-controller-manager/app"
"k8s.io/kubernetes/cmd/kube-controller-manager/app/options" "k8s.io/kubernetes/cmd/kube-controller-manager/app/options"
"k8s.io/kubernetes/contrib/mesos/pkg/node" "k8s.io/kubernetes/contrib/mesos/pkg/node"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
client "k8s.io/kubernetes/pkg/client/unversioned" client "k8s.io/kubernetes/pkg/client/unversioned"
...@@ -53,6 +54,7 @@ import ( ...@@ -53,6 +54,7 @@ import (
servicecontroller "k8s.io/kubernetes/pkg/controller/service" servicecontroller "k8s.io/kubernetes/pkg/controller/service"
serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount" serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
"k8s.io/kubernetes/pkg/healthz" "k8s.io/kubernetes/pkg/healthz"
quotainstall "k8s.io/kubernetes/pkg/quota/install"
"k8s.io/kubernetes/pkg/serviceaccount" "k8s.io/kubernetes/pkg/serviceaccount"
"k8s.io/kubernetes/pkg/util" "k8s.io/kubernetes/pkg/util"
"k8s.io/kubernetes/pkg/util/wait" "k8s.io/kubernetes/pkg/util/wait"
...@@ -173,8 +175,23 @@ func (s *CMServer) Run(_ []string) error { ...@@ -173,8 +175,23 @@ func (s *CMServer) Run(_ []string) error {
routeController.Run(s.NodeSyncPeriod.Duration) routeController.Run(s.NodeSyncPeriod.Duration)
} }
go resourcequotacontroller.NewResourceQuotaController( resourceQuotaControllerClient := clientset.NewForConfigOrDie(client.AddUserAgent(kubeconfig, "resource-quota-controller"))
clientset.NewForConfigOrDie(client.AddUserAgent(kubeconfig, "resource-quota-controller")), controller.StaticResyncPeriodFunc(s.ResourceQuotaSyncPeriod.Duration)).Run(s.ConcurrentResourceQuotaSyncs, wait.NeverStop) resourceQuotaRegistry := quotainstall.NewRegistry(resourceQuotaControllerClient)
groupKindsToReplenish := []unversioned.GroupKind{
api.Kind("Pod"),
api.Kind("Service"),
api.Kind("ReplicationController"),
api.Kind("PersistentVolumeClaim"),
api.Kind("Secret"),
}
resourceQuotaControllerOptions := &resourcequotacontroller.ResourceQuotaControllerOptions{
KubeClient: resourceQuotaControllerClient,
ResyncPeriod: controller.StaticResyncPeriodFunc(s.ResourceQuotaSyncPeriod.Duration),
Registry: resourceQuotaRegistry,
GroupKindsToReplenish: groupKindsToReplenish,
ControllerFactory: resourcequotacontroller.NewReplenishmentControllerFactory(resourceQuotaControllerClient),
}
go resourcequotacontroller.NewResourceQuotaController(resourceQuotaControllerOptions).Run(s.ConcurrentResourceQuotaSyncs, wait.NeverStop)
// If apiserver is not running we should wait for some time and fail only then. This is particularly // If apiserver is not running we should wait for some time and fail only then. This is particularly
// important when we start apiserver and controller manager at the same time. // important when we start apiserver and controller manager at the same time.
......
...@@ -22,7 +22,6 @@ import ( ...@@ -22,7 +22,6 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/controller/resourcequota"
) )
type resources struct { type resources struct {
...@@ -81,7 +80,6 @@ func TestResources(tst *testing.T) { ...@@ -81,7 +80,6 @@ func TestResources(tst *testing.T) {
} }
tst.Logf("Testing resource computation for %v => request=%v limit=%v", t, pod.Spec.Containers[0].Resources.Requests, pod.Spec.Containers[0].Resources.Limits) tst.Logf("Testing resource computation for %v => request=%v limit=%v", t, pod.Spec.Containers[0].Resources.Requests, pod.Spec.Containers[0].Resources.Limits)
tst.Logf("hasRequests: cpu => %v, mem => %v", resourcequota.PodHasRequests(pod, api.ResourceCPU), resourcequota.PodHasRequests(pod, api.ResourceMemory))
beforeCpuR, beforeCpuL, _, err := LimitedCPUForPod(pod, DefaultDefaultContainerCPULimit) beforeCpuR, beforeCpuL, _, err := LimitedCPUForPod(pod, DefaultDefaultContainerCPULimit)
assert.NoError(err, "CPUForPod should not return an error") assert.NoError(err, "CPUForPod should not return an error")
......
...@@ -5591,7 +5591,7 @@ Populated by the system when a graceful deletion is requested. Read-only. More i ...@@ -5591,7 +5591,7 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
</div> </div>
<div id="footer"> <div id="footer">
<div id="footer-text"> <div id="footer-text">
Last updated 2016-02-26 21:38:01 UTC Last updated 2016-02-27 01:28:36 UTC
</div> </div>
</div> </div>
</body> </body>
......
...@@ -1064,6 +1064,13 @@ Examples:<br> ...@@ -1064,6 +1064,13 @@ Examples:<br>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_any">any</a></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_any">any</a></p></td>
<td class="tableblock halign-left valign-top"></td> <td class="tableblock halign-left valign-top"></td>
</tr> </tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">scopes</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_resourcequotascope">v1.ResourceQuotaScope</a> array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody> </tbody>
</table> </table>
...@@ -6190,6 +6197,10 @@ The resulting set of endpoints can be viewed as:<br> ...@@ -6190,6 +6197,10 @@ The resulting set of endpoints can be viewed as:<br>
</div> </div>
<div class="sect2"> <div class="sect2">
<h3 id="_v1_resourcequotascope">v1.ResourceQuotaScope</h3>
</div>
<div class="sect2">
<h3 id="_v1_replicationcontrollerlist">v1.ReplicationControllerList</h3> <h3 id="_v1_replicationcontrollerlist">v1.ReplicationControllerList</h3>
<div class="paragraph"> <div class="paragraph">
<p>ReplicationControllerList is a collection of replication controllers.</p> <p>ReplicationControllerList is a collection of replication controllers.</p>
...@@ -7477,7 +7488,7 @@ The resulting set of endpoints can be viewed as:<br> ...@@ -7477,7 +7488,7 @@ The resulting set of endpoints can be viewed as:<br>
</div> </div>
<div id="footer"> <div id="footer">
<div id="footer-text"> <div id="footer-text">
Last updated 2016-02-26 21:37:52 UTC Last updated 2016-02-27 01:28:28 UTC
</div> </div>
</div> </div>
</body> </body>
......
...@@ -112,11 +112,11 @@ Support the following resources that can be tracked by quota. ...@@ -112,11 +112,11 @@ Support the following resources that can be tracked by quota.
| Resource Name | Description | | Resource Name | Description |
| ------------- | ----------- | | ------------- | ----------- |
| cpu | total cpu requests (backwards compatibility) | | cpu | total cpu requests (backwards compatibility) |
| cpu.request | total cpu requests |
| cpu.limit | total cpu limits |
| memory | total memory requests (backwards compatibility) | | memory | total memory requests (backwards compatibility) |
| memory.request | total memory requests | | requests.cpu | total cpu requests |
| memory.limit | total memory limits | | requests.memory | total memory requests |
| limits.cpu | total cpu limits |
| limits.memory | total memory limits |
### Resource Quota Scopes ### Resource Quota Scopes
...@@ -145,22 +145,22 @@ A `Terminating`, `NotTerminating`, `NotBestEffort` scope restricts a quota to ...@@ -145,22 +145,22 @@ A `Terminating`, `NotTerminating`, `NotBestEffort` scope restricts a quota to
tracking the following resources: tracking the following resources:
* pod * pod
* memory, memory.request, memory.limit * memory, requests.memory, limits.memory
* cpu, cpu.request, cpu.limit * cpu, requests.cpu, limits.cpu
## Data Model Impact ## Data Model Impact
``` ```
// The following identify resource constants for Kubernetes object types // The following identify resource constants for Kubernetes object types
const ( const (
// CPU Request, in cores // CPU request, in cores. (500m = .5 cores)
ResourceCPURequest ResourceName = "cpu.request" ResourceRequestsCPU ResourceName = "requests.cpu"
// CPU Limit, in bytes // Memory request, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024)
ResourceCPULimit ResourceName = "cpu.limit" ResourceRequestsMemory ResourceName = "requests.memory"
// Memory Request, in bytes // CPU limit, in cores. (500m = .5 cores)
ResourceMemoryRequest ResourceName = "memory.request" ResourceLimitsCPU ResourceName = "limits.cpu"
// Memory Limit, in bytes // Memory limit, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024)
ResourceMemoryLimit ResourceName = "memory.limit" ResourceLimitsMemory ResourceName = "limits.memory"
) )
// A scope is a filter that matches an object // A scope is a filter that matches an object
...@@ -178,8 +178,8 @@ const ( ...@@ -178,8 +178,8 @@ const (
type ResourceQuotaSpec struct { type ResourceQuotaSpec struct {
// Hard is the set of desired hard limits for each named resource // Hard is the set of desired hard limits for each named resource
Hard ResourceList `json:"hard,omitempty"` Hard ResourceList `json:"hard,omitempty"`
// Scopes is the set of filters that must match an object for it to be // A collection of filters that must match each object tracked by a quota.
// tracked by the quota // If not specified, the quota matches all objects.
Scopes []ResourceQuotaScope `json:"scopes,omitempty"` Scopes []ResourceQuotaScope `json:"scopes,omitempty"`
} }
``` ```
......
...@@ -2455,6 +2455,15 @@ func DeepCopy_api_ResourceQuotaSpec(in ResourceQuotaSpec, out *ResourceQuotaSpec ...@@ -2455,6 +2455,15 @@ func DeepCopy_api_ResourceQuotaSpec(in ResourceQuotaSpec, out *ResourceQuotaSpec
} else { } else {
out.Hard = nil out.Hard = nil
} }
if in.Scopes != nil {
in, out := in.Scopes, &out.Scopes
*out = make([]ResourceQuotaScope, len(in))
for i := range in {
(*out)[i] = in[i]
}
} else {
out.Scopes = nil
}
return nil return nil
} }
......
...@@ -79,9 +79,82 @@ var Semantic = conversion.EqualitiesOrDie( ...@@ -79,9 +79,82 @@ var Semantic = conversion.EqualitiesOrDie(
}, },
) )
var standardResourceQuotaScopes = sets.NewString(
string(ResourceQuotaScopeTerminating),
string(ResourceQuotaScopeNotTerminating),
string(ResourceQuotaScopeBestEffort),
string(ResourceQuotaScopeNotBestEffort),
)
// IsStandardResourceQuotaScope returns true if the scope is a standard value
func IsStandardResourceQuotaScope(str string) bool {
return standardResourceQuotaScopes.Has(str)
}
var podObjectCountQuotaResources = sets.NewString(
string(ResourcePods),
)
var podComputeQuotaResources = sets.NewString(
string(ResourceCPU),
string(ResourceMemory),
string(ResourceLimitsCPU),
string(ResourceLimitsMemory),
string(ResourceRequestsCPU),
string(ResourceRequestsMemory),
)
// IsResourceQuotaScopeValidForResource returns true if the resource applies to the specified scope
func IsResourceQuotaScopeValidForResource(scope ResourceQuotaScope, resource string) bool {
switch scope {
case ResourceQuotaScopeTerminating, ResourceQuotaScopeNotTerminating, ResourceQuotaScopeNotBestEffort:
return podObjectCountQuotaResources.Has(resource) || podComputeQuotaResources.Has(resource)
case ResourceQuotaScopeBestEffort:
return podObjectCountQuotaResources.Has(resource)
default:
return true
}
}
var standardContainerResources = sets.NewString(
string(ResourceCPU),
string(ResourceMemory),
)
// IsStandardContainerResourceName returns true if the container can make a resource request
// for the specified resource
func IsStandardContainerResourceName(str string) bool {
return standardContainerResources.Has(str)
}
var standardQuotaResources = sets.NewString(
string(ResourceCPU),
string(ResourceMemory),
string(ResourceRequestsCPU),
string(ResourceRequestsMemory),
string(ResourceLimitsCPU),
string(ResourceLimitsMemory),
string(ResourcePods),
string(ResourceQuotas),
string(ResourceServices),
string(ResourceReplicationControllers),
string(ResourceSecrets),
string(ResourcePersistentVolumeClaims),
)
// IsStandardQuotaResourceName returns true if the resource is known to
// the quota tracking system
func IsStandardQuotaResourceName(str string) bool {
return standardQuotaResources.Has(str)
}
var standardResources = sets.NewString( var standardResources = sets.NewString(
string(ResourceCPU), string(ResourceCPU),
string(ResourceMemory), string(ResourceMemory),
string(ResourceRequestsCPU),
string(ResourceRequestsMemory),
string(ResourceLimitsCPU),
string(ResourceLimitsMemory),
string(ResourcePods), string(ResourcePods),
string(ResourceQuotas), string(ResourceQuotas),
string(ResourceServices), string(ResourceServices),
......
...@@ -44187,6 +44187,32 @@ func (x *LimitRangeList) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { ...@@ -44187,6 +44187,32 @@ func (x *LimitRangeList) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
} }
func (x ResourceQuotaScope) CodecEncodeSelf(e *codec1978.Encoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperEncoder(e)
_, _, _ = h, z, r
yym1 := z.EncBinary()
_ = yym1
if false {
} else if z.HasExtensions() && z.EncExt(x) {
} else {
r.EncodeString(codecSelferC_UTF81234, string(x))
}
}
func (x *ResourceQuotaScope) CodecDecodeSelf(d *codec1978.Decoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperDecoder(d)
_, _, _ = h, z, r
yym1 := z.DecBinary()
_ = yym1
if false {
} else if z.HasExtensions() && z.DecExt(x) {
} else {
*((*string)(x)) = r.DecodeString()
}
}
func (x *ResourceQuotaSpec) CodecEncodeSelf(e *codec1978.Encoder) { func (x *ResourceQuotaSpec) CodecEncodeSelf(e *codec1978.Encoder) {
var h codecSelfer1234 var h codecSelfer1234
z, r := codec1978.GenHelperEncoder(e) z, r := codec1978.GenHelperEncoder(e)
...@@ -44201,13 +44227,14 @@ func (x *ResourceQuotaSpec) CodecEncodeSelf(e *codec1978.Encoder) { ...@@ -44201,13 +44227,14 @@ func (x *ResourceQuotaSpec) CodecEncodeSelf(e *codec1978.Encoder) {
} else { } else {
yysep2 := !z.EncBinary() yysep2 := !z.EncBinary()
yy2arr2 := z.EncBasicHandle().StructToArray yy2arr2 := z.EncBasicHandle().StructToArray
var yyq2 [1]bool var yyq2 [2]bool
_, _, _ = yysep2, yyq2, yy2arr2 _, _, _ = yysep2, yyq2, yy2arr2
const yyr2 bool = false const yyr2 bool = false
yyq2[0] = len(x.Hard) != 0 yyq2[0] = len(x.Hard) != 0
yyq2[1] = len(x.Scopes) != 0
var yynn2 int var yynn2 int
if yyr2 || yy2arr2 { if yyr2 || yy2arr2 {
r.EncodeArrayStart(1) r.EncodeArrayStart(2)
} else { } else {
yynn2 = 0 yynn2 = 0
for _, b := range yyq2 { for _, b := range yyq2 {
...@@ -44242,6 +44269,39 @@ func (x *ResourceQuotaSpec) CodecEncodeSelf(e *codec1978.Encoder) { ...@@ -44242,6 +44269,39 @@ func (x *ResourceQuotaSpec) CodecEncodeSelf(e *codec1978.Encoder) {
} }
} }
if yyr2 || yy2arr2 { if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
if yyq2[1] {
if x.Scopes == nil {
r.EncodeNil()
} else {
yym7 := z.EncBinary()
_ = yym7
if false {
} else {
h.encSliceResourceQuotaScope(([]ResourceQuotaScope)(x.Scopes), e)
}
}
} else {
r.EncodeNil()
}
} else {
if yyq2[1] {
z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("scopes"))
z.EncSendContainerState(codecSelfer_containerMapValue1234)
if x.Scopes == nil {
r.EncodeNil()
} else {
yym8 := z.EncBinary()
_ = yym8
if false {
} else {
h.encSliceResourceQuotaScope(([]ResourceQuotaScope)(x.Scopes), e)
}
}
}
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayEnd1234) z.EncSendContainerState(codecSelfer_containerArrayEnd1234)
} else { } else {
z.EncSendContainerState(codecSelfer_containerMapEnd1234) z.EncSendContainerState(codecSelfer_containerMapEnd1234)
...@@ -44309,6 +44369,18 @@ func (x *ResourceQuotaSpec) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) ...@@ -44309,6 +44369,18 @@ func (x *ResourceQuotaSpec) codecDecodeSelfFromMap(l int, d *codec1978.Decoder)
yyv4 := &x.Hard yyv4 := &x.Hard
yyv4.CodecDecodeSelf(d) yyv4.CodecDecodeSelf(d)
} }
case "scopes":
if r.TryDecodeAsNil() {
x.Scopes = nil
} else {
yyv5 := &x.Scopes
yym6 := z.DecBinary()
_ = yym6
if false {
} else {
h.decSliceResourceQuotaScope((*[]ResourceQuotaScope)(yyv5), d)
}
}
default: default:
z.DecStructFieldNotFound(-1, yys3) z.DecStructFieldNotFound(-1, yys3)
} // end switch yys3 } // end switch yys3
...@@ -44320,16 +44392,16 @@ func (x *ResourceQuotaSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder ...@@ -44320,16 +44392,16 @@ func (x *ResourceQuotaSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder
var h codecSelfer1234 var h codecSelfer1234
z, r := codec1978.GenHelperDecoder(d) z, r := codec1978.GenHelperDecoder(d)
_, _, _ = h, z, r _, _, _ = h, z, r
var yyj5 int var yyj7 int
var yyb5 bool var yyb7 bool
var yyhl5 bool = l >= 0 var yyhl7 bool = l >= 0
yyj5++ yyj7++
if yyhl5 { if yyhl7 {
yyb5 = yyj5 > l yyb7 = yyj7 > l
} else { } else {
yyb5 = r.CheckBreak() yyb7 = r.CheckBreak()
} }
if yyb5 { if yyb7 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return return
} }
...@@ -44337,21 +44409,43 @@ func (x *ResourceQuotaSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder ...@@ -44337,21 +44409,43 @@ func (x *ResourceQuotaSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder
if r.TryDecodeAsNil() { if r.TryDecodeAsNil() {
x.Hard = nil x.Hard = nil
} else { } else {
yyv6 := &x.Hard yyv8 := &x.Hard
yyv6.CodecDecodeSelf(d) yyv8.CodecDecodeSelf(d)
}
yyj7++
if yyhl7 {
yyb7 = yyj7 > l
} else {
yyb7 = r.CheckBreak()
}
if yyb7 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
if r.TryDecodeAsNil() {
x.Scopes = nil
} else {
yyv9 := &x.Scopes
yym10 := z.DecBinary()
_ = yym10
if false {
} else {
h.decSliceResourceQuotaScope((*[]ResourceQuotaScope)(yyv9), d)
}
} }
for { for {
yyj5++ yyj7++
if yyhl5 { if yyhl7 {
yyb5 = yyj5 > l yyb7 = yyj7 > l
} else { } else {
yyb5 = r.CheckBreak() yyb7 = r.CheckBreak()
} }
if yyb5 { if yyb7 {
break break
} }
z.DecSendContainerState(codecSelfer_containerArrayElem1234) z.DecSendContainerState(codecSelfer_containerArrayElem1234)
z.DecStructFieldNotFound(yyj5-1, "") z.DecStructFieldNotFound(yyj7-1, "")
} }
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
} }
...@@ -53768,6 +53862,116 @@ func (x codecSelfer1234) decSliceLimitRange(v *[]LimitRange, d *codec1978.Decode ...@@ -53768,6 +53862,116 @@ func (x codecSelfer1234) decSliceLimitRange(v *[]LimitRange, d *codec1978.Decode
} }
} }
func (x codecSelfer1234) encSliceResourceQuotaScope(v []ResourceQuotaScope, e *codec1978.Encoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperEncoder(e)
_, _, _ = h, z, r
r.EncodeArrayStart(len(v))
for _, yyv1 := range v {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
yyv1.CodecEncodeSelf(e)
}
z.EncSendContainerState(codecSelfer_containerArrayEnd1234)
}
func (x codecSelfer1234) decSliceResourceQuotaScope(v *[]ResourceQuotaScope, d *codec1978.Decoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperDecoder(d)
_, _, _ = h, z, r
yyv1 := *v
yyh1, yyl1 := z.DecSliceHelperStart()
var yyc1 bool
_ = yyc1
if yyl1 == 0 {
if yyv1 == nil {
yyv1 = []ResourceQuotaScope{}
yyc1 = true
} else if len(yyv1) != 0 {
yyv1 = yyv1[:0]
yyc1 = true
}
} else if yyl1 > 0 {
var yyrr1, yyrl1 int
var yyrt1 bool
_, _ = yyrl1, yyrt1
yyrr1 = yyl1 // len(yyv1)
if yyl1 > cap(yyv1) {
yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 16)
if yyrt1 {
if yyrl1 <= cap(yyv1) {
yyv1 = yyv1[:yyrl1]
} else {
yyv1 = make([]ResourceQuotaScope, yyrl1)
}
} else {
yyv1 = make([]ResourceQuotaScope, yyrl1)
}
yyc1 = true
yyrr1 = len(yyv1)
} else if yyl1 != len(yyv1) {
yyv1 = yyv1[:yyl1]
yyc1 = true
}
yyj1 := 0
for ; yyj1 < yyrr1; yyj1++ {
yyh1.ElemContainerState(yyj1)
if r.TryDecodeAsNil() {
yyv1[yyj1] = ""
} else {
yyv1[yyj1] = ResourceQuotaScope(r.DecodeString())
}
}
if yyrt1 {
for ; yyj1 < yyl1; yyj1++ {
yyv1 = append(yyv1, "")
yyh1.ElemContainerState(yyj1)
if r.TryDecodeAsNil() {
yyv1[yyj1] = ""
} else {
yyv1[yyj1] = ResourceQuotaScope(r.DecodeString())
}
}
}
} else {
yyj1 := 0
for ; !r.CheckBreak(); yyj1++ {
if yyj1 >= len(yyv1) {
yyv1 = append(yyv1, "") // var yyz1 ResourceQuotaScope
yyc1 = true
}
yyh1.ElemContainerState(yyj1)
if yyj1 < len(yyv1) {
if r.TryDecodeAsNil() {
yyv1[yyj1] = ""
} else {
yyv1[yyj1] = ResourceQuotaScope(r.DecodeString())
}
} else {
z.DecSwallow()
}
}
if yyj1 < len(yyv1) {
yyv1 = yyv1[:yyj1]
yyc1 = true
} else if yyj1 == 0 && yyv1 == nil {
yyv1 = []ResourceQuotaScope{}
yyc1 = true
}
}
yyh1.End()
if yyc1 {
*v = yyv1
}
}
func (x codecSelfer1234) encSliceResourceQuota(v []ResourceQuota, e *codec1978.Encoder) { func (x codecSelfer1234) encSliceResourceQuota(v []ResourceQuota, e *codec1978.Encoder) {
var h codecSelfer1234 var h codecSelfer1234
z, r := codec1978.GenHelperEncoder(e) z, r := codec1978.GenHelperEncoder(e)
...@@ -53807,7 +54011,7 @@ func (x codecSelfer1234) decSliceResourceQuota(v *[]ResourceQuota, d *codec1978. ...@@ -53807,7 +54011,7 @@ func (x codecSelfer1234) decSliceResourceQuota(v *[]ResourceQuota, d *codec1978.
yyrg1 := len(yyv1) > 0 yyrg1 := len(yyv1) > 0
yyv21 := yyv1 yyv21 := yyv1
yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 216) yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 240)
if yyrt1 { if yyrt1 {
if yyrl1 <= cap(yyv1) { if yyrl1 <= cap(yyv1) {
yyv1 = yyv1[:yyrl1] yyv1 = yyv1[:yyrl1]
...@@ -2189,12 +2189,37 @@ const ( ...@@ -2189,12 +2189,37 @@ const (
ResourceSecrets ResourceName = "secrets" ResourceSecrets ResourceName = "secrets"
// ResourcePersistentVolumeClaims, number // ResourcePersistentVolumeClaims, number
ResourcePersistentVolumeClaims ResourceName = "persistentvolumeclaims" ResourcePersistentVolumeClaims ResourceName = "persistentvolumeclaims"
// CPU request, in cores. (500m = .5 cores)
ResourceRequestsCPU ResourceName = "requests.cpu"
// Memory request, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024)
ResourceRequestsMemory ResourceName = "requests.memory"
// CPU limit, in cores. (500m = .5 cores)
ResourceLimitsCPU ResourceName = "limits.cpu"
// Memory limit, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024)
ResourceLimitsMemory ResourceName = "limits.memory"
)
// A ResourceQuotaScope defines a filter that must match each object tracked by a quota
type ResourceQuotaScope string
const (
// Match all pod objects where spec.activeDeadlineSeconds
ResourceQuotaScopeTerminating ResourceQuotaScope = "Terminating"
// Match all pod objects where !spec.activeDeadlineSeconds
ResourceQuotaScopeNotTerminating ResourceQuotaScope = "NotTerminating"
// Match all pod objects that have best effort quality of service
ResourceQuotaScopeBestEffort ResourceQuotaScope = "BestEffort"
// Match all pod objects that do not have best effort quality of service
ResourceQuotaScopeNotBestEffort ResourceQuotaScope = "NotBestEffort"
) )
// ResourceQuotaSpec defines the desired hard limits to enforce for Quota // ResourceQuotaSpec defines the desired hard limits to enforce for Quota
type ResourceQuotaSpec struct { type ResourceQuotaSpec struct {
// Hard is the set of desired hard limits for each named resource // Hard is the set of desired hard limits for each named resource
Hard ResourceList `json:"hard,omitempty"` Hard ResourceList `json:"hard,omitempty"`
// A collection of filters that must match each object tracked by a quota.
// If not specified, the quota matches all objects.
Scopes []ResourceQuotaScope `json:"scopes,omitempty"`
} }
// ResourceQuotaStatus defines the enforced hard limits and observed use // ResourceQuotaStatus defines the enforced hard limits and observed use
......
...@@ -2666,6 +2666,14 @@ func autoConvert_api_ResourceQuotaSpec_To_v1_ResourceQuotaSpec(in *api.ResourceQ ...@@ -2666,6 +2666,14 @@ func autoConvert_api_ResourceQuotaSpec_To_v1_ResourceQuotaSpec(in *api.ResourceQ
} else { } else {
out.Hard = nil out.Hard = nil
} }
if in.Scopes != nil {
out.Scopes = make([]ResourceQuotaScope, len(in.Scopes))
for i := range in.Scopes {
out.Scopes[i] = ResourceQuotaScope(in.Scopes[i])
}
} else {
out.Scopes = nil
}
return nil return nil
} }
...@@ -5887,6 +5895,14 @@ func autoConvert_v1_ResourceQuotaSpec_To_api_ResourceQuotaSpec(in *ResourceQuota ...@@ -5887,6 +5895,14 @@ func autoConvert_v1_ResourceQuotaSpec_To_api_ResourceQuotaSpec(in *ResourceQuota
if err := s.Convert(&in.Hard, &out.Hard, 0); err != nil { if err := s.Convert(&in.Hard, &out.Hard, 0); err != nil {
return err return err
} }
if in.Scopes != nil {
out.Scopes = make([]api.ResourceQuotaScope, len(in.Scopes))
for i := range in.Scopes {
out.Scopes[i] = api.ResourceQuotaScope(in.Scopes[i])
}
} else {
out.Scopes = nil
}
return nil return nil
} }
......
...@@ -2084,6 +2084,14 @@ func deepCopy_v1_ResourceQuotaSpec(in ResourceQuotaSpec, out *ResourceQuotaSpec, ...@@ -2084,6 +2084,14 @@ func deepCopy_v1_ResourceQuotaSpec(in ResourceQuotaSpec, out *ResourceQuotaSpec,
} else { } else {
out.Hard = nil out.Hard = nil
} }
if in.Scopes != nil {
out.Scopes = make([]ResourceQuotaScope, len(in.Scopes))
for i := range in.Scopes {
out.Scopes[i] = in.Scopes[i]
}
} else {
out.Scopes = nil
}
return nil return nil
} }
......
...@@ -43999,6 +43999,32 @@ func (x *LimitRangeList) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { ...@@ -43999,6 +43999,32 @@ func (x *LimitRangeList) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
} }
func (x ResourceQuotaScope) CodecEncodeSelf(e *codec1978.Encoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperEncoder(e)
_, _, _ = h, z, r
yym1 := z.EncBinary()
_ = yym1
if false {
} else if z.HasExtensions() && z.EncExt(x) {
} else {
r.EncodeString(codecSelferC_UTF81234, string(x))
}
}
func (x *ResourceQuotaScope) CodecDecodeSelf(d *codec1978.Decoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperDecoder(d)
_, _, _ = h, z, r
yym1 := z.DecBinary()
_ = yym1
if false {
} else if z.HasExtensions() && z.DecExt(x) {
} else {
*((*string)(x)) = r.DecodeString()
}
}
func (x *ResourceQuotaSpec) CodecEncodeSelf(e *codec1978.Encoder) { func (x *ResourceQuotaSpec) CodecEncodeSelf(e *codec1978.Encoder) {
var h codecSelfer1234 var h codecSelfer1234
z, r := codec1978.GenHelperEncoder(e) z, r := codec1978.GenHelperEncoder(e)
...@@ -44013,13 +44039,14 @@ func (x *ResourceQuotaSpec) CodecEncodeSelf(e *codec1978.Encoder) { ...@@ -44013,13 +44039,14 @@ func (x *ResourceQuotaSpec) CodecEncodeSelf(e *codec1978.Encoder) {
} else { } else {
yysep2 := !z.EncBinary() yysep2 := !z.EncBinary()
yy2arr2 := z.EncBasicHandle().StructToArray yy2arr2 := z.EncBasicHandle().StructToArray
var yyq2 [1]bool var yyq2 [2]bool
_, _, _ = yysep2, yyq2, yy2arr2 _, _, _ = yysep2, yyq2, yy2arr2
const yyr2 bool = false const yyr2 bool = false
yyq2[0] = len(x.Hard) != 0 yyq2[0] = len(x.Hard) != 0
yyq2[1] = len(x.Scopes) != 0
var yynn2 int var yynn2 int
if yyr2 || yy2arr2 { if yyr2 || yy2arr2 {
r.EncodeArrayStart(1) r.EncodeArrayStart(2)
} else { } else {
yynn2 = 0 yynn2 = 0
for _, b := range yyq2 { for _, b := range yyq2 {
...@@ -44054,6 +44081,39 @@ func (x *ResourceQuotaSpec) CodecEncodeSelf(e *codec1978.Encoder) { ...@@ -44054,6 +44081,39 @@ func (x *ResourceQuotaSpec) CodecEncodeSelf(e *codec1978.Encoder) {
} }
} }
if yyr2 || yy2arr2 { if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
if yyq2[1] {
if x.Scopes == nil {
r.EncodeNil()
} else {
yym7 := z.EncBinary()
_ = yym7
if false {
} else {
h.encSliceResourceQuotaScope(([]ResourceQuotaScope)(x.Scopes), e)
}
}
} else {
r.EncodeNil()
}
} else {
if yyq2[1] {
z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("scopes"))
z.EncSendContainerState(codecSelfer_containerMapValue1234)
if x.Scopes == nil {
r.EncodeNil()
} else {
yym8 := z.EncBinary()
_ = yym8
if false {
} else {
h.encSliceResourceQuotaScope(([]ResourceQuotaScope)(x.Scopes), e)
}
}
}
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayEnd1234) z.EncSendContainerState(codecSelfer_containerArrayEnd1234)
} else { } else {
z.EncSendContainerState(codecSelfer_containerMapEnd1234) z.EncSendContainerState(codecSelfer_containerMapEnd1234)
...@@ -44121,6 +44181,18 @@ func (x *ResourceQuotaSpec) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) ...@@ -44121,6 +44181,18 @@ func (x *ResourceQuotaSpec) codecDecodeSelfFromMap(l int, d *codec1978.Decoder)
yyv4 := &x.Hard yyv4 := &x.Hard
yyv4.CodecDecodeSelf(d) yyv4.CodecDecodeSelf(d)
} }
case "scopes":
if r.TryDecodeAsNil() {
x.Scopes = nil
} else {
yyv5 := &x.Scopes
yym6 := z.DecBinary()
_ = yym6
if false {
} else {
h.decSliceResourceQuotaScope((*[]ResourceQuotaScope)(yyv5), d)
}
}
default: default:
z.DecStructFieldNotFound(-1, yys3) z.DecStructFieldNotFound(-1, yys3)
} // end switch yys3 } // end switch yys3
...@@ -44132,16 +44204,16 @@ func (x *ResourceQuotaSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder ...@@ -44132,16 +44204,16 @@ func (x *ResourceQuotaSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder
var h codecSelfer1234 var h codecSelfer1234
z, r := codec1978.GenHelperDecoder(d) z, r := codec1978.GenHelperDecoder(d)
_, _, _ = h, z, r _, _, _ = h, z, r
var yyj5 int var yyj7 int
var yyb5 bool var yyb7 bool
var yyhl5 bool = l >= 0 var yyhl7 bool = l >= 0
yyj5++ yyj7++
if yyhl5 { if yyhl7 {
yyb5 = yyj5 > l yyb7 = yyj7 > l
} else { } else {
yyb5 = r.CheckBreak() yyb7 = r.CheckBreak()
} }
if yyb5 { if yyb7 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return return
} }
...@@ -44149,21 +44221,43 @@ func (x *ResourceQuotaSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder ...@@ -44149,21 +44221,43 @@ func (x *ResourceQuotaSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder
if r.TryDecodeAsNil() { if r.TryDecodeAsNil() {
x.Hard = nil x.Hard = nil
} else { } else {
yyv6 := &x.Hard yyv8 := &x.Hard
yyv6.CodecDecodeSelf(d) yyv8.CodecDecodeSelf(d)
}
yyj7++
if yyhl7 {
yyb7 = yyj7 > l
} else {
yyb7 = r.CheckBreak()
}
if yyb7 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
if r.TryDecodeAsNil() {
x.Scopes = nil
} else {
yyv9 := &x.Scopes
yym10 := z.DecBinary()
_ = yym10
if false {
} else {
h.decSliceResourceQuotaScope((*[]ResourceQuotaScope)(yyv9), d)
}
} }
for { for {
yyj5++ yyj7++
if yyhl5 { if yyhl7 {
yyb5 = yyj5 > l yyb7 = yyj7 > l
} else { } else {
yyb5 = r.CheckBreak() yyb7 = r.CheckBreak()
} }
if yyb5 { if yyb7 {
break break
} }
z.DecSendContainerState(codecSelfer_containerArrayElem1234) z.DecSendContainerState(codecSelfer_containerArrayElem1234)
z.DecStructFieldNotFound(yyj5-1, "") z.DecStructFieldNotFound(yyj7-1, "")
} }
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
} }
...@@ -53826,6 +53920,116 @@ func (x codecSelfer1234) decSliceLimitRange(v *[]LimitRange, d *codec1978.Decode ...@@ -53826,6 +53920,116 @@ func (x codecSelfer1234) decSliceLimitRange(v *[]LimitRange, d *codec1978.Decode
} }
} }
func (x codecSelfer1234) encSliceResourceQuotaScope(v []ResourceQuotaScope, e *codec1978.Encoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperEncoder(e)
_, _, _ = h, z, r
r.EncodeArrayStart(len(v))
for _, yyv1 := range v {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
yyv1.CodecEncodeSelf(e)
}
z.EncSendContainerState(codecSelfer_containerArrayEnd1234)
}
func (x codecSelfer1234) decSliceResourceQuotaScope(v *[]ResourceQuotaScope, d *codec1978.Decoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperDecoder(d)
_, _, _ = h, z, r
yyv1 := *v
yyh1, yyl1 := z.DecSliceHelperStart()
var yyc1 bool
_ = yyc1
if yyl1 == 0 {
if yyv1 == nil {
yyv1 = []ResourceQuotaScope{}
yyc1 = true
} else if len(yyv1) != 0 {
yyv1 = yyv1[:0]
yyc1 = true
}
} else if yyl1 > 0 {
var yyrr1, yyrl1 int
var yyrt1 bool
_, _ = yyrl1, yyrt1
yyrr1 = yyl1 // len(yyv1)
if yyl1 > cap(yyv1) {
yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 16)
if yyrt1 {
if yyrl1 <= cap(yyv1) {
yyv1 = yyv1[:yyrl1]
} else {
yyv1 = make([]ResourceQuotaScope, yyrl1)
}
} else {
yyv1 = make([]ResourceQuotaScope, yyrl1)
}
yyc1 = true
yyrr1 = len(yyv1)
} else if yyl1 != len(yyv1) {
yyv1 = yyv1[:yyl1]
yyc1 = true
}
yyj1 := 0
for ; yyj1 < yyrr1; yyj1++ {
yyh1.ElemContainerState(yyj1)
if r.TryDecodeAsNil() {
yyv1[yyj1] = ""
} else {
yyv1[yyj1] = ResourceQuotaScope(r.DecodeString())
}
}
if yyrt1 {
for ; yyj1 < yyl1; yyj1++ {
yyv1 = append(yyv1, "")
yyh1.ElemContainerState(yyj1)
if r.TryDecodeAsNil() {
yyv1[yyj1] = ""
} else {
yyv1[yyj1] = ResourceQuotaScope(r.DecodeString())
}
}
}
} else {
yyj1 := 0
for ; !r.CheckBreak(); yyj1++ {
if yyj1 >= len(yyv1) {
yyv1 = append(yyv1, "") // var yyz1 ResourceQuotaScope
yyc1 = true
}
yyh1.ElemContainerState(yyj1)
if yyj1 < len(yyv1) {
if r.TryDecodeAsNil() {
yyv1[yyj1] = ""
} else {
yyv1[yyj1] = ResourceQuotaScope(r.DecodeString())
}
} else {
z.DecSwallow()
}
}
if yyj1 < len(yyv1) {
yyv1 = yyv1[:yyj1]
yyc1 = true
} else if yyj1 == 0 && yyv1 == nil {
yyv1 = []ResourceQuotaScope{}
yyc1 = true
}
}
yyh1.End()
if yyc1 {
*v = yyv1
}
}
func (x codecSelfer1234) encSliceResourceQuota(v []ResourceQuota, e *codec1978.Encoder) { func (x codecSelfer1234) encSliceResourceQuota(v []ResourceQuota, e *codec1978.Encoder) {
var h codecSelfer1234 var h codecSelfer1234
z, r := codec1978.GenHelperEncoder(e) z, r := codec1978.GenHelperEncoder(e)
...@@ -53865,7 +54069,7 @@ func (x codecSelfer1234) decSliceResourceQuota(v *[]ResourceQuota, d *codec1978. ...@@ -53865,7 +54069,7 @@ func (x codecSelfer1234) decSliceResourceQuota(v *[]ResourceQuota, d *codec1978.
yyrg1 := len(yyv1) > 0 yyrg1 := len(yyv1) > 0
yyv21 := yyv1 yyv21 := yyv1
yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 216) yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 240)
if yyrt1 { if yyrt1 {
if yyrl1 <= cap(yyv1) { if yyrl1 <= cap(yyv1) {
yyv1 = yyv1[:yyrl1] yyv1 = yyv1[:yyrl1]
...@@ -2645,6 +2645,28 @@ const ( ...@@ -2645,6 +2645,28 @@ const (
ResourceSecrets ResourceName = "secrets" ResourceSecrets ResourceName = "secrets"
// ResourcePersistentVolumeClaims, number // ResourcePersistentVolumeClaims, number
ResourcePersistentVolumeClaims ResourceName = "persistentvolumeclaims" ResourcePersistentVolumeClaims ResourceName = "persistentvolumeclaims"
// CPU request, in cores. (500m = .5 cores)
ResourceCPURequest ResourceName = "cpu.request"
// CPU limit, in cores. (500m = .5 cores)
ResourceCPULimit ResourceName = "cpu.limit"
// Memory request, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024)
ResourceMemoryRequest ResourceName = "memory.request"
// Memory limit, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024)
ResourceMemoryLimit ResourceName = "memory.limit"
)
// A ResourceQuotaScope defines a filter that must match each object tracked by a quota
type ResourceQuotaScope string
const (
// Match all pod objects where spec.activeDeadlineSeconds
ResourceQuotaScopeTerminating ResourceQuotaScope = "Terminating"
// Match all pod objects where !spec.activeDeadlineSeconds
ResourceQuotaScopeNotTerminating ResourceQuotaScope = "NotTerminating"
// Match all pod objects that have best effort quality of service
ResourceQuotaScopeBestEffort ResourceQuotaScope = "BestEffort"
// Match all pod objects that do not have best effort quality of service
ResourceQuotaScopeNotBestEffort ResourceQuotaScope = "NotBestEffort"
) )
// ResourceQuotaSpec defines the desired hard limits to enforce for Quota. // ResourceQuotaSpec defines the desired hard limits to enforce for Quota.
...@@ -2652,6 +2674,9 @@ type ResourceQuotaSpec struct { ...@@ -2652,6 +2674,9 @@ type ResourceQuotaSpec struct {
// Hard is the set of desired hard limits for each named resource. // Hard is the set of desired hard limits for each named resource.
// More info: http://releases.k8s.io/HEAD/docs/design/admission_control_resource_quota.md#admissioncontrol-plugin-resourcequota // More info: http://releases.k8s.io/HEAD/docs/design/admission_control_resource_quota.md#admissioncontrol-plugin-resourcequota
Hard ResourceList `json:"hard,omitempty"` Hard ResourceList `json:"hard,omitempty"`
// A collection of filters that must match each object tracked by a quota.
// If not specified, the quota matches all objects.
Scopes []ResourceQuotaScope `json:"scopes,omitempty"`
} }
// ResourceQuotaStatus defines the enforced hard limits and observed use. // ResourceQuotaStatus defines the enforced hard limits and observed use.
......
...@@ -1351,8 +1351,9 @@ func (ResourceQuotaList) SwaggerDoc() map[string]string { ...@@ -1351,8 +1351,9 @@ func (ResourceQuotaList) SwaggerDoc() map[string]string {
} }
var map_ResourceQuotaSpec = map[string]string{ var map_ResourceQuotaSpec = map[string]string{
"": "ResourceQuotaSpec defines the desired hard limits to enforce for Quota.", "": "ResourceQuotaSpec defines the desired hard limits to enforce for Quota.",
"hard": "Hard is the set of desired hard limits for each named resource. More info: http://releases.k8s.io/HEAD/docs/design/admission_control_resource_quota.md#admissioncontrol-plugin-resourcequota", "hard": "Hard is the set of desired hard limits for each named resource. More info: http://releases.k8s.io/HEAD/docs/design/admission_control_resource_quota.md#admissioncontrol-plugin-resourcequota",
"scopes": "A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects.",
} }
func (ResourceQuotaSpec) SwaggerDoc() map[string]string { func (ResourceQuotaSpec) SwaggerDoc() map[string]string {
......
...@@ -4044,6 +4044,10 @@ func TestValidateResourceQuota(t *testing.T) { ...@@ -4044,6 +4044,10 @@ func TestValidateResourceQuota(t *testing.T) {
Hard: api.ResourceList{ Hard: api.ResourceList{
api.ResourceCPU: resource.MustParse("100"), api.ResourceCPU: resource.MustParse("100"),
api.ResourceMemory: resource.MustParse("10000"), api.ResourceMemory: resource.MustParse("10000"),
api.ResourceRequestsCPU: resource.MustParse("100"),
api.ResourceRequestsMemory: resource.MustParse("10000"),
api.ResourceLimitsCPU: resource.MustParse("100"),
api.ResourceLimitsMemory: resource.MustParse("10000"),
api.ResourcePods: resource.MustParse("10"), api.ResourcePods: resource.MustParse("10"),
api.ResourceServices: resource.MustParse("0"), api.ResourceServices: resource.MustParse("0"),
api.ResourceReplicationControllers: resource.MustParse("10"), api.ResourceReplicationControllers: resource.MustParse("10"),
...@@ -4051,6 +4055,42 @@ func TestValidateResourceQuota(t *testing.T) { ...@@ -4051,6 +4055,42 @@ func TestValidateResourceQuota(t *testing.T) {
}, },
} }
terminatingSpec := api.ResourceQuotaSpec{
Hard: api.ResourceList{
api.ResourceCPU: resource.MustParse("100"),
api.ResourceLimitsCPU: resource.MustParse("200"),
},
Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeTerminating},
}
nonTerminatingSpec := api.ResourceQuotaSpec{
Hard: api.ResourceList{
api.ResourceCPU: resource.MustParse("100"),
},
Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeNotTerminating},
}
bestEffortSpec := api.ResourceQuotaSpec{
Hard: api.ResourceList{
api.ResourcePods: resource.MustParse("100"),
},
Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeBestEffort},
}
nonBestEffortSpec := api.ResourceQuotaSpec{
Hard: api.ResourceList{
api.ResourceCPU: resource.MustParse("100"),
},
Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeNotBestEffort},
}
// storage is not yet supported as a quota tracked resource
invalidQuotaResourceSpec := api.ResourceQuotaSpec{
Hard: api.ResourceList{
api.ResourceStorage: resource.MustParse("10"),
},
}
negativeSpec := api.ResourceQuotaSpec{ negativeSpec := api.ResourceQuotaSpec{
Hard: api.ResourceList{ Hard: api.ResourceList{
api.ResourceCPU: resource.MustParse("-100"), api.ResourceCPU: resource.MustParse("-100"),
...@@ -4077,6 +4117,27 @@ func TestValidateResourceQuota(t *testing.T) { ...@@ -4077,6 +4117,27 @@ func TestValidateResourceQuota(t *testing.T) {
}, },
} }
invalidTerminatingScopePairsSpec := api.ResourceQuotaSpec{
Hard: api.ResourceList{
api.ResourceCPU: resource.MustParse("100"),
},
Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeTerminating, api.ResourceQuotaScopeNotTerminating},
}
invalidBestEffortScopePairsSpec := api.ResourceQuotaSpec{
Hard: api.ResourceList{
api.ResourcePods: resource.MustParse("100"),
},
Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeBestEffort, api.ResourceQuotaScopeNotBestEffort},
}
invalidScopeNameSpec := api.ResourceQuotaSpec{
Hard: api.ResourceList{
api.ResourceCPU: resource.MustParse("100"),
},
Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScope("foo")},
}
successCases := []api.ResourceQuota{ successCases := []api.ResourceQuota{
{ {
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
...@@ -4092,6 +4153,34 @@ func TestValidateResourceQuota(t *testing.T) { ...@@ -4092,6 +4153,34 @@ func TestValidateResourceQuota(t *testing.T) {
}, },
Spec: fractionalComputeSpec, Spec: fractionalComputeSpec,
}, },
{
ObjectMeta: api.ObjectMeta{
Name: "abc",
Namespace: "foo",
},
Spec: terminatingSpec,
},
{
ObjectMeta: api.ObjectMeta{
Name: "abc",
Namespace: "foo",
},
Spec: nonTerminatingSpec,
},
{
ObjectMeta: api.ObjectMeta{
Name: "abc",
Namespace: "foo",
},
Spec: bestEffortSpec,
},
{
ObjectMeta: api.ObjectMeta{
Name: "abc",
Namespace: "foo",
},
Spec: nonBestEffortSpec,
},
} }
for _, successCase := range successCases { for _, successCase := range successCases {
...@@ -4128,6 +4217,22 @@ func TestValidateResourceQuota(t *testing.T) { ...@@ -4128,6 +4217,22 @@ func TestValidateResourceQuota(t *testing.T) {
api.ResourceQuota{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: fractionalPodSpec}, api.ResourceQuota{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: fractionalPodSpec},
isNotIntegerErrorMsg, isNotIntegerErrorMsg,
}, },
"invalid-quota-resource": {
api.ResourceQuota{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidQuotaResourceSpec},
isInvalidQuotaResource,
},
"invalid-quota-terminating-pair": {
api.ResourceQuota{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidTerminatingScopePairsSpec},
"conflicting scopes",
},
"invalid-quota-besteffort-pair": {
api.ResourceQuota{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidBestEffortScopePairsSpec},
"conflicting scopes",
},
"invalid-quota-scope-name": {
api.ResourceQuota{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidScopeNameSpec},
"unsupported scope",
},
} }
for k, v := range errorCases { for k, v := range errorCases {
errs := ValidateResourceQuota(&v.R) errs := ValidateResourceQuota(&v.R)
......
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
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 resourcequota
import (
"fmt"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/client/cache"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/controller"
"k8s.io/kubernetes/pkg/controller/framework"
"k8s.io/kubernetes/pkg/quota/evaluator/core"
"k8s.io/kubernetes/pkg/runtime"
utilruntime "k8s.io/kubernetes/pkg/util/runtime"
"k8s.io/kubernetes/pkg/watch"
)
// ReplenishmentFunc is a function that is invoked when controller sees a change
// that may require a quota to be replenished (i.e. object deletion, or object moved to terminal state)
type ReplenishmentFunc func(groupKind unversioned.GroupKind, namespace string, object runtime.Object)
// ReplenishmentControllerOptions is an options struct that tells a factory
// how to configure a controller that can inform the quota system it should
// replenish quota
type ReplenishmentControllerOptions struct {
// The kind monitored for replenishment
GroupKind unversioned.GroupKind
// The period that should be used to re-sync the monitored resource
ResyncPeriod controller.ResyncPeriodFunc
// The function to invoke when a change is observed that should trigger
// replenishment
ReplenishmentFunc ReplenishmentFunc
}
// PodReplenishmentUpdateFunc will replenish if the old pod was quota tracked but the new is not
func PodReplenishmentUpdateFunc(options *ReplenishmentControllerOptions) func(oldObj, newObj interface{}) {
return func(oldObj, newObj interface{}) {
oldPod := oldObj.(*api.Pod)
newPod := newObj.(*api.Pod)
if core.QuotaPod(oldPod) && !core.QuotaPod(newPod) {
options.ReplenishmentFunc(options.GroupKind, newPod.Namespace, newPod)
}
}
}
// ObjectReplenenishmentDeleteFunc will replenish on every delete
func ObjectReplenishmentDeleteFunc(options *ReplenishmentControllerOptions) func(obj interface{}) {
return func(obj interface{}) {
metaObject, err := meta.Accessor(obj)
if err != nil {
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
if !ok {
glog.Errorf("replenishment controller could not get object from tombstone %+v, could take up to %v before quota is replenished", obj, options.ResyncPeriod())
utilruntime.HandleError(err)
return
}
metaObject, err = meta.Accessor(tombstone.Obj)
if err != nil {
glog.Errorf("replenishment controller tombstone contained object that is not a meta %+v, could take up to %v before quota is replenished", tombstone.Obj, options.ResyncPeriod())
utilruntime.HandleError(err)
return
}
}
options.ReplenishmentFunc(options.GroupKind, metaObject.GetNamespace(), nil)
}
}
// ReplenishmentControllerFactory knows how to build replenishment controllers
type ReplenishmentControllerFactory interface {
// NewController returns a controller configured with the specified options
NewController(options *ReplenishmentControllerOptions) (*framework.Controller, error)
}
// replenishmentControllerFactory implements ReplenishmentControllerFactory
type replenishmentControllerFactory struct {
kubeClient clientset.Interface
}
// NewReplenishmentControllerFactory returns a factory that knows how to build controllers
// to replenish resources when updated or deleted
func NewReplenishmentControllerFactory(kubeClient clientset.Interface) ReplenishmentControllerFactory {
return &replenishmentControllerFactory{
kubeClient: kubeClient,
}
}
func (r *replenishmentControllerFactory) NewController(options *ReplenishmentControllerOptions) (*framework.Controller, error) {
var result *framework.Controller
switch options.GroupKind {
case api.Kind("Pod"):
_, result = framework.NewInformer(
&cache.ListWatch{
ListFunc: func(options api.ListOptions) (runtime.Object, error) {
return r.kubeClient.Core().Pods(api.NamespaceAll).List(options)
},
WatchFunc: func(options api.ListOptions) (watch.Interface, error) {
return r.kubeClient.Core().Pods(api.NamespaceAll).Watch(options)
},
},
&api.Pod{},
options.ResyncPeriod(),
framework.ResourceEventHandlerFuncs{
UpdateFunc: PodReplenishmentUpdateFunc(options),
DeleteFunc: ObjectReplenishmentDeleteFunc(options),
},
)
case api.Kind("Service"):
_, result = framework.NewInformer(
&cache.ListWatch{
ListFunc: func(options api.ListOptions) (runtime.Object, error) {
return r.kubeClient.Core().Services(api.NamespaceAll).List(options)
},
WatchFunc: func(options api.ListOptions) (watch.Interface, error) {
return r.kubeClient.Core().Services(api.NamespaceAll).Watch(options)
},
},
&api.Service{},
options.ResyncPeriod(),
framework.ResourceEventHandlerFuncs{
DeleteFunc: ObjectReplenishmentDeleteFunc(options),
},
)
case api.Kind("ReplicationController"):
_, result = framework.NewInformer(
&cache.ListWatch{
ListFunc: func(options api.ListOptions) (runtime.Object, error) {
return r.kubeClient.Core().ReplicationControllers(api.NamespaceAll).List(options)
},
WatchFunc: func(options api.ListOptions) (watch.Interface, error) {
return r.kubeClient.Core().ReplicationControllers(api.NamespaceAll).Watch(options)
},
},
&api.ReplicationController{},
options.ResyncPeriod(),
framework.ResourceEventHandlerFuncs{
DeleteFunc: ObjectReplenishmentDeleteFunc(options),
},
)
case api.Kind("PersistentVolumeClaim"):
_, result = framework.NewInformer(
&cache.ListWatch{
ListFunc: func(options api.ListOptions) (runtime.Object, error) {
return r.kubeClient.Core().PersistentVolumeClaims(api.NamespaceAll).List(options)
},
WatchFunc: func(options api.ListOptions) (watch.Interface, error) {
return r.kubeClient.Core().PersistentVolumeClaims(api.NamespaceAll).Watch(options)
},
},
&api.PersistentVolumeClaim{},
options.ResyncPeriod(),
framework.ResourceEventHandlerFuncs{
DeleteFunc: ObjectReplenishmentDeleteFunc(options),
},
)
case api.Kind("Secret"):
_, result = framework.NewInformer(
&cache.ListWatch{
ListFunc: func(options api.ListOptions) (runtime.Object, error) {
return r.kubeClient.Core().Secrets(api.NamespaceAll).List(options)
},
WatchFunc: func(options api.ListOptions) (watch.Interface, error) {
return r.kubeClient.Core().Secrets(api.NamespaceAll).Watch(options)
},
},
&api.PersistentVolumeClaim{},
options.ResyncPeriod(),
framework.ResourceEventHandlerFuncs{
DeleteFunc: ObjectReplenishmentDeleteFunc(options),
},
)
default:
return nil, fmt.Errorf("no replenishment controller available for %s", options.GroupKind)
}
return result, nil
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
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 resourcequota
import (
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/controller"
"k8s.io/kubernetes/pkg/runtime"
)
// testReplenishment lets us test replenishment functions are invoked
type testReplenishment struct {
groupKind unversioned.GroupKind
namespace string
}
// mock function that holds onto the last kind that was replenished
func (t *testReplenishment) Replenish(groupKind unversioned.GroupKind, namespace string, object runtime.Object) {
t.groupKind = groupKind
t.namespace = namespace
}
func TestPodReplenishmentUpdateFunc(t *testing.T) {
mockReplenish := &testReplenishment{}
options := ReplenishmentControllerOptions{
GroupKind: api.Kind("Pod"),
ReplenishmentFunc: mockReplenish.Replenish,
ResyncPeriod: controller.NoResyncPeriodFunc,
}
oldPod := &api.Pod{
ObjectMeta: api.ObjectMeta{Namespace: "test", Name: "pod"},
Status: api.PodStatus{Phase: api.PodRunning},
}
newPod := &api.Pod{
ObjectMeta: api.ObjectMeta{Namespace: "test", Name: "pod"},
Status: api.PodStatus{Phase: api.PodFailed},
}
updateFunc := PodReplenishmentUpdateFunc(&options)
updateFunc(oldPod, newPod)
if mockReplenish.groupKind != api.Kind("Pod") {
t.Errorf("Unexpected group kind %v", mockReplenish.groupKind)
}
if mockReplenish.namespace != oldPod.Namespace {
t.Errorf("Unexpected namespace %v", mockReplenish.namespace)
}
}
func TestObjectReplenishmentDeleteFunc(t *testing.T) {
mockReplenish := &testReplenishment{}
options := ReplenishmentControllerOptions{
GroupKind: api.Kind("Pod"),
ReplenishmentFunc: mockReplenish.Replenish,
ResyncPeriod: controller.NoResyncPeriodFunc,
}
oldPod := &api.Pod{
ObjectMeta: api.ObjectMeta{Namespace: "test", Name: "pod"},
Status: api.PodStatus{Phase: api.PodRunning},
}
deleteFunc := ObjectReplenishmentDeleteFunc(&options)
deleteFunc(oldPod)
if mockReplenish.groupKind != api.Kind("Pod") {
t.Errorf("Unexpected group kind %v", mockReplenish.groupKind)
}
if mockReplenish.namespace != oldPod.Namespace {
t.Errorf("Unexpected namespace %v", mockReplenish.namespace)
}
}
...@@ -262,36 +262,41 @@ func DescribeResourceQuotas(quotas *api.ResourceQuotaList, w io.Writer) { ...@@ -262,36 +262,41 @@ func DescribeResourceQuotas(quotas *api.ResourceQuotaList, w io.Writer) {
fmt.Fprint(w, "No resource quota.\n") fmt.Fprint(w, "No resource quota.\n")
return return
} }
resources := []api.ResourceName{} sort.Sort(SortableResourceQuotas(quotas.Items))
hard := map[api.ResourceName]resource.Quantity{}
used := map[api.ResourceName]resource.Quantity{} fmt.Fprint(w, "Resource Quotas")
for _, q := range quotas.Items { for _, q := range quotas.Items {
fmt.Fprintf(w, "\n Name:\t%s\n", q.Name)
if len(q.Spec.Scopes) > 0 {
scopes := []string{}
for _, scope := range q.Spec.Scopes {
scopes = append(scopes, string(scope))
}
sort.Strings(scopes)
fmt.Fprintf(w, " Scopes:\t%s\n", strings.Join(scopes, ", "))
for _, scope := range scopes {
helpText := helpTextForResourceQuotaScope(api.ResourceQuotaScope(scope))
if len(helpText) > 0 {
fmt.Fprintf(w, " * %s\n", helpText)
}
}
}
fmt.Fprintf(w, " Resource\tUsed\tHard\n")
fmt.Fprint(w, " --------\t---\t---\n")
resources := []api.ResourceName{}
for resource := range q.Status.Hard { for resource := range q.Status.Hard {
resources = append(resources, resource) resources = append(resources, resource)
}
sort.Sort(SortableResourceNames(resources))
for _, resource := range resources {
hardQuantity := q.Status.Hard[resource] hardQuantity := q.Status.Hard[resource]
usedQuantity := q.Status.Used[resource] usedQuantity := q.Status.Used[resource]
fmt.Fprintf(w, " %s\t%s\t%s\n", string(resource), usedQuantity.String(), hardQuantity.String())
// if for some reason there are multiple quota documents, we take least permissive
prevQuantity, ok := hard[resource]
if ok {
if hardQuantity.Value() < prevQuantity.Value() {
hard[resource] = hardQuantity
}
} else {
hard[resource] = hardQuantity
}
used[resource] = usedQuantity
} }
} }
sort.Sort(SortableResourceNames(resources))
fmt.Fprint(w, "Resource Quotas\n Resource\tUsed\tHard\n")
fmt.Fprint(w, " ---\t---\t---\n")
for _, resource := range resources {
hardQuantity := hard[resource]
usedQuantity := used[resource]
fmt.Fprintf(w, " %s\t%s\t%s\n", string(resource), usedQuantity.String(), hardQuantity.String())
}
} }
// LimitRangeDescriber generates information about a limit range // LimitRangeDescriber generates information about a limit range
...@@ -397,10 +402,38 @@ func (d *ResourceQuotaDescriber) Describe(namespace, name string) (string, error ...@@ -397,10 +402,38 @@ func (d *ResourceQuotaDescriber) Describe(namespace, name string) (string, error
return describeQuota(resourceQuota) return describeQuota(resourceQuota)
} }
func helpTextForResourceQuotaScope(scope api.ResourceQuotaScope) string {
switch scope {
case api.ResourceQuotaScopeTerminating:
return "Matches all pods that have an active deadline."
case api.ResourceQuotaScopeNotTerminating:
return "Matches all pods that do not have an active deadline."
case api.ResourceQuotaScopeBestEffort:
return "Matches all pods that have best effort quality of service."
case api.ResourceQuotaScopeNotBestEffort:
return "Matches all pods that do not have best effort quality of service."
default:
return ""
}
}
func describeQuota(resourceQuota *api.ResourceQuota) (string, error) { func describeQuota(resourceQuota *api.ResourceQuota) (string, error) {
return tabbedString(func(out io.Writer) error { return tabbedString(func(out io.Writer) error {
fmt.Fprintf(out, "Name:\t%s\n", resourceQuota.Name) fmt.Fprintf(out, "Name:\t%s\n", resourceQuota.Name)
fmt.Fprintf(out, "Namespace:\t%s\n", resourceQuota.Namespace) fmt.Fprintf(out, "Namespace:\t%s\n", resourceQuota.Namespace)
if len(resourceQuota.Spec.Scopes) > 0 {
scopes := []string{}
for _, scope := range resourceQuota.Spec.Scopes {
scopes = append(scopes, string(scope))
}
sort.Strings(scopes)
fmt.Fprintf(out, "Scopes:\t%s\n", strings.Join(scopes, ", "))
for _, scope := range scopes {
helpText := helpTextForResourceQuotaScope(api.ResourceQuotaScope(scope))
if len(helpText) > 0 {
fmt.Fprintf(out, " * %s\n", helpText)
}
}
}
fmt.Fprintf(out, "Resource\tUsed\tHard\n") fmt.Fprintf(out, "Resource\tUsed\tHard\n")
fmt.Fprintf(out, "--------\t----\t----\n") fmt.Fprintf(out, "--------\t----\t----\n")
......
...@@ -33,3 +33,17 @@ func (list SortableResourceNames) Swap(i, j int) { ...@@ -33,3 +33,17 @@ func (list SortableResourceNames) Swap(i, j int) {
func (list SortableResourceNames) Less(i, j int) bool { func (list SortableResourceNames) Less(i, j int) bool {
return list[i] < list[j] return list[i] < list[j]
} }
type SortableResourceQuotas []api.ResourceQuota
func (list SortableResourceQuotas) Len() int {
return len(list)
}
func (list SortableResourceQuotas) Swap(i, j int) {
list[i], list[j] = list[j], list[i]
}
func (list SortableResourceQuotas) Less(i, j int) bool {
return list[i].Name < list[j].Name
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
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.
*/
// core contains modules that interface with the core api group
package core
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package core
import (
"k8s.io/kubernetes/pkg/admission"
"k8s.io/kubernetes/pkg/api"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/quota"
"k8s.io/kubernetes/pkg/quota/generic"
"k8s.io/kubernetes/pkg/runtime"
)
// NewPersistentVolumeClaimEvaluator returns an evaluator that can evaluate persistent volume claims
func NewPersistentVolumeClaimEvaluator(kubeClient clientset.Interface) quota.Evaluator {
allResources := []api.ResourceName{api.ResourcePersistentVolumeClaims}
return &generic.GenericEvaluator{
Name: "Evaluator.PersistentVolumeClaim",
InternalGroupKind: api.Kind("PersistentVolumeClaim"),
InternalOperationResources: map[admission.Operation][]api.ResourceName{
admission.Create: allResources,
},
MatchedResourceNames: allResources,
MatchesScopeFunc: generic.MatchesNoScopeFunc,
ConstraintsFunc: generic.ObjectCountConstraintsFunc(api.ResourcePersistentVolumeClaims),
UsageFunc: generic.ObjectCountUsageFunc(api.ResourcePersistentVolumeClaims),
ListFuncByNamespace: func(namespace string, options api.ListOptions) (runtime.Object, error) {
return kubeClient.Core().PersistentVolumeClaims(namespace).List(options)
},
}
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package core
import (
"fmt"
"strings"
"k8s.io/kubernetes/pkg/admission"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/kubelet/qos/util"
"k8s.io/kubernetes/pkg/quota"
"k8s.io/kubernetes/pkg/quota/generic"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/sets"
)
// NewPodEvaluator returns an evaluator that can evaluate pods
func NewPodEvaluator(kubeClient clientset.Interface) quota.Evaluator {
computeResources := []api.ResourceName{
api.ResourceCPU,
api.ResourceMemory,
api.ResourceRequestsCPU,
api.ResourceRequestsMemory,
api.ResourceLimitsCPU,
api.ResourceLimitsMemory,
}
allResources := append(computeResources, api.ResourcePods)
return &generic.GenericEvaluator{
Name: "Evaluator.Pod",
InternalGroupKind: api.Kind("Pod"),
InternalOperationResources: map[admission.Operation][]api.ResourceName{
admission.Create: allResources,
admission.Update: computeResources,
},
GetFuncByNamespace: func(namespace, name string) (runtime.Object, error) {
return kubeClient.Core().Pods(namespace).Get(name)
},
ConstraintsFunc: PodConstraintsFunc,
MatchedResourceNames: allResources,
MatchesScopeFunc: PodMatchesScopeFunc,
UsageFunc: PodUsageFunc,
ListFuncByNamespace: func(namespace string, options api.ListOptions) (runtime.Object, error) {
return kubeClient.Core().Pods(namespace).List(options)
},
}
}
// PodConstraintsFunc verifies that all required resources are present on the pod
func PodConstraintsFunc(required []api.ResourceName, object runtime.Object) error {
pod, ok := object.(*api.Pod)
if !ok {
return fmt.Errorf("Unexpected input object %v", object)
}
// TODO: fix this when we have pod level cgroups
// since we do not yet pod level requests/limits, we need to ensure each
// container makes an explict request or limit for a quota tracked resource
requiredSet := quota.ToSet(required)
missingSet := sets.NewString()
for i := range pod.Spec.Containers {
requests := pod.Spec.Containers[i].Resources.Requests
limits := pod.Spec.Containers[i].Resources.Limits
containerUsage := podUsageHelper(requests, limits)
containerSet := quota.ToSet(quota.ResourceNames(containerUsage))
if !containerSet.Equal(requiredSet) {
difference := requiredSet.Difference(containerSet)
missingSet.Insert(difference.List()...)
}
}
if len(missingSet) == 0 {
return nil
}
return fmt.Errorf("must specify %s", strings.Join(missingSet.List(), ","))
}
// podUsageHelper can summarize the pod quota usage based on requests and limits
func podUsageHelper(requests api.ResourceList, limits api.ResourceList) api.ResourceList {
result := api.ResourceList{}
result[api.ResourcePods] = resource.MustParse("1")
if request, found := requests[api.ResourceCPU]; found {
result[api.ResourceCPU] = request
result[api.ResourceRequestsCPU] = request
}
if limit, found := limits[api.ResourceCPU]; found {
result[api.ResourceLimitsCPU] = limit
}
if request, found := requests[api.ResourceMemory]; found {
result[api.ResourceMemory] = request
result[api.ResourceRequestsMemory] = request
}
if limit, found := limits[api.ResourceMemory]; found {
result[api.ResourceLimitsMemory] = limit
}
return result
}
// PodUsageFunc knows how to measure usage associated with pods
func PodUsageFunc(object runtime.Object) api.ResourceList {
pod, ok := object.(*api.Pod)
if !ok {
return api.ResourceList{}
}
// by convention, we do not quota pods that have reached an end-of-life state
if !QuotaPod(pod) {
return api.ResourceList{}
}
// TODO: fix this when we have pod level cgroups
// when we have pod level cgroups, we can just read pod level requests/limits
requests := api.ResourceList{}
limits := api.ResourceList{}
for i := range pod.Spec.Containers {
requests = quota.Add(requests, pod.Spec.Containers[i].Resources.Requests)
limits = quota.Add(limits, pod.Spec.Containers[i].Resources.Limits)
}
return podUsageHelper(requests, limits)
}
// PodMatchesScopeFunc is a function that knows how to evaluate if a pod matches a scope
func PodMatchesScopeFunc(scope api.ResourceQuotaScope, object runtime.Object) bool {
pod, ok := object.(*api.Pod)
if !ok {
return false
}
switch scope {
case api.ResourceQuotaScopeTerminating:
return isTerminating(pod)
case api.ResourceQuotaScopeNotTerminating:
return !isTerminating(pod)
case api.ResourceQuotaScopeBestEffort:
return isBestEffort(pod)
case api.ResourceQuotaScopeNotBestEffort:
return !isBestEffort(pod)
}
return false
}
func isBestEffort(pod *api.Pod) bool {
// TODO: when we have request/limits on a pod scope, we need to revisit this
for _, container := range pod.Spec.Containers {
qosPerResource := util.GetQoS(&container)
for _, qos := range qosPerResource {
if util.BestEffort == qos {
return true
}
}
}
return false
}
func isTerminating(pod *api.Pod) bool {
if pod.Spec.ActiveDeadlineSeconds != nil && *pod.Spec.ActiveDeadlineSeconds >= int64(0) {
return true
}
return false
}
// QuotaPod returns true if the pod is eligible to track against a quota
// if it's not in a terminal state according to its phase.
func QuotaPod(pod *api.Pod) bool {
// see GetPhase in kubelet.go for details on how it covers all restart policy conditions
// https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/kubelet.go#L3001
return !(api.PodFailed == pod.Status.Phase || api.PodSucceeded == pod.Status.Phase)
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package core
import (
"k8s.io/kubernetes/pkg/api/unversioned"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/quota"
"k8s.io/kubernetes/pkg/quota/generic"
)
// NewRegistry returns a registry that knows how to deal with core kubernetes resources
func NewRegistry(kubeClient clientset.Interface) quota.Registry {
pod := NewPodEvaluator(kubeClient)
service := NewServiceEvaluator(kubeClient)
replicationController := NewReplicationControllerEvaluator(kubeClient)
resourceQuota := NewResourceQuotaEvaluator(kubeClient)
secret := NewSecretEvaluator(kubeClient)
persistentVolumeClaim := NewPersistentVolumeClaimEvaluator(kubeClient)
return &generic.GenericRegistry{
InternalEvaluators: map[unversioned.GroupKind]quota.Evaluator{
pod.GroupKind(): pod,
service.GroupKind(): service,
replicationController.GroupKind(): replicationController,
secret.GroupKind(): secret,
resourceQuota.GroupKind(): resourceQuota,
persistentVolumeClaim.GroupKind(): persistentVolumeClaim,
},
}
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package core
import (
"k8s.io/kubernetes/pkg/admission"
"k8s.io/kubernetes/pkg/api"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/quota"
"k8s.io/kubernetes/pkg/quota/generic"
"k8s.io/kubernetes/pkg/runtime"
)
// NewReplicationControllerEvaluator returns an evaluator that can evaluate replication controllers
func NewReplicationControllerEvaluator(kubeClient clientset.Interface) quota.Evaluator {
allResources := []api.ResourceName{api.ResourceReplicationControllers}
return &generic.GenericEvaluator{
Name: "Evaluator.ReplicationController",
InternalGroupKind: api.Kind("ReplicationController"),
InternalOperationResources: map[admission.Operation][]api.ResourceName{
admission.Create: allResources,
},
MatchedResourceNames: allResources,
MatchesScopeFunc: generic.MatchesNoScopeFunc,
ConstraintsFunc: generic.ObjectCountConstraintsFunc(api.ResourceReplicationControllers),
UsageFunc: generic.ObjectCountUsageFunc(api.ResourceReplicationControllers),
ListFuncByNamespace: func(namespace string, options api.ListOptions) (runtime.Object, error) {
return kubeClient.Core().ReplicationControllers(namespace).List(options)
},
}
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package core
import (
"k8s.io/kubernetes/pkg/admission"
"k8s.io/kubernetes/pkg/api"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/quota"
"k8s.io/kubernetes/pkg/quota/generic"
"k8s.io/kubernetes/pkg/runtime"
)
// NewResourceQuotaEvaluator returns an evaluator that can evaluate resource quotas
func NewResourceQuotaEvaluator(kubeClient clientset.Interface) quota.Evaluator {
allResources := []api.ResourceName{api.ResourceQuotas}
return &generic.GenericEvaluator{
Name: "Evaluator.ResourceQuota",
InternalGroupKind: api.Kind("ResourceQuota"),
InternalOperationResources: map[admission.Operation][]api.ResourceName{
admission.Create: allResources,
},
MatchedResourceNames: allResources,
MatchesScopeFunc: generic.MatchesNoScopeFunc,
ConstraintsFunc: generic.ObjectCountConstraintsFunc(api.ResourceQuotas),
UsageFunc: generic.ObjectCountUsageFunc(api.ResourceQuotas),
ListFuncByNamespace: func(namespace string, options api.ListOptions) (runtime.Object, error) {
return kubeClient.Core().ResourceQuotas(namespace).List(options)
},
}
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package core
import (
"k8s.io/kubernetes/pkg/admission"
"k8s.io/kubernetes/pkg/api"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/quota"
"k8s.io/kubernetes/pkg/quota/generic"
"k8s.io/kubernetes/pkg/runtime"
)
// NewSecretEvaluator returns an evaluator that can evaluate secrets
func NewSecretEvaluator(kubeClient clientset.Interface) quota.Evaluator {
allResources := []api.ResourceName{api.ResourceSecrets}
return &generic.GenericEvaluator{
Name: "Evaluator.Secret",
InternalGroupKind: api.Kind("Secret"),
InternalOperationResources: map[admission.Operation][]api.ResourceName{
admission.Create: allResources,
},
MatchedResourceNames: allResources,
MatchesScopeFunc: generic.MatchesNoScopeFunc,
ConstraintsFunc: generic.ObjectCountConstraintsFunc(api.ResourceSecrets),
UsageFunc: generic.ObjectCountUsageFunc(api.ResourceSecrets),
ListFuncByNamespace: func(namespace string, options api.ListOptions) (runtime.Object, error) {
return kubeClient.Core().Secrets(namespace).List(options)
},
}
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package core
import (
"k8s.io/kubernetes/pkg/admission"
"k8s.io/kubernetes/pkg/api"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/quota"
"k8s.io/kubernetes/pkg/quota/generic"
"k8s.io/kubernetes/pkg/runtime"
)
// NewServiceEvaluator returns an evaluator that can evaluate service quotas
func NewServiceEvaluator(kubeClient clientset.Interface) quota.Evaluator {
allResources := []api.ResourceName{api.ResourceServices}
return &generic.GenericEvaluator{
Name: "Evaluator.Service",
InternalGroupKind: api.Kind("Service"),
InternalOperationResources: map[admission.Operation][]api.ResourceName{
admission.Create: allResources,
},
MatchedResourceNames: allResources,
MatchesScopeFunc: generic.MatchesNoScopeFunc,
ConstraintsFunc: generic.ObjectCountConstraintsFunc(api.ResourceServices),
UsageFunc: generic.ObjectCountUsageFunc(api.ResourceServices),
ListFuncByNamespace: func(namespace string, options api.ListOptions) (runtime.Object, error) {
return kubeClient.Core().Services(namespace).List(options)
},
}
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
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 generic
import (
"fmt"
"k8s.io/kubernetes/pkg/admission"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/resource"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/quota"
"k8s.io/kubernetes/pkg/runtime"
)
// ConstraintsFunc takes a list of required resources that must match on the input item
type ConstraintsFunc func(required []api.ResourceName, item runtime.Object) error
// GetFuncByNamespace knows how to get a resource with specified namespace and name
type GetFuncByNamespace func(namespace, name string) (runtime.Object, error)
// ListFuncByNamespace knows how to list resources in a namespace
type ListFuncByNamespace func(namespace string, options api.ListOptions) (runtime.Object, error)
// MatchesScopeFunc knows how to evaluate if an object matches a scope
type MatchesScopeFunc func(scope api.ResourceQuotaScope, object runtime.Object) bool
// UsageFunc knows how to measure usage associated with an object
type UsageFunc func(object runtime.Object) api.ResourceList
// MatchesNoScopeFunc returns false on all match checks
func MatchesNoScopeFunc(scope api.ResourceQuotaScope, object runtime.Object) bool {
return false
}
// ObjectCountConstraintsFunc returns true if the specified resource name is in
// the required set of resource names
func ObjectCountConstraintsFunc(resourceName api.ResourceName) ConstraintsFunc {
return func(required []api.ResourceName, item runtime.Object) error {
if !quota.Contains(required, resourceName) {
return fmt.Errorf("missing %s", resourceName)
}
return nil
}
}
// ObjectCountUsageFunc is useful if you are only counting your object
// It always returns 1 as the usage for the named resource
func ObjectCountUsageFunc(resourceName api.ResourceName) UsageFunc {
return func(object runtime.Object) api.ResourceList {
return api.ResourceList{
resourceName: resource.MustParse("1"),
}
}
}
// GenericEvaluator provides an implementation for quota.Evaluator
type GenericEvaluator struct {
// Name used for logging
Name string
// The GroupKind that this evaluator tracks
InternalGroupKind unversioned.GroupKind
// The set of resources that are pertinent to the mapped operation
InternalOperationResources map[admission.Operation][]api.ResourceName
// The set of resource names this evaluator matches
MatchedResourceNames []api.ResourceName
// A function that knows how to evaluate a matches scope request
MatchesScopeFunc MatchesScopeFunc
// A function that knows how to return usage for an object
UsageFunc UsageFunc
// A function that knows how to list resources by namespace
ListFuncByNamespace ListFuncByNamespace
// A function that knows how to get resource in a namespace
// This function must be specified if the evaluator needs to handle UPDATE
GetFuncByNamespace GetFuncByNamespace
// A function that checks required constraints are satisfied
ConstraintsFunc ConstraintsFunc
}
// Ensure that GenericEvaluator implements quota.Evaluator
var _ quota.Evaluator = &GenericEvaluator{}
// Constraints checks required constraints are satisfied on the input object
func (g *GenericEvaluator) Constraints(required []api.ResourceName, item runtime.Object) error {
return g.ConstraintsFunc(required, item)
}
// Get returns the object by namespace and name
func (g *GenericEvaluator) Get(namespace, name string) (runtime.Object, error) {
return g.GetFuncByNamespace(namespace, name)
}
// OperationResources returns the set of resources that could be updated for the
// specified operation for this kind. If empty, admission control will ignore
// quota processing for the operation.
func (g *GenericEvaluator) OperationResources(operation admission.Operation) []api.ResourceName {
return g.InternalOperationResources[operation]
}
// GroupKind that this evaluator tracks
func (g *GenericEvaluator) GroupKind() unversioned.GroupKind {
return g.InternalGroupKind
}
// MatchesResources is the list of resources that this evaluator matches
func (g *GenericEvaluator) MatchesResources() []api.ResourceName {
return g.MatchedResourceNames
}
// Matches returns true if the evaluator matches the specified quota with the provided input item
func (g *GenericEvaluator) Matches(resourceQuota *api.ResourceQuota, item runtime.Object) bool {
if resourceQuota == nil {
return false
}
// verify the quota matches on resource, by default its false
matchResource := false
for resourceName := range resourceQuota.Status.Hard {
if g.MatchesResource(resourceName) {
matchResource = true
}
}
// by default, no scopes matches all
matchScope := true
for _, scope := range resourceQuota.Spec.Scopes {
matchScope = matchScope && g.MatchesScope(scope, item)
}
return matchResource && matchScope
}
// MatchesResource returns true if this evaluator can match on the specified resource
func (g *GenericEvaluator) MatchesResource(resourceName api.ResourceName) bool {
for _, matchedResourceName := range g.MatchedResourceNames {
if resourceName == matchedResourceName {
return true
}
}
return false
}
// MatchesScope returns true if the input object matches the specified scope
func (g *GenericEvaluator) MatchesScope(scope api.ResourceQuotaScope, object runtime.Object) bool {
return g.MatchesScopeFunc(scope, object)
}
// Usage returns the resource usage for the specified object
func (g *GenericEvaluator) Usage(object runtime.Object) api.ResourceList {
return g.UsageFunc(object)
}
// UsageStats calculates latest observed usage stats for all objects
func (g *GenericEvaluator) UsageStats(options quota.UsageStatsOptions) (quota.UsageStats, error) {
// default each tracked resource to zero
result := quota.UsageStats{Used: api.ResourceList{}}
for _, resourceName := range g.MatchedResourceNames {
result.Used[resourceName] = resource.MustParse("0")
}
list, err := g.ListFuncByNamespace(options.Namespace, api.ListOptions{})
if err != nil {
return result, fmt.Errorf("%s: Failed to list %v: %v", g.Name, g.GroupKind, err)
}
_, err = meta.Accessor(list)
if err != nil {
return result, fmt.Errorf("%s: Unable to understand list result %#v", g.Name, list)
}
items, err := meta.ExtractList(list)
if err != nil {
return result, fmt.Errorf("%s: Unable to understand list result %#v (%v)", g.Name, list, err)
}
for _, item := range items {
// need to verify that the item matches the set of scopes
matchesScopes := true
for _, scope := range options.Scopes {
if !g.MatchesScope(scope, item) {
matchesScopes = false
}
}
// only count usage if there was a match
if matchesScopes {
result.Used = quota.Add(result.Used, g.Usage(item))
}
}
return result, nil
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
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 generic
import (
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/quota"
)
// Ensure it implements the required interface
var _ quota.Registry = &GenericRegistry{}
// GenericRegistry implements Registry
type GenericRegistry struct {
// internal evaluators by group kind
InternalEvaluators map[unversioned.GroupKind]quota.Evaluator
}
// Evaluators returns the map of evaluators by groupKind
func (r *GenericRegistry) Evaluators() map[unversioned.GroupKind]quota.Evaluator {
return r.InternalEvaluators
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
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 install
import (
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/quota"
"k8s.io/kubernetes/pkg/quota/evaluator/core"
)
// NewRegistry returns a registry that knows how to deal kubernetes resources
// across API groups
func NewRegistry(kubeClient clientset.Interface) quota.Registry {
// TODO: when quota supports resources in other api groups, we will need to merge
return core.NewRegistry(kubeClient)
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
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 quota
import (
"k8s.io/kubernetes/pkg/admission"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/runtime"
)
// UsageStatsOptions is an options structs that describes how stats should be calculated
type UsageStatsOptions struct {
// Namespace where stats should be calculate
Namespace string
// Scopes that must match counted objects
Scopes []api.ResourceQuotaScope
}
// UsageStats is result of measuring observed resource use in the system
type UsageStats struct {
// Used maps resource to quantity used
Used api.ResourceList
}
// Evaluator knows how to evaluate quota usage for a particular group kind
type Evaluator interface {
// Constraints ensures that each required resource is present on item
Constraints(required []api.ResourceName, item runtime.Object) error
// Get returns the object with specified namespace and name
Get(namespace, name string) (runtime.Object, error)
// GroupKind returns the groupKind that this object knows how to evaluate
GroupKind() unversioned.GroupKind
// MatchesResources is the list of resources that this evaluator matches
MatchesResources() []api.ResourceName
// Matches returns true if the specified quota matches the input item
Matches(resourceQuota *api.ResourceQuota, item runtime.Object) bool
// OperationResources returns the set of resources that could be updated for the
// specified operation for this kind. If empty, admission control will ignore
// quota processing for the operation.
OperationResources(operation admission.Operation) []api.ResourceName
// Usage returns the resource usage for the specified object
Usage(object runtime.Object) api.ResourceList
// UsageStats calculates latest observed usage stats for all objects
UsageStats(options UsageStatsOptions) (UsageStats, error)
}
// Registry holds the list of evaluators associated to a particular group kind
type Registry interface {
// Evaluators returns the set Evaluator objects registered to a groupKind
Evaluators() map[unversioned.GroupKind]Evaluator
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
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 quota
import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource"
"k8s.io/kubernetes/pkg/util/sets"
)
// Equals returns true if the two lists are equivalent
func Equals(a api.ResourceList, b api.ResourceList) bool {
for key, value1 := range a {
value2, found := b[key]
if !found {
return false
}
if value1.Cmp(value2) != 0 {
return false
}
}
for key, value1 := range b {
value2, found := a[key]
if !found {
return false
}
if value1.Cmp(value2) != 0 {
return false
}
}
return true
}
// LessThanOrEqual returns true if a < b for each key in b
// If false, it returns the keys in a that exceeded b
func LessThanOrEqual(a api.ResourceList, b api.ResourceList) (bool, []api.ResourceName) {
result := true
resourceNames := []api.ResourceName{}
for key, value := range b {
if other, found := a[key]; found {
if other.Cmp(value) > 0 {
result = false
resourceNames = append(resourceNames, key)
}
}
}
return result, resourceNames
}
// Add returns the result of a + b for each named resource
func Add(a api.ResourceList, b api.ResourceList) api.ResourceList {
result := api.ResourceList{}
for key, value := range a {
quantity := *value.Copy()
if other, found := b[key]; found {
quantity.Add(other)
}
result[key] = quantity
}
for key, value := range b {
if _, found := result[key]; !found {
quantity := *value.Copy()
result[key] = quantity
}
}
return result
}
// Subtract returns the result of a - b for each named resource
func Subtract(a api.ResourceList, b api.ResourceList) api.ResourceList {
result := api.ResourceList{}
for key, value := range a {
quantity := *value.Copy()
if other, found := b[key]; found {
quantity.Sub(other)
}
result[key] = quantity
}
for key, value := range b {
if _, found := result[key]; !found {
quantity := *value.Copy()
quantity.Neg(value)
result[key] = quantity
}
}
return result
}
// Mask returns a new resource list that only has the values with the specified names
func Mask(resources api.ResourceList, names []api.ResourceName) api.ResourceList {
nameSet := ToSet(names)
result := api.ResourceList{}
for key, value := range resources {
if nameSet.Has(string(key)) {
result[key] = *value.Copy()
}
}
return result
}
// ResourceNames returns a list of all resource names in the ResourceList
func ResourceNames(resources api.ResourceList) []api.ResourceName {
result := []api.ResourceName{}
for resourceName := range resources {
result = append(result, resourceName)
}
return result
}
// Contains returns true if the specified item is in the list of items
func Contains(items []api.ResourceName, item api.ResourceName) bool {
return ToSet(items).Has(string(item))
}
// Intersection returns the intersection of both list of resources
func Intersection(a []api.ResourceName, b []api.ResourceName) []api.ResourceName {
setA := ToSet(a)
setB := ToSet(b)
setC := setA.Intersection(setB)
result := []api.ResourceName{}
for _, resourceName := range setC.List() {
result = append(result, api.ResourceName(resourceName))
}
return result
}
// IsZero returns true if each key maps to the quantity value 0
func IsZero(a api.ResourceList) bool {
zero := resource.MustParse("0")
for _, v := range a {
if v.Cmp(zero) != 0 {
return false
}
}
return true
}
// ToSet takes a list of resource names and converts to a string set
func ToSet(resourceNames []api.ResourceName) sets.String {
result := sets.NewString()
for _, resourceName := range resourceNames {
result.Insert(string(resourceName))
}
return result
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
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 quota
import (
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource"
)
func TestEquals(t *testing.T) {
testCases := map[string]struct {
a api.ResourceList
b api.ResourceList
expected bool
}{
"isEqual": {
a: api.ResourceList{},
b: api.ResourceList{},
expected: true,
},
"isEqualWithKeys": {
a: api.ResourceList{
api.ResourceCPU: resource.MustParse("100m"),
api.ResourceMemory: resource.MustParse("1Gi"),
},
b: api.ResourceList{
api.ResourceCPU: resource.MustParse("100m"),
api.ResourceMemory: resource.MustParse("1Gi"),
},
expected: true,
},
"isNotEqualSameKeys": {
a: api.ResourceList{
api.ResourceCPU: resource.MustParse("200m"),
api.ResourceMemory: resource.MustParse("1Gi"),
},
b: api.ResourceList{
api.ResourceCPU: resource.MustParse("100m"),
api.ResourceMemory: resource.MustParse("1Gi"),
},
expected: false,
},
"isNotEqualDiffKeys": {
a: api.ResourceList{
api.ResourceCPU: resource.MustParse("100m"),
api.ResourceMemory: resource.MustParse("1Gi"),
},
b: api.ResourceList{
api.ResourceCPU: resource.MustParse("100m"),
api.ResourceMemory: resource.MustParse("1Gi"),
api.ResourcePods: resource.MustParse("1"),
},
expected: false,
},
}
for testName, testCase := range testCases {
if result := Equals(testCase.a, testCase.b); result != testCase.expected {
t.Errorf("%s expected: %v, actual: %v, a=%v, b=%v", testName, testCase.expected, result, testCase.a, testCase.b)
}
}
}
func TestAdd(t *testing.T) {
testCases := map[string]struct {
a api.ResourceList
b api.ResourceList
expected api.ResourceList
}{
"noKeys": {
a: api.ResourceList{},
b: api.ResourceList{},
expected: api.ResourceList{},
},
"toEmpty": {
a: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
b: api.ResourceList{},
expected: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
},
"matching": {
a: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
b: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
expected: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
},
}
for testName, testCase := range testCases {
sum := Add(testCase.a, testCase.b)
if result := Equals(testCase.expected, sum); !result {
t.Errorf("%s expected: %v, actual: %v", testName, testCase.expected, sum)
}
}
}
func TestSubtract(t *testing.T) {
testCases := map[string]struct {
a api.ResourceList
b api.ResourceList
expected api.ResourceList
}{
"noKeys": {
a: api.ResourceList{},
b: api.ResourceList{},
expected: api.ResourceList{},
},
"value-empty": {
a: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
b: api.ResourceList{},
expected: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
},
"empty-value": {
a: api.ResourceList{},
b: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
expected: api.ResourceList{api.ResourceCPU: resource.MustParse("-100m")},
},
"value-value": {
a: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")},
b: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
expected: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")},
},
}
for testName, testCase := range testCases {
sub := Subtract(testCase.a, testCase.b)
if result := Equals(testCase.expected, sub); !result {
t.Errorf("%s expected: %v, actual: %v", testName, testCase.expected, sub)
}
}
}
func TestResourceNames(t *testing.T) {
testCases := map[string]struct {
a api.ResourceList
expected []api.ResourceName
}{
"empty": {
a: api.ResourceList{},
expected: []api.ResourceName{},
},
"values": {
a: api.ResourceList{
api.ResourceCPU: resource.MustParse("100m"),
api.ResourceMemory: resource.MustParse("1Gi"),
},
expected: []api.ResourceName{api.ResourceMemory, api.ResourceCPU},
},
}
for testName, testCase := range testCases {
actualSet := ToSet(ResourceNames(testCase.a))
expectedSet := ToSet(testCase.expected)
if !actualSet.Equal(expectedSet) {
t.Errorf("%s expected: %v, actual: %v", testName, expectedSet, actualSet)
}
}
}
func TestContains(t *testing.T) {
testCases := map[string]struct {
a []api.ResourceName
b api.ResourceName
expected bool
}{
"does-not-contain": {
a: []api.ResourceName{api.ResourceMemory},
b: api.ResourceCPU,
expected: false,
},
"does-contain": {
a: []api.ResourceName{api.ResourceMemory, api.ResourceCPU},
b: api.ResourceCPU,
expected: true,
},
}
for testName, testCase := range testCases {
if actual := Contains(testCase.a, testCase.b); actual != testCase.expected {
t.Errorf("%s expected: %v, actual: %v", testName, testCase.expected, actual)
}
}
}
func TestIsZero(t *testing.T) {
testCases := map[string]struct {
a api.ResourceList
expected bool
}{
"empty": {
a: api.ResourceList{},
expected: true,
},
"zero": {
a: api.ResourceList{
api.ResourceCPU: resource.MustParse("0"),
api.ResourceMemory: resource.MustParse("0"),
},
expected: true,
},
"non-zero": {
a: api.ResourceList{
api.ResourceCPU: resource.MustParse("200m"),
api.ResourceMemory: resource.MustParse("1Gi"),
},
expected: false,
},
}
for testName, testCase := range testCases {
if result := IsZero(testCase.a); result != testCase.expected {
t.Errorf("%s expected: %v, actual: %v", testName, testCase.expected)
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment