Support for both map-based and set-based selectors in extensions/v1beta1.Scale

Here are a list of changes along with an explanation of how they work:
1. Add a new string field called TargetSelector to the external version of
   extensions Scale type (extensions/v1beta1.Scale). This is a serialized
   version of either the map-based selector (in case of ReplicationControllers)
   or the unversioned.LabelSelector struct (in case of Deployments and
   ReplicaSets).
2. Change the selector field in the internal Scale type (extensions.Scale) to
   unversioned.LabelSelector.
3. Add conversion functions to convert from two external selector fields to a
   single internal selector field. The rules for conversion are as follows:
   i.   If the target resource that this scale targets supports LabelSelector
        (Deployments and ReplicaSets), then serialize the LabelSelector and
        store the string in the TargetSelector field in the external version
        and leave the map-based Selector field as nil.
   ii.  If the target resource only supports a map-based selector
        (ReplicationControllers), then still serialize that selector and
	store the serialized string in the TargetSelector field. Also,
	set the the Selector map field in the external Scale type.
   iii. When converting from external to internal version, parse the
        TargetSelector string into LabelSelector struct if the string isn't
	empty. If it is empty, then check if the Selector map is set and just
	assign that map to the MatchLabels component of the LabelSelector.
   iv.  When converting from internal to external version, serialize the
        LabelSelector and store it in the TargetSelector field. If only
	the MatchLabel component is set, then also copy that value to
	the Selector map field in the external version.
4. HPA now just converts the LabelSelector field to a Selector interface
   type to list the pods.
5. Scale Get and Update etcd methods for Deployments and ReplicaSets now
   return extensions.Scale instead of autoscaling.Scale.
6. Consequently, SubresourceGroupVersion override and is "autoscaling"
   enabled check is now removed from pkg/master/master.go
7. Other small changes to labels package, fuzzer and LabelSelector
   helpers to piece this all together.
8. Add unit tests to HPA targeting Deployments and ReplicaSets.
9. Add an e2e test to HPA targeting ReplicaSets.
This commit is contained in:
Madhusudan.C.S 2016-03-08 16:27:13 -08:00
parent bf3cc9d126
commit fe26381c90
29 changed files with 670 additions and 439 deletions

View File

