Merge pull request #22724 from madhusudancs/scale-hpa-stopgap-1.2

Auto commit by PR queue bot
pull/6/head
k8s-merge-robot 2016-03-09 19:16:26 -08:00
commit 108f722657
29 changed files with 670 additions and 439 deletions

View File

@ -1659,7 +1659,7 @@
"description": "API at /apis/extensions/v1beta1",
"operations": [
{
"type": "v1.Scale",
"type": "v1beta1.Scale",
"method": "GET",
"summary": "read scale of the specified Scale",
"nickname": "readNamespacedScaleScale",
@ -1693,7 +1693,7 @@
{
"code": 200,
"message": "OK",
"responseModel": "v1.Scale"
"responseModel": "v1beta1.Scale"
}
],
"produces": [
@ -1705,7 +1705,7 @@
]
},
{
"type": "v1.Scale",
"type": "v1beta1.Scale",
"method": "PUT",
"summary": "replace scale of the specified Scale",
"nickname": "replaceNamespacedScaleScale",
@ -1719,7 +1719,7 @@
"allowMultiple": false
},
{
"type": "v1.Scale",
"type": "v1beta1.Scale",
"paramType": "body",
"name": "body",
"description": "",
@ -1747,7 +1747,7 @@
{
"code": 200,
"message": "OK",
"responseModel": "v1.Scale"
"responseModel": "v1beta1.Scale"
}
],
"produces": [
@ -1759,7 +1759,7 @@
]
},
{
"type": "v1.Scale",
"type": "v1beta1.Scale",
"method": "PATCH",
"summary": "partially update scale of the specified Scale",
"nickname": "patchNamespacedScaleScale",
@ -1801,7 +1801,7 @@
{
"code": 200,
"message": "OK",
"responseModel": "v1.Scale"
"responseModel": "v1beta1.Scale"
}
],
"produces": [
@ -5121,7 +5121,7 @@
"description": "API at /apis/extensions/v1beta1",
"operations": [
{
"type": "v1.Scale",
"type": "v1beta1.Scale",
"method": "GET",
"summary": "read scale of the specified Scale",
"nickname": "readNamespacedScaleScale",
@ -5155,7 +5155,7 @@
{
"code": 200,
"message": "OK",
"responseModel": "v1.Scale"
"responseModel": "v1beta1.Scale"
}
],
"produces": [
@ -5167,7 +5167,7 @@
]
},
{
"type": "v1.Scale",
"type": "v1beta1.Scale",
"method": "PUT",
"summary": "replace scale of the specified Scale",
"nickname": "replaceNamespacedScaleScale",
@ -5181,7 +5181,7 @@
"allowMultiple": false
},
{
"type": "v1.Scale",
"type": "v1beta1.Scale",
"paramType": "body",
"name": "body",
"description": "",
@ -5209,7 +5209,7 @@
{
"code": 200,
"message": "OK",
"responseModel": "v1.Scale"
"responseModel": "v1beta1.Scale"
}
],
"produces": [
@ -5221,7 +5221,7 @@
]
},
{
"type": "v1.Scale",
"type": "v1beta1.Scale",
"method": "PATCH",
"summary": "partially update scale of the specified Scale",
"nickname": "patchNamespacedScaleScale",
@ -5263,7 +5263,7 @@
{
"code": 200,
"message": "OK",
"responseModel": "v1.Scale"
"responseModel": "v1beta1.Scale"
}
],
"produces": [
@ -7234,9 +7234,9 @@
}
}
},
"v1.Scale": {
"id": "v1.Scale",
"description": "Scale represents a scaling request for a resource.",
"v1beta1.Scale": {
"id": "v1beta1.Scale",
"description": "represents a scaling request for a resource.",
"properties": {
"kind": {
"type": "string",
@ -7251,18 +7251,18 @@
"description": "Standard object metadata; More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata."
},
"spec": {
"$ref": "v1.ScaleSpec",
"$ref": "v1beta1.ScaleSpec",
"description": "defines the behavior of the scale. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status."
},
"status": {
"$ref": "v1.ScaleStatus",
"$ref": "v1beta1.ScaleStatus",
"description": "current status of the scale. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status. Read-only."
}
}
},
"v1.ScaleSpec": {
"id": "v1.ScaleSpec",
"description": "ScaleSpec describes the attributes of a scale subresource.",
"v1beta1.ScaleSpec": {
"id": "v1beta1.ScaleSpec",
"description": "describes the attributes of a scale subresource",
"properties": {
"replicas": {
"type": "integer",
@ -7271,9 +7271,9 @@
}
}
},
"v1.ScaleStatus": {
"id": "v1.ScaleStatus",
"description": "ScaleStatus represents the current status of a scale subresource.",
"v1beta1.ScaleStatus": {
"id": "v1beta1.ScaleStatus",
"description": "represents the current status of a scale subresource.",
"required": [
"replicas"
],
@ -7284,8 +7284,12 @@
"description": "actual number of observed instances of the scaled object."
},
"selector": {
"type": "any",
"description": "label query over pods that should match the replicas count. More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors"
},
"targetSelector": {
"type": "string",
"description": "label query over pods that should match the replicas count. This is same as the label selector but in the string format to avoid introspection by clients. The string will be in the same format as the query-param syntax. More info about label selectors: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors"
"description": "label selector for pods that should match the replicas count. This is a serializated version of both map-based and more expressive set-based selectors. This is done to avoid introspection in the clients. The string will be in the same format as the query-param syntax. If the target type only supports map-based selectors, both this field and map-based selector field are populated. More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors"
}
}
},
@ -7887,61 +7891,6 @@
"description": "ObservedGeneration reflects the generation of the most recently observed ReplicaSet."
}
}
},
"v1beta1.Scale": {
"id": "v1beta1.Scale",
"description": "represents a scaling request for a resource.",
"properties": {
"kind": {
"type": "string",
"description": "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: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds"
},
"apiVersion": {
"type": "string",
"description": "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: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#resources"
},
"metadata": {
"$ref": "v1.ObjectMeta",
"description": "Standard object metadata; More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata."
},
"spec": {
"$ref": "v1beta1.ScaleSpec",
"description": "defines the behavior of the scale. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status."
},
"status": {
"$ref": "v1beta1.ScaleStatus",
"description": "current status of the scale. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status. Read-only."
}
}
},
"v1beta1.ScaleSpec": {
"id": "v1beta1.ScaleSpec",
"description": "describes the attributes of a scale subresource",
"properties": {
"replicas": {
"type": "integer",
"format": "int32",
"description": "desired number of instances for the scaled object."
}
}
},
"v1beta1.ScaleStatus": {
"id": "v1beta1.ScaleStatus",
"description": "represents the current status of a scale subresource.",
"required": [
"replicas"
],
"properties": {
"replicas": {
"type": "integer",
"format": "int32",
"description": "actual number of observed instances of the scaled object."
},
"selector": {
"type": "any",
"description": "label query over pods that should match the replicas count. More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors"
}
}
}
}
}

View File

@ -1332,47 +1332,6 @@ Examples:<br>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_scalestatus">v1.ScaleStatus</h3>
<div class="paragraph">
<p>ScaleStatus represents the current status of a scale subresource.</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">replicas</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">actual number of observed instances of the scaled object.</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">integer (int32)</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">selector</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">label query over pods that should match the replicas count. This is same as the label selector but in the string format to avoid introspection by clients. The string will be in the same format as the query-param syntax. More info about label selectors: <a href="http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors">http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors</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>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1beta1_jobstatus">v1beta1.JobStatus</h3>
@ -2457,68 +2416,6 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_scale">v1.Scale</h3>
<div class="paragraph">
<p>Scale represents a scaling request for a resource.</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="http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds">http://releases.k8s.io/HEAD/docs/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="http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#resources">http://releases.k8s.io/HEAD/docs/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 object metadata; More info: <a href="http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata">http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata</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_objectmeta">v1.ObjectMeta</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">spec</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">defines the behavior of the scale. More info: <a href="http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status">http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status</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_scalespec">v1.ScaleSpec</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">status</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">current status of the scale. More info: <a href="http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status">http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status</a>. Read-only.</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_scalestatus">v1.ScaleStatus</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_loadbalanceringress">v1.LoadBalancerIngress</h3>
@ -3737,6 +3634,13 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_any">any</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">targetSelector</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">label selector for pods that should match the replicas count. This is a serializated version of both map-based and more expressive set-based selectors. This is done to avoid introspection in the clients. The string will be in the same format as the query-param syntax. If the target type only supports map-based selectors, both this field and map-based selector field are populated. More info: <a href="http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors">http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors</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>
</tbody>
</table>
@ -4686,40 +4590,6 @@ Both these may change in the future. Incoming requests are matched against the h
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_scalespec">v1.ScaleSpec</h3>
<div class="paragraph">
<p>ScaleSpec describes the attributes of a scale subresource.</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">replicas</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">desired number of instances for the scaled object.</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">integer (int32)</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1beta1_labelselector">v1beta1.LabelSelector</h3>
@ -5728,7 +5598,7 @@ Both these may change in the future. Incoming requests are matched against the h
</div>
<div id="footer">
<div id="footer-text">
Last updated 2016-03-03 20:23:06 UTC
Last updated 2016-03-09 19:21:59 UTC
</div>
</div>
</body>

