mirror of https://github.com/k3s-io/k3s
Introduce priority class in the resource quota
parent
4c13f5fdf5
commit
3cfe6412c7
|
@ -80125,12 +80125,16 @@
|
|||
"description": "ResourceQuotaSpec defines the desired hard limits to enforce for Quota.",
|
||||
"properties": {
|
||||
"hard": {
|
||||
"description": "Hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/",
|
||||
"description": "hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/io.k8s.apimachinery.pkg.api.resource.Quantity"
|
||||
}
|
||||
},
|
||||
"scopeSelector": {
|
||||
"description": "scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched.",
|
||||
"$ref": "#/definitions/io.k8s.api.core.v1.ScopeSelector"
|
||||
},
|
||||
"scopes": {
|
||||
"description": "A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects.",
|
||||
"type": "array",
|
||||
|
@ -80299,6 +80303,42 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"io.k8s.api.core.v1.ScopeSelector": {
|
||||
"description": "A scope selector represents the AND of the selectors represented by the scoped-resource selector requirements.",
|
||||
"properties": {
|
||||
"matchExpressions": {
|
||||
"description": "A list of scope selector requirements by scope of the resources.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/io.k8s.api.core.v1.ScopedResourceSelectorRequirement"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"io.k8s.api.core.v1.ScopedResourceSelectorRequirement": {
|
||||
"description": "A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values.",
|
||||
"required": [
|
||||
"scopeName",
|
||||
"operator"
|
||||
],
|
||||
"properties": {
|
||||
"operator": {
|
||||
"description": "Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist.",
|
||||
"type": "string"
|
||||
},
|
||||
"scopeName": {
|
||||
"description": "The name of the scope that the selector applies to.",
|
||||
"type": "string"
|
||||
},
|
||||
"values": {
|
||||
"description": "An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"io.k8s.api.core.v1.Secret": {
|
||||
"description": "Secret holds secret data of a certain type. The total bytes of the values in the Data field must be less than MaxSecretSize bytes.",
|
||||
"properties": {
|
||||
|
|
|
@ -22532,7 +22532,7 @@
|
|||
"properties": {
|
||||
"hard": {
|
||||
"type": "object",
|
||||
"description": "Hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/"
|
||||
"description": "hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/"
|
||||
},
|
||||
"scopes": {
|
||||
"type": "array",
|
||||
|
@ -22540,6 +22540,10 @@
|
|||
"$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."
|
||||
},
|
||||
"scopeSelector": {
|
||||
"$ref": "v1.ScopeSelector",
|
||||
"description": "scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -22547,6 +22551,44 @@
|
|||
"id": "v1.ResourceQuotaScope",
|
||||
"properties": {}
|
||||
},
|
||||
"v1.ScopeSelector": {
|
||||
"id": "v1.ScopeSelector",
|
||||
"description": "A scope selector represents the AND of the selectors represented by the scoped-resource selector requirements.",
|
||||
"properties": {
|
||||
"matchExpressions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "v1.ScopedResourceSelectorRequirement"
|
||||
},
|
||||
"description": "A list of scope selector requirements by scope of the resources."
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.ScopedResourceSelectorRequirement": {
|
||||
"id": "v1.ScopedResourceSelectorRequirement",
|
||||
"description": "A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values.",
|
||||
"required": [
|
||||
"scopeName",
|
||||
"operator"
|
||||
],
|
||||
"properties": {
|
||||
"scopeName": {
|
||||
"type": "string",
|
||||
"description": "The name of the scope that the selector applies to."
|
||||
},
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"description": "Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist."
|
||||
},
|
||||
"values": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch."
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.ResourceQuotaStatus": {
|
||||
"id": "v1.ResourceQuotaStatus",
|
||||
"description": "ResourceQuotaStatus defines the enforced hard limits and observed use.",
|
||||
|
|
|
@ -5963,6 +5963,40 @@ Examples:<br>
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_v1_scopeselector">v1.ScopeSelector</h3>
|
||||
<div class="paragraph">
|
||||
<p>A scope selector represents the AND of the selectors represented by the scoped-resource selector requirements.</p>
|
||||
</div>
|
||||
<table class="tableblock frame-all grid-all" style="width:100%; ">
|
||||
<colgroup>
|
||||
<col style="width:20%;">
|
||||
<col style="width:20%;">
|
||||
<col style="width:20%;">
|
||||
<col style="width:20%;">
|
||||
<col style="width:20%;">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="tableblock halign-left valign-top">Name</th>
|
||||
<th class="tableblock halign-left valign-top">Description</th>
|
||||
<th class="tableblock halign-left valign-top">Required</th>
|
||||
<th class="tableblock halign-left valign-top">Schema</th>
|
||||
<th class="tableblock halign-left valign-top">Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">matchExpressions</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">A list of scope selector requirements by scope of the resources.</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_scopedresourceselectorrequirement">v1.ScopedResourceSelectorRequirement</a> array</p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_v1_nodesysteminfo">v1.NodeSystemInfo</h3>
|
||||
|
@ -6870,7 +6904,7 @@ Examples:<br>
|
|||
<tbody>
|
||||
<tr>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">hard</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Hard is the set of desired hard limits for each named resource. More info: <a href="https://kubernetes.io/docs/concepts/policy/resource-quotas/">https://kubernetes.io/docs/concepts/policy/resource-quotas/</a></p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">hard is the set of desired hard limits for each named resource. More info: <a href="https://kubernetes.io/docs/concepts/policy/resource-quotas/">https://kubernetes.io/docs/concepts/policy/resource-quotas/</a></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">object</p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
|
@ -6882,6 +6916,13 @@ Examples:<br>
|
|||
<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>
|
||||
<tr>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">scopeSelector</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched.</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_scopeselector">v1.ScopeSelector</a></p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
@ -8377,61 +8418,6 @@ Examples:<br>
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_v1_limitrangelist">v1.LimitRangeList</h3>
|
||||
<div class="paragraph">
|
||||
<p>LimitRangeList is a list of LimitRange items.</p>
|
||||
</div>
|
||||
<table class="tableblock frame-all grid-all" style="width:100%; ">
|
||||
<colgroup>
|
||||
<col style="width:20%;">
|
||||
<col style="width:20%;">
|
||||
<col style="width:20%;">
|
||||
<col style="width:20%;">
|
||||
<col style="width:20%;">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="tableblock halign-left valign-top">Name</th>
|
||||
<th class="tableblock halign-left valign-top">Description</th>
|
||||
<th class="tableblock halign-left valign-top">Required</th>
|
||||
<th class="tableblock halign-left valign-top">Schema</th>
|
||||
<th class="tableblock halign-left valign-top">Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">kind</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: <a href="https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds">https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds</a></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">string</p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">apiVersion</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: <a href="https://git.k8s.io/community/contributors/devel/api-conventions.md#resources">https://git.k8s.io/community/contributors/devel/api-conventions.md#resources</a></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">string</p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">metadata</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Standard list metadata. More info: <a href="https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds">https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds</a></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_listmeta">v1.ListMeta</a></p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">items</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Items is a list of LimitRange objects. More info: <a href="https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/">https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/</a></p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_limitrange">v1.LimitRange</a> array</p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_v1_serviceaccountlist">v1.ServiceAccountList</h3>
|
||||
|
@ -8487,6 +8473,61 @@ Examples:<br>
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_v1_limitrangelist">v1.LimitRangeList</h3>
|
||||
<div class="paragraph">
|
||||
<p>LimitRangeList is a list of LimitRange items.</p>
|
||||
</div>
|
||||
<table class="tableblock frame-all grid-all" style="width:100%; ">
|
||||
<colgroup>
|
||||
<col style="width:20%;">
|
||||
<col style="width:20%;">
|
||||
<col style="width:20%;">
|
||||
<col style="width:20%;">
|
||||
<col style="width:20%;">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="tableblock halign-left valign-top">Name</th>
|
||||
<th class="tableblock halign-left valign-top">Description</th>
|
||||
<th class="tableblock halign-left valign-top">Required</th>
|
||||
<th class="tableblock halign-left valign-top">Schema</th>
|
||||
<th class="tableblock halign-left valign-top">Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">kind</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: <a href="https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds">https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds</a></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">string</p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">apiVersion</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: <a href="https://git.k8s.io/community/contributors/devel/api-conventions.md#resources">https://git.k8s.io/community/contributors/devel/api-conventions.md#resources</a></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">string</p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">metadata</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Standard list metadata. More info: <a href="https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds">https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds</a></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_listmeta">v1.ListMeta</a></p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">items</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Items is a list of LimitRange objects. More info: <a href="https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/">https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/</a></p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_limitrange">v1.LimitRange</a> array</p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_v1_endpoints">v1.Endpoints</h3>
|
||||
|
@ -9607,6 +9648,54 @@ More info: <a href="https://kubernetes.io/docs/concepts/workloads/pods/pod-lifec
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_v1_scopedresourceselectorrequirement">v1.ScopedResourceSelectorRequirement</h3>
|
||||
<div class="paragraph">
|
||||
<p>A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values.</p>
|
||||
</div>
|
||||
<table class="tableblock frame-all grid-all" style="width:100%; ">
|
||||
<colgroup>
|
||||
<col style="width:20%;">
|
||||
<col style="width:20%;">
|
||||
<col style="width:20%;">
|
||||
<col style="width:20%;">
|
||||
<col style="width:20%;">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="tableblock halign-left valign-top">Name</th>
|
||||
<th class="tableblock halign-left valign-top">Description</th>
|
||||
<th class="tableblock halign-left valign-top">Required</th>
|
||||
<th class="tableblock halign-left valign-top">Schema</th>
|
||||
<th class="tableblock halign-left valign-top">Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">scopeName</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">The name of the scope that the selector applies to.</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">operator</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Represents a scope’s relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist.</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">values</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.</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">string array</p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<div class="sect2">
|
||||
<h3 id="_v1_eventlist">v1.EventList</h3>
|
||||
|
|
|
@ -103,6 +103,7 @@ var standardResourceQuotaScopes = sets.NewString(
|
|||
string(core.ResourceQuotaScopeNotTerminating),
|
||||
string(core.ResourceQuotaScopeBestEffort),
|
||||
string(core.ResourceQuotaScopeNotBestEffort),
|
||||
string(core.ResourceQuotaScopePriorityClass),
|
||||
)
|
||||
|
||||
// IsStandardResourceQuotaScope returns true if the scope is a standard value
|
||||
|
@ -126,7 +127,7 @@ var podComputeQuotaResources = sets.NewString(
|
|||
// IsResourceQuotaScopeValidForResource returns true if the resource applies to the specified scope
|
||||
func IsResourceQuotaScopeValidForResource(scope core.ResourceQuotaScope, resource string) bool {
|
||||
switch scope {
|
||||
case core.ResourceQuotaScopeTerminating, core.ResourceQuotaScopeNotTerminating, core.ResourceQuotaScopeNotBestEffort:
|
||||
case core.ResourceQuotaScopeTerminating, core.ResourceQuotaScopeNotTerminating, core.ResourceQuotaScopeNotBestEffort, core.ResourceQuotaScopePriorityClass:
|
||||
return podObjectCountQuotaResources.Has(resource) || podComputeQuotaResources.Has(resource)
|
||||
case core.ResourceQuotaScopeBestEffort:
|
||||
return podObjectCountQuotaResources.Has(resource)
|
||||
|
@ -584,3 +585,28 @@ func PersistentVolumeClaimHasClass(claim *core.PersistentVolumeClaim) bool {
|
|||
|
||||
return false
|
||||
}
|
||||
|
||||
// ScopedResourceSelectorRequirementsAsSelector converts the ScopedResourceSelectorRequirement api type into a struct that implements
|
||||
// labels.Selector.
|
||||
func ScopedResourceSelectorRequirementsAsSelector(ssr core.ScopedResourceSelectorRequirement) (labels.Selector, error) {
|
||||
selector := labels.NewSelector()
|
||||
var op selection.Operator
|
||||
switch ssr.Operator {
|
||||
case core.ScopeSelectorOpIn:
|
||||
op = selection.In
|
||||
case core.ScopeSelectorOpNotIn:
|
||||
op = selection.NotIn
|
||||
case core.ScopeSelectorOpExists:
|
||||
op = selection.Exists
|
||||
case core.ScopeSelectorOpDoesNotExist:
|
||||
op = selection.DoesNotExist
|
||||
default:
|
||||
return nil, fmt.Errorf("%q is not a valid scope selector operator", ssr.Operator)
|
||||
}
|
||||
r, err := labels.NewRequirement(string(ssr.ScopeName), op, ssr.Values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
selector = selector.Add(*r)
|
||||
return selector, nil
|
||||
}
|
||||
|
|
|
@ -4168,6 +4168,8 @@ const (
|
|||
ResourceQuotaScopeBestEffort ResourceQuotaScope = "BestEffort"
|
||||
// Match all pod objects that do not have best effort quality of service
|
||||
ResourceQuotaScopeNotBestEffort ResourceQuotaScope = "NotBestEffort"
|
||||
// Match all pod objects that have priority class mentioned
|
||||
ResourceQuotaScopePriorityClass ResourceQuotaScope = "PriorityClass"
|
||||
)
|
||||
|
||||
// ResourceQuotaSpec defines the desired hard limits to enforce for Quota
|
||||
|
@ -4179,8 +4181,47 @@ type ResourceQuotaSpec struct {
|
|||
// If not specified, the quota matches all objects.
|
||||
// +optional
|
||||
Scopes []ResourceQuotaScope
|
||||
// ScopeSelector is also a collection of filters like Scopes that must match each object tracked by a quota
|
||||
// but expressed using ScopeSelectorOperator in combination with possible values.
|
||||
// +optional
|
||||
ScopeSelector *ScopeSelector
|
||||
}
|
||||
|
||||
// A scope selector represents the AND of the selectors represented
|
||||
// by the scoped-resource selector terms.
|
||||
type ScopeSelector struct {
|
||||
// A list of scope selector requirements by scope of the resources.
|
||||
// +optional
|
||||
MatchExpressions []ScopedResourceSelectorRequirement
|
||||
}
|
||||
|
||||
// A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator
|
||||
// that relates the scope name and values.
|
||||
type ScopedResourceSelectorRequirement struct {
|
||||
// The name of the scope that the selector applies to.
|
||||
ScopeName ResourceQuotaScope
|
||||
// Represents a scope's relationship to a set of values.
|
||||
// Valid operators are In, NotIn, Exists, DoesNotExist.
|
||||
Operator ScopeSelectorOperator
|
||||
// An array of string values. If the operator is In or NotIn,
|
||||
// the values array must be non-empty. If the operator is Exists or DoesNotExist,
|
||||
// the values array must be empty.
|
||||
// This array is replaced during a strategic merge patch.
|
||||
// +optional
|
||||
Values []string
|
||||
}
|
||||
|
||||
// A scope selector operator is the set of operators that can be used in
|
||||
// a scope selector requirement.
|
||||
type ScopeSelectorOperator string
|
||||
|
||||
const (
|
||||
ScopeSelectorOpIn ScopeSelectorOperator = "In"
|
||||
ScopeSelectorOpNotIn ScopeSelectorOperator = "NotIn"
|
||||
ScopeSelectorOpExists ScopeSelectorOperator = "Exists"
|
||||
ScopeSelectorOpDoesNotExist ScopeSelectorOperator = "DoesNotExist"
|
||||
)
|
||||
|
||||
// ResourceQuotaStatus defines the enforced hard limits and observed use
|
||||
type ResourceQuotaStatus struct {
|
||||
// Hard is the set of enforced hard limits for each named resource
|
||||
|
|
|
@ -354,6 +354,10 @@ func RegisterConversions(scheme *runtime.Scheme) error {
|
|||
Convert_core_ScaleIOPersistentVolumeSource_To_v1_ScaleIOPersistentVolumeSource,
|
||||
Convert_v1_ScaleIOVolumeSource_To_core_ScaleIOVolumeSource,
|
||||
Convert_core_ScaleIOVolumeSource_To_v1_ScaleIOVolumeSource,
|
||||
Convert_v1_ScopeSelector_To_core_ScopeSelector,
|
||||
Convert_core_ScopeSelector_To_v1_ScopeSelector,
|
||||
Convert_v1_ScopedResourceSelectorRequirement_To_core_ScopedResourceSelectorRequirement,
|
||||
Convert_core_ScopedResourceSelectorRequirement_To_v1_ScopedResourceSelectorRequirement,
|
||||
Convert_v1_Secret_To_core_Secret,
|
||||
Convert_core_Secret_To_v1_Secret,
|
||||
Convert_v1_SecretEnvSource_To_core_SecretEnvSource,
|
||||
|
@ -4641,6 +4645,7 @@ func Convert_core_ResourceQuotaList_To_v1_ResourceQuotaList(in *core.ResourceQuo
|
|||
func autoConvert_v1_ResourceQuotaSpec_To_core_ResourceQuotaSpec(in *v1.ResourceQuotaSpec, out *core.ResourceQuotaSpec, s conversion.Scope) error {
|
||||
out.Hard = *(*core.ResourceList)(unsafe.Pointer(&in.Hard))
|
||||
out.Scopes = *(*[]core.ResourceQuotaScope)(unsafe.Pointer(&in.Scopes))
|
||||
out.ScopeSelector = (*core.ScopeSelector)(unsafe.Pointer(in.ScopeSelector))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -4652,6 +4657,7 @@ func Convert_v1_ResourceQuotaSpec_To_core_ResourceQuotaSpec(in *v1.ResourceQuota
|
|||
func autoConvert_core_ResourceQuotaSpec_To_v1_ResourceQuotaSpec(in *core.ResourceQuotaSpec, out *v1.ResourceQuotaSpec, s conversion.Scope) error {
|
||||
out.Hard = *(*v1.ResourceList)(unsafe.Pointer(&in.Hard))
|
||||
out.Scopes = *(*[]v1.ResourceQuotaScope)(unsafe.Pointer(&in.Scopes))
|
||||
out.ScopeSelector = (*v1.ScopeSelector)(unsafe.Pointer(in.ScopeSelector))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -4806,6 +4812,50 @@ func Convert_core_ScaleIOVolumeSource_To_v1_ScaleIOVolumeSource(in *core.ScaleIO
|
|||
return autoConvert_core_ScaleIOVolumeSource_To_v1_ScaleIOVolumeSource(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_ScopeSelector_To_core_ScopeSelector(in *v1.ScopeSelector, out *core.ScopeSelector, s conversion.Scope) error {
|
||||
out.MatchExpressions = *(*[]core.ScopedResourceSelectorRequirement)(unsafe.Pointer(&in.MatchExpressions))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_ScopeSelector_To_core_ScopeSelector is an autogenerated conversion function.
|
||||
func Convert_v1_ScopeSelector_To_core_ScopeSelector(in *v1.ScopeSelector, out *core.ScopeSelector, s conversion.Scope) error {
|
||||
return autoConvert_v1_ScopeSelector_To_core_ScopeSelector(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_core_ScopeSelector_To_v1_ScopeSelector(in *core.ScopeSelector, out *v1.ScopeSelector, s conversion.Scope) error {
|
||||
out.MatchExpressions = *(*[]v1.ScopedResourceSelectorRequirement)(unsafe.Pointer(&in.MatchExpressions))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_core_ScopeSelector_To_v1_ScopeSelector is an autogenerated conversion function.
|
||||
func Convert_core_ScopeSelector_To_v1_ScopeSelector(in *core.ScopeSelector, out *v1.ScopeSelector, s conversion.Scope) error {
|
||||
return autoConvert_core_ScopeSelector_To_v1_ScopeSelector(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_ScopedResourceSelectorRequirement_To_core_ScopedResourceSelectorRequirement(in *v1.ScopedResourceSelectorRequirement, out *core.ScopedResourceSelectorRequirement, s conversion.Scope) error {
|
||||
out.ScopeName = core.ResourceQuotaScope(in.ScopeName)
|
||||
out.Operator = core.ScopeSelectorOperator(in.Operator)
|
||||
out.Values = *(*[]string)(unsafe.Pointer(&in.Values))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_ScopedResourceSelectorRequirement_To_core_ScopedResourceSelectorRequirement is an autogenerated conversion function.
|
||||
func Convert_v1_ScopedResourceSelectorRequirement_To_core_ScopedResourceSelectorRequirement(in *v1.ScopedResourceSelectorRequirement, out *core.ScopedResourceSelectorRequirement, s conversion.Scope) error {
|
||||
return autoConvert_v1_ScopedResourceSelectorRequirement_To_core_ScopedResourceSelectorRequirement(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_core_ScopedResourceSelectorRequirement_To_v1_ScopedResourceSelectorRequirement(in *core.ScopedResourceSelectorRequirement, out *v1.ScopedResourceSelectorRequirement, s conversion.Scope) error {
|
||||
out.ScopeName = v1.ResourceQuotaScope(in.ScopeName)
|
||||
out.Operator = v1.ScopeSelectorOperator(in.Operator)
|
||||
out.Values = *(*[]string)(unsafe.Pointer(&in.Values))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_core_ScopedResourceSelectorRequirement_To_v1_ScopedResourceSelectorRequirement is an autogenerated conversion function.
|
||||
func Convert_core_ScopedResourceSelectorRequirement_To_v1_ScopedResourceSelectorRequirement(in *core.ScopedResourceSelectorRequirement, out *v1.ScopedResourceSelectorRequirement, s conversion.Scope) error {
|
||||
return autoConvert_core_ScopedResourceSelectorRequirement_To_v1_ScopedResourceSelectorRequirement(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_Secret_To_core_Secret(in *v1.Secret, out *core.Secret, s conversion.Scope) error {
|
||||
out.ObjectMeta = in.ObjectMeta
|
||||
out.Data = *(*map[string][]byte)(unsafe.Pointer(&in.Data))
|
||||
|
|
|
@ -4786,6 +4786,74 @@ func validateResourceQuotaScopes(resourceQuotaSpec *core.ResourceQuotaSpec, fld
|
|||
return allErrs
|
||||
}
|
||||
|
||||
// validateScopedResourceSelectorRequirement tests that the match expressions has valid data
|
||||
func validateScopedResourceSelectorRequirement(resourceQuotaSpec *core.ResourceQuotaSpec, fld *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
hardLimits := sets.NewString()
|
||||
for k := range resourceQuotaSpec.Hard {
|
||||
hardLimits.Insert(string(k))
|
||||
}
|
||||
fldPath := fld.Child("matchExpressions")
|
||||
scopeSet := sets.NewString()
|
||||
for _, req := range resourceQuotaSpec.ScopeSelector.MatchExpressions {
|
||||
if !helper.IsStandardResourceQuotaScope(string(req.ScopeName)) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("scopeName"), req.ScopeName, "unsupported scope"))
|
||||
}
|
||||
for _, k := range hardLimits.List() {
|
||||
if helper.IsStandardQuotaResourceName(k) && !helper.IsResourceQuotaScopeValidForResource(req.ScopeName, k) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, resourceQuotaSpec.ScopeSelector, "unsupported scope applied to resource"))
|
||||
}
|
||||
}
|
||||
switch req.ScopeName {
|
||||
case core.ResourceQuotaScopeBestEffort, core.ResourceQuotaScopeNotBestEffort, core.ResourceQuotaScopeTerminating, core.ResourceQuotaScopeNotTerminating:
|
||||
if req.Operator != core.ScopeSelectorOpExists {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), req.Operator,
|
||||
"must be 'Exist' only operator when scope is any of ResourceQuotaScopeTerminating, ResourceQuotaScopeNotTerminating, ResourceQuotaScopeBestEffort and ResourceQuotaScopeNotBestEffort"))
|
||||
}
|
||||
}
|
||||
|
||||
switch req.Operator {
|
||||
case core.ScopeSelectorOpIn, core.ScopeSelectorOpNotIn:
|
||||
if len(req.Values) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("values"),
|
||||
"must be atleast one value when `operator` is 'In' or 'NotIn' for scope selector"))
|
||||
}
|
||||
case core.ScopeSelectorOpExists, core.ScopeSelectorOpDoesNotExist:
|
||||
if len(req.Values) != 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("values"), req.Values,
|
||||
"must be no value when `operator` is 'Exist' or 'DoesNotExist' for scope selector"))
|
||||
}
|
||||
default:
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), req.Operator, "not a valid selector operator"))
|
||||
}
|
||||
scopeSet.Insert(string(req.ScopeName))
|
||||
}
|
||||
invalidScopePairs := []sets.String{
|
||||
sets.NewString(string(core.ResourceQuotaScopeBestEffort), string(core.ResourceQuotaScopeNotBestEffort)),
|
||||
sets.NewString(string(core.ResourceQuotaScopeTerminating), string(core.ResourceQuotaScopeNotTerminating)),
|
||||
}
|
||||
for _, invalidScopePair := range invalidScopePairs {
|
||||
if scopeSet.HasAll(invalidScopePair.List()...) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, resourceQuotaSpec.Scopes, "conflicting scopes"))
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validateScopeSelector tests that the specified scope selector has valid data
|
||||
func validateScopeSelector(resourceQuotaSpec *core.ResourceQuotaSpec, fld *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if resourceQuotaSpec.ScopeSelector == nil {
|
||||
return allErrs
|
||||
}
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.ResourceQuotaScopeSelectors) && resourceQuotaSpec.ScopeSelector != nil {
|
||||
allErrs = append(allErrs, field.Forbidden(fld.Child("scopeSelector"), "ResourceQuotaScopeSelectors feature-gate is disabled"))
|
||||
}
|
||||
allErrs = append(allErrs, validateScopedResourceSelectorRequirement(resourceQuotaSpec, fld.Child("scopeSelector"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateResourceQuota tests if required fields in the ResourceQuota are set.
|
||||
func ValidateResourceQuota(resourceQuota *core.ResourceQuota) field.ErrorList {
|
||||
allErrs := ValidateObjectMeta(&resourceQuota.ObjectMeta, true, ValidateResourceQuotaName, field.NewPath("metadata"))
|
||||
|
@ -4825,6 +4893,7 @@ func ValidateResourceQuotaSpec(resourceQuotaSpec *core.ResourceQuotaSpec, fld *f
|
|||
allErrs = append(allErrs, ValidateResourceQuantityValue(string(k), v, resPath)...)
|
||||
}
|
||||
allErrs = append(allErrs, validateResourceQuotaScopes(resourceQuotaSpec, fld)...)
|
||||
allErrs = append(allErrs, validateScopeSelector(resourceQuotaSpec, fld)...)
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
|
|
@ -11508,6 +11508,18 @@ func TestValidateResourceQuota(t *testing.T) {
|
|||
Scopes: []core.ResourceQuotaScope{core.ResourceQuotaScopeNotBestEffort},
|
||||
}
|
||||
|
||||
scopeSelectorSpec := core.ResourceQuotaSpec{
|
||||
ScopeSelector: &core.ScopeSelector{
|
||||
MatchExpressions: []core.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: core.ResourceQuotaScopePriorityClass,
|
||||
Operator: core.ScopeSelectorOpIn,
|
||||
Values: []string{"cluster-services"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// storage is not yet supported as a quota tracked resource
|
||||
invalidQuotaResourceSpec := core.ResourceQuotaSpec{
|
||||
Hard: core.ResourceList{
|
||||
|
@ -11600,6 +11612,13 @@ func TestValidateResourceQuota(t *testing.T) {
|
|||
},
|
||||
Spec: bestEffortSpec,
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "abc",
|
||||
Namespace: "foo",
|
||||
},
|
||||
Spec: scopeSelectorSpec,
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "abc",
|
||||
|
@ -11608,12 +11627,13 @@ func TestValidateResourceQuota(t *testing.T) {
|
|||
Spec: nonBestEffortSpec,
|
||||
},
|
||||
}
|
||||
|
||||
utilfeature.DefaultFeatureGate.Set("ResourceQuotaScopeSelectors=true")
|
||||
for _, successCase := range successCases {
|
||||
if errs := ValidateResourceQuota(&successCase); len(errs) != 0 {
|
||||
t.Errorf("expected success: %v", errs)
|
||||
}
|
||||
}
|
||||
utilfeature.DefaultFeatureGate.Set("ResourceQuotaScopeSelectors=false")
|
||||
|
||||
errorCases := map[string]struct {
|
||||
R core.ResourceQuota
|
||||
|
@ -11659,6 +11679,10 @@ func TestValidateResourceQuota(t *testing.T) {
|
|||
core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidScopeNameSpec},
|
||||
"unsupported scope",
|
||||
},
|
||||
"forbidden-quota-scope-selector": {
|
||||
core.ResourceQuota{ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: scopeSelectorSpec},
|
||||
"feature-gate is disabled",
|
||||
},
|
||||
}
|
||||
for k, v := range errorCases {
|
||||
errs := ValidateResourceQuota(&v.R)
|
||||
|
|
|
@ -4630,6 +4630,15 @@ func (in *ResourceQuotaSpec) DeepCopyInto(out *ResourceQuotaSpec) {
|
|||
*out = make([]ResourceQuotaScope, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ScopeSelector != nil {
|
||||
in, out := &in.ScopeSelector, &out.ScopeSelector
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(ScopeSelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -4769,6 +4778,50 @@ func (in *ScaleIOVolumeSource) DeepCopy() *ScaleIOVolumeSource {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ScopeSelector) DeepCopyInto(out *ScopeSelector) {
|
||||
*out = *in
|
||||
if in.MatchExpressions != nil {
|
||||
in, out := &in.MatchExpressions, &out.MatchExpressions
|
||||
*out = make([]ScopedResourceSelectorRequirement, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScopeSelector.
|
||||
func (in *ScopeSelector) DeepCopy() *ScopeSelector {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ScopeSelector)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ScopedResourceSelectorRequirement) DeepCopyInto(out *ScopedResourceSelectorRequirement) {
|
||||
*out = *in
|
||||
if in.Values != nil {
|
||||
in, out := &in.Values, &out.Values
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScopedResourceSelectorRequirement.
|
||||
func (in *ScopedResourceSelectorRequirement) DeepCopy() *ScopedResourceSelectorRequirement {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ScopedResourceSelectorRequirement)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Secret) DeepCopyInto(out *Secret) {
|
||||
*out = *in
|
||||
|
|
|
@ -324,6 +324,13 @@ const (
|
|||
//
|
||||
// Enable probe based plugin watcher utility for discovering Kubelet plugins
|
||||
KubeletPluginsWatcher utilfeature.Feature = "KubeletPluginsWatcher"
|
||||
|
||||
// owner: @vikaschoudhary16
|
||||
// alpha: v1.11
|
||||
//
|
||||
//
|
||||
// Enable resource quota scope selectors
|
||||
ResourceQuotaScopeSelectors utilfeature.Feature = "ResourceQuotaScopeSelectors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -379,6 +386,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
|
|||
PodReadinessGates: {Default: false, PreRelease: utilfeature.Beta},
|
||||
VolumeSubpathEnvExpansion: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
KubeletPluginsWatcher: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
ResourceQuotaScopeSelectors: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
|
||||
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
|
||||
// unintentionally on either side:
|
||||
|
|
|
@ -27,6 +27,7 @@ go_library(
|
|||
"//pkg/quota/generic:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library",
|
||||
|
|
|
@ -123,6 +123,17 @@ func (p *pvcEvaluator) Matches(resourceQuota *api.ResourceQuota, item runtime.Ob
|
|||
return generic.Matches(resourceQuota, item, p.MatchingResources, generic.MatchesNoScopeFunc)
|
||||
}
|
||||
|
||||
// MatchingScopes takes the input specified list of scopes and input object. Returns the set of scopes resource matches.
|
||||
func (p *pvcEvaluator) MatchingScopes(item runtime.Object, scopes []api.ScopedResourceSelectorRequirement) ([]api.ScopedResourceSelectorRequirement, error) {
|
||||
return []api.ScopedResourceSelectorRequirement{}, nil
|
||||
}
|
||||
|
||||
// UncoveredQuotaScopes takes the input matched scopes which are limited by configuration and the matched quota scopes.
|
||||
// It returns the scopes which are in limited scopes but dont have a corresponding covering quota scope
|
||||
func (p *pvcEvaluator) UncoveredQuotaScopes(limitedScopes []api.ScopedResourceSelectorRequirement, matchedQuotaScopes []api.ScopedResourceSelectorRequirement) ([]api.ScopedResourceSelectorRequirement, error) {
|
||||
return []api.ScopedResourceSelectorRequirement{}, nil
|
||||
}
|
||||
|
||||
// MatchingResources takes the input specified list of resources and returns the set of resources it matches.
|
||||
func (p *pvcEvaluator) MatchingResources(items []api.ResourceName) []api.ResourceName {
|
||||
result := []api.ResourceName{}
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
|
@ -117,7 +118,7 @@ type podEvaluator struct {
|
|||
func (p *podEvaluator) Constraints(required []api.ResourceName, item runtime.Object) error {
|
||||
pod, ok := item.(*api.Pod)
|
||||
if !ok {
|
||||
return fmt.Errorf("Unexpected input object %v", item)
|
||||
return fmt.Errorf("unexpected input object %v", item)
|
||||
}
|
||||
|
||||
// BACKWARD COMPATIBILITY REQUIREMENT: if we quota cpu or memory, then each container
|
||||
|
@ -181,6 +182,41 @@ func (p *podEvaluator) MatchingResources(input []api.ResourceName) []api.Resourc
|
|||
return result
|
||||
}
|
||||
|
||||
// MatchingScopes takes the input specified list of scopes and pod object. Returns the set of scope selectors pod matches.
|
||||
func (p *podEvaluator) MatchingScopes(item runtime.Object, scopeSelectors []api.ScopedResourceSelectorRequirement) ([]api.ScopedResourceSelectorRequirement, error) {
|
||||
matchedScopes := []api.ScopedResourceSelectorRequirement{}
|
||||
for _, selector := range scopeSelectors {
|
||||
match, err := podMatchesScopeFunc(selector, item)
|
||||
if err != nil {
|
||||
return []api.ScopedResourceSelectorRequirement{}, fmt.Errorf("error on matching scope %v: %v", selector, err)
|
||||
}
|
||||
if match {
|
||||
matchedScopes = append(matchedScopes, selector)
|
||||
}
|
||||
}
|
||||
return matchedScopes, nil
|
||||
}
|
||||
|
||||
// UncoveredQuotaScopes takes the input matched scopes which are limited by configuration and the matched quota scopes.
|
||||
// It returns the scopes which are in limited scopes but dont have a corresponding covering quota scope
|
||||
func (p *podEvaluator) UncoveredQuotaScopes(limitedScopes []api.ScopedResourceSelectorRequirement, matchedQuotaScopes []api.ScopedResourceSelectorRequirement) ([]api.ScopedResourceSelectorRequirement, error) {
|
||||
uncoveredScopes := []api.ScopedResourceSelectorRequirement{}
|
||||
for _, selector := range limitedScopes {
|
||||
isCovered := false
|
||||
for _, matchedScopeSelector := range matchedQuotaScopes {
|
||||
if matchedScopeSelector.ScopeName == selector.ScopeName {
|
||||
isCovered = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isCovered {
|
||||
uncoveredScopes = append(uncoveredScopes, selector)
|
||||
}
|
||||
}
|
||||
return uncoveredScopes, nil
|
||||
}
|
||||
|
||||
// Usage knows how to measure usage associated with pods
|
||||
func (p *podEvaluator) Usage(item runtime.Object) (api.ResourceList, error) {
|
||||
// delegate to normal usage
|
||||
|
@ -265,12 +301,12 @@ func toInternalPodOrError(obj runtime.Object) (*api.Pod, error) {
|
|||
}
|
||||
|
||||
// podMatchesScopeFunc is a function that knows how to evaluate if a pod matches a scope
|
||||
func podMatchesScopeFunc(scope api.ResourceQuotaScope, object runtime.Object) (bool, error) {
|
||||
func podMatchesScopeFunc(selector api.ScopedResourceSelectorRequirement, object runtime.Object) (bool, error) {
|
||||
pod, err := toInternalPodOrError(object)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
switch scope {
|
||||
switch selector.ScopeName {
|
||||
case api.ResourceQuotaScopeTerminating:
|
||||
return isTerminating(pod), nil
|
||||
case api.ResourceQuotaScopeNotTerminating:
|
||||
|
@ -279,6 +315,8 @@ func podMatchesScopeFunc(scope api.ResourceQuotaScope, object runtime.Object) (b
|
|||
return isBestEffort(pod), nil
|
||||
case api.ResourceQuotaScopeNotBestEffort:
|
||||
return !isBestEffort(pod), nil
|
||||
case api.ResourceQuotaScopePriorityClass:
|
||||
return podMatchesSelector(pod, selector)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
@ -337,6 +375,18 @@ func isTerminating(pod *api.Pod) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func podMatchesSelector(pod *api.Pod, selector api.ScopedResourceSelectorRequirement) (bool, error) {
|
||||
labelSelector, err := helper.ScopedResourceSelectorRequirementsAsSelector(selector)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to parse and convert selector: %v", err)
|
||||
}
|
||||
m := map[string]string{string(api.ResourceQuotaScopePriorityClass): pod.Spec.PriorityClassName}
|
||||
if labelSelector.Matches(labels.Set(m)) {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// QuotaPod returns true if the pod is eligible to track against a quota
|
||||
// A pod is eligible for quota, unless any of the following are true:
|
||||
// - pod has a terminal phase (failed or succeeded)
|
||||
|
|
|
@ -82,6 +82,17 @@ func (p *serviceEvaluator) MatchingResources(input []api.ResourceName) []api.Res
|
|||
return quota.Intersection(input, serviceResources)
|
||||
}
|
||||
|
||||
// MatchingScopes takes the input specified list of scopes and input object. Returns the set of scopes resource matches.
|
||||
func (p *serviceEvaluator) MatchingScopes(item runtime.Object, scopes []api.ScopedResourceSelectorRequirement) ([]api.ScopedResourceSelectorRequirement, error) {
|
||||
return []api.ScopedResourceSelectorRequirement{}, nil
|
||||
}
|
||||
|
||||
// UncoveredQuotaScopes takes the input matched scopes which are limited by configuration and the matched quota scopes.
|
||||
// It returns the scopes which are in limited scopes but dont have a corresponding covering quota scope
|
||||
func (p *serviceEvaluator) UncoveredQuotaScopes(limitedScopes []api.ScopedResourceSelectorRequirement, matchedQuotaScopes []api.ScopedResourceSelectorRequirement) ([]api.ScopedResourceSelectorRequirement, error) {
|
||||
return []api.ScopedResourceSelectorRequirement{}, nil
|
||||
}
|
||||
|
||||
// convert the input object to an internal service object or error.
|
||||
func toInternalServiceOrError(obj runtime.Object) (*api.Service, error) {
|
||||
svc := &api.Service{}
|
||||
|
|
|
@ -67,7 +67,7 @@ func ObjectCountQuotaResourceNameFor(groupResource schema.GroupResource) api.Res
|
|||
type ListFuncByNamespace func(namespace string) ([]runtime.Object, error)
|
||||
|
||||
// MatchesScopeFunc knows how to evaluate if an object matches a scope
|
||||
type MatchesScopeFunc func(scope api.ResourceQuotaScope, object runtime.Object) (bool, error)
|
||||
type MatchesScopeFunc func(scope api.ScopedResourceSelectorRequirement, object runtime.Object) (bool, error)
|
||||
|
||||
// UsageFunc knows how to measure usage associated with an object
|
||||
type UsageFunc func(object runtime.Object) (api.ResourceList, error)
|
||||
|
@ -76,12 +76,14 @@ type UsageFunc func(object runtime.Object) (api.ResourceList, error)
|
|||
type MatchingResourceNamesFunc func(input []api.ResourceName) []api.ResourceName
|
||||
|
||||
// MatchesNoScopeFunc returns false on all match checks
|
||||
func MatchesNoScopeFunc(scope api.ResourceQuotaScope, object runtime.Object) (bool, error) {
|
||||
func MatchesNoScopeFunc(scope api.ScopedResourceSelectorRequirement, object runtime.Object) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Matches returns true if the quota matches the specified item.
|
||||
func Matches(resourceQuota *api.ResourceQuota, item runtime.Object, matchFunc MatchingResourceNamesFunc, scopeFunc MatchesScopeFunc) (bool, error) {
|
||||
func Matches(
|
||||
resourceQuota *api.ResourceQuota, item runtime.Object,
|
||||
matchFunc MatchingResourceNamesFunc, scopeFunc MatchesScopeFunc) (bool, error) {
|
||||
if resourceQuota == nil {
|
||||
return false, fmt.Errorf("expected non-nil quota")
|
||||
}
|
||||
|
@ -89,7 +91,7 @@ func Matches(resourceQuota *api.ResourceQuota, item runtime.Object, matchFunc Ma
|
|||
matchResource := len(matchFunc(quota.ResourceNames(resourceQuota.Status.Hard))) > 0
|
||||
// by default, no scopes matches all
|
||||
matchScope := true
|
||||
for _, scope := range resourceQuota.Spec.Scopes {
|
||||
for _, scope := range getScopeSelectorsFromQuota(resourceQuota) {
|
||||
innerMatch, err := scopeFunc(scope, item)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
@ -99,6 +101,21 @@ func Matches(resourceQuota *api.ResourceQuota, item runtime.Object, matchFunc Ma
|
|||
return matchResource && matchScope, nil
|
||||
}
|
||||
|
||||
func getScopeSelectorsFromQuota(quota *api.ResourceQuota) []api.ScopedResourceSelectorRequirement {
|
||||
selectors := []api.ScopedResourceSelectorRequirement{}
|
||||
for _, scope := range quota.Spec.Scopes {
|
||||
selectors = append(selectors, api.ScopedResourceSelectorRequirement{
|
||||
ScopeName: scope,
|
||||
Operator: api.ScopeSelectorOpExists})
|
||||
}
|
||||
if quota.Spec.ScopeSelector != nil {
|
||||
for _, scopeSelector := range quota.Spec.ScopeSelector.MatchExpressions {
|
||||
selectors = append(selectors, scopeSelector)
|
||||
}
|
||||
}
|
||||
return selectors
|
||||
}
|
||||
|
||||
// CalculateUsageStats is a utility function that knows how to calculate aggregate usage.
|
||||
func CalculateUsageStats(options quota.UsageStatsOptions,
|
||||
listFunc ListFuncByNamespace,
|
||||
|
@ -117,7 +134,7 @@ func CalculateUsageStats(options quota.UsageStatsOptions,
|
|||
// need to verify that the item matches the set of scopes
|
||||
matchesScopes := true
|
||||
for _, scope := range options.Scopes {
|
||||
innerMatch, err := scopeFunc(scope, item)
|
||||
innerMatch, err := scopeFunc(api.ScopedResourceSelectorRequirement{ScopeName: scope}, item)
|
||||
if err != nil {
|
||||
return result, nil
|
||||
}
|
||||
|
@ -125,6 +142,15 @@ func CalculateUsageStats(options quota.UsageStatsOptions,
|
|||
matchesScopes = false
|
||||
}
|
||||
}
|
||||
if options.ScopeSelector != nil {
|
||||
for _, selector := range options.ScopeSelector.MatchExpressions {
|
||||
innerMatch, err := scopeFunc(selector, item)
|
||||
if err != nil {
|
||||
return result, nil
|
||||
}
|
||||
matchesScopes = matchesScopes && innerMatch
|
||||
}
|
||||
}
|
||||
// only count usage if there was a match
|
||||
if matchesScopes {
|
||||
usage, err := usageFunc(item)
|
||||
|
@ -176,6 +202,17 @@ func (o *objectCountEvaluator) MatchingResources(input []api.ResourceName) []api
|
|||
return quota.Intersection(input, o.resourceNames)
|
||||
}
|
||||
|
||||
// MatchingScopes takes the input specified list of scopes and input object. Returns the set of scopes resource matches.
|
||||
func (o *objectCountEvaluator) MatchingScopes(item runtime.Object, scopes []api.ScopedResourceSelectorRequirement) ([]api.ScopedResourceSelectorRequirement, error) {
|
||||
return []api.ScopedResourceSelectorRequirement{}, nil
|
||||
}
|
||||
|
||||
// UncoveredQuotaScopes takes the input matched scopes which are limited by configuration and the matched quota scopes.
|
||||
// It returns the scopes which are in limited scopes but dont have a corresponding covering quota scope
|
||||
func (o *objectCountEvaluator) UncoveredQuotaScopes(limitedScopes []api.ScopedResourceSelectorRequirement, matchedQuotaScopes []api.ScopedResourceSelectorRequirement) ([]api.ScopedResourceSelectorRequirement, error) {
|
||||
return []api.ScopedResourceSelectorRequirement{}, nil
|
||||
}
|
||||
|
||||
// Usage returns the resource usage for the specified object
|
||||
func (o *objectCountEvaluator) Usage(object runtime.Object) (api.ResourceList, error) {
|
||||
quantity := resource.NewQuantity(1, resource.DecimalSI)
|
||||
|
|
|
@ -31,7 +31,8 @@ type UsageStatsOptions struct {
|
|||
// Scopes that must match counted objects
|
||||
Scopes []api.ResourceQuotaScope
|
||||
// Resources are the set of resources to include in the measurement
|
||||
Resources []api.ResourceName
|
||||
Resources []api.ResourceName
|
||||
ScopeSelector *api.ScopeSelector
|
||||
}
|
||||
|
||||
// UsageStats is result of measuring observed resource use in the system
|
||||
|
@ -51,6 +52,10 @@ type Evaluator interface {
|
|||
Handles(operation admission.Attributes) bool
|
||||
// Matches returns true if the specified quota matches the input item
|
||||
Matches(resourceQuota *api.ResourceQuota, item runtime.Object) (bool, error)
|
||||
// MatchingScopes takes the input specified list of scopes and input object and returns the set of scopes that matches input object.
|
||||
MatchingScopes(item runtime.Object, scopes []api.ScopedResourceSelectorRequirement) ([]api.ScopedResourceSelectorRequirement, error)
|
||||
// UncoveredQuotaScopes takes the input matched scopes which are limited by configuration and the matched quota scopes. It returns the scopes which are in limited scopes but dont have a corresponding covering quota scope
|
||||
UncoveredQuotaScopes(limitedScopes []api.ScopedResourceSelectorRequirement, matchedQuotaScopes []api.ScopedResourceSelectorRequirement) ([]api.ScopedResourceSelectorRequirement, error)
|
||||
// MatchingResources takes the input specified list of resources and returns the set of resources evaluator matches.
|
||||
MatchingResources(input []api.ResourceName) []api.ResourceName
|
||||
// Usage returns the resource usage for the specified object
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package resourcequota
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -73,6 +74,14 @@ func validPod(name string, numContainers int, resources api.ResourceRequirements
|
|||
return pod
|
||||
}
|
||||
|
||||
func validPodWithPriority(name string, numContainers int, resources api.ResourceRequirements, priorityClass string) *api.Pod {
|
||||
pod := validPod(name, numContainers, resources)
|
||||
if priorityClass != "" {
|
||||
pod.Spec.PriorityClassName = priorityClass
|
||||
}
|
||||
return pod
|
||||
}
|
||||
|
||||
func validPersistentVolumeClaim(name string, resources api.ResourceRequirements) *api.PersistentVolumeClaim {
|
||||
return &api.PersistentVolumeClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "test"},
|
||||
|
@ -1445,3 +1454,672 @@ func TestAdmitLimitedResourceWithQuotaThatDoesNotCover(t *testing.T) {
|
|||
t.Fatalf("Expected an error since the quota did not cover cpu")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdmitLimitedScopeWithQuota verifies if a limited scope is configured the quota must cover the resource.
|
||||
func TestAdmitLimitedScopeWithCoverQuota(t *testing.T) {
|
||||
testCases := []struct {
|
||||
description string
|
||||
testPod *api.Pod
|
||||
quota *api.ResourceQuota
|
||||
anotherQuota *api.ResourceQuota
|
||||
config *resourcequotaapi.Configuration
|
||||
expErr string
|
||||
}{
|
||||
{
|
||||
description: "Covering quota exists for configured limited scope PriorityClassNameExists.",
|
||||
testPod: validPodWithPriority("allowed-pod", 1, getResourceRequirements(getResourceList("3", "2Gi"), getResourceList("", "")), "fake-priority"),
|
||||
quota: &api.ResourceQuota{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "quota", Namespace: "test", ResourceVersion: "124"},
|
||||
Spec: api.ResourceQuotaSpec{
|
||||
ScopeSelector: &api.ScopeSelector{
|
||||
MatchExpressions: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpExists},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
config: &resourcequotaapi.Configuration{
|
||||
LimitedResources: []resourcequotaapi.LimitedResource{
|
||||
{
|
||||
Resource: "pods",
|
||||
MatchScopes: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpExists,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expErr: "",
|
||||
},
|
||||
{
|
||||
description: "configured limited scope PriorityClassNameExists and limited cpu resource. No covering quota for cpu and pod admit fails.",
|
||||
testPod: validPodWithPriority("not-allowed-pod", 1, getResourceRequirements(getResourceList("3", "2Gi"), getResourceList("", "")), "fake-priority"),
|
||||
quota: &api.ResourceQuota{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "quota", Namespace: "test", ResourceVersion: "124"},
|
||||
Spec: api.ResourceQuotaSpec{
|
||||
ScopeSelector: &api.ScopeSelector{
|
||||
MatchExpressions: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpExists},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
config: &resourcequotaapi.Configuration{
|
||||
LimitedResources: []resourcequotaapi.LimitedResource{
|
||||
{
|
||||
Resource: "pods",
|
||||
MatchScopes: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpExists,
|
||||
},
|
||||
},
|
||||
MatchContains: []string{"requests.cpu"}, // match on "requests.cpu" only
|
||||
},
|
||||
},
|
||||
},
|
||||
expErr: "insufficient quota to consume: requests.cpu",
|
||||
},
|
||||
{
|
||||
description: "Covering quota does not exist for configured limited scope PriorityClassNameExists.",
|
||||
testPod: validPodWithPriority("not-allowed-pod", 1, getResourceRequirements(getResourceList("3", "2Gi"), getResourceList("", "")), "fake-priority"),
|
||||
quota: &api.ResourceQuota{},
|
||||
config: &resourcequotaapi.Configuration{
|
||||
LimitedResources: []resourcequotaapi.LimitedResource{
|
||||
{
|
||||
Resource: "pods",
|
||||
MatchScopes: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpExists,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expErr: "insufficient quota to match these scopes: [{PriorityClass Exists []}]",
|
||||
},
|
||||
{
|
||||
description: "Covering quota does not exist for configured limited scope resourceQuotaBestEffort",
|
||||
testPod: validPodWithPriority("not-allowed-pod", 1, getResourceRequirements(getResourceList("", ""), getResourceList("", "")), "fake-priority"),
|
||||
quota: &api.ResourceQuota{},
|
||||
config: &resourcequotaapi.Configuration{
|
||||
LimitedResources: []resourcequotaapi.LimitedResource{
|
||||
{
|
||||
Resource: "pods",
|
||||
MatchScopes: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopeBestEffort,
|
||||
Operator: api.ScopeSelectorOpExists,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expErr: "insufficient quota to match these scopes: [{BestEffort Exists []}]",
|
||||
},
|
||||
{
|
||||
description: "Covering quota exist for configured limited scope resourceQuotaBestEffort",
|
||||
testPod: validPodWithPriority("allowed-pod", 1, getResourceRequirements(getResourceList("", ""), getResourceList("", "")), "fake-priority"),
|
||||
quota: &api.ResourceQuota{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "quota-besteffort", Namespace: "test", ResourceVersion: "124"},
|
||||
Spec: api.ResourceQuotaSpec{
|
||||
Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeBestEffort},
|
||||
},
|
||||
Status: api.ResourceQuotaStatus{
|
||||
Hard: api.ResourceList{
|
||||
api.ResourcePods: resource.MustParse("5"),
|
||||
},
|
||||
Used: api.ResourceList{
|
||||
api.ResourcePods: resource.MustParse("3"),
|
||||
},
|
||||
},
|
||||
},
|
||||
config: &resourcequotaapi.Configuration{
|
||||
LimitedResources: []resourcequotaapi.LimitedResource{
|
||||
{
|
||||
Resource: "pods",
|
||||
MatchScopes: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopeBestEffort,
|
||||
Operator: api.ScopeSelectorOpExists,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expErr: "",
|
||||
},
|
||||
{
|
||||
description: "Two scopes,BestEffort and PriorityClassIN, in two LimitedResources. Neither matches pod. Pod allowed",
|
||||
testPod: validPodWithPriority("allowed-pod", 1, getResourceRequirements(getResourceList("100m", "1Gi"), getResourceList("", "")), "fake-priority"),
|
||||
quota: &api.ResourceQuota{},
|
||||
config: &resourcequotaapi.Configuration{
|
||||
LimitedResources: []resourcequotaapi.LimitedResource{
|
||||
{
|
||||
Resource: "pods",
|
||||
MatchScopes: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopeBestEffort,
|
||||
Operator: api.ScopeSelectorOpExists,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Resource: "pods",
|
||||
MatchScopes: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpIn,
|
||||
Values: []string{"cluster-services"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expErr: "",
|
||||
},
|
||||
{
|
||||
description: "Two scopes,BestEffort and PriorityClassIN, in two LimitedResources. Only BestEffort scope matches pod. Pod admit fails because covering quota is missing for BestEffort scope",
|
||||
testPod: validPodWithPriority("allowed-pod", 1, getResourceRequirements(getResourceList("", ""), getResourceList("", "")), "fake-priority"),
|
||||
quota: &api.ResourceQuota{},
|
||||
config: &resourcequotaapi.Configuration{
|
||||
LimitedResources: []resourcequotaapi.LimitedResource{
|
||||
{
|
||||
Resource: "pods",
|
||||
MatchScopes: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopeBestEffort,
|
||||
Operator: api.ScopeSelectorOpExists,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Resource: "pods",
|
||||
MatchScopes: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpIn,
|
||||
Values: []string{"cluster-services"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expErr: "insufficient quota to match these scopes: [{BestEffort Exists []}]",
|
||||
},
|
||||
{
|
||||
description: "Two scopes,BestEffort and PriorityClassIN, in two LimitedResources. Only PriorityClass scope matches pod. Pod admit fails because covering quota is missing for PriorityClass scope",
|
||||
testPod: validPodWithPriority("allowed-pod", 1, getResourceRequirements(getResourceList("100m", "1Gi"), getResourceList("", "")), "cluster-services"),
|
||||
quota: &api.ResourceQuota{},
|
||||
config: &resourcequotaapi.Configuration{
|
||||
LimitedResources: []resourcequotaapi.LimitedResource{
|
||||
{
|
||||
Resource: "pods",
|
||||
MatchScopes: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopeBestEffort,
|
||||
Operator: api.ScopeSelectorOpExists,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Resource: "pods",
|
||||
MatchScopes: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpIn,
|
||||
Values: []string{"cluster-services"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expErr: "insufficient quota to match these scopes: [{PriorityClass In [cluster-services]}]",
|
||||
},
|
||||
{
|
||||
description: "Two scopes,BestEffort and PriorityClassIN, in two LimitedResources. Both the scopes matches pod. Pod admit fails because covering quota is missing for PriorityClass scope and BestEffort scope",
|
||||
testPod: validPodWithPriority("allowed-pod", 1, getResourceRequirements(getResourceList("", ""), getResourceList("", "")), "cluster-services"),
|
||||
quota: &api.ResourceQuota{},
|
||||
config: &resourcequotaapi.Configuration{
|
||||
LimitedResources: []resourcequotaapi.LimitedResource{
|
||||
{
|
||||
Resource: "pods",
|
||||
MatchScopes: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopeBestEffort,
|
||||
Operator: api.ScopeSelectorOpExists,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Resource: "pods",
|
||||
MatchScopes: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpIn,
|
||||
Values: []string{"cluster-services"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expErr: "insufficient quota to match these scopes: [{BestEffort Exists []} {PriorityClass In [cluster-services]}]",
|
||||
},
|
||||
{
|
||||
description: "Two scopes,BestEffort and PriorityClassIN, in two LimitedResources. Both the scopes matches pod. Quota available only for BestEffort scope. Pod admit fails because covering quota is missing for PriorityClass scope",
|
||||
testPod: validPodWithPriority("allowed-pod", 1, getResourceRequirements(getResourceList("", ""), getResourceList("", "")), "cluster-services"),
|
||||
quota: &api.ResourceQuota{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "quota-besteffort", Namespace: "test", ResourceVersion: "124"},
|
||||
Spec: api.ResourceQuotaSpec{
|
||||
Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeBestEffort},
|
||||
},
|
||||
Status: api.ResourceQuotaStatus{
|
||||
Hard: api.ResourceList{
|
||||
api.ResourcePods: resource.MustParse("5"),
|
||||
},
|
||||
Used: api.ResourceList{
|
||||
api.ResourcePods: resource.MustParse("3"),
|
||||
},
|
||||
},
|
||||
},
|
||||
config: &resourcequotaapi.Configuration{
|
||||
LimitedResources: []resourcequotaapi.LimitedResource{
|
||||
{
|
||||
Resource: "pods",
|
||||
MatchScopes: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopeBestEffort,
|
||||
Operator: api.ScopeSelectorOpExists,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Resource: "pods",
|
||||
MatchScopes: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpIn,
|
||||
Values: []string{"cluster-services"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expErr: "insufficient quota to match these scopes: [{PriorityClass In [cluster-services]}]",
|
||||
},
|
||||
{
|
||||
description: "Two scopes,BestEffort and PriorityClassIN, in two LimitedResources. Both the scopes matches pod. Quota available only for PriorityClass scope. Pod admit fails because covering quota is missing for BestEffort scope",
|
||||
testPod: validPodWithPriority("allowed-pod", 1, getResourceRequirements(getResourceList("", ""), getResourceList("", "")), "cluster-services"),
|
||||
quota: &api.ResourceQuota{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "quota", Namespace: "test", ResourceVersion: "124"},
|
||||
Spec: api.ResourceQuotaSpec{
|
||||
ScopeSelector: &api.ScopeSelector{
|
||||
MatchExpressions: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpIn,
|
||||
Values: []string{"cluster-services"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
config: &resourcequotaapi.Configuration{
|
||||
LimitedResources: []resourcequotaapi.LimitedResource{
|
||||
{
|
||||
Resource: "pods",
|
||||
MatchScopes: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopeBestEffort,
|
||||
Operator: api.ScopeSelectorOpExists,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Resource: "pods",
|
||||
MatchScopes: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpIn,
|
||||
Values: []string{"cluster-services"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expErr: "insufficient quota to match these scopes: [{BestEffort Exists []}]",
|
||||
},
|
||||
{
|
||||
description: "Two scopes,BestEffort and PriorityClassIN, in two LimitedResources. Both the scopes matches pod. Quota available only for both the scopes. Pod admit success. No Error",
|
||||
testPod: validPodWithPriority("allowed-pod", 1, getResourceRequirements(getResourceList("", ""), getResourceList("", "")), "cluster-services"),
|
||||
quota: &api.ResourceQuota{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "quota-besteffort", Namespace: "test", ResourceVersion: "124"},
|
||||
Spec: api.ResourceQuotaSpec{
|
||||
Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeBestEffort},
|
||||
},
|
||||
Status: api.ResourceQuotaStatus{
|
||||
Hard: api.ResourceList{
|
||||
api.ResourcePods: resource.MustParse("5"),
|
||||
},
|
||||
Used: api.ResourceList{
|
||||
api.ResourcePods: resource.MustParse("3"),
|
||||
},
|
||||
},
|
||||
},
|
||||
anotherQuota: &api.ResourceQuota{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "quota", Namespace: "test", ResourceVersion: "124"},
|
||||
Spec: api.ResourceQuotaSpec{
|
||||
ScopeSelector: &api.ScopeSelector{
|
||||
MatchExpressions: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpIn,
|
||||
Values: []string{"cluster-services"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
config: &resourcequotaapi.Configuration{
|
||||
LimitedResources: []resourcequotaapi.LimitedResource{
|
||||
{
|
||||
Resource: "pods",
|
||||
MatchScopes: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopeBestEffort,
|
||||
Operator: api.ScopeSelectorOpExists,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Resource: "pods",
|
||||
MatchScopes: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpIn,
|
||||
Values: []string{"cluster-services"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expErr: "",
|
||||
},
|
||||
{
|
||||
description: "Pod allowed with priorityclass if limited scope PriorityClassNameExists not configured.",
|
||||
testPod: validPodWithPriority("allowed-pod", 1, getResourceRequirements(getResourceList("3", "2Gi"), getResourceList("", "")), "fake-priority"),
|
||||
quota: &api.ResourceQuota{},
|
||||
config: &resourcequotaapi.Configuration{},
|
||||
expErr: "",
|
||||
},
|
||||
{
|
||||
description: "quota fails, though covering quota for configured limited scope PriorityClassNameExists exists.",
|
||||
testPod: validPodWithPriority("not-allowed-pod", 1, getResourceRequirements(getResourceList("3", "20Gi"), getResourceList("", "")), "fake-priority"),
|
||||
quota: &api.ResourceQuota{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "quota", Namespace: "test", ResourceVersion: "124"},
|
||||
Spec: api.ResourceQuotaSpec{
|
||||
ScopeSelector: &api.ScopeSelector{
|
||||
MatchExpressions: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpExists},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: api.ResourceQuotaStatus{
|
||||
Hard: api.ResourceList{
|
||||
api.ResourceMemory: resource.MustParse("10Gi"),
|
||||
},
|
||||
Used: api.ResourceList{
|
||||
api.ResourceMemory: resource.MustParse("1Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
config: &resourcequotaapi.Configuration{
|
||||
LimitedResources: []resourcequotaapi.LimitedResource{
|
||||
{
|
||||
Resource: "pods",
|
||||
MatchScopes: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpExists,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expErr: "forbidden: exceeded quota: quota, requested: memory=20Gi, used: memory=1Gi, limited: memory=10Gi",
|
||||
},
|
||||
{
|
||||
description: "Pod has different priorityclass than configured limited. Covering quota exists for configured limited scope PriorityClassIn.",
|
||||
testPod: validPodWithPriority("allowed-pod", 1, getResourceRequirements(getResourceList("3", "2Gi"), getResourceList("", "")), "fake-priority"),
|
||||
quota: &api.ResourceQuota{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "quota", Namespace: "test", ResourceVersion: "124"},
|
||||
Spec: api.ResourceQuotaSpec{
|
||||
ScopeSelector: &api.ScopeSelector{
|
||||
MatchExpressions: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpIn,
|
||||
Values: []string{"cluster-services"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
config: &resourcequotaapi.Configuration{
|
||||
LimitedResources: []resourcequotaapi.LimitedResource{
|
||||
{
|
||||
Resource: "pods",
|
||||
MatchScopes: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpIn,
|
||||
Values: []string{"cluster-services"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expErr: "",
|
||||
},
|
||||
{
|
||||
description: "Pod has limited priorityclass. Covering quota exists for configured limited scope PriorityClassIn.",
|
||||
testPod: validPodWithPriority("allowed-pod", 1, getResourceRequirements(getResourceList("3", "2Gi"), getResourceList("", "")), "cluster-services"),
|
||||
quota: &api.ResourceQuota{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "quota", Namespace: "test", ResourceVersion: "124"},
|
||||
Spec: api.ResourceQuotaSpec{
|
||||
ScopeSelector: &api.ScopeSelector{
|
||||
MatchExpressions: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpIn,
|
||||
Values: []string{"cluster-services"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
config: &resourcequotaapi.Configuration{
|
||||
LimitedResources: []resourcequotaapi.LimitedResource{
|
||||
{
|
||||
Resource: "pods",
|
||||
MatchScopes: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpIn,
|
||||
Values: []string{"another-priorityclass-name", "cluster-services"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expErr: "",
|
||||
},
|
||||
{
|
||||
description: "Pod has limited priorityclass. Covering quota does not exist for configured limited scope PriorityClassIn.",
|
||||
testPod: validPodWithPriority("not-allowed-pod", 1, getResourceRequirements(getResourceList("3", "2Gi"), getResourceList("", "")), "cluster-services"),
|
||||
quota: &api.ResourceQuota{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "quota", Namespace: "test", ResourceVersion: "124"},
|
||||
Spec: api.ResourceQuotaSpec{
|
||||
ScopeSelector: &api.ScopeSelector{
|
||||
MatchExpressions: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpIn,
|
||||
Values: []string{"another-priorityclass-name"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
config: &resourcequotaapi.Configuration{
|
||||
LimitedResources: []resourcequotaapi.LimitedResource{
|
||||
{
|
||||
Resource: "pods",
|
||||
MatchScopes: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpIn,
|
||||
Values: []string{"another-priorityclass-name", "cluster-services"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expErr: "insufficient quota to match these scopes: [{PriorityClass In [another-priorityclass-name cluster-services]}]",
|
||||
},
|
||||
{
|
||||
description: "From the above test case, just changing pod priority from cluster-services to another-priorityclass-name. expecting no error",
|
||||
testPod: validPodWithPriority("allowed-pod", 1, getResourceRequirements(getResourceList("3", "2Gi"), getResourceList("", "")), "another-priorityclass-name"),
|
||||
quota: &api.ResourceQuota{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "quota", Namespace: "test", ResourceVersion: "124"},
|
||||
Spec: api.ResourceQuotaSpec{
|
||||
ScopeSelector: &api.ScopeSelector{
|
||||
MatchExpressions: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpIn,
|
||||
Values: []string{"another-priorityclass-name"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
config: &resourcequotaapi.Configuration{
|
||||
LimitedResources: []resourcequotaapi.LimitedResource{
|
||||
{
|
||||
Resource: "pods",
|
||||
MatchScopes: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpIn,
|
||||
Values: []string{"another-priorityclass-name", "cluster-services"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expErr: "",
|
||||
},
|
||||
{
|
||||
description: "Pod has limited priorityclass. Covering quota does NOT exists for configured limited scope PriorityClassIn.",
|
||||
testPod: validPodWithPriority("not-allowed-pod", 1, getResourceRequirements(getResourceList("3", "2Gi"), getResourceList("", "")), "cluster-services"),
|
||||
quota: &api.ResourceQuota{},
|
||||
config: &resourcequotaapi.Configuration{
|
||||
LimitedResources: []resourcequotaapi.LimitedResource{
|
||||
{
|
||||
Resource: "pods",
|
||||
MatchScopes: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpIn,
|
||||
Values: []string{"another-priorityclass-name", "cluster-services"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expErr: "insufficient quota to match these scopes: [{PriorityClass In [another-priorityclass-name cluster-services]}]",
|
||||
},
|
||||
{
|
||||
description: "Pod has limited priorityclass. Covering quota exists for configured limited scope PriorityClassIn through PriorityClassNameExists",
|
||||
testPod: validPodWithPriority("allowed-pod", 1, getResourceRequirements(getResourceList("3", "2Gi"), getResourceList("", "")), "cluster-services"),
|
||||
quota: &api.ResourceQuota{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "quota", Namespace: "test", ResourceVersion: "124"},
|
||||
Spec: api.ResourceQuotaSpec{
|
||||
ScopeSelector: &api.ScopeSelector{
|
||||
MatchExpressions: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpExists},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
config: &resourcequotaapi.Configuration{
|
||||
LimitedResources: []resourcequotaapi.LimitedResource{
|
||||
{
|
||||
Resource: "pods",
|
||||
MatchScopes: []api.ScopedResourceSelectorRequirement{
|
||||
{
|
||||
ScopeName: api.ResourceQuotaScopePriorityClass,
|
||||
Operator: api.ScopeSelectorOpIn,
|
||||
Values: []string{"another-priorityclass-name", "cluster-services"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expErr: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
newPod := testCase.testPod
|
||||
config := testCase.config
|
||||
resourceQuota := testCase.quota
|
||||
kubeClient := fake.NewSimpleClientset(resourceQuota)
|
||||
if testCase.anotherQuota != nil {
|
||||
kubeClient = fake.NewSimpleClientset(resourceQuota, testCase.anotherQuota)
|
||||
}
|
||||
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc})
|
||||
stopCh := make(chan struct{})
|
||||
defer close(stopCh)
|
||||
|
||||
informerFactory := informers.NewSharedInformerFactory(kubeClient, controller.NoResyncPeriodFunc())
|
||||
quotaAccessor, _ := newQuotaAccessor()
|
||||
quotaAccessor.client = kubeClient
|
||||
quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister()
|
||||
|
||||
quotaConfiguration := install.NewQuotaConfigurationForAdmission()
|
||||
evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration.IgnoredResources(), generic.NewRegistry(quotaConfiguration.Evaluators()), nil, config, 5, stopCh)
|
||||
|
||||
handler := &QuotaAdmission{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
evaluator: evaluator,
|
||||
}
|
||||
indexer.Add(resourceQuota)
|
||||
if testCase.anotherQuota != nil {
|
||||
indexer.Add(testCase.anotherQuota)
|
||||
}
|
||||
err := handler.Validate(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if testCase.expErr == "" {
|
||||
if err != nil {
|
||||
t.Fatalf("Testcase, %v, failed with unexpected error: %v. ExpErr: %v", testCase.description, err, testCase.expErr)
|
||||
}
|
||||
} else {
|
||||
if !strings.Contains(fmt.Sprintf("%v", err), testCase.expErr) {
|
||||
t.Fatalf("Testcase, %v, failed with unexpected error: %v. ExpErr: %v", testCase.description, err, testCase.expErr)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ go_library(
|
|||
],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/resourcequota/apis/resourcequota",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
|
|
|
@ -16,7 +16,10 @@ limitations under the License.
|
|||
|
||||
package resourcequota
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/apis/core"
|
||||
)
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
|
@ -54,4 +57,16 @@ type LimitedResource struct {
|
|||
// with any storage class, the list would include
|
||||
// ".storageclass.storage.k8s.io/requests.storage"
|
||||
MatchContains []string
|
||||
|
||||
// For each intercepted request, the quota system will figure out if the input object
|
||||
// satisfies a scope which is present in this listing, then
|
||||
// quota system will ensure that there is a covering quota. In the
|
||||
// absence of a covering quota, the quota system will deny the request.
|
||||
// For example, if an administrator wants to globally enforce that
|
||||
// a quota must exist to create a pod with "cluster-services" priorityclass
|
||||
// the list would include
|
||||
// "PriorityClassNameIn=cluster-services"
|
||||
// +optional
|
||||
// MatchScopes []string `json:"matchScopes,omitempty"`
|
||||
MatchScopes []core.ScopedResourceSelectorRequirement `json:"matchScopes,omitempty"`
|
||||
}
|
||||
|
|
|
@ -18,7 +18,9 @@ go_library(
|
|||
],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/resourcequota/apis/resourcequota/v1alpha1",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//plugin/pkg/admission/resourcequota/apis/resourcequota:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
|
|
|
@ -16,7 +16,10 @@ limitations under the License.
|
|||
|
||||
package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
import (
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
|
@ -54,4 +57,13 @@ type LimitedResource struct {
|
|||
// with any storage class, the list would include
|
||||
// ".storageclass.storage.k8s.io/requests.storage"
|
||||
MatchContains []string `json:"matchContains,omitempty"`
|
||||
// For each intercepted request, the quota system will figure out if the input object
|
||||
// satisfies a scope which is present in this listing, then
|
||||
// quota system will ensure that there is a covering quota. In the
|
||||
// absence of a covering quota, the quota system will deny the request.
|
||||
// For example, if an administrator wants to globally enforce that
|
||||
// a quota must exist to create a pod with "cluster-services" priorityclass
|
||||
// the list would include "scopeName=PriorityClass, Operator=In, Value=cluster-services"
|
||||
// +optional
|
||||
MatchScopes []v1.ScopedResourceSelectorRequirement `json:"matchScopes,omitempty"`
|
||||
}
|
||||
|
|
|
@ -23,8 +23,10 @@ package v1alpha1
|
|||
import (
|
||||
unsafe "unsafe"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
conversion "k8s.io/apimachinery/pkg/conversion"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
core "k8s.io/kubernetes/pkg/apis/core"
|
||||
resourcequota "k8s.io/kubernetes/plugin/pkg/admission/resourcequota/apis/resourcequota"
|
||||
)
|
||||
|
||||
|
@ -67,6 +69,7 @@ func autoConvert_v1alpha1_LimitedResource_To_resourcequota_LimitedResource(in *L
|
|||
out.APIGroup = in.APIGroup
|
||||
out.Resource = in.Resource
|
||||
out.MatchContains = *(*[]string)(unsafe.Pointer(&in.MatchContains))
|
||||
out.MatchScopes = *(*[]core.ScopedResourceSelectorRequirement)(unsafe.Pointer(&in.MatchScopes))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -79,6 +82,7 @@ func autoConvert_resourcequota_LimitedResource_To_v1alpha1_LimitedResource(in *r
|
|||
out.APIGroup = in.APIGroup
|
||||
out.Resource = in.Resource
|
||||
out.MatchContains = *(*[]string)(unsafe.Pointer(&in.MatchContains))
|
||||
out.MatchScopes = *(*[]v1.ScopedResourceSelectorRequirement)(unsafe.Pointer(&in.MatchScopes))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ limitations under the License.
|
|||
package v1alpha1
|
||||
|
||||
import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
|
@ -64,6 +65,13 @@ func (in *LimitedResource) DeepCopyInto(out *LimitedResource) {
|
|||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.MatchScopes != nil {
|
||||
in, out := &in.MatchScopes, &out.MatchScopes
|
||||
*out = make([]v1.ScopedResourceSelectorRequirement, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ package resourcequota
|
|||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
core "k8s.io/kubernetes/pkg/apis/core"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
|
@ -64,6 +65,13 @@ func (in *LimitedResource) DeepCopyInto(out *LimitedResource) {
|
|||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.MatchScopes != nil {
|
||||
in, out := &in.MatchScopes, &out.MatchScopes
|
||||
*out = make([]core.ScopedResourceSelectorRequirement, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import (
|
|||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
|
@ -367,6 +368,21 @@ func limitedByDefault(usage api.ResourceList, limitedResources []resourcequotaap
|
|||
return result
|
||||
}
|
||||
|
||||
func getMatchedLimitedScopes(evaluator quota.Evaluator, inputObject runtime.Object, limitedResources []resourcequotaapi.LimitedResource) ([]api.ScopedResourceSelectorRequirement, error) {
|
||||
scopes := []api.ScopedResourceSelectorRequirement{}
|
||||
for _, limitedResource := range limitedResources {
|
||||
matched, err := evaluator.MatchingScopes(inputObject, limitedResource.MatchScopes)
|
||||
if err != nil {
|
||||
glog.Errorf("Error while matching limited Scopes: %v", err)
|
||||
return []api.ScopedResourceSelectorRequirement{}, err
|
||||
}
|
||||
for _, scope := range matched {
|
||||
scopes = append(scopes, scope)
|
||||
}
|
||||
}
|
||||
return scopes, nil
|
||||
}
|
||||
|
||||
// checkRequest verifies that the request does not exceed any quota constraint. it returns a copy of quotas not yet persisted
|
||||
// that capture what the usage would be if the request succeeded. It return an error if there is insufficient quota to satisfy the request
|
||||
func (e *quotaEvaluator) checkRequest(quotas []api.ResourceQuota, a admission.Attributes) ([]api.ResourceQuota, error) {
|
||||
|
@ -383,6 +399,12 @@ func (e *quotaEvaluator) checkRequest(quotas []api.ResourceQuota, a admission.At
|
|||
// if we have limited resources enabled for this resource, always calculate usage
|
||||
inputObject := a.GetObject()
|
||||
|
||||
// Check if object matches AdmissionConfiguration matchScopes
|
||||
limitedScopes, err := getMatchedLimitedScopes(evaluator, inputObject, e.config.LimitedResources)
|
||||
if err != nil {
|
||||
return quotas, nil
|
||||
}
|
||||
|
||||
// determine the set of resource names that must exist in a covering quota
|
||||
limitedResourceNames := []api.ResourceName{}
|
||||
limitedResources := filterLimitedResourcesByGroupResource(e.config.LimitedResources, a.GetResource().GroupResource())
|
||||
|
@ -404,10 +426,21 @@ func (e *quotaEvaluator) checkRequest(quotas []api.ResourceQuota, a admission.At
|
|||
// this is needed to know if we have satisfied any constraints where consumption
|
||||
// was limited by default.
|
||||
restrictedResourcesSet := sets.String{}
|
||||
restrictedScopes := []api.ScopedResourceSelectorRequirement{}
|
||||
for i := range quotas {
|
||||
resourceQuota := quotas[i]
|
||||
scopeSelectors := getScopeSelectorsFromQuota(resourceQuota)
|
||||
localRestrictedScopes, err := evaluator.MatchingScopes(inputObject, scopeSelectors)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error matching scopes of quota %s, err: %v", resourceQuota.Name, err)
|
||||
}
|
||||
for _, scope := range localRestrictedScopes {
|
||||
restrictedScopes = append(restrictedScopes, scope)
|
||||
}
|
||||
|
||||
match, err := evaluator.Matches(&resourceQuota, inputObject)
|
||||
if err != nil {
|
||||
glog.Errorf("Error occurred while matching resource quota, %v, against input object. Err: %v", resourceQuota, err)
|
||||
return quotas, err
|
||||
}
|
||||
if !match {
|
||||
|
@ -435,6 +468,17 @@ func (e *quotaEvaluator) checkRequest(quotas []api.ResourceQuota, a admission.At
|
|||
return quotas, admission.NewForbidden(a, fmt.Errorf("insufficient quota to consume: %v", strings.Join(hasNoCoveringQuota.List(), ",")))
|
||||
}
|
||||
|
||||
// verify that for every scope that had limited access enabled
|
||||
// that there was a corresponding quota that covered it.
|
||||
// if not, we reject the request.
|
||||
scopesHasNoCoveringQuota, err := evaluator.UncoveredQuotaScopes(limitedScopes, restrictedScopes)
|
||||
if err != nil {
|
||||
return quotas, err
|
||||
}
|
||||
if len(scopesHasNoCoveringQuota) > 0 {
|
||||
return quotas, fmt.Errorf("insufficient quota to match these scopes: %v", scopesHasNoCoveringQuota)
|
||||
}
|
||||
|
||||
if len(interestingQuotaIndexes) == 0 {
|
||||
return quotas, nil
|
||||
}
|
||||
|
@ -516,6 +560,21 @@ func (e *quotaEvaluator) checkRequest(quotas []api.ResourceQuota, a admission.At
|
|||
return outQuotas, nil
|
||||
}
|
||||
|
||||
func getScopeSelectorsFromQuota(quota api.ResourceQuota) []api.ScopedResourceSelectorRequirement {
|
||||
selectors := []api.ScopedResourceSelectorRequirement{}
|
||||
for _, scope := range quota.Spec.Scopes {
|
||||
selectors = append(selectors, api.ScopedResourceSelectorRequirement{
|
||||
ScopeName: scope,
|
||||
Operator: api.ScopeSelectorOpExists})
|
||||
}
|
||||
if quota.Spec.ScopeSelector != nil {
|
||||
for _, scopeSelector := range quota.Spec.ScopeSelector.MatchExpressions {
|
||||
selectors = append(selectors, scopeSelector)
|
||||
}
|
||||
}
|
||||
return selectors
|
||||
}
|
||||
|
||||
func (e *quotaEvaluator) Evaluate(a admission.Attributes) error {
|
||||
e.init.Do(func() {
|
||||
go e.run()
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -3639,7 +3639,7 @@ message ResourceQuotaList {
|
|||
|
||||
// ResourceQuotaSpec defines the desired hard limits to enforce for Quota.
|
||||
message ResourceQuotaSpec {
|
||||
// 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: https://kubernetes.io/docs/concepts/policy/resource-quotas/
|
||||
// +optional
|
||||
map<string, k8s.io.apimachinery.pkg.api.resource.Quantity> hard = 1;
|
||||
|
@ -3648,6 +3648,12 @@ message ResourceQuotaSpec {
|
|||
// If not specified, the quota matches all objects.
|
||||
// +optional
|
||||
repeated string scopes = 2;
|
||||
|
||||
// scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota
|
||||
// but expressed using ScopeSelectorOperator in combination with possible values.
|
||||
// For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched.
|
||||
// +optional
|
||||
optional ScopeSelector scopeSelector = 3;
|
||||
}
|
||||
|
||||
// ResourceQuotaStatus defines the enforced hard limits and observed use.
|
||||
|
@ -3784,6 +3790,32 @@ message ScaleIOVolumeSource {
|
|||
optional bool readOnly = 10;
|
||||
}
|
||||
|
||||
// A scope selector represents the AND of the selectors represented
|
||||
// by the scoped-resource selector requirements.
|
||||
message ScopeSelector {
|
||||
// A list of scope selector requirements by scope of the resources.
|
||||
// +optional
|
||||
repeated ScopedResourceSelectorRequirement matchExpressions = 1;
|
||||
}
|
||||
|
||||
// A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator
|
||||
// that relates the scope name and values.
|
||||
message ScopedResourceSelectorRequirement {
|
||||
// The name of the scope that the selector applies to.
|
||||
optional string scopeName = 1;
|
||||
|
||||
// Represents a scope's relationship to a set of values.
|
||||
// Valid operators are In, NotIn, Exists, DoesNotExist.
|
||||
optional string operator = 2;
|
||||
|
||||
// An array of string values. If the operator is In or NotIn,
|
||||
// the values array must be non-empty. If the operator is Exists or DoesNotExist,
|
||||
// the values array must be empty.
|
||||
// This array is replaced during a strategic merge patch.
|
||||
// +optional
|
||||
repeated string values = 3;
|
||||
}
|
||||
|
||||
// Secret holds secret data of a certain type. The total bytes of the values in
|
||||
// the Data field must be less than MaxSecretSize bytes.
|
||||
message Secret {
|
||||
|
|
|
@ -4698,11 +4698,13 @@ const (
|
|||
ResourceQuotaScopeBestEffort ResourceQuotaScope = "BestEffort"
|
||||
// Match all pod objects that do not have best effort quality of service
|
||||
ResourceQuotaScopeNotBestEffort ResourceQuotaScope = "NotBestEffort"
|
||||
// Match all pod objects that have priority class mentioned
|
||||
ResourceQuotaScopePriorityClass ResourceQuotaScope = "PriorityClass"
|
||||
)
|
||||
|
||||
// ResourceQuotaSpec defines the desired hard limits to enforce for Quota.
|
||||
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: https://kubernetes.io/docs/concepts/policy/resource-quotas/
|
||||
// +optional
|
||||
Hard ResourceList `json:"hard,omitempty" protobuf:"bytes,1,rep,name=hard,casttype=ResourceList,castkey=ResourceName"`
|
||||
|
@ -4710,8 +4712,48 @@ type ResourceQuotaSpec struct {
|
|||
// If not specified, the quota matches all objects.
|
||||
// +optional
|
||||
Scopes []ResourceQuotaScope `json:"scopes,omitempty" protobuf:"bytes,2,rep,name=scopes,casttype=ResourceQuotaScope"`
|
||||
// scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota
|
||||
// but expressed using ScopeSelectorOperator in combination with possible values.
|
||||
// For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched.
|
||||
// +optional
|
||||
ScopeSelector *ScopeSelector `json:"scopeSelector,omitempty" protobuf:"bytes,3,opt,name=scopeSelector"`
|
||||
}
|
||||
|
||||
// A scope selector represents the AND of the selectors represented
|
||||
// by the scoped-resource selector requirements.
|
||||
type ScopeSelector struct {
|
||||
// A list of scope selector requirements by scope of the resources.
|
||||
// +optional
|
||||
MatchExpressions []ScopedResourceSelectorRequirement `json:"matchExpressions,omitempty" protobuf:"bytes,1,rep,name=matchExpressions"`
|
||||
}
|
||||
|
||||
// A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator
|
||||
// that relates the scope name and values.
|
||||
type ScopedResourceSelectorRequirement struct {
|
||||
// The name of the scope that the selector applies to.
|
||||
ScopeName ResourceQuotaScope `json:"scopeName" protobuf:"bytes,1,opt,name=scopeName"`
|
||||
// Represents a scope's relationship to a set of values.
|
||||
// Valid operators are In, NotIn, Exists, DoesNotExist.
|
||||
Operator ScopeSelectorOperator `json:"operator" protobuf:"bytes,2,opt,name=operator,casttype=ScopedResourceSelectorOperator"`
|
||||
// An array of string values. If the operator is In or NotIn,
|
||||
// the values array must be non-empty. If the operator is Exists or DoesNotExist,
|
||||
// the values array must be empty.
|
||||
// This array is replaced during a strategic merge patch.
|
||||
// +optional
|
||||
Values []string `json:"values,omitempty" protobuf:"bytes,3,rep,name=values"`
|
||||
}
|
||||
|
||||
// A scope selector operator is the set of operators that can be used in
|
||||
// a scope selector requirement.
|
||||
type ScopeSelectorOperator string
|
||||
|
||||
const (
|
||||
ScopeSelectorOpIn ScopeSelectorOperator = "In"
|
||||
ScopeSelectorOpNotIn ScopeSelectorOperator = "NotIn"
|
||||
ScopeSelectorOpExists ScopeSelectorOperator = "Exists"
|
||||
ScopeSelectorOpDoesNotExist ScopeSelectorOperator = "DoesNotExist"
|
||||
)
|
||||
|
||||
// ResourceQuotaStatus defines the enforced hard limits and observed use.
|
||||
type ResourceQuotaStatus struct {
|
||||
// Hard is the set of enforced hard limits for each named resource.
|
||||
|
|
|
@ -1803,9 +1803,10 @@ func (ResourceQuotaList) SwaggerDoc() map[string]string {
|
|||
}
|
||||
|
||||
var map_ResourceQuotaSpec = map[string]string{
|
||||
"": "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: https://kubernetes.io/docs/concepts/policy/resource-quotas/",
|
||||
"scopes": "A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects.",
|
||||
"": "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: https://kubernetes.io/docs/concepts/policy/resource-quotas/",
|
||||
"scopes": "A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects.",
|
||||
"scopeSelector": "scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched.",
|
||||
}
|
||||
|
||||
func (ResourceQuotaSpec) SwaggerDoc() map[string]string {
|
||||
|
@ -1880,6 +1881,26 @@ func (ScaleIOVolumeSource) SwaggerDoc() map[string]string {
|
|||
return map_ScaleIOVolumeSource
|
||||
}
|
||||
|
||||
var map_ScopeSelector = map[string]string{
|
||||
"": "A scope selector represents the AND of the selectors represented by the scoped-resource selector requirements.",
|
||||
"matchExpressions": "A list of scope selector requirements by scope of the resources.",
|
||||
}
|
||||
|
||||
func (ScopeSelector) SwaggerDoc() map[string]string {
|
||||
return map_ScopeSelector
|
||||
}
|
||||
|
||||
var map_ScopedResourceSelectorRequirement = map[string]string{
|
||||
"": "A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values.",
|
||||
"scopeName": "The name of the scope that the selector applies to.",
|
||||
"operator": "Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist.",
|
||||
"values": "An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.",
|
||||
}
|
||||
|
||||
func (ScopedResourceSelectorRequirement) SwaggerDoc() map[string]string {
|
||||
return map_ScopedResourceSelectorRequirement
|
||||
}
|
||||
|
||||
var map_Secret = map[string]string{
|
||||
"": "Secret holds secret data of a certain type. The total bytes of the values in the Data field must be less than MaxSecretSize bytes.",
|
||||
"metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata",
|
||||
|
|
|
@ -4635,6 +4635,15 @@ func (in *ResourceQuotaSpec) DeepCopyInto(out *ResourceQuotaSpec) {
|
|||
*out = make([]ResourceQuotaScope, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ScopeSelector != nil {
|
||||
in, out := &in.ScopeSelector, &out.ScopeSelector
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(ScopeSelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -4774,6 +4783,50 @@ func (in *ScaleIOVolumeSource) DeepCopy() *ScaleIOVolumeSource {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ScopeSelector) DeepCopyInto(out *ScopeSelector) {
|
||||
*out = *in
|
||||
if in.MatchExpressions != nil {
|
||||
in, out := &in.MatchExpressions, &out.MatchExpressions
|
||||
*out = make([]ScopedResourceSelectorRequirement, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScopeSelector.
|
||||
func (in *ScopeSelector) DeepCopy() *ScopeSelector {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ScopeSelector)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ScopedResourceSelectorRequirement) DeepCopyInto(out *ScopedResourceSelectorRequirement) {
|
||||
*out = *in
|
||||
if in.Values != nil {
|
||||
in, out := &in.Values, &out.Values
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScopedResourceSelectorRequirement.
|
||||
func (in *ScopedResourceSelectorRequirement) DeepCopy() *ScopedResourceSelectorRequirement {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ScopedResourceSelectorRequirement)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Secret) DeepCopyInto(out *Secret) {
|
||||
*out = *in
|
||||
|
|
Loading…
Reference in New Issue