@ -1659,7 +1659,7 @@
"description": "API at /apis/extensions/v1beta1", "description": "API at /apis/extensions/v1beta1",
"operations": [ "operations": [
{ {
"type": "v1.Scale", "type": "v1beta1.Scale",
"method": "GET", "method": "GET",
"summary": "read scale of the specified Scale", "summary": "read scale of the specified Scale",
"nickname": "readNamespacedScaleScale", "nickname": "readNamespacedScaleScale",
@ -1693,7 +1693,7 @@
{ {
"code": 200, "code": 200,
"message": "OK", "message": "OK",
"responseModel": "v1.Scale" "responseModel": "v1beta1.Scale"
} }
], ],
"produces": [ "produces": [
@ -1705,7 +1705,7 @@
] ]
}, },
{ {
"type": "v1.Scale", "type": "v1beta1.Scale",
"method": "PUT", "method": "PUT",
"summary": "replace scale of the specified Scale", "summary": "replace scale of the specified Scale",
"nickname": "replaceNamespacedScaleScale", "nickname": "replaceNamespacedScaleScale",
@ -1719,7 +1719,7 @@
"allowMultiple": false "allowMultiple": false
}, },
{ {
"type": "v1.Scale", "type": "v1beta1.Scale",
"paramType": "body", "paramType": "body",
"name": "body", "name": "body",
"description": "", "description": "",
@ -1747,7 +1747,7 @@
{ {
"code": 200, "code": 200,
"message": "OK", "message": "OK",
"responseModel": "v1.Scale" "responseModel": "v1beta1.Scale"
} }
], ],
"produces": [ "produces": [
@ -1759,7 +1759,7 @@
] ]
}, },
{ {
"type": "v1.Scale", "type": "v1beta1.Scale",
"method": "PATCH", "method": "PATCH",
"summary": "partially update scale of the specified Scale", "summary": "partially update scale of the specified Scale",
"nickname": "patchNamespacedScaleScale", "nickname": "patchNamespacedScaleScale",
@ -1801,7 +1801,7 @@
{ {
"code": 200, "code": 200,
"message": "OK", "message": "OK",
"responseModel": "v1.Scale" "responseModel": "v1beta1.Scale"
} }
], ],
"produces": [ "produces": [
@ -5121,7 +5121,7 @@
"description": "API at /apis/extensions/v1beta1", "description": "API at /apis/extensions/v1beta1",
"operations": [ "operations": [
{ {
"type": "v1.Scale", "type": "v1beta1.Scale",
"method": "GET", "method": "GET",
"summary": "read scale of the specified Scale", "summary": "read scale of the specified Scale",
"nickname": "readNamespacedScaleScale", "nickname": "readNamespacedScaleScale",
@ -5155,7 +5155,7 @@
{ {
"code": 200, "code": 200,
"message": "OK", "message": "OK",
"responseModel": "v1.Scale" "responseModel": "v1beta1.Scale"
} }
], ],
"produces": [ "produces": [
@ -5167,7 +5167,7 @@
] ]
}, },
{ {
"type": "v1.Scale", "type": "v1beta1.Scale",
"method": "PUT", "method": "PUT",
"summary": "replace scale of the specified Scale", "summary": "replace scale of the specified Scale",
"nickname": "replaceNamespacedScaleScale", "nickname": "replaceNamespacedScaleScale",
@ -5181,7 +5181,7 @@
"allowMultiple": false "allowMultiple": false
}, },
{ {
"type": "v1.Scale", "type": "v1beta1.Scale",
"paramType": "body", "paramType": "body",
"name": "body", "name": "body",
"description": "", "description": "",
@ -5209,7 +5209,7 @@
{ {
"code": 200, "code": 200,
"message": "OK", "message": "OK",
"responseModel": "v1.Scale" "responseModel": "v1beta1.Scale"
} }
], ],
"produces": [ "produces": [
@ -5221,7 +5221,7 @@
] ]
}, },
{ {
"type": "v1.Scale", "type": "v1beta1.Scale",
"method": "PATCH", "method": "PATCH",
"summary": "partially update scale of the specified Scale", "summary": "partially update scale of the specified Scale",
"nickname": "patchNamespacedScaleScale", "nickname": "patchNamespacedScaleScale",
@ -5263,7 +5263,7 @@
{ {
"code": 200, "code": 200,
"message": "OK", "message": "OK",
"responseModel": "v1.Scale" "responseModel": "v1beta1.Scale"
} }
], ],
"produces": [ "produces": [
@ -7234,9 +7234,9 @@
} }
} }
}, },
"v1.Scale": { "v1beta1.Scale": {
"id": "v1.Scale", "id": "v1beta1.Scale",
"description": "Scale represents a scaling request for a resource.", "description": "represents a scaling request for a resource.",
"properties": { "properties": {
"kind": { "kind": {
"type": "string", "type": "string",
@ -7251,18 +7251,18 @@
"description": "Standard object metadata; More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata." "description": "Standard object metadata; More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata."
}, },
"spec": { "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." "description": "defines the behavior of the scale. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status."
}, },
"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." "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": { "v1beta1.ScaleSpec": {
"id": "v1.ScaleSpec", "id": "v1beta1.ScaleSpec",
"description": "ScaleSpec describes the attributes of a scale subresource.", "description": "describes the attributes of a scale subresource",
"properties": { "properties": {
"replicas": { "replicas": {
"type": "integer", "type": "integer",
@ -7271,9 +7271,9 @@
} }
} }
}, },
"v1.ScaleStatus": { "v1beta1.ScaleStatus": {
"id": "v1.ScaleStatus", "id": "v1beta1.ScaleStatus",
"description": "ScaleStatus represents the current status of a scale subresource.", "description": "represents the current status of a scale subresource.",
"required": [ "required": [
"replicas" "replicas"
], ],
@ -7284,8 +7284,12 @@
"description": "actual number of observed instances of the scaled object." "description": "actual number of observed instances of the scaled object."
}, },
"selector": { "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", "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." "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> </tbody>
</table> </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>
<div class="sect2"> <div class="sect2">
<h3 id="_v1beta1_jobstatus">v1beta1.JobStatus</h3> <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> </tbody>
</table> </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>
<div class="sect2"> <div class="sect2">
<h3 id="_v1_loadbalanceringress">v1.LoadBalancerIngress</h3> <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"><p class="tableblock"><a href="#_any">any</a></p></td>
<td class="tableblock halign-left valign-top"></td> <td class="tableblock halign-left valign-top"></td>
</tr> </tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">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> </tbody>
</table> </table>
@ -4686,40 +4590,6 @@ Both these may change in the future. Incoming requests are matched against the h
</tbody> </tbody>
</table> </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>
<div class="sect2"> <div class="sect2">
<h3 id="_v1beta1_labelselector">v1beta1.LabelSelector</h3> <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>
<div id="footer"> <div id="footer">
<div id="footer-text"> <div id="footer-text">
Last updated 2016-03-03 20:23:06 UTC Last updated 2016-03-09 19:21:59 UTC
</div> </div>
</div> </div>
</body> </body>

View File

@ -3304,7 +3304,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<tr> <tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">200</p></td> <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">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> </tr>
</tbody> </tbody>
</table> </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"><p class="tableblock">body</p></td>
<td class="tableblock halign-left valign-top"></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">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> <td class="tableblock halign-left valign-top"></td>
</tr> </tr>
<tr> <tr>
@ -3428,7 +3428,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<tr> <tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">200</p></td> <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">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> </tr>
</tbody> </tbody>
</table> </table>
@ -3552,7 +3552,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<tr> <tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">200</p></td> <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">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> </tr>
</tbody> </tbody>
</table> </table>
@ -7858,7 +7858,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<tr> <tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">200</p></td> <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">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> </tr>
</tbody> </tbody>
</table> </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"><p class="tableblock">body</p></td>
<td class="tableblock halign-left valign-top"></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">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> <td class="tableblock halign-left valign-top"></td>
</tr> </tr>
<tr> <tr>
@ -7982,7 +7982,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<tr> <tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">200</p></td> <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">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> </tr>
</tbody> </tbody>
</table> </table>
@ -8106,7 +8106,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<tr> <tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">200</p></td> <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">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> </tr>
</tbody> </tbody>
</table> </table>
@ -11401,7 +11401,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
</div> </div>
<div id="footer"> <div id="footer">
<div id="footer-text"> <div id="footer-text">
Last updated 2016-02-28 04:44:42 UTC Last updated 2016-03-09 19:21:59 UTC
</div> </div>
</div> </div>
</body> </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} seLinuxRules := []extensions.SELinuxStrategy{extensions.SELinuxStrategyRunAsAny, extensions.SELinuxStrategyMustRunAs}
psp.SELinux.Rule = seLinuxRules[c.Rand.Intn(len(seLinuxRules))] 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 return f
} }

View File

@ -25,6 +25,7 @@ import (
// LabelSelectorAsSelector converts the LabelSelector api type into a struct that implements // LabelSelectorAsSelector converts the LabelSelector api type into a struct that implements
// labels.Selector // 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) { func LabelSelectorAsSelector(ps *LabelSelector) (labels.Selector, error) {
if ps == nil { if ps == nil {
return labels.Nothing(), nil return labels.Nothing(), nil
@ -34,7 +35,7 @@ func LabelSelectorAsSelector(ps *LabelSelector) (labels.Selector, error) {
} }
selector := labels.NewSelector() selector := labels.NewSelector()
for k, v := range ps.MatchLabels { 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 { if err != nil {
return nil, err return nil, err
} }
@ -63,6 +64,55 @@ func LabelSelectorAsSelector(ps *LabelSelector) (labels.Selector, error) {
return selector, nil 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. // SetAsLabelSelector converts the labels.Set object into a LabelSelector api object.
func SetAsLabelSelector(ls labels.Set) *LabelSelector { func SetAsLabelSelector(ls labels.Set) *LabelSelector {
if ls == nil { if ls == nil {

View File

@ -46,7 +46,7 @@ func TestLabelSelectorAsSelector(t *testing.T) {
{in: &LabelSelector{}, out: labels.Everything()}, {in: &LabelSelector{}, out: labels.Everything()},
{ {
in: &LabelSelector{MatchLabels: matchLabels}, in: &LabelSelector{MatchLabels: matchLabels},
out: mustParse("foo in (bar)"), out: mustParse("foo=bar"),
}, },
{ {
in: &LabelSelector{MatchExpressions: matchExpressions}, in: &LabelSelector{MatchExpressions: matchExpressions},
@ -54,7 +54,7 @@ func TestLabelSelectorAsSelector(t *testing.T) {
}, },
{ {
in: &LabelSelector{MatchLabels: matchLabels, MatchExpressions: matchExpressions}, 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{ in: &LabelSelector{

View File

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

View File

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

View File

@ -34,6 +34,8 @@ func addConversionFuncs(scheme *runtime.Scheme) {
err := scheme.AddConversionFuncs( err := scheme.AddConversionFuncs(
Convert_api_PodSpec_To_v1_PodSpec, Convert_api_PodSpec_To_v1_PodSpec,
Convert_v1_PodSpec_To_api_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_extensions_DeploymentSpec_To_v1beta1_DeploymentSpec,
Convert_v1beta1_DeploymentSpec_To_extensions_DeploymentSpec, Convert_v1beta1_DeploymentSpec_To_extensions_DeploymentSpec,
Convert_extensions_DeploymentStrategy_To_v1beta1_DeploymentStrategy, 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) 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 { 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 { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*extensions.DeploymentSpec))(in) 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) defaulting.(func(*extensions.ScaleStatus))(in)
} }
out.Replicas = int32(in.Replicas) out.Replicas = int32(in.Replicas)
if in.Selector != nil { // in.Selector has no peer in out
out.Selector = make(map[string]string)
for key, val := range in.Selector {
out.Selector[key] = val
}
} else {
out.Selector = nil
}
return nil 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 { 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 { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*extensions.SubresourceReference))(in) defaulting.(func(*extensions.SubresourceReference))(in)
@ -5002,21 +4991,11 @@ func autoConvert_v1beta1_ScaleStatus_To_extensions_ScaleStatus(in *ScaleStatus,
defaulting.(func(*ScaleStatus))(in) defaulting.(func(*ScaleStatus))(in)
} }
out.Replicas = int(in.Replicas) out.Replicas = int(in.Replicas)
if in.Selector != nil { // in.Selector has no peer in out
out.Selector = make(map[string]string) // in.TargetSelector has no peer in out
for key, val := range in.Selector {
out.Selector[key] = val
}
} else {
out.Selector = nil
}
return nil 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 { 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 { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*SubresourceReference))(in) defaulting.(func(*SubresourceReference))(in)

View File

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

View File

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

View File

@ -593,9 +593,10 @@ func (ScaleSpec) SwaggerDoc() map[string]string {
} }
var map_ScaleStatus = map[string]string{ var map_ScaleStatus = map[string]string{
"": "represents the current status of a scale subresource.", "": "represents the current status of a scale subresource.",
"replicas": "actual number of observed instances of the scaled object.", "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", "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 { func (ScaleStatus) SwaggerDoc() map[string]string {

View File

@ -129,7 +129,20 @@ func (a *HorizontalController) computeReplicasForCPUUtilization(hpa *extensions.
targetUtilization = hpa.Spec.CPUUtilization.TargetPercentage targetUtilization = hpa.Spec.CPUUtilization.TargetPercentage
} }
currentReplicas := scale.Status.Replicas 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). // TODO: what to do on partial errors (like metrics obtained for 75% of pods).
if err != nil { if err != nil {
@ -177,7 +190,19 @@ func (a *HorizontalController) computeReplicasForCustomMetrics(hpa *extensions.H
} }
for _, customMetricTarget := range targetList.Items { 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). // TODO: what to do on partial errors (like metrics obtained for 75% of pods).
if err != nil { if err != nil {
a.eventRecorder.Event(hpa, api.EventTypeWarning, "FailedGetCustomMetrics", err.Error()) 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"
"k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/resource"
"k8s.io/kubernetes/pkg/api/unversioned"
_ "k8s.io/kubernetes/pkg/apimachinery/registered" _ "k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
@ -56,6 +57,12 @@ type fakeResponseWrapper struct {
raw []byte raw []byte
} }
type fakeResource struct {
name string
apiVersion string
kind string
}
type testCase struct { type testCase struct {
minReplicas int minReplicas int
maxReplicas int maxReplicas int
@ -74,6 +81,9 @@ type testCase struct {
verifyEvents bool verifyEvents bool
// Channel with names of HPA objects which we have reconciled. // Channel with names of HPA objects which we have reconciled.
processed chan string processed chan string
// Target resource information.
resource *fakeResource
} }
func (tc *testCase) computeCPUCurrent() { func (tc *testCase) computeCPUCurrent() {
@ -94,8 +104,10 @@ func (tc *testCase) computeCPUCurrent() {
func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset { func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset {
namespace := "test-namespace" namespace := "test-namespace"
hpaName := "test-hpa" hpaName := "test-hpa"
rcName := "test-rc"
podNamePrefix := "test-pod" podNamePrefix := "test-pod"
selector := &unversioned.LabelSelector{
MatchLabels: map[string]string{"name": podNamePrefix},
}
tc.scaleUpdated = false tc.scaleUpdated = false
tc.statusUpdated = false tc.statusUpdated = false
@ -103,6 +115,16 @@ func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset {
tc.processed = make(chan string, 100) tc.processed = make(chan string, 100)
tc.computeCPUCurrent() 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 := &fake.Clientset{}
fakeClient.AddReactor("list", "horizontalpodautoscalers", func(action core.Action) (handled bool, ret runtime.Object, err error) { fakeClient.AddReactor("list", "horizontalpodautoscalers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
obj := &extensions.HorizontalPodAutoscalerList{ obj := &extensions.HorizontalPodAutoscalerList{
@ -115,8 +137,9 @@ func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset {
}, },
Spec: extensions.HorizontalPodAutoscalerSpec{ Spec: extensions.HorizontalPodAutoscalerSpec{
ScaleRef: extensions.SubresourceReference{ ScaleRef: extensions.SubresourceReference{
Kind: "replicationController", Kind: tc.resource.kind,
Name: rcName, Name: tc.resource.name,
APIVersion: tc.resource.apiVersion,
Subresource: "scale", Subresource: "scale",
}, },
MinReplicas: &tc.minReplicas, MinReplicas: &tc.minReplicas,
@ -143,10 +166,10 @@ func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset {
return true, obj, nil 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{ obj := &extensions.Scale{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: rcName, Name: tc.resource.name,
Namespace: namespace, Namespace: namespace,
}, },
Spec: extensions.ScaleSpec{ Spec: extensions.ScaleSpec{
@ -154,7 +177,41 @@ func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset {
}, },
Status: extensions.ScaleStatus{ Status: extensions.ScaleStatus{
Replicas: tc.initialReplicas, 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 return true, obj, nil
@ -206,7 +263,23 @@ func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset {
return true, newFakeResponseWrapper(heapsterRawMemResponse), nil 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) obj := action.(testclient.UpdateAction).GetObject().(*extensions.Scale)
replicas := action.(testclient.UpdateAction).GetObject().(*extensions.Scale).Spec.Replicas replicas := action.(testclient.UpdateAction).GetObject().(*extensions.Scale).Spec.Replicas
assert.Equal(t, tc.desiredReplicas, replicas) assert.Equal(t, tc.desiredReplicas, replicas)
@ -269,7 +342,7 @@ func (tc *testCase) runTest(t *testing.T) {
tc.verifyResults(t) tc.verifyResults(t)
} }
func TestDefaultScaleUp(t *testing.T) { func TestDefaultScaleUpRC(t *testing.T) {
tc := testCase{ tc := testCase{
minReplicas: 2, minReplicas: 2,
maxReplicas: 6, maxReplicas: 6,
@ -282,6 +355,42 @@ func TestDefaultScaleUp(t *testing.T) {
tc.runTest(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) { func TestScaleUp(t *testing.T) {
tc := testCase{ tc := testCase{
minReplicas: 2, minReplicas: 2,
@ -296,6 +405,44 @@ func TestScaleUp(t *testing.T) {
tc.runTest(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) { func TestScaleUpCM(t *testing.T) {
tc := testCase{ tc := testCase{
minReplicas: 2, 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 // 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) // (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. // 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 // GetCustomMetric returns the average value of the given custom metrics from the
// pods picked using the namespace and selector passed as arguments. // 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 { 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) avgConsumption, avgRequest, timestamp, err := h.GetCpuConsumptionAndRequestInMillis(namespace, selector)
if err != nil { if err != nil {
return nil, time.Time{}, fmt.Errorf("failed to get CPU consumption and request: %v", err) 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 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) { avgRequest int64, timestamp time.Time, err error) {
labelSelector := labels.SelectorFromSet(labels.Set(selector))
podList, err := h.client.Core().Pods(namespace). podList, err := h.client.Core().Pods(namespace).
List(api.ListOptions{LabelSelector: labelSelector}) List(api.ListOptions{LabelSelector: selector})
if err != nil { if err != nil {
return 0, 0, time.Time{}, fmt.Errorf("failed to get pod list: %v", err) 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 { if missing || requestSum == 0 {
return 0, 0, time.Time{}, fmt.Errorf("some pods do not have request for cpu") 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)) requestAvg := requestSum / int64(len(podList.Items))
// Consumption is already averaged and in millis. // Consumption is already averaged and in millis.
consumption, timestamp, err := h.getForPods(heapsterCpuUsageMetricDefinition, namespace, podNames) 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 // GetCustomMetric returns the average value of the given custom metric from the
// pods picked using the namespace and selector passed as arguments. // 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) metricSpec := getHeapsterCustomMetricDefinition(customMetricName)
labelSelector := labels.SelectorFromSet(labels.Set(selector)) podList, err := h.client.Core().Pods(namespace).List(api.ListOptions{LabelSelector: selector})
podList, err := h.client.Core().Pods(namespace).List(api.ListOptions{LabelSelector: labelSelector})
if err != nil { if err != nil {
return nil, time.Time{}, fmt.Errorf("failed to get pod list: %v", err) 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/clientset_generated/internalclientset/fake"
"k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/testing/core" "k8s.io/kubernetes/pkg/client/testing/core"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
heapster "k8s.io/heapster/api/v1/types" heapster "k8s.io/heapster/api/v1/types"
@ -69,15 +70,15 @@ type testCase struct {
reportedMetricsPoints [][]metricPoint reportedMetricsPoints [][]metricPoint
namespace string namespace string
podListOverride *api.PodList podListOverride *api.PodList
selector map[string]string selector labels.Selector
} }
func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset { func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset {
namespace := "test-namespace" namespace := "test-namespace"
tc.namespace = namespace tc.namespace = namespace
podNamePrefix := "test-pod" podNamePrefix := "test-pod"
selector := map[string]string{"name": podNamePrefix} podLabels := map[string]string{"name": podNamePrefix}
tc.selector = selector tc.selector = labels.SelectorFromSet(podLabels)
fakeClient := &fake.Clientset{} fakeClient := &fake.Clientset{}
@ -88,7 +89,7 @@ func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset {
obj := &api.PodList{} obj := &api.PodList{}
for i := 0; i < tc.replicas; i++ { for i := 0; i < tc.replicas; i++ {
podName := fmt.Sprintf("%s-%d", podNamePrefix, 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) obj.Items = append(obj.Items, pod)
} }
return true, obj, nil return true, obj, nil
@ -120,12 +121,12 @@ func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset {
return fakeClient 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{ return api.Pod{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: podName, Name: podName,
Namespace: namespace, Namespace: namespace,
Labels: selector, Labels: podLabels,
}, },
Spec: api.PodSpec{ Spec: api.PodSpec{
Containers: []api.Container{ Containers: []api.Container{
@ -193,10 +194,10 @@ func TestCPUPending(t *testing.T) {
namespace := "test-namespace" namespace := "test-namespace"
podNamePrefix := "test-pod" podNamePrefix := "test-pod"
selector := map[string]string{"name": podNamePrefix} podLabels := map[string]string{"name": podNamePrefix}
for i := 0; i < tc.replicas; i++ { for i := 0; i < tc.replicas; i++ {
podName := fmt.Sprintf("%s-%d", podNamePrefix, 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 = append(tc.podListOverride.Items, pod)
} }
tc.podListOverride.Items[0].Status.Phase = api.PodPending tc.podListOverride.Items[0].Status.Phase = api.PodPending
@ -216,10 +217,10 @@ func TestCPUAllPending(t *testing.T) {
namespace := "test-namespace" namespace := "test-namespace"
podNamePrefix := "test-pod" podNamePrefix := "test-pod"
selector := map[string]string{"name": podNamePrefix} podLabels := map[string]string{"name": podNamePrefix}
for i := 0; i < tc.replicas; i++ { for i := 0; i < tc.replicas; i++ {
podName := fmt.Sprintf("%s-%d", podNamePrefix, 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 = append(tc.podListOverride.Items, pod)
} }
tc.runTest(t) tc.runTest(t)
@ -248,10 +249,10 @@ func TestQPSPending(t *testing.T) {
namespace := "test-namespace" namespace := "test-namespace"
podNamePrefix := "test-pod" podNamePrefix := "test-pod"
selector := map[string]string{"name": podNamePrefix} podLabels := map[string]string{"name": podNamePrefix}
for i := 0; i < tc.replicas; i++ { for i := 0; i < tc.replicas; i++ {
podName := fmt.Sprintf("%s-%d", podNamePrefix, 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 = append(tc.podListOverride.Items, pod)
} }
tc.podListOverride.Items[0].Status.Phase = api.PodPending tc.podListOverride.Items[0].Status.Phase = api.PodPending
@ -270,10 +271,10 @@ func TestQPSAllPending(t *testing.T) {
namespace := "test-namespace" namespace := "test-namespace"
podNamePrefix := "test-pod" podNamePrefix := "test-pod"
selector := map[string]string{"name": podNamePrefix} podLabels := map[string]string{"name": podNamePrefix}
for i := 0; i < tc.replicas; i++ { for i := 0; i < tc.replicas; i++ {
podName := fmt.Sprintf("%s-%d", podNamePrefix, 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 = append(tc.podListOverride.Items, pod)
} }
tc.podListOverride.Items[0].Status.Phase = api.PodPending 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. // (5) A requirement with just !KEY requires that the KEY not exist.
// //
func Parse(selector string) (Selector, error) { func Parse(selector string) (Selector, error) {
p := &Parser{l: &Lexer{s: selector, pos: 0}} parsedSelector, err := parse(selector)
items, error := p.parse() if err == nil {
if error == nil { return parsedSelector, nil
sort.Sort(ByKey(items)) // sort to grant determistic parsing
return internalSelector(items), error
} }
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) 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)) sort.Sort(ByKey(requirements))
return internalSelector(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, ParameterCodec: api.ParameterCodec,
NegotiatedSerializer: api.Codecs, 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) apiGroupsInfo = append(apiGroupsInfo, apiGroupInfo)
extensionsGVForDiscovery := unversioned.GroupVersionForDiscovery{ extensionsGVForDiscovery := unversioned.GroupVersionForDiscovery{
@ -723,10 +717,7 @@ func (m *Master) getExtensionResources(c *Config) map[string]rest.Storage {
storage["deployments"] = deploymentStorage.Deployment storage["deployments"] = deploymentStorage.Deployment
storage["deployments/status"] = deploymentStorage.Status storage["deployments/status"] = deploymentStorage.Status
storage["deployments/rollback"] = deploymentStorage.Rollback storage["deployments/rollback"] = deploymentStorage.Rollback
storage["deployments/scale"] = deploymentStorage.Scale
if registered.IsEnabledVersion(unversioned.GroupVersion{Group: "autoscaling", Version: "v1"}) {
storage["deployments/scale"] = deploymentStorage.Scale
}
} }
if isEnabled("jobs") { if isEnabled("jobs") {
m.constructJobResources(c, storage) m.constructJobResources(c, storage)
@ -744,9 +735,7 @@ func (m *Master) getExtensionResources(c *Config) map[string]rest.Storage {
replicaSetStorage := replicasetetcd.NewStorage(restOptions("replicasets")) replicaSetStorage := replicasetetcd.NewStorage(restOptions("replicasets"))
storage["replicasets"] = replicaSetStorage.ReplicaSet storage["replicasets"] = replicaSetStorage.ReplicaSet
storage["replicasets/status"] = replicaSetStorage.Status 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 return storage

View File

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

View File

@ -24,7 +24,6 @@ import (
"k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/errors"
etcderrors "k8s.io/kubernetes/pkg/api/errors/etcd" etcderrors "k8s.io/kubernetes/pkg/api/errors/etcd"
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/autoscaling"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels" "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) t.Fatalf("error setting new deployment (key: %s) %v: %v", key, validDeployment, err)
} }
selector, err := unversioned.LabelSelectorAsSelector(validDeployment.Spec.Selector) want := &extensions.Scale{
if err != nil {
t.Errorf("invalid deployment selector %+v: %v", validDeployment.Spec.Selector, err)
}
want := &autoscaling.Scale{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: name, Name: name,
Namespace: namespace, Namespace: namespace,
@ -207,19 +202,19 @@ func TestScaleGet(t *testing.T) {
ResourceVersion: deployment.ResourceVersion, ResourceVersion: deployment.ResourceVersion,
CreationTimestamp: deployment.CreationTimestamp, CreationTimestamp: deployment.CreationTimestamp,
}, },
Spec: autoscaling.ScaleSpec{ Spec: extensions.ScaleSpec{
Replicas: validDeployment.Spec.Replicas, Replicas: validDeployment.Spec.Replicas,
}, },
Status: autoscaling.ScaleStatus{ Status: extensions.ScaleStatus{
Replicas: validDeployment.Status.Replicas, Replicas: validDeployment.Status.Replicas,
Selector: selector.String(), Selector: validDeployment.Spec.Selector,
}, },
} }
obj, err := storage.Scale.Get(ctx, name) obj, err := storage.Scale.Get(ctx, name)
if err != nil { if err != nil {
t.Fatalf("error fetching scale for %s: %v", name, err) t.Fatalf("error fetching scale for %s: %v", name, err)
} }
got := obj.(*autoscaling.Scale) got := obj.(*extensions.Scale)
if !api.Semantic.DeepEqual(want, got) { if !api.Semantic.DeepEqual(want, got) {
t.Errorf("unexpected scale: %s", util.ObjectDiff(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) t.Fatalf("error setting new deployment (key: %s) %v: %v", key, validDeployment, err)
} }
replicas := 12 replicas := 12
update := autoscaling.Scale{ update := extensions.Scale{
ObjectMeta: api.ObjectMeta{Name: name, Namespace: namespace}, ObjectMeta: api.ObjectMeta{Name: name, Namespace: namespace},
Spec: autoscaling.ScaleSpec{ Spec: extensions.ScaleSpec{
Replicas: replicas, Replicas: replicas,
}, },
} }
@ -250,7 +245,7 @@ func TestScaleUpdate(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("error fetching scale for %s: %v", name, err) t.Fatalf("error fetching scale for %s: %v", name, err)
} }
scale := obj.(*autoscaling.Scale) scale := obj.(*extensions.Scale)
if scale.Spec.Replicas != replicas { if scale.Spec.Replicas != replicas {
t.Errorf("wrong replicas count expected: %d got: %d", replicas, deployment.Spec.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"
"k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/rest" "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"
"k8s.io/kubernetes/pkg/registry/controller/etcd" "k8s.io/kubernetes/pkg/registry/controller/etcd"
"k8s.io/kubernetes/pkg/registry/generic" "k8s.io/kubernetes/pkg/registry/generic"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/apis/extensions" "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 { if err != nil {
return nil, errors.NewNotFound(extensions.Resource("replicationcontrollers/scale"), name) return nil, errors.NewNotFound(extensions.Resource("replicationcontrollers/scale"), name)
} }
return &extensions.Scale{ return scaleFromRC(rc), nil
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
} }
func (r *ScaleREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { 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 { if err != nil {
return nil, false, errors.NewConflict(extensions.Resource("replicationcontrollers/scale"), scale.Name, err) 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{ return &extensions.Scale{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: rc.Name, Name: rc.Name,
Namespace: rc.Namespace, Namespace: rc.Namespace,
UID: rc.UID,
ResourceVersion: rc.ResourceVersion,
CreationTimestamp: rc.CreationTimestamp, CreationTimestamp: rc.CreationTimestamp,
}, },
Spec: extensions.ScaleSpec{ Spec: extensions.ScaleSpec{
@ -116,9 +110,11 @@ func (r *ScaleREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object,
}, },
Status: extensions.ScaleStatus{ Status: extensions.ScaleStatus{
Replicas: rc.Status.Replicas, Replicas: rc.Status.Replicas,
Selector: rc.Spec.Selector, Selector: &unversioned.LabelSelector{
MatchLabels: rc.Spec.Selector,
},
}, },
}, false, nil }
} }
// Dummy implementation // Dummy implementation

View File

@ -20,6 +20,7 @@ import (
"testing" "testing"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/registry/generic" "k8s.io/kubernetes/pkg/registry/generic"
"k8s.io/kubernetes/pkg/registry/registrytest" "k8s.io/kubernetes/pkg/registry/registrytest"
@ -73,7 +74,9 @@ var validScale = extensions.Scale{
}, },
Status: extensions.ScaleStatus{ Status: extensions.ScaleStatus{
Replicas: 0, 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"
"k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/rest" "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" "k8s.io/kubernetes/pkg/apis/extensions"
extvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation"
"k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/registry/cachesize" "k8s.io/kubernetes/pkg/registry/cachesize"
@ -130,13 +128,13 @@ var _ = rest.Patcher(&ScaleREST{})
// New creates a new Scale object // New creates a new Scale object
func (r *ScaleREST) New() runtime.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) { func (r *ScaleREST) Get(ctx api.Context, name string) (runtime.Object, error) {
rs, err := r.registry.GetReplicaSet(ctx, name) rs, err := r.registry.GetReplicaSet(ctx, name)
if err != nil { 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) scale, err := scaleFromReplicaSet(rs)
if err != nil { if err != nil {
@ -149,18 +147,18 @@ func (r *ScaleREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object,
if obj == nil { if obj == nil {
return nil, false, errors.NewBadRequest(fmt.Sprintf("nil update passed to Scale")) return nil, false, errors.NewBadRequest(fmt.Sprintf("nil update passed to Scale"))
} }
scale, ok := obj.(*autoscaling.Scale) scale, ok := obj.(*extensions.Scale)
if !ok { if !ok {
return nil, false, errors.NewBadRequest(fmt.Sprintf("wrong object passed to Scale update: %v", obj)) return nil, false, errors.NewBadRequest(fmt.Sprintf("wrong object passed to Scale update: %v", obj))
} }
if errs := asvalidation.ValidateScale(scale); len(errs) > 0 { if errs := extvalidation.ValidateScale(scale); len(errs) > 0 {
return nil, false, errors.NewInvalid(autoscaling.Kind("Scale"), scale.Name, errs) return nil, false, errors.NewInvalid(extensions.Kind("Scale"), scale.Name, errs)
} }
rs, err := r.registry.GetReplicaSet(ctx, scale.Name) rs, err := r.registry.GetReplicaSet(ctx, scale.Name)
if err != nil { 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.Spec.Replicas = scale.Spec.Replicas
rs.ResourceVersion = scale.ResourceVersion 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. // scaleFromReplicaSet returns a scale subresource for a replica set.
func scaleFromReplicaSet(rs *extensions.ReplicaSet) (*autoscaling.Scale, error) { func scaleFromReplicaSet(rs *extensions.ReplicaSet) (*extensions.Scale, error) {
selector, err := unversioned.LabelSelectorAsSelector(rs.Spec.Selector) return &extensions.Scale{
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{
// TODO: Create a variant of ObjectMeta type that only contains the fields below. // TODO: Create a variant of ObjectMeta type that only contains the fields below.
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: rs.Name, Name: rs.Name,
@ -190,12 +184,12 @@ func scaleFromReplicaSet(rs *extensions.ReplicaSet) (*autoscaling.Scale, error)
ResourceVersion: rs.ResourceVersion, ResourceVersion: rs.ResourceVersion,
CreationTimestamp: rs.CreationTimestamp, CreationTimestamp: rs.CreationTimestamp,
}, },
Spec: autoscaling.ScaleSpec{ Spec: extensions.ScaleSpec{
Replicas: rs.Spec.Replicas, Replicas: rs.Spec.Replicas,
}, },
Status: autoscaling.ScaleStatus{ Status: extensions.ScaleStatus{
Replicas: rs.Status.Replicas, Replicas: rs.Status.Replicas,
Selector: selector.String(), Selector: rs.Spec.Selector,
}, },
}, nil }, nil
} }

View File

@ -22,7 +22,6 @@ import (
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/autoscaling"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels" "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) t.Fatalf("error setting new replica set (key: %s) %v: %v", key, validReplicaSet, err)
} }
selector, err := unversioned.LabelSelectorAsSelector(validReplicaSet.Spec.Selector) want := &extensions.Scale{
if err != nil {
t.Errorf("invalid replicaset selector %+v: %v", validReplicaSet.Spec.Selector, err)
}
want := &autoscaling.Scale{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: name, Name: name,
Namespace: api.NamespaceDefault, Namespace: api.NamespaceDefault,
@ -268,16 +263,16 @@ func TestScaleGet(t *testing.T) {
ResourceVersion: rs.ResourceVersion, ResourceVersion: rs.ResourceVersion,
CreationTimestamp: rs.CreationTimestamp, CreationTimestamp: rs.CreationTimestamp,
}, },
Spec: autoscaling.ScaleSpec{ Spec: extensions.ScaleSpec{
Replicas: validReplicaSet.Spec.Replicas, Replicas: validReplicaSet.Spec.Replicas,
}, },
Status: autoscaling.ScaleStatus{ Status: extensions.ScaleStatus{
Replicas: validReplicaSet.Status.Replicas, Replicas: validReplicaSet.Status.Replicas,
Selector: selector.String(), Selector: validReplicaSet.Spec.Selector,
}, },
} }
obj, err := storage.Scale.Get(ctx, name) obj, err := storage.Scale.Get(ctx, name)
got := obj.(*autoscaling.Scale) got := obj.(*extensions.Scale)
if err != nil { if err != nil {
t.Fatalf("error fetching scale for %s: %v", name, err) 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) t.Fatalf("error setting new replica set (key: %s) %v: %v", key, validReplicaSet, err)
} }
replicas := 12 replicas := 12
update := autoscaling.Scale{ update := extensions.Scale{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: name, Name: name,
Namespace: api.NamespaceDefault, Namespace: api.NamespaceDefault,
}, },
Spec: autoscaling.ScaleSpec{ Spec: extensions.ScaleSpec{
Replicas: replicas, Replicas: replicas,
}, },
} }
@ -317,7 +312,7 @@ func TestScaleUpdate(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("error fetching scale for %s: %v", name, err) t.Fatalf("error fetching scale for %s: %v", name, err)
} }
scale := obj.(*autoscaling.Scale) scale := obj.(*extensions.Scale)
if scale.Spec.Replicas != replicas { if scale.Spec.Replicas != replicas {
t.Errorf("wrong replicas count expected: %d got: %d", replicas, scale.Spec.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" resourceConsumerImage = "gcr.io/google_containers/resource_consumer:beta2"
rcIsNil = "ERROR: replicationController = nil" rcIsNil = "ERROR: replicationController = nil"
deploymentIsNil = "ERROR: deployment = nil" deploymentIsNil = "ERROR: deployment = nil"
rsIsNil = "ERROR: replicaset = nil"
invalidKind = "ERROR: invalid workload kind for resource consumer" invalidKind = "ERROR: invalid workload kind for resource consumer"
customMetricName = "QPS" customMetricName = "QPS"
) )
@ -298,6 +299,13 @@ func (rc *ResourceConsumer) GetReplicas() int {
Failf(deploymentIsNil) Failf(deploymentIsNil)
} }
return deployment.Status.Replicas 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: default:
Failf(invalidKind) Failf(invalidKind)
} }
@ -381,6 +389,12 @@ func runServiceAndWorkloadForResourceConsumer(c *client.Client, ns, name, kind s
} }
expectNoError(RunDeployment(dpConfig)) expectNoError(RunDeployment(dpConfig))
break break
case kindReplicaSet:
rsConfig := ReplicaSetConfig{
rcConfig,
}
expectNoError(RunReplicaSet(rsConfig))
break
default: default:
Failf(invalidKind) Failf(invalidKind)
} }

View File

@ -28,6 +28,7 @@ import (
const ( const (
kindRC = "replicationController" kindRC = "replicationController"
kindDeployment = "deployment" kindDeployment = "deployment"
kindReplicaSet = "replicaset"
subresource = "scale" 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" 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" 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. // These tests take ~20 minutes each.
// Describe("[Serial] [Slow] Deployment", func() { Describe("[Serial] [Slow] Deployment", func() {
// // CPU tests via deployments // CPU tests via deployments
// It(titleUp, func() { It(titleUp, func() {
// scaleUp("deployment", kindDeployment, rc, f) scaleUp("test-deployment", kindDeployment, rc, f)
// }) })
// It(titleDown, func() { It(titleDown, func() {
// scaleDown("deployment", kindDeployment, rc, f) 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. // These tests take ~20 minutes each.
Describe("[Serial] [Slow] ReplicationController", func() { Describe("[Serial] [Slow] ReplicationController", func() {

View File

@ -294,6 +294,10 @@ type DeploymentConfig struct {
RCConfig RCConfig
} }
type ReplicaSetConfig struct {
RCConfig
}
func nowStamp() string { func nowStamp() string {
return time.Now().Format(time.StampMilli) return time.Now().Format(time.StampMilli)
} }
@ -1788,6 +1792,59 @@ func (config *DeploymentConfig) create() error {
return nil 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 // RunRC Launches (and verifies correctness) of a Replication Controller
// and will wait for all pods it spawns to become "Running". // 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 // It's the caller's responsibility to clean up externally (i.e. use the