View File

@ -3304,7 +3304,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">200</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">success</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="definitions.html#_v1_scale">v1.Scale</a></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="definitions.html#_v1beta1_scale">v1beta1.Scale</a></p></td>
</tr>
</tbody>
</table>
@ -3386,7 +3386,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<td class="tableblock halign-left valign-top"><p class="tableblock">body</p></td>
<td class="tableblock halign-left valign-top"></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="definitions.html#_v1_scale">v1.Scale</a></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="definitions.html#_v1beta1_scale">v1beta1.Scale</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
@ -3428,7 +3428,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">200</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">success</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="definitions.html#_v1_scale">v1.Scale</a></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="definitions.html#_v1beta1_scale">v1beta1.Scale</a></p></td>
</tr>
</tbody>
</table>
@ -3552,7 +3552,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">200</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">success</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="definitions.html#_v1_scale">v1.Scale</a></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="definitions.html#_v1beta1_scale">v1beta1.Scale</a></p></td>
</tr>
</tbody>
</table>
@ -7858,7 +7858,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">200</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">success</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="definitions.html#_v1_scale">v1.Scale</a></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="definitions.html#_v1beta1_scale">v1beta1.Scale</a></p></td>
</tr>
</tbody>
</table>
@ -7940,7 +7940,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<td class="tableblock halign-left valign-top"><p class="tableblock">body</p></td>
<td class="tableblock halign-left valign-top"></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="definitions.html#_v1_scale">v1.Scale</a></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="definitions.html#_v1beta1_scale">v1beta1.Scale</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
@ -7982,7 +7982,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">200</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">success</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="definitions.html#_v1_scale">v1.Scale</a></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="definitions.html#_v1beta1_scale">v1beta1.Scale</a></p></td>
</tr>
</tbody>
</table>
@ -8106,7 +8106,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">200</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">success</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="definitions.html#_v1_scale">v1.Scale</a></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="definitions.html#_v1beta1_scale">v1beta1.Scale</a></p></td>
</tr>
</tbody>
</table>
@ -11401,7 +11401,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
</div>
<div id="footer">
<div id="footer-text">
Last updated 2016-02-28 04:44:42 UTC
Last updated 2016-03-09 19:21:59 UTC
</div>
</div>
</body>

View File

@ -413,6 +413,25 @@ func FuzzerFor(t *testing.T, version unversioned.GroupVersion, src rand.Source)
seLinuxRules := []extensions.SELinuxStrategy{extensions.SELinuxStrategyRunAsAny, extensions.SELinuxStrategyMustRunAs}
psp.SELinux.Rule = seLinuxRules[c.Rand.Intn(len(seLinuxRules))]
},
func(s *extensions.Scale, c fuzz.Continue) {
c.FuzzNoCustom(s) // fuzz self without calling this function again
// TODO: Implement a fuzzer to generate valid keys, values and operators for
// selector requirements.
if s.Status.Selector != nil {
s.Status.Selector = &unversioned.LabelSelector{
MatchLabels: map[string]string{
"testlabelkey": "testlabelval",
},
MatchExpressions: []unversioned.LabelSelectorRequirement{
{
Key: "testkey",
Operator: unversioned.LabelSelectorOpIn,
Values: []string{"val1", "val2", "val3"},
},
},
}
}
},
)
return f
}

View File

@ -25,6 +25,7 @@ import (
// LabelSelectorAsSelector converts the LabelSelector api type into a struct that implements
// labels.Selector
// Note: This function should be kept in sync with the selector methods in pkg/labels/selector.go
func LabelSelectorAsSelector(ps *LabelSelector) (labels.Selector, error) {
if ps == nil {
return labels.Nothing(), nil
@ -34,7 +35,7 @@ func LabelSelectorAsSelector(ps *LabelSelector) (labels.Selector, error) {
}
selector := labels.NewSelector()
for k, v := range ps.MatchLabels {
r, err := labels.NewRequirement(k, labels.InOperator, sets.NewString(v))
r, err := labels.NewRequirement(k, labels.EqualsOperator, sets.NewString(v))
if err != nil {
return nil, err
}
@ -63,6 +64,55 @@ func LabelSelectorAsSelector(ps *LabelSelector) (labels.Selector, error) {
return selector, nil
}
// ParseToLabelSelector parses a string representing a selector into a LabelSelector object.
// Note: This function should be kept in sync with the parser in pkg/labels/selector.go
func ParseToLabelSelector(selector string) (*LabelSelector, error) {
reqs, err := labels.ParseToRequirements(selector)
if err != nil {
return nil, fmt.Errorf("couldn't parse the selector string \"%s\": %v", selector, err)
}
labelSelector := &LabelSelector{
MatchLabels: map[string]string{},
MatchExpressions: []LabelSelectorRequirement{},
}
for _, req := range reqs {
var op LabelSelectorOperator
switch req.Operator() {
case labels.EqualsOperator, labels.DoubleEqualsOperator:
vals := req.Values()
if vals.Len() != 1 {
return nil, fmt.Errorf("equals operator must have exactly one value")
}
val, ok := vals.PopAny()
if !ok {
return nil, fmt.Errorf("equals operator has exactly one value but it cannot be retrieved")
}
labelSelector.MatchLabels[req.Key()] = val
continue
case labels.InOperator:
op = LabelSelectorOpIn
case labels.NotInOperator:
op = LabelSelectorOpNotIn
case labels.ExistsOperator:
op = LabelSelectorOpExists
case labels.DoesNotExistOperator:
op = LabelSelectorOpDoesNotExist
case labels.GreaterThanOperator, labels.LessThanOperator:
// Adding a separate case for these operators to indicate that this is deliberate
return nil, fmt.Errorf("%q isn't supported in label selectors", req.Operator())
default:
return nil, fmt.Errorf("%q is not a valid label selector operator", req.Operator())
}
labelSelector.MatchExpressions = append(labelSelector.MatchExpressions, LabelSelectorRequirement{
Key: req.Key(),
Operator: op,
Values: req.Values().List(),
})
}
return labelSelector, nil
}
// SetAsLabelSelector converts the labels.Set object into a LabelSelector api object.
func SetAsLabelSelector(ls labels.Set) *LabelSelector {
if ls == nil {

View File

@ -46,7 +46,7 @@ func TestLabelSelectorAsSelector(t *testing.T) {
{in: &LabelSelector{}, out: labels.Everything()},
{
in: &LabelSelector{MatchLabels: matchLabels},
out: mustParse("foo in (bar)"),
out: mustParse("foo=bar"),
},
{
in: &LabelSelector{MatchExpressions: matchExpressions},
@ -54,7 +54,7 @@ func TestLabelSelectorAsSelector(t *testing.T) {
},
{
in: &LabelSelector{MatchLabels: matchLabels, MatchExpressions: matchExpressions},
out: mustParse("foo in (bar),baz in (norf,qux)"),
out: mustParse("baz in (norf,qux),foo=bar"),
},
{
in: &LabelSelector{

View File

@ -68,7 +68,7 @@ func init() {
if false { // reference the types, but skip this branch at build/run time
var v0 pkg2_api.ObjectMeta
var v1 pkg4_resource.Quantity
var v2 pkg1_unversioned.TypeMeta
var v2 pkg1_unversioned.LabelSelector
var v3 pkg3_types.UID
var v4 pkg6_intstr.IntOrString
var v5 pkg5_inf.Dec
@ -263,7 +263,7 @@ func (x *ScaleStatus) CodecEncodeSelf(e *codec1978.Encoder) {
var yyq2 [2]bool
_, _, _ = yysep2, yyq2, yy2arr2
const yyr2 bool = false
yyq2[1] = len(x.Selector) != 0
yyq2[1] = x.Selector != nil
var yynn2 int
if yyr2 || yy2arr2 {
r.EncodeArrayStart(2)
@ -305,8 +305,9 @@ func (x *ScaleStatus) CodecEncodeSelf(e *codec1978.Encoder) {
yym7 := z.EncBinary()
_ = yym7
if false {
} else if z.HasExtensions() && z.EncExt(x.Selector) {
} else {
z.F.EncMapStringStringV(x.Selector, false, e)
z.EncFallback(x.Selector)
}
}
} else {
@ -323,8 +324,9 @@ func (x *ScaleStatus) CodecEncodeSelf(e *codec1978.Encoder) {
yym8 := z.EncBinary()
_ = yym8
if false {
} else if z.HasExtensions() && z.EncExt(x.Selector) {
} else {
z.F.EncMapStringStringV(x.Selector, false, e)
z.EncFallback(x.Selector)
}
}
}
@ -398,14 +400,19 @@ func (x *ScaleStatus) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) {
}
case "selector":
if r.TryDecodeAsNil() {
x.Selector = nil
if x.Selector != nil {
x.Selector = nil
}
} else {
yyv5 := &x.Selector
if x.Selector == nil {
x.Selector = new(pkg1_unversioned.LabelSelector)
}
yym6 := z.DecBinary()
_ = yym6
if false {
} else if z.HasExtensions() && z.DecExt(x.Selector) {
} else {
z.F.DecMapStringStringX(yyv5, false, d)
z.DecFallback(x.Selector, false)
}
}
default:
@ -450,14 +457,19 @@ func (x *ScaleStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
if r.TryDecodeAsNil() {
x.Selector = nil
if x.Selector != nil {
x.Selector = nil
}
} else {
yyv9 := &x.Selector
if x.Selector == nil {
x.Selector = new(pkg1_unversioned.LabelSelector)
}
yym10 := z.DecBinary()
_ = yym10
if false {
} else if z.HasExtensions() && z.DecExt(x.Selector) {
} else {
z.F.DecMapStringStringX(yyv9, false, d)
z.DecFallback(x.Selector, false)
}
}
for {

View File

@ -46,8 +46,9 @@ type ScaleStatus struct {
// actual number of observed instances of the scaled object.
Replicas int `json:"replicas"`
// label query over pods that should match the replicas count. More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors
Selector map[string]string `json:"selector,omitempty"`
// label query over pods that should match the replicas count.
// More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors
Selector *unversioned.LabelSelector `json:"selector,omitempty"`
}
// +genclient=true,noMethods=true

View File

@ -34,6 +34,8 @@ func addConversionFuncs(scheme *runtime.Scheme) {
err := scheme.AddConversionFuncs(
Convert_api_PodSpec_To_v1_PodSpec,
Convert_v1_PodSpec_To_api_PodSpec,
Convert_extensions_ScaleStatus_To_v1beta1_ScaleStatus,
Convert_v1beta1_ScaleStatus_To_extensions_ScaleStatus,
Convert_extensions_DeploymentSpec_To_v1beta1_DeploymentSpec,
Convert_v1beta1_DeploymentSpec_To_extensions_DeploymentSpec,
Convert_extensions_DeploymentStrategy_To_v1beta1_DeploymentStrategy,
@ -93,6 +95,58 @@ func Convert_v1_PodSpec_To_api_PodSpec(in *v1.PodSpec, out *api.PodSpec, s conve
return v1.Convert_v1_PodSpec_To_api_PodSpec(in, out, s)
}
func Convert_extensions_ScaleStatus_To_v1beta1_ScaleStatus(in *extensions.ScaleStatus, out *ScaleStatus, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*extensions.ScaleStatus))(in)
}
out.Replicas = int32(in.Replicas)
out.Selector = nil
out.TargetSelector = ""
if in.Selector != nil {
if in.Selector.MatchExpressions == nil || len(in.Selector.MatchExpressions) == 0 {
out.Selector = in.Selector.MatchLabels
}
selector, err := unversioned.LabelSelectorAsSelector(in.Selector)
if err != nil {
return fmt.Errorf("invalid label selector: %v", err)
}
out.TargetSelector = selector.String()
}
return nil
}
func Convert_v1beta1_ScaleStatus_To_extensions_ScaleStatus(in *ScaleStatus, out *extensions.ScaleStatus, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*ScaleStatus))(in)
}
out.Replicas = int(in.Replicas)
// Normally when 2 fields map to the same internal value we favor the old field, since
// old clients can't be expected to know about new fields but clients that know about the
// new field can be expected to know about the old field (though that's not quite true, due
// to kubectl apply). However, these fields are readonly, so any non-nil value should work.
if in.TargetSelector != "" {
labelSelector, err := unversioned.ParseToLabelSelector(in.TargetSelector)
if err != nil {
out.Selector = nil
return fmt.Errorf("failed to parse target selector: %v", err)
}
out.Selector = labelSelector
} else if in.Selector != nil {
out.Selector = new(unversioned.LabelSelector)
selector := make(map[string]string)
for key, val := range in.Selector {
selector[key] = val
}
out.Selector.MatchLabels = selector
} else {
out.Selector = nil
}
return nil
}
func Convert_extensions_DeploymentSpec_To_v1beta1_DeploymentSpec(in *extensions.DeploymentSpec, out *DeploymentSpec, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*extensions.DeploymentSpec))(in)

View File

@ -3746,21 +3746,10 @@ func autoConvert_extensions_ScaleStatus_To_v1beta1_ScaleStatus(in *extensions.Sc
defaulting.(func(*extensions.ScaleStatus))(in)
}
out.Replicas = int32(in.Replicas)
if in.Selector != nil {
out.Selector = make(map[string]string)
for key, val := range in.Selector {
out.Selector[key] = val
}
} else {
out.Selector = nil
}
// in.Selector has no peer in out
return nil
}
func Convert_extensions_ScaleStatus_To_v1beta1_ScaleStatus(in *extensions.ScaleStatus, out *ScaleStatus, s conversion.Scope) error {
return autoConvert_extensions_ScaleStatus_To_v1beta1_ScaleStatus(in, out, s)
}
func autoConvert_extensions_SubresourceReference_To_v1beta1_SubresourceReference(in *extensions.SubresourceReference, out *SubresourceReference, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*extensions.SubresourceReference))(in)
@ -5002,21 +4991,11 @@ func autoConvert_v1beta1_ScaleStatus_To_extensions_ScaleStatus(in *ScaleStatus,
defaulting.(func(*ScaleStatus))(in)
}
out.Replicas = int(in.Replicas)
if in.Selector != nil {
out.Selector = make(map[string]string)
for key, val := range in.Selector {
out.Selector[key] = val
}
} else {
out.Selector = nil
}
// in.Selector has no peer in out
// in.TargetSelector has no peer in out
return nil
}
func Convert_v1beta1_ScaleStatus_To_extensions_ScaleStatus(in *ScaleStatus, out *extensions.ScaleStatus, s conversion.Scope) error {
return autoConvert_v1beta1_ScaleStatus_To_extensions_ScaleStatus(in, out, s)
}
func autoConvert_v1beta1_SubresourceReference_To_extensions_SubresourceReference(in *SubresourceReference, out *extensions.SubresourceReference, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*SubresourceReference))(in)

View File

@ -1846,6 +1846,7 @@ func deepCopy_v1beta1_ScaleStatus(in ScaleStatus, out *ScaleStatus, c *conversio
} else {
out.Selector = nil
}
out.TargetSelector = in.TargetSelector
return nil
}

View File

@ -260,13 +260,14 @@ func (x *ScaleStatus) CodecEncodeSelf(e *codec1978.Encoder) {
} else {
yysep2 := !z.EncBinary()
yy2arr2 := z.EncBasicHandle().StructToArray
var yyq2 [2]bool
var yyq2 [3]bool
_, _, _ = yysep2, yyq2, yy2arr2
const yyr2 bool = false
yyq2[1] = len(x.Selector) != 0
yyq2[2] = x.TargetSelector != ""
var yynn2 int
if yyr2 || yy2arr2 {
r.EncodeArrayStart(2)
r.EncodeArrayStart(3)
} else {
yynn2 = 1
for _, b := range yyq2 {
@ -329,6 +330,31 @@ func (x *ScaleStatus) CodecEncodeSelf(e *codec1978.Encoder) {
}
}
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
if yyq2[2] {
yym10 := z.EncBinary()
_ = yym10
if false {
} else {
r.EncodeString(codecSelferC_UTF81234, string(x.TargetSelector))
}
} else {
r.EncodeString(codecSelferC_UTF81234, "")
}
} else {
if yyq2[2] {
z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("targetSelector"))
z.EncSendContainerState(codecSelfer_containerMapValue1234)
yym11 := z.EncBinary()
_ = yym11
if false {
} else {
r.EncodeString(codecSelferC_UTF81234, string(x.TargetSelector))
}
}
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayEnd1234)
} else {
@ -408,6 +434,12 @@ func (x *ScaleStatus) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) {
z.F.DecMapStringStringX(yyv5, false, d)
}
}
case "targetSelector":
if r.TryDecodeAsNil() {
x.TargetSelector = ""
} else {
x.TargetSelector = string(r.DecodeString())
}
default:
z.DecStructFieldNotFound(-1, yys3)
} // end switch yys3
@ -419,16 +451,16 @@ func (x *ScaleStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperDecoder(d)
_, _, _ = h, z, r
var yyj7 int
var yyb7 bool
var yyhl7 bool = l >= 0
yyj7++
if yyhl7 {
yyb7 = yyj7 > l
var yyj8 int
var yyb8 bool
var yyhl8 bool = l >= 0
yyj8++
if yyhl8 {
yyb8 = yyj8 > l
} else {
yyb7 = r.CheckBreak()
yyb8 = r.CheckBreak()
}
if yyb7 {
if yyb8 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
@ -438,13 +470,13 @@ func (x *ScaleStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
} else {
x.Replicas = int32(r.DecodeInt(32))
}
yyj7++
if yyhl7 {
yyb7 = yyj7 > l
yyj8++
if yyhl8 {
yyb8 = yyj8 > l
} else {
yyb7 = r.CheckBreak()
yyb8 = r.CheckBreak()
}
if yyb7 {
if yyb8 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
@ -452,26 +484,42 @@ func (x *ScaleStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
if r.TryDecodeAsNil() {
x.Selector = nil
} else {
yyv9 := &x.Selector
yym10 := z.DecBinary()
_ = yym10
yyv10 := &x.Selector
yym11 := z.DecBinary()
_ = yym11
if false {
} else {
z.F.DecMapStringStringX(yyv9, false, d)
z.F.DecMapStringStringX(yyv10, false, d)
}
}
yyj8++
if yyhl8 {
yyb8 = yyj8 > l
} else {
yyb8 = r.CheckBreak()
}
if yyb8 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
if r.TryDecodeAsNil() {
x.TargetSelector = ""
} else {
x.TargetSelector = string(r.DecodeString())
}
for {
yyj7++
if yyhl7 {
yyb7 = yyj7 > l
yyj8++
if yyhl8 {
yyb8 = yyj8 > l
} else {
yyb7 = r.CheckBreak()
yyb8 = r.CheckBreak()
}
if yyb7 {
if yyb8 {
break
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
z.DecStructFieldNotFound(yyj7-1, "")
z.DecStructFieldNotFound(yyj8-1, "")
}
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
}

View File

@ -36,6 +36,14 @@ type ScaleStatus struct {
// label query over pods that should match the replicas count. More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors
Selector map[string]string `json:"selector,omitempty"`
// label selector for pods that should match the replicas count. This is a serializated
// version of both map-based and more expressive set-based selectors. This is done to
// avoid introspection in the clients. The string will be in the same format as the
// query-param syntax. If the target type only supports map-based selectors, both this
// field and map-based selector field are populated.
// More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors
TargetSelector string `json:"targetSelector,omitempty"`
}
// +genclient=true,noMethods=true

View File

@ -593,9 +593,10 @@ func (ScaleSpec) SwaggerDoc() map[string]string {
}
var map_ScaleStatus = map[string]string{
"": "represents the current status of a scale subresource.",
"replicas": "actual number of observed instances of the scaled object.",
"selector": "label query over pods that should match the replicas count. More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors",
"": "represents the current status of a scale subresource.",
"replicas": "actual number of observed instances of the scaled object.",
"selector": "label query over pods that should match the replicas count. More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors",
"targetSelector": "label selector for pods that should match the replicas count. This is a serializated version of both map-based and more expressive set-based selectors. This is done to avoid introspection in the clients. The string will be in the same format as the query-param syntax. If the target type only supports map-based selectors, both this field and map-based selector field are populated. More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors",
}
func (ScaleStatus) SwaggerDoc() map[string]string {

View File

@ -129,7 +129,20 @@ func (a *HorizontalController) computeReplicasForCPUUtilization(hpa *extensions.
targetUtilization = hpa.Spec.CPUUtilization.TargetPercentage
}
currentReplicas := scale.Status.Replicas
currentUtilization, timestamp, err := a.metricsClient.GetCPUUtilization(hpa.Namespace, scale.Status.Selector)
if scale.Status.Selector == nil {
errMsg := "selector is required"
a.eventRecorder.Event(hpa, api.EventTypeWarning, "SelectorRequired", errMsg)
return 0, nil, time.Time{}, fmt.Errorf(errMsg)
}
selector, err := unversioned.LabelSelectorAsSelector(scale.Status.Selector)
if err != nil {
errMsg := fmt.Sprintf("couldn't convert selector string to a corresponding selector object: %v", err)
a.eventRecorder.Event(hpa, api.EventTypeWarning, "InvalidSelector", errMsg)
return 0, nil, time.Time{}, fmt.Errorf(errMsg)
}
currentUtilization, timestamp, err := a.metricsClient.GetCPUUtilization(hpa.Namespace, selector)
// TODO: what to do on partial errors (like metrics obtained for 75% of pods).
if err != nil {
@ -177,7 +190,19 @@ func (a *HorizontalController) computeReplicasForCustomMetrics(hpa *extensions.H
}
for _, customMetricTarget := range targetList.Items {
value, currentTimestamp, err := a.metricsClient.GetCustomMetric(customMetricTarget.Name, hpa.Namespace, scale.Status.Selector)
if scale.Status.Selector == nil {
errMsg := "selector is required"
a.eventRecorder.Event(hpa, api.EventTypeWarning, "SelectorRequired", errMsg)
return 0, "", "", time.Time{}, fmt.Errorf("selector is required")
}
selector, err := unversioned.LabelSelectorAsSelector(scale.Status.Selector)
if err != nil {
errMsg := fmt.Sprintf("couldn't convert selector string to a corresponding selector object: %v", err)
a.eventRecorder.Event(hpa, api.EventTypeWarning, "InvalidSelector", errMsg)
return 0, "", "", time.Time{}, fmt.Errorf("couldn't convert selector string to a corresponding selector object: %v", err)
}
value, currentTimestamp, err := a.metricsClient.GetCustomMetric(customMetricTarget.Name, hpa.Namespace, selector)
// TODO: what to do on partial errors (like metrics obtained for 75% of pods).
if err != nil {
a.eventRecorder.Event(hpa, api.EventTypeWarning, "FailedGetCustomMetrics", err.Error())

View File

@ -25,6 +25,7 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource"
"k8s.io/kubernetes/pkg/api/unversioned"
_ "k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
@ -56,6 +57,12 @@ type fakeResponseWrapper struct {
raw []byte
}
type fakeResource struct {
name string
apiVersion string
kind string
}
type testCase struct {
minReplicas int
maxReplicas int
@ -74,6 +81,9 @@ type testCase struct {
verifyEvents bool
// Channel with names of HPA objects which we have reconciled.
processed chan string
// Target resource information.
resource *fakeResource
}
func (tc *testCase) computeCPUCurrent() {
@ -94,8 +104,10 @@ func (tc *testCase) computeCPUCurrent() {
func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset {
namespace := "test-namespace"
hpaName := "test-hpa"
rcName := "test-rc"
podNamePrefix := "test-pod"
selector := &unversioned.LabelSelector{
MatchLabels: map[string]string{"name": podNamePrefix},
}
tc.scaleUpdated = false
tc.statusUpdated = false
@ -103,6 +115,16 @@ func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset {
tc.processed = make(chan string, 100)
tc.computeCPUCurrent()
// TODO(madhusudancs): HPA only supports resources in extensions/v1beta1 right now. Add
// tests for "v1" replicationcontrollers when HPA adds support for cross-group scale.
if tc.resource == nil {
tc.resource = &fakeResource{
name: "test-rc",
apiVersion: "extensions/v1beta1",
kind: "replicationcontrollers",
}
}
fakeClient := &fake.Clientset{}
fakeClient.AddReactor("list", "horizontalpodautoscalers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
obj := &extensions.HorizontalPodAutoscalerList{
@ -115,8 +137,9 @@ func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset {
},
Spec: extensions.HorizontalPodAutoscalerSpec{
ScaleRef: extensions.SubresourceReference{
Kind: "replicationController",
Name: rcName,
Kind: tc.resource.kind,
Name: tc.resource.name,
APIVersion: tc.resource.apiVersion,
Subresource: "scale",
},
MinReplicas: &tc.minReplicas,
@ -143,10 +166,10 @@ func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset {
return true, obj, nil
})
fakeClient.AddReactor("get", "replicationController", func(action core.Action) (handled bool, ret runtime.Object, err error) {
fakeClient.AddReactor("get", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
obj := &extensions.Scale{
ObjectMeta: api.ObjectMeta{
Name: rcName,
Name: tc.resource.name,
Namespace: namespace,
},
Spec: extensions.ScaleSpec{
@ -154,7 +177,41 @@ func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset {
},
Status: extensions.ScaleStatus{
Replicas: tc.initialReplicas,
Selector: map[string]string{"name": podNamePrefix},
Selector: selector,
},
}
return true, obj, nil
})
fakeClient.AddReactor("get", "deployments", func(action core.Action) (handled bool, ret runtime.Object, err error) {
obj := &extensions.Scale{
ObjectMeta: api.ObjectMeta{
Name: tc.resource.name,
Namespace: namespace,
},
Spec: extensions.ScaleSpec{
Replicas: tc.initialReplicas,
},
Status: extensions.ScaleStatus{
Replicas: tc.initialReplicas,
Selector: selector,
},
}
return true, obj, nil
})
fakeClient.AddReactor("get", "replicasets", func(action core.Action) (handled bool, ret runtime.Object, err error) {
obj := &extensions.Scale{
ObjectMeta: api.ObjectMeta{
Name: tc.resource.name,
Namespace: namespace,
},
Spec: extensions.ScaleSpec{
Replicas: tc.initialReplicas,
},
Status: extensions.ScaleStatus{
Replicas: tc.initialReplicas,
Selector: selector,
},
}
return true, obj, nil
@ -206,7 +263,23 @@ func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset {
return true, newFakeResponseWrapper(heapsterRawMemResponse), nil
})
fakeClient.AddReactor("update", "replicationController", func(action core.Action) (handled bool, ret runtime.Object, err error) {
fakeClient.AddReactor("update", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
obj := action.(testclient.UpdateAction).GetObject().(*extensions.Scale)
replicas := action.(testclient.UpdateAction).GetObject().(*extensions.Scale).Spec.Replicas
assert.Equal(t, tc.desiredReplicas, replicas)
tc.scaleUpdated = true
return true, obj, nil
})
fakeClient.AddReactor("update", "deployments", func(action core.Action) (handled bool, ret runtime.Object, err error) {
obj := action.(testclient.UpdateAction).GetObject().(*extensions.Scale)
replicas := action.(testclient.UpdateAction).GetObject().(*extensions.Scale).Spec.Replicas
assert.Equal(t, tc.desiredReplicas, replicas)
tc.scaleUpdated = true
return true, obj, nil
})
fakeClient.AddReactor("update", "replicasets", func(action core.Action) (handled bool, ret runtime.Object, err error) {
obj := action.(testclient.UpdateAction).GetObject().(*extensions.Scale)
replicas := action.(testclient.UpdateAction).GetObject().(*extensions.Scale).Spec.Replicas
assert.Equal(t, tc.desiredReplicas, replicas)
@ -269,7 +342,7 @@ func (tc *testCase) runTest(t *testing.T) {
tc.verifyResults(t)
}
func TestDefaultScaleUp(t *testing.T) {
func TestDefaultScaleUpRC(t *testing.T) {
tc := testCase{
minReplicas: 2,
maxReplicas: 6,
@ -282,6 +355,42 @@ func TestDefaultScaleUp(t *testing.T) {
tc.runTest(t)
}
func TestDefaultScaleUpDeployment(t *testing.T) {
tc := testCase{
minReplicas: 2,
maxReplicas: 6,
initialReplicas: 4,
desiredReplicas: 5,
verifyCPUCurrent: true,
reportedLevels: []uint64{900, 950, 950, 1000},
reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
resource: &fakeResource{
name: "test-dep",
apiVersion: "extensions/v1beta1",
kind: "deployments",
},
}
tc.runTest(t)
}
func TestDefaultScaleUpReplicaSet(t *testing.T) {
tc := testCase{
minReplicas: 2,
maxReplicas: 6,
initialReplicas: 4,
desiredReplicas: 5,
verifyCPUCurrent: true,
reportedLevels: []uint64{900, 950, 950, 1000},
reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
resource: &fakeResource{
name: "test-replicaset",
apiVersion: "extensions/v1beta1",
kind: "replicasets",
},
}
tc.runTest(t)
}
func TestScaleUp(t *testing.T) {
tc := testCase{
minReplicas: 2,
@ -296,6 +405,44 @@ func TestScaleUp(t *testing.T) {
tc.runTest(t)
}
func TestScaleUpDeployment(t *testing.T) {
tc := testCase{
minReplicas: 2,
maxReplicas: 6,
initialReplicas: 3,
desiredReplicas: 5,
CPUTarget: 30,
verifyCPUCurrent: true,
reportedLevels: []uint64{300, 500, 700},
reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
resource: &fakeResource{
name: "test-dep",
apiVersion: "extensions/v1beta1",
kind: "deployments",
},
}
tc.runTest(t)
}
func TestScaleUpReplicaSet(t *testing.T) {
tc := testCase{
minReplicas: 2,
maxReplicas: 6,
initialReplicas: 3,
desiredReplicas: 5,
CPUTarget: 30,
verifyCPUCurrent: true,
reportedLevels: []uint64{300, 500, 700},
reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
resource: &fakeResource{
name: "test-replicaset",
apiVersion: "extensions/v1beta1",
kind: "replicasets",
},
}
tc.runTest(t)
}
func TestScaleUpCM(t *testing.T) {
tc := testCase{
minReplicas: 2,

View File

@ -44,11 +44,11 @@ type MetricsClient interface {
// GetCPUUtilization returns the average utilization over all pods represented as a percent of requested CPU
// (e.g. 70 means that an average pod uses 70% of the requested CPU)
// and the time of generation of the oldest of utilization reports for pods.
GetCPUUtilization(namespace string, selector map[string]string) (*int, time.Time, error)
GetCPUUtilization(namespace string, selector labels.Selector) (*int, time.Time, error)
// GetCustomMetric returns the average value of the given custom metrics from the
// pods picked using the namespace and selector passed as arguments.
GetCustomMetric(customMetricName string, namespace string, selector map[string]string) (*float64, time.Time, error)
GetCustomMetric(customMetricName string, namespace string, selector labels.Selector) (*float64, time.Time, error)
}
type intAndFloat struct {
@ -100,7 +100,7 @@ func NewHeapsterMetricsClient(client clientset.Interface, namespace, scheme, ser
}
}
func (h *HeapsterMetricsClient) GetCPUUtilization(namespace string, selector map[string]string) (*int, time.Time, error) {
func (h *HeapsterMetricsClient) GetCPUUtilization(namespace string, selector labels.Selector) (*int, time.Time, error) {
avgConsumption, avgRequest, timestamp, err := h.GetCpuConsumptionAndRequestInMillis(namespace, selector)
if err != nil {
return nil, time.Time{}, fmt.Errorf("failed to get CPU consumption and request: %v", err)
@ -109,12 +109,11 @@ func (h *HeapsterMetricsClient) GetCPUUtilization(namespace string, selector map
return &utilization, timestamp, nil
}
func (h *HeapsterMetricsClient) GetCpuConsumptionAndRequestInMillis(namespace string, selector map[string]string) (avgConsumption int64,
func (h *HeapsterMetricsClient) GetCpuConsumptionAndRequestInMillis(namespace string, selector labels.Selector) (avgConsumption int64,
avgRequest int64, timestamp time.Time, err error) {
labelSelector := labels.SelectorFromSet(labels.Set(selector))
podList, err := h.client.Core().Pods(namespace).
List(api.ListOptions{LabelSelector: labelSelector})
List(api.ListOptions{LabelSelector: selector})
if err != nil {
return 0, 0, time.Time{}, fmt.Errorf("failed to get pod list: %v", err)
@ -144,7 +143,7 @@ func (h *HeapsterMetricsClient) GetCpuConsumptionAndRequestInMillis(namespace st
if missing || requestSum == 0 {
return 0, 0, time.Time{}, fmt.Errorf("some pods do not have request for cpu")
}
glog.V(4).Infof("%s %v - sum of CPU requested: %d", namespace, selector, requestSum)
glog.V(4).Infof("%s %s - sum of CPU requested: %d", namespace, selector, requestSum)
requestAvg := requestSum / int64(len(podList.Items))
// Consumption is already averaged and in millis.
consumption, timestamp, err := h.getForPods(heapsterCpuUsageMetricDefinition, namespace, podNames)
@ -156,11 +155,10 @@ func (h *HeapsterMetricsClient) GetCpuConsumptionAndRequestInMillis(namespace st
// GetCustomMetric returns the average value of the given custom metric from the
// pods picked using the namespace and selector passed as arguments.
func (h *HeapsterMetricsClient) GetCustomMetric(customMetricName string, namespace string, selector map[string]string) (*float64, time.Time, error) {
func (h *HeapsterMetricsClient) GetCustomMetric(customMetricName string, namespace string, selector labels.Selector) (*float64, time.Time, error) {
metricSpec := getHeapsterCustomMetricDefinition(customMetricName)
labelSelector := labels.SelectorFromSet(labels.Set(selector))
podList, err := h.client.Core().Pods(namespace).List(api.ListOptions{LabelSelector: labelSelector})
podList, err := h.client.Core().Pods(namespace).List(api.ListOptions{LabelSelector: selector})
if err != nil {
return nil, time.Time{}, fmt.Errorf("failed to get pod list: %v", err)

View File

@ -29,6 +29,7 @@ import (
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/testing/core"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/runtime"
heapster "k8s.io/heapster/api/v1/types"
@ -69,15 +70,15 @@ type testCase struct {
reportedMetricsPoints [][]metricPoint
namespace string
podListOverride *api.PodList
selector map[string]string
selector labels.Selector
}
func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset {
namespace := "test-namespace"
tc.namespace = namespace
podNamePrefix := "test-pod"
selector := map[string]string{"name": podNamePrefix}
tc.selector = selector
podLabels := map[string]string{"name": podNamePrefix}
tc.selector = labels.SelectorFromSet(podLabels)
fakeClient := &fake.Clientset{}
@ -88,7 +89,7 @@ func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset {
obj := &api.PodList{}
for i := 0; i < tc.replicas; i++ {
podName := fmt.Sprintf("%s-%d", podNamePrefix, i)
pod := buildPod(namespace, podName, selector, api.PodRunning)
pod := buildPod(namespace, podName, podLabels, api.PodRunning)
obj.Items = append(obj.Items, pod)
}
return true, obj, nil
@ -120,12 +121,12 @@ func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset {
return fakeClient
}
func buildPod(namespace, podName string, selector map[string]string, phase api.PodPhase) api.Pod {
func buildPod(namespace, podName string, podLabels map[string]string, phase api.PodPhase) api.Pod {
return api.Pod{
ObjectMeta: api.ObjectMeta{
Name: podName,
Namespace: namespace,
Labels: selector,
Labels: podLabels,
},
Spec: api.PodSpec{
Containers: []api.Container{
@ -193,10 +194,10 @@ func TestCPUPending(t *testing.T) {
namespace := "test-namespace"
podNamePrefix := "test-pod"
selector := map[string]string{"name": podNamePrefix}
podLabels := map[string]string{"name": podNamePrefix}
for i := 0; i < tc.replicas; i++ {
podName := fmt.Sprintf("%s-%d", podNamePrefix, i)
pod := buildPod(namespace, podName, selector, api.PodRunning)
pod := buildPod(namespace, podName, podLabels, api.PodRunning)
tc.podListOverride.Items = append(tc.podListOverride.Items, pod)
}
tc.podListOverride.Items[0].Status.Phase = api.PodPending
@ -216,10 +217,10 @@ func TestCPUAllPending(t *testing.T) {
namespace := "test-namespace"
podNamePrefix := "test-pod"
selector := map[string]string{"name": podNamePrefix}
podLabels := map[string]string{"name": podNamePrefix}
for i := 0; i < tc.replicas; i++ {
podName := fmt.Sprintf("%s-%d", podNamePrefix, i)
pod := buildPod(namespace, podName, selector, api.PodPending)
pod := buildPod(namespace, podName, podLabels, api.PodPending)
tc.podListOverride.Items = append(tc.podListOverride.Items, pod)
}
tc.runTest(t)
@ -248,10 +249,10 @@ func TestQPSPending(t *testing.T) {
namespace := "test-namespace"
podNamePrefix := "test-pod"
selector := map[string]string{"name": podNamePrefix}
podLabels := map[string]string{"name": podNamePrefix}
for i := 0; i < tc.replicas; i++ {
podName := fmt.Sprintf("%s-%d", podNamePrefix, i)
pod := buildPod(namespace, podName, selector, api.PodRunning)
pod := buildPod(namespace, podName, podLabels, api.PodRunning)
tc.podListOverride.Items = append(tc.podListOverride.Items, pod)
}
tc.podListOverride.Items[0].Status.Phase = api.PodPending
@ -270,10 +271,10 @@ func TestQPSAllPending(t *testing.T) {
namespace := "test-namespace"
podNamePrefix := "test-pod"
selector := map[string]string{"name": podNamePrefix}
podLabels := map[string]string{"name": podNamePrefix}
for i := 0; i < tc.replicas; i++ {
podName := fmt.Sprintf("%s-%d", podNamePrefix, i)
pod := buildPod(namespace, podName, selector, api.PodPending)
pod := buildPod(namespace, podName, podLabels, api.PodPending)
tc.podListOverride.Items = append(tc.podListOverride.Items, pod)
}
tc.podListOverride.Items[0].Status.Phase = api.PodPending

View File

@ -743,13 +743,25 @@ func (p *Parser) parseExactValue() (sets.String, error) {
// (5) A requirement with just !KEY requires that the KEY not exist.
//
func Parse(selector string) (Selector, error) {
p := &Parser{l: &Lexer{s: selector, pos: 0}}
items, error := p.parse()
if error == nil {
sort.Sort(ByKey(items)) // sort to grant determistic parsing
return internalSelector(items), error
parsedSelector, err := parse(selector)
if err == nil {
return parsedSelector, nil
}
return nil, error
return nil, err
}
// parse parses the string representation of the selector and returns the internalSelector struct.
// The callers of this method can then decide how to return the internalSelector struct to their
// callers. This function has two callers now, one returns a Selector interface and the other
// returns a list of requirements.
func parse(selector string) (internalSelector, error) {
p := &Parser{l: &Lexer{s: selector, pos: 0}}
items, err := p.parse()
if err != nil {
return nil, err
}
sort.Sort(ByKey(items)) // sort to grant determistic parsing
return internalSelector(items), err
}
var qualifiedNameErrorMsg string = fmt.Sprintf(`must be a qualified name (at most %d characters, matching regex %s), with an optional DNS subdomain prefix (at most %d characters, matching regex %s) and slash (/): e.g. "MyName" or "example.com/MyName"`, validation.QualifiedNameMaxLength, validation.QualifiedNameFmt, validation.DNS1123SubdomainMaxLength, validation.DNS1123SubdomainFmt)
@ -788,3 +800,12 @@ func SelectorFromSet(ls Set) Selector {
sort.Sort(ByKey(requirements))
return internalSelector(requirements)
}
// ParseToRequirements takes a string representing a selector and returns a list of
// requirements. This function is suitable for those callers that perform additional
// processing on selector requirements.
// See the documentation for Parse() function for more details.
// TODO: Consider exporting the internalSelector type instead.
func ParseToRequirements(selector string) ([]Requirement, error) {
return parse(selector)
}

View File

@ -248,12 +248,6 @@ func (m *Master) InstallAPIs(c *Config) {
ParameterCodec: api.ParameterCodec,
NegotiatedSerializer: api.Codecs,
}
if autoscalingGroupVersion := (unversioned.GroupVersion{Group: "autoscaling", Version: "v1"}); registered.IsEnabledVersion(autoscalingGroupVersion) {
apiGroupInfo.SubresourceGroupVersionKind = map[string]unversioned.GroupVersionKind{
"deployments/scale": autoscalingGroupVersion.WithKind("Scale"),
"replicasets/scale": autoscalingGroupVersion.WithKind("Scale"),
}
}
apiGroupsInfo = append(apiGroupsInfo, apiGroupInfo)
extensionsGVForDiscovery := unversioned.GroupVersionForDiscovery{
@ -723,10 +717,7 @@ func (m *Master) getExtensionResources(c *Config) map[string]rest.Storage {
storage["deployments"] = deploymentStorage.Deployment
storage["deployments/status"] = deploymentStorage.Status
storage["deployments/rollback"] = deploymentStorage.Rollback
if registered.IsEnabledVersion(unversioned.GroupVersion{Group: "autoscaling", Version: "v1"}) {
storage["deployments/scale"] = deploymentStorage.Scale
}
storage["deployments/scale"] = deploymentStorage.Scale
}
if isEnabled("jobs") {
m.constructJobResources(c, storage)
@ -744,9 +735,7 @@ func (m *Master) getExtensionResources(c *Config) map[string]rest.Storage {
replicaSetStorage := replicasetetcd.NewStorage(restOptions("replicasets"))
storage["replicasets"] = replicaSetStorage.ReplicaSet
storage["replicasets/status"] = replicaSetStorage.Status
if registered.IsEnabledVersion(unversioned.GroupVersion{Group: "autoscaling", Version: "v1"}) {
storage["replicasets/scale"] = replicaSetStorage.Scale
}
storage["replicasets/scale"] = replicaSetStorage.Scale
}
return storage

View File

@ -23,9 +23,6 @@ import (
"k8s.io/kubernetes/pkg/api/errors"
etcderr "k8s.io/kubernetes/pkg/api/errors/etcd"
"k8s.io/kubernetes/pkg/api/rest"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/autoscaling"
asvalidation "k8s.io/kubernetes/pkg/apis/autoscaling/validation"
"k8s.io/kubernetes/pkg/apis/extensions"
extvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation"
"k8s.io/kubernetes/pkg/fields"
@ -192,13 +189,13 @@ var _ = rest.Patcher(&ScaleREST{})
// New creates a new Scale object
func (r *ScaleREST) New() runtime.Object {
return &autoscaling.Scale{}
return &extensions.Scale{}
}
func (r *ScaleREST) Get(ctx api.Context, name string) (runtime.Object, error) {
deployment, err := r.registry.GetDeployment(ctx, name)
if err != nil {
return nil, errors.NewNotFound(autoscaling.Resource("deployments/scale"), name)
return nil, errors.NewNotFound(extensions.Resource("deployments/scale"), name)
}
scale, err := scaleFromDeployment(deployment)
if err != nil {
@ -211,18 +208,18 @@ func (r *ScaleREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object,
if obj == nil {
return nil, false, errors.NewBadRequest(fmt.Sprintf("nil update passed to Scale"))
}
scale, ok := obj.(*autoscaling.Scale)
scale, ok := obj.(*extensions.Scale)
if !ok {
return nil, false, errors.NewBadRequest(fmt.Sprintf("expected input object type to be Scale, but %T", obj))
}
if errs := asvalidation.ValidateScale(scale); len(errs) > 0 {
return nil, false, errors.NewInvalid(autoscaling.Kind("Scale"), scale.Name, errs)
if errs := extvalidation.ValidateScale(scale); len(errs) > 0 {
return nil, false, errors.NewInvalid(extensions.Kind("Scale"), scale.Name, errs)
}
deployment, err := r.registry.GetDeployment(ctx, scale.Name)
if err != nil {
return nil, false, errors.NewNotFound(autoscaling.Resource("deployments/scale"), scale.Name)
return nil, false, errors.NewNotFound(extensions.Resource("deployments/scale"), scale.Name)
}
deployment.Spec.Replicas = scale.Spec.Replicas
deployment.ResourceVersion = scale.ResourceVersion
@ -238,12 +235,8 @@ func (r *ScaleREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object,
}
// scaleFromDeployment returns a scale subresource for a deployment.
func scaleFromDeployment(deployment *extensions.Deployment) (*autoscaling.Scale, error) {
selector, err := unversioned.LabelSelectorAsSelector(deployment.Spec.Selector)
if err != nil {
return nil, fmt.Errorf("stored deployment object can't be represented in the form of a scale subresource because the label selector ('%v') can't be parsed: %v", deployment.Spec.Selector, err)
}
return &autoscaling.Scale{
func scaleFromDeployment(deployment *extensions.Deployment) (*extensions.Scale, error) {
return &extensions.Scale{
// TODO: Create a variant of ObjectMeta type that only contains the fields below.
ObjectMeta: api.ObjectMeta{
Name: deployment.Name,
@ -252,12 +245,12 @@ func scaleFromDeployment(deployment *extensions.Deployment) (*autoscaling.Scale,
ResourceVersion: deployment.ResourceVersion,
CreationTimestamp: deployment.CreationTimestamp,
},
Spec: autoscaling.ScaleSpec{
Spec: extensions.ScaleSpec{
Replicas: deployment.Spec.Replicas,
},
Status: autoscaling.ScaleStatus{
Status: extensions.ScaleStatus{
Replicas: deployment.Status.Replicas,
Selector: selector.String(),
Selector: deployment.Spec.Selector,
},
}, nil
}

View File

@ -24,7 +24,6 @@ import (
"k8s.io/kubernetes/pkg/api/errors"
etcderrors "k8s.io/kubernetes/pkg/api/errors/etcd"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/autoscaling"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
@ -195,11 +194,7 @@ func TestScaleGet(t *testing.T) {
t.Fatalf("error setting new deployment (key: %s) %v: %v", key, validDeployment, err)
}
selector, err := unversioned.LabelSelectorAsSelector(validDeployment.Spec.Selector)
if err != nil {
t.Errorf("invalid deployment selector %+v: %v", validDeployment.Spec.Selector, err)
}
want := &autoscaling.Scale{
want := &extensions.Scale{
ObjectMeta: api.ObjectMeta{
Name: name,
Namespace: namespace,
@ -207,19 +202,19 @@ func TestScaleGet(t *testing.T) {
ResourceVersion: deployment.ResourceVersion,
CreationTimestamp: deployment.CreationTimestamp,
},
Spec: autoscaling.ScaleSpec{
Spec: extensions.ScaleSpec{
Replicas: validDeployment.Spec.Replicas,
},
Status: autoscaling.ScaleStatus{
Status: extensions.ScaleStatus{
Replicas: validDeployment.Status.Replicas,
Selector: selector.String(),
Selector: validDeployment.Spec.Selector,
},
}
obj, err := storage.Scale.Get(ctx, name)
if err != nil {
t.Fatalf("error fetching scale for %s: %v", name, err)
}
got := obj.(*autoscaling.Scale)
got := obj.(*extensions.Scale)
if !api.Semantic.DeepEqual(want, got) {
t.Errorf("unexpected scale: %s", util.ObjectDiff(want, got))
}
@ -236,9 +231,9 @@ func TestScaleUpdate(t *testing.T) {
t.Fatalf("error setting new deployment (key: %s) %v: %v", key, validDeployment, err)
}
replicas := 12
update := autoscaling.Scale{
update := extensions.Scale{
ObjectMeta: api.ObjectMeta{Name: name, Namespace: namespace},
Spec: autoscaling.ScaleSpec{
Spec: extensions.ScaleSpec{
Replicas: replicas,
},
}
@ -250,7 +245,7 @@ func TestScaleUpdate(t *testing.T) {
if err != nil {
t.Fatalf("error fetching scale for %s: %v", name, err)
}
scale := obj.(*autoscaling.Scale)
scale := obj.(*extensions.Scale)
if scale.Spec.Replicas != replicas {
t.Errorf("wrong replicas count expected: %d got: %d", replicas, deployment.Spec.Replicas)
}

View File

@ -22,11 +22,11 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/rest"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/registry/controller"
"k8s.io/kubernetes/pkg/registry/controller/etcd"
"k8s.io/kubernetes/pkg/registry/generic"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/apis/extensions"
@ -67,20 +67,7 @@ func (r *ScaleREST) Get(ctx api.Context, name string) (runtime.Object, error) {
if err != nil {
return nil, errors.NewNotFound(extensions.Resource("replicationcontrollers/scale"), name)
}
return &extensions.Scale{
ObjectMeta: api.ObjectMeta{
Name: name,
Namespace: rc.Namespace,
CreationTimestamp: rc.CreationTimestamp,
},
Spec: extensions.ScaleSpec{
Replicas: rc.Spec.Replicas,
},
Status: extensions.ScaleStatus{
Replicas: rc.Status.Replicas,
Selector: rc.Spec.Selector,
},
}, nil
return scaleFromRC(rc), nil
}
func (r *ScaleREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
@ -105,10 +92,17 @@ func (r *ScaleREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object,
if err != nil {
return nil, false, errors.NewConflict(extensions.Resource("replicationcontrollers/scale"), scale.Name, err)
}
return scaleFromRC(rc), false, nil
}
// scaleFromRC returns a scale subresource for a replication controller.
func scaleFromRC(rc *api.ReplicationController) *extensions.Scale {
return &extensions.Scale{
ObjectMeta: api.ObjectMeta{
Name: rc.Name,
Namespace: rc.Namespace,
UID: rc.UID,
ResourceVersion: rc.ResourceVersion,
CreationTimestamp: rc.CreationTimestamp,
},
Spec: extensions.ScaleSpec{
@ -116,9 +110,11 @@ func (r *ScaleREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object,
},
Status: extensions.ScaleStatus{
Replicas: rc.Status.Replicas,
Selector: rc.Spec.Selector,
Selector: &unversioned.LabelSelector{
MatchLabels: rc.Spec.Selector,
},
},
}, false, nil
}
}
// Dummy implementation

View File

@ -20,6 +20,7 @@ import (
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/registry/generic"
"k8s.io/kubernetes/pkg/registry/registrytest"
@ -73,7 +74,9 @@ var validScale = extensions.Scale{
},
Status: extensions.ScaleStatus{
Replicas: 0,
Selector: validPodTemplate.Template.Labels,
Selector: &unversioned.LabelSelector{
MatchLabels: validPodTemplate.Template.Labels,
},
},
}

View File

@ -24,10 +24,8 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/rest"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/autoscaling"
asvalidation "k8s.io/kubernetes/pkg/apis/autoscaling/validation"
"k8s.io/kubernetes/pkg/apis/extensions"
extvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/registry/cachesize"
@ -130,13 +128,13 @@ var _ = rest.Patcher(&ScaleREST{})
// New creates a new Scale object
func (r *ScaleREST) New() runtime.Object {
return &autoscaling.Scale{}
return &extensions.Scale{}
}
func (r *ScaleREST) Get(ctx api.Context, name string) (runtime.Object, error) {
rs, err := r.registry.GetReplicaSet(ctx, name)
if err != nil {
return nil, errors.NewNotFound(autoscaling.Resource("replicasets/scale"), name)
return nil, errors.NewNotFound(extensions.Resource("replicasets/scale"), name)
}
scale, err := scaleFromReplicaSet(rs)
if err != nil {
@ -149,18 +147,18 @@ func (r *ScaleREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object,
if obj == nil {
return nil, false, errors.NewBadRequest(fmt.Sprintf("nil update passed to Scale"))
}
scale, ok := obj.(*autoscaling.Scale)
scale, ok := obj.(*extensions.Scale)
if !ok {
return nil, false, errors.NewBadRequest(fmt.Sprintf("wrong object passed to Scale update: %v", obj))
}
if errs := asvalidation.ValidateScale(scale); len(errs) > 0 {
return nil, false, errors.NewInvalid(autoscaling.Kind("Scale"), scale.Name, errs)
if errs := extvalidation.ValidateScale(scale); len(errs) > 0 {
return nil, false, errors.NewInvalid(extensions.Kind("Scale"), scale.Name, errs)
}
rs, err := r.registry.GetReplicaSet(ctx, scale.Name)
if err != nil {
return nil, false, errors.NewNotFound(autoscaling.Resource("replicasets/scale"), scale.Name)
return nil, false, errors.NewNotFound(extensions.Resource("replicasets/scale"), scale.Name)
}
rs.Spec.Replicas = scale.Spec.Replicas
rs.ResourceVersion = scale.ResourceVersion
@ -176,12 +174,8 @@ func (r *ScaleREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object,
}
// scaleFromReplicaSet returns a scale subresource for a replica set.
func scaleFromReplicaSet(rs *extensions.ReplicaSet) (*autoscaling.Scale, error) {
selector, err := unversioned.LabelSelectorAsSelector(rs.Spec.Selector)
if err != nil {
return nil, fmt.Errorf("stored replica set object can't be represented in the form of a scale subresource because the label selector ('%v') can't be parsed: %v", rs.Spec.Selector, err)
}
return &autoscaling.Scale{
func scaleFromReplicaSet(rs *extensions.ReplicaSet) (*extensions.Scale, error) {
return &extensions.Scale{
// TODO: Create a variant of ObjectMeta type that only contains the fields below.
ObjectMeta: api.ObjectMeta{
Name: rs.Name,
@ -190,12 +184,12 @@ func scaleFromReplicaSet(rs *extensions.ReplicaSet) (*autoscaling.Scale, error)
ResourceVersion: rs.ResourceVersion,
CreationTimestamp: rs.CreationTimestamp,
},
Spec: autoscaling.ScaleSpec{
Spec: extensions.ScaleSpec{
Replicas: rs.Spec.Replicas,
},
Status: autoscaling.ScaleStatus{
Status: extensions.ScaleStatus{
Replicas: rs.Status.Replicas,
Selector: selector.String(),
Selector: rs.Spec.Selector,
},
}, nil
}

View File

@ -22,7 +22,6 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/autoscaling"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
@ -256,11 +255,7 @@ func TestScaleGet(t *testing.T) {
t.Fatalf("error setting new replica set (key: %s) %v: %v", key, validReplicaSet, err)
}
selector, err := unversioned.LabelSelectorAsSelector(validReplicaSet.Spec.Selector)
if err != nil {
t.Errorf("invalid replicaset selector %+v: %v", validReplicaSet.Spec.Selector, err)
}
want := &autoscaling.Scale{
want := &extensions.Scale{
ObjectMeta: api.ObjectMeta{
Name: name,
Namespace: api.NamespaceDefault,
@ -268,16 +263,16 @@ func TestScaleGet(t *testing.T) {
ResourceVersion: rs.ResourceVersion,
CreationTimestamp: rs.CreationTimestamp,
},
Spec: autoscaling.ScaleSpec{
Spec: extensions.ScaleSpec{
Replicas: validReplicaSet.Spec.Replicas,
},
Status: autoscaling.ScaleStatus{
Status: extensions.ScaleStatus{
Replicas: validReplicaSet.Status.Replicas,
Selector: selector.String(),
Selector: validReplicaSet.Spec.Selector,
},
}
obj, err := storage.Scale.Get(ctx, name)
got := obj.(*autoscaling.Scale)
got := obj.(*extensions.Scale)
if err != nil {
t.Fatalf("error fetching scale for %s: %v", name, err)
}
@ -299,12 +294,12 @@ func TestScaleUpdate(t *testing.T) {
t.Fatalf("error setting new replica set (key: %s) %v: %v", key, validReplicaSet, err)
}
replicas := 12
update := autoscaling.Scale{
update := extensions.Scale{
ObjectMeta: api.ObjectMeta{
Name: name,
Namespace: api.NamespaceDefault,
},
Spec: autoscaling.ScaleSpec{
Spec: extensions.ScaleSpec{
Replicas: replicas,
},
}
@ -317,7 +312,7 @@ func TestScaleUpdate(t *testing.T) {
if err != nil {
t.Fatalf("error fetching scale for %s: %v", name, err)
}
scale := obj.(*autoscaling.Scale)
scale := obj.(*extensions.Scale)
if scale.Spec.Replicas != replicas {
t.Errorf("wrong replicas count expected: %d got: %d", replicas, scale.Spec.Replicas)
}

View File

@ -42,6 +42,7 @@ const (
resourceConsumerImage = "gcr.io/google_containers/resource_consumer:beta2"
rcIsNil = "ERROR: replicationController = nil"
deploymentIsNil = "ERROR: deployment = nil"
rsIsNil = "ERROR: replicaset = nil"
invalidKind = "ERROR: invalid workload kind for resource consumer"
customMetricName = "QPS"
)
@ -298,6 +299,13 @@ func (rc *ResourceConsumer) GetReplicas() int {
Failf(deploymentIsNil)
}
return deployment.Status.Replicas
case kindReplicaSet:
rs, err := rc.framework.Client.ReplicaSets(rc.framework.Namespace.Name).Get(rc.name)
expectNoError(err)
if rs == nil {
Failf(rsIsNil)
}
return rs.Status.Replicas
default:
Failf(invalidKind)
}
@ -381,6 +389,12 @@ func runServiceAndWorkloadForResourceConsumer(c *client.Client, ns, name, kind s
}
expectNoError(RunDeployment(dpConfig))
break
case kindReplicaSet:
rsConfig := ReplicaSetConfig{
rcConfig,
}
expectNoError(RunReplicaSet(rsConfig))
break
default:
Failf(invalidKind)
}

View File

@ -28,6 +28,7 @@ import (
const (
kindRC = "replicationController"
kindDeployment = "deployment"
kindReplicaSet = "replicaset"
subresource = "scale"
)
@ -41,17 +42,27 @@ var _ = Describe("Horizontal pod autoscaling (scale resource: CPU)", func() {
titleUp := "Should scale from 1 pod to 3 pods and from 3 to 5 and verify decision stability"
titleDown := "Should scale from 5 pods to 3 pods and from 3 to 1 and verify decision stability"
// TODO(madhusudancs): Fix this when Scale group issues are resolved (see issue #18528).
// These tests take ~20 minutes each.
// Describe("[Serial] [Slow] Deployment", func() {
// // CPU tests via deployments
// It(titleUp, func() {
// scaleUp("deployment", kindDeployment, rc, f)
// })
// It(titleDown, func() {
// scaleDown("deployment", kindDeployment, rc, f)
// })
// })
Describe("[Serial] [Slow] Deployment", func() {
// CPU tests via deployments
It(titleUp, func() {
scaleUp("test-deployment", kindDeployment, rc, f)
})
It(titleDown, func() {
scaleDown("test-deployment", kindDeployment, rc, f)
})
})
// These tests take ~20 minutes each.
Describe("[Serial] [Slow] ReplicaSet", func() {
// CPU tests via deployments
It(titleUp, func() {
scaleUp("rs", kindReplicaSet, rc, f)
})
It(titleDown, func() {
scaleDown("rs", kindReplicaSet, rc, f)
})
})
// These tests take ~20 minutes each.
Describe("[Serial] [Slow] ReplicationController", func() {

View File

@ -294,6 +294,10 @@ type DeploymentConfig struct {
RCConfig
}
type ReplicaSetConfig struct {
RCConfig
}
func nowStamp() string {
return time.Now().Format(time.StampMilli)
}
@ -1788,6 +1792,59 @@ func (config *DeploymentConfig) create() error {
return nil
}
// RunReplicaSet launches (and verifies correctness) of a ReplicaSet
// and waits until all the pods it launches to reach the "Running" state.
// It's the caller's responsibility to clean up externally (i.e. use the
// namespace lifecycle for handling cleanup).
func RunReplicaSet(config ReplicaSetConfig) error {
err := config.create()
if err != nil {
return err
}
return config.start()
}
func (config *ReplicaSetConfig) create() error {
By(fmt.Sprintf("creating replicaset %s in namespace %s", config.Name, config.Namespace))
rs := &extensions.ReplicaSet{
ObjectMeta: api.ObjectMeta{
Name: config.Name,
},
Spec: extensions.ReplicaSetSpec{
Replicas: config.Replicas,
Selector: &unversioned.LabelSelector{
MatchLabels: map[string]string{
"name": config.Name,
},
},
Template: &api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"name": config.Name},
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: config.Name,
Image: config.Image,
Command: config.Command,
Ports: []api.ContainerPort{{ContainerPort: 80}},
},
},
},
},
},
}
config.applyTo(rs.Spec.Template)
_, err := config.Client.ReplicaSets(config.Namespace).Create(rs)
if err != nil {
return fmt.Errorf("Error creating replica set: %v", err)
}
Logf("Created replica set with name: %v, namespace: %v, replica count: %v", rs.Name, config.Namespace, rs.Spec.Replicas)
return nil
}
// RunRC Launches (and verifies correctness) of a Replication Controller
// and will wait for all pods it spawns to become "Running".
// It's the caller's responsibility to clean up externally (i.e. use the