Commit a2d1bb7a authored by Rudi Chiarito's avatar Rudi Chiarito

Add httpHeaders to httpGet liveness probe

Also update existing documentation and try to steer users away from 'host'. Add validation.
parent faa0fc3d
......@@ -15902,11 +15902,36 @@
},
"host": {
"type": "string",
"description": "Host name to connect to, defaults to the pod IP."
"description": "Host name to connect to, defaults to the pod IP. You probably want to set \"Host\" in httpHeaders instead."
},
"scheme": {
"type": "string",
"description": "Scheme to use for connecting to the host. Defaults to HTTP."
},
"httpHeaders": {
"type": "array",
"items": {
"$ref": "v1.HTTPHeader"
},
"description": "Custom headers to set in the request. HTTP allows repeated headers."
}
}
},
"v1.HTTPHeader": {
"id": "v1.HTTPHeader",
"description": "HTTPHeader describes a custom header to be used in HTTP probes",
"required": [
"name",
"value"
],
"properties": {
"name": {
"type": "string",
"description": "The header field name"
},
"value": {
"type": "string",
"description": "The header field value"
}
}
},
......
......@@ -4781,11 +4781,36 @@
},
"host": {
"type": "string",
"description": "Host name to connect to, defaults to the pod IP."
"description": "Host name to connect to, defaults to the pod IP. You probably want to set \"Host\" in httpHeaders instead."
},
"scheme": {
"type": "string",
"description": "Scheme to use for connecting to the host. Defaults to HTTP."
},
"httpHeaders": {
"type": "array",
"items": {
"$ref": "v1.HTTPHeader"
},
"description": "Custom headers to set in the request. HTTP allows repeated headers."
}
}
},
"v1.HTTPHeader": {
"id": "v1.HTTPHeader",
"description": "HTTPHeader describes a custom header to be used in HTTP probes",
"required": [
"name",
"value"
],
"properties": {
"name": {
"type": "string",
"description": "The header field name"
},
"value": {
"type": "string",
"description": "The header field value"
}
}
},
......
......@@ -895,6 +895,47 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
</div>
<div class="sect2">
<h3 id="_v1_httpheader">v1.HTTPHeader</h3>
<div class="paragraph">
<p>HTTPHeader describes a custom header to be used in HTTP probes</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">name</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">The header field name</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">value</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">The header field value</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1beta1_horizontalpodautoscalerstatus">v1beta1.HorizontalPodAutoscalerStatus</h3>
<div class="paragraph">
<p>current status of a horizontal pod autoscaler</p>
......@@ -1562,7 +1603,7 @@ Both these may change in the future. Incoming requests are matched against the h
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">host</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Host name to connect to, defaults to the pod IP.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead.</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>
......@@ -1574,6 +1615,13 @@ Both these may change in the future. Incoming requests are matched against the h
<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">httpHeaders</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Custom headers to set in the request. HTTP allows repeated headers.</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_httpheader">v1.HTTPHeader</a> array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
......@@ -4838,7 +4886,7 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
</div>
<div id="footer">
<div id="footer-text">
Last updated 2016-02-05 15:52:54 UTC
Last updated 2016-02-05 16:19:07 UTC
</div>
</div>
</body>
......
......@@ -66,11 +66,14 @@ The [http-liveness.yaml](http-liveness.yaml) demonstrates the HTTP check.
httpGet:
path: /healthz
port: 8080
httpHeaders:
- name: X-Custom-Header
value: Awesome
initialDelaySeconds: 15
timeoutSeconds: 1
```
The Kubelet sends an HTTP request to the specified path and port to perform the health check. If you take a look at image/server.go, you will see the server starts to respond with an error code 500 after 10 seconds, so the check fails. The Kubelet sends the probe to the container's ip address by default which could be specified with `host` as part of httpGet probe. If the container listens on `127.0.0.1`, `host` should be specified as `127.0.0.1`. In general, if the container listens on its ip address or on all interfaces (0.0.0.0), there is no need to specify the `host` as part of the httpGet probe.
The Kubelet sends an HTTP request to the specified path and port to perform the health check. If you take a look at image/server.go, you will see the server starts to respond with an error code 500 after 10 seconds, so the check fails. The Kubelet sends probes to the container's IP address, unless overridden by the optional `host` field in httpGet. If the container listens on `127.0.0.1` and `hostNetwork` is `true` (i.e., it does not use the pod-specific network), then `host` should be specified as `127.0.0.1`. Be warned that, outside of less common cases like that, `host` does probably not result in what you would expect. If you set it to a non-existing hostname (or your competitor's!), probes will never reach the pod, defeating the whole point of health checks. If your pod relies on e.g. virtual hosts, which is probably the more common case, you should not use `host`, but rather set the `Host` header in `httpHeaders`.
This [guide](../walkthrough/k8s201.md#health-checking) has more information on health checks.
......
......@@ -13,6 +13,9 @@ spec:
httpGet:
path: /healthz
port: 8080
httpHeaders:
- name: X-Custom-Header
value: Awesome
initialDelaySeconds: 15
timeoutSeconds: 1
name: liveness
......@@ -76,6 +76,7 @@ func init() {
DeepCopy_api_GitRepoVolumeSource,
DeepCopy_api_GlusterfsVolumeSource,
DeepCopy_api_HTTPGetAction,
DeepCopy_api_HTTPHeader,
DeepCopy_api_Handler,
DeepCopy_api_HostPathVolumeSource,
DeepCopy_api_ISCSIVolumeSource,
......@@ -930,6 +931,23 @@ func DeepCopy_api_HTTPGetAction(in HTTPGetAction, out *HTTPGetAction, c *convers
}
out.Host = in.Host
out.Scheme = in.Scheme
if in.HTTPHeaders != nil {
in, out := in.HTTPHeaders, &out.HTTPHeaders
*out = make([]HTTPHeader, len(in))
for i := range in {
if err := DeepCopy_api_HTTPHeader(in[i], &(*out)[i], c); err != nil {
return err
}
}
} else {
out.HTTPHeaders = nil
}
return nil
}
func DeepCopy_api_HTTPHeader(in HTTPHeader, out *HTTPHeader, c *conversion.Cloner) error {
out.Name = in.Name
out.Value = in.Value
return nil
}
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -752,16 +752,27 @@ type SecretKeySelector struct {
Key string `json:"key"`
}
// HTTPHeader describes a custom header to be used in HTTP probes
type HTTPHeader struct {
// The header field name
Name string `json:"name"`
// The header field value
Value string `json:"value"`
}
// HTTPGetAction describes an action based on HTTP Get requests.
type HTTPGetAction struct {
// Optional: Path to access on the HTTP server.
Path string `json:"path,omitempty"`
// Required: Name or number of the port to access on the container.
Port intstr.IntOrString `json:"port,omitempty"`
// Optional: Host name to connect to, defaults to the pod IP.
// Optional: Host name to connect to, defaults to the pod IP. You
// probably want to set "Host" in httpHeaders instead.
Host string `json:"host,omitempty"`
// Optional: Scheme to use for connecting to the host, defaults to HTTP.
Scheme URIScheme `json:"scheme,omitempty"`
// Optional: Custom headers to set in the request. HTTP allows repeated headers.
HTTPHeaders []HTTPHeader `json:"httpHeaders,omitempty"`
}
// URIScheme identifies the scheme used for connection to a host for Get actions
......
......@@ -977,6 +977,16 @@ func autoConvert_api_HTTPGetAction_To_v1_HTTPGetAction(in *api.HTTPGetAction, ou
}
out.Host = in.Host
out.Scheme = URIScheme(in.Scheme)
if in.HTTPHeaders != nil {
out.HTTPHeaders = make([]HTTPHeader, len(in.HTTPHeaders))
for i := range in.HTTPHeaders {
if err := Convert_api_HTTPHeader_To_v1_HTTPHeader(&in.HTTPHeaders[i], &out.HTTPHeaders[i], s); err != nil {
return err
}
}
} else {
out.HTTPHeaders = nil
}
return nil
}
......@@ -984,6 +994,19 @@ func Convert_api_HTTPGetAction_To_v1_HTTPGetAction(in *api.HTTPGetAction, out *H
return autoConvert_api_HTTPGetAction_To_v1_HTTPGetAction(in, out, s)
}
func autoConvert_api_HTTPHeader_To_v1_HTTPHeader(in *api.HTTPHeader, out *HTTPHeader, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*api.HTTPHeader))(in)
}
out.Name = in.Name
out.Value = in.Value
return nil
}
func Convert_api_HTTPHeader_To_v1_HTTPHeader(in *api.HTTPHeader, out *HTTPHeader, s conversion.Scope) error {
return autoConvert_api_HTTPHeader_To_v1_HTTPHeader(in, out, s)
}
func autoConvert_api_Handler_To_v1_Handler(in *api.Handler, out *Handler, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*api.Handler))(in)
......@@ -4155,6 +4178,16 @@ func autoConvert_v1_HTTPGetAction_To_api_HTTPGetAction(in *HTTPGetAction, out *a
}
out.Host = in.Host
out.Scheme = api.URIScheme(in.Scheme)
if in.HTTPHeaders != nil {
out.HTTPHeaders = make([]api.HTTPHeader, len(in.HTTPHeaders))
for i := range in.HTTPHeaders {
if err := Convert_v1_HTTPHeader_To_api_HTTPHeader(&in.HTTPHeaders[i], &out.HTTPHeaders[i], s); err != nil {
return err
}
}
} else {
out.HTTPHeaders = nil
}
return nil
}
......@@ -4162,6 +4195,19 @@ func Convert_v1_HTTPGetAction_To_api_HTTPGetAction(in *HTTPGetAction, out *api.H
return autoConvert_v1_HTTPGetAction_To_api_HTTPGetAction(in, out, s)
}
func autoConvert_v1_HTTPHeader_To_api_HTTPHeader(in *HTTPHeader, out *api.HTTPHeader, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*HTTPHeader))(in)
}
out.Name = in.Name
out.Value = in.Value
return nil
}
func Convert_v1_HTTPHeader_To_api_HTTPHeader(in *HTTPHeader, out *api.HTTPHeader, s conversion.Scope) error {
return autoConvert_v1_HTTPHeader_To_api_HTTPHeader(in, out, s)
}
func autoConvert_v1_Handler_To_api_Handler(in *Handler, out *api.Handler, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*Handler))(in)
......@@ -6279,6 +6325,7 @@ func init() {
autoConvert_api_GitRepoVolumeSource_To_v1_GitRepoVolumeSource,
autoConvert_api_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource,
autoConvert_api_HTTPGetAction_To_v1_HTTPGetAction,
autoConvert_api_HTTPHeader_To_v1_HTTPHeader,
autoConvert_api_Handler_To_v1_Handler,
autoConvert_api_HostPathVolumeSource_To_v1_HostPathVolumeSource,
autoConvert_api_ISCSIVolumeSource_To_v1_ISCSIVolumeSource,
......@@ -6405,6 +6452,7 @@ func init() {
autoConvert_v1_GitRepoVolumeSource_To_api_GitRepoVolumeSource,
autoConvert_v1_GlusterfsVolumeSource_To_api_GlusterfsVolumeSource,
autoConvert_v1_HTTPGetAction_To_api_HTTPGetAction,
autoConvert_v1_HTTPHeader_To_api_HTTPHeader,
autoConvert_v1_Handler_To_api_Handler,
autoConvert_v1_HostPathVolumeSource_To_api_HostPathVolumeSource,
autoConvert_v1_ISCSIVolumeSource_To_api_ISCSIVolumeSource,
......
......@@ -749,6 +749,22 @@ func deepCopy_v1_HTTPGetAction(in HTTPGetAction, out *HTTPGetAction, c *conversi
}
out.Host = in.Host
out.Scheme = in.Scheme
if in.HTTPHeaders != nil {
out.HTTPHeaders = make([]HTTPHeader, len(in.HTTPHeaders))
for i := range in.HTTPHeaders {
if err := deepCopy_v1_HTTPHeader(in.HTTPHeaders[i], &out.HTTPHeaders[i], c); err != nil {
return err
}
}
} else {
out.HTTPHeaders = nil
}
return nil
}
func deepCopy_v1_HTTPHeader(in HTTPHeader, out *HTTPHeader, c *conversion.Cloner) error {
out.Name = in.Name
out.Value = in.Value
return nil
}
......@@ -2576,6 +2592,7 @@ func init() {
deepCopy_v1_GitRepoVolumeSource,
deepCopy_v1_GlusterfsVolumeSource,
deepCopy_v1_HTTPGetAction,
deepCopy_v1_HTTPHeader,
deepCopy_v1_Handler,
deepCopy_v1_HostPathVolumeSource,
deepCopy_v1_ISCSIVolumeSource,
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -877,6 +877,14 @@ type SecretKeySelector struct {
Key string `json:"key"`
}
// HTTPHeader describes a custom header to be used in HTTP probes
type HTTPHeader struct {
// The header field name
Name string `json:"name"`
// The header field value
Value string `json:"value"`
}
// HTTPGetAction describes an action based on HTTP Get requests.
type HTTPGetAction struct {
// Path to access on the HTTP server.
......@@ -885,11 +893,14 @@ type HTTPGetAction struct {
// Number must be in the range 1 to 65535.
// Name must be an IANA_SVC_NAME.
Port intstr.IntOrString `json:"port"`
// Host name to connect to, defaults to the pod IP.
// Host name to connect to, defaults to the pod IP. You probably want to set
// "Host" in httpHeaders instead.
Host string `json:"host,omitempty"`
// Scheme to use for connecting to the host.
// Defaults to HTTP.
Scheme URIScheme `json:"scheme,omitempty"`
// Custom headers to set in the request. HTTP allows repeated headers.
HTTPHeaders []HTTPHeader `json:"httpHeaders,omitempty"`
}
// URIScheme identifies the scheme used for connection to a host for Get actions
......
......@@ -509,17 +509,28 @@ func (GlusterfsVolumeSource) SwaggerDoc() map[string]string {
}
var map_HTTPGetAction = map[string]string{
"": "HTTPGetAction describes an action based on HTTP Get requests.",
"path": "Path to access on the HTTP server.",
"port": "Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME.",
"host": "Host name to connect to, defaults to the pod IP.",
"scheme": "Scheme to use for connecting to the host. Defaults to HTTP.",
"": "HTTPGetAction describes an action based on HTTP Get requests.",
"path": "Path to access on the HTTP server.",
"port": "Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME.",
"host": "Host name to connect to, defaults to the pod IP. You probably want to set \"Host\" in httpHeaders instead.",
"scheme": "Scheme to use for connecting to the host. Defaults to HTTP.",
"httpHeaders": "Custom headers to set in the request. HTTP allows repeated headers.",
}
func (HTTPGetAction) SwaggerDoc() map[string]string {
return map_HTTPGetAction
}
var map_HTTPHeader = map[string]string{
"": "HTTPHeader describes a custom header to be used in HTTP probes",
"name": "The header field name",
"value": "The header field value",
}
func (HTTPHeader) SwaggerDoc() map[string]string {
return map_HTTPHeader
}
var map_Handler = map[string]string{
"": "Handler defines a specific action that should be taken",
"exec": "One and only one of the following should be specified. Exec specifies the action to take.",
......
......@@ -1128,6 +1128,11 @@ func validateHTTPGetAction(http *api.HTTPGetAction, fldPath *field.Path) field.E
if !supportedSchemes.Has(string(http.Scheme)) {
allErrors = append(allErrors, field.Invalid(fldPath.Child("scheme"), http.Scheme, fmt.Sprintf("must be one of %v", supportedSchemes.List())))
}
for _, header := range http.HTTPHeaders {
if !validation.IsHTTPHeaderName(header.Name) {
allErrors = append(allErrors, field.Invalid(fldPath.Child("httpHeaders"), header.Name, fmt.Sprintf("name must match %s", validation.HTTPHeaderNameFmt)))
}
}
return allErrors
}
......
......@@ -1082,6 +1082,8 @@ func TestValidateHandler(t *testing.T) {
{HTTPGet: &api.HTTPGetAction{Path: "/", Port: intstr.FromInt(1), Host: "", Scheme: "HTTP"}},
{HTTPGet: &api.HTTPGetAction{Path: "/foo", Port: intstr.FromInt(65535), Host: "host", Scheme: "HTTP"}},
{HTTPGet: &api.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP"}},
{HTTPGet: &api.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []api.HTTPHeader{{"Host", "foo.example.com"}}}},
{HTTPGet: &api.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []api.HTTPHeader{{"X-Forwarded-For", "1.2.3.4"}, {"X-Forwarded-For", "5.6.7.8"}}}},
}
for _, h := range successCases {
if errs := validateHandler(&h, field.NewPath("field")); len(errs) != 0 {
......@@ -1095,6 +1097,8 @@ func TestValidateHandler(t *testing.T) {
{HTTPGet: &api.HTTPGetAction{Path: "", Port: intstr.FromInt(0), Host: ""}},
{HTTPGet: &api.HTTPGetAction{Path: "/foo", Port: intstr.FromInt(65536), Host: "host"}},
{HTTPGet: &api.HTTPGetAction{Path: "", Port: intstr.FromString(""), Host: ""}},
{HTTPGet: &api.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []api.HTTPHeader{{"Host:", "foo.example.com"}}}},
{HTTPGet: &api.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP", HTTPHeaders: []api.HTTPHeader{{"X_Forwarded_For", "foo.example.com"}}}},
}
for _, h := range errorCases {
if errs := validateHandler(&h, field.NewPath("field")); len(errs) == 0 {
......
......@@ -502,6 +502,16 @@ func autoConvert_api_HTTPGetAction_To_v1_HTTPGetAction(in *api.HTTPGetAction, ou
}
out.Host = in.Host
out.Scheme = v1.URIScheme(in.Scheme)
if in.HTTPHeaders != nil {
out.HTTPHeaders = make([]v1.HTTPHeader, len(in.HTTPHeaders))
for i := range in.HTTPHeaders {
if err := Convert_api_HTTPHeader_To_v1_HTTPHeader(&in.HTTPHeaders[i], &out.HTTPHeaders[i], s); err != nil {
return err
}
}
} else {
out.HTTPHeaders = nil
}
return nil
}
......@@ -509,6 +519,19 @@ func Convert_api_HTTPGetAction_To_v1_HTTPGetAction(in *api.HTTPGetAction, out *v
return autoConvert_api_HTTPGetAction_To_v1_HTTPGetAction(in, out, s)
}
func autoConvert_api_HTTPHeader_To_v1_HTTPHeader(in *api.HTTPHeader, out *v1.HTTPHeader, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*api.HTTPHeader))(in)
}
out.Name = in.Name
out.Value = in.Value
return nil
}
func Convert_api_HTTPHeader_To_v1_HTTPHeader(in *api.HTTPHeader, out *v1.HTTPHeader, s conversion.Scope) error {
return autoConvert_api_HTTPHeader_To_v1_HTTPHeader(in, out, s)
}
func autoConvert_api_Handler_To_v1_Handler(in *api.Handler, out *v1.Handler, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*api.Handler))(in)
......@@ -1764,6 +1787,16 @@ func autoConvert_v1_HTTPGetAction_To_api_HTTPGetAction(in *v1.HTTPGetAction, out
}
out.Host = in.Host
out.Scheme = api.URIScheme(in.Scheme)
if in.HTTPHeaders != nil {
out.HTTPHeaders = make([]api.HTTPHeader, len(in.HTTPHeaders))
for i := range in.HTTPHeaders {
if err := Convert_v1_HTTPHeader_To_api_HTTPHeader(&in.HTTPHeaders[i], &out.HTTPHeaders[i], s); err != nil {
return err
}
}
} else {
out.HTTPHeaders = nil
}
return nil
}
......@@ -1771,6 +1804,19 @@ func Convert_v1_HTTPGetAction_To_api_HTTPGetAction(in *v1.HTTPGetAction, out *ap
return autoConvert_v1_HTTPGetAction_To_api_HTTPGetAction(in, out, s)
}
func autoConvert_v1_HTTPHeader_To_api_HTTPHeader(in *v1.HTTPHeader, out *api.HTTPHeader, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*v1.HTTPHeader))(in)
}
out.Name = in.Name
out.Value = in.Value
return nil
}
func Convert_v1_HTTPHeader_To_api_HTTPHeader(in *v1.HTTPHeader, out *api.HTTPHeader, s conversion.Scope) error {
return autoConvert_v1_HTTPHeader_To_api_HTTPHeader(in, out, s)
}
func autoConvert_v1_Handler_To_api_Handler(in *v1.Handler, out *api.Handler, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*v1.Handler))(in)
......@@ -5132,6 +5178,7 @@ func init() {
autoConvert_api_GitRepoVolumeSource_To_v1_GitRepoVolumeSource,
autoConvert_api_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource,
autoConvert_api_HTTPGetAction_To_v1_HTTPGetAction,
autoConvert_api_HTTPHeader_To_v1_HTTPHeader,
autoConvert_api_Handler_To_v1_Handler,
autoConvert_api_HostPathVolumeSource_To_v1_HostPathVolumeSource,
autoConvert_api_ISCSIVolumeSource_To_v1_ISCSIVolumeSource,
......@@ -5237,6 +5284,7 @@ func init() {
autoConvert_v1_GitRepoVolumeSource_To_api_GitRepoVolumeSource,
autoConvert_v1_GlusterfsVolumeSource_To_api_GlusterfsVolumeSource,
autoConvert_v1_HTTPGetAction_To_api_HTTPGetAction,
autoConvert_v1_HTTPHeader_To_api_HTTPHeader,
autoConvert_v1_Handler_To_api_Handler,
autoConvert_v1_HostPathVolumeSource_To_api_HostPathVolumeSource,
autoConvert_v1_ISCSIVolumeSource_To_api_ISCSIVolumeSource,
......
......@@ -392,6 +392,22 @@ func deepCopy_v1_HTTPGetAction(in v1.HTTPGetAction, out *v1.HTTPGetAction, c *co
}
out.Host = in.Host
out.Scheme = in.Scheme
if in.HTTPHeaders != nil {
out.HTTPHeaders = make([]v1.HTTPHeader, len(in.HTTPHeaders))
for i := range in.HTTPHeaders {
if err := deepCopy_v1_HTTPHeader(in.HTTPHeaders[i], &out.HTTPHeaders[i], c); err != nil {
return err
}
}
} else {
out.HTTPHeaders = nil
}
return nil
}
func deepCopy_v1_HTTPHeader(in v1.HTTPHeader, out *v1.HTTPHeader, c *conversion.Cloner) error {
out.Name = in.Name
out.Value = in.Value
return nil
}
......@@ -1967,6 +1983,7 @@ func init() {
deepCopy_v1_GitRepoVolumeSource,
deepCopy_v1_GlusterfsVolumeSource,
deepCopy_v1_HTTPGetAction,
deepCopy_v1_HTTPHeader,
deepCopy_v1_Handler,
deepCopy_v1_HostPathVolumeSource,
deepCopy_v1_ISCSIVolumeSource,
......
......@@ -59,7 +59,7 @@ func (server *Server) DoServerCheck(prober httpprober.HTTPProber) (probe.Result,
}
url := utilnet.FormatURL(scheme, server.Addr, server.Port, server.Path)
result, data, err := prober.Probe(url, probeTimeOut)
result, data, err := prober.Probe(url, nil, probeTimeOut)
if err != nil {
return probe.Unknown, "", err
......
......@@ -22,6 +22,7 @@ import (
"testing"
"k8s.io/kubernetes/pkg/probe"
"net/http"
"net/url"
"time"
)
......@@ -32,7 +33,7 @@ type fakeHttpProber struct {
err error
}
func (f *fakeHttpProber) Probe(*url.URL, time.Duration) (probe.Result, string, error) {
func (f *fakeHttpProber) Probe(*url.URL, http.Header, time.Duration) (probe.Result, string, error) {
return f.result, f.body, f.err
}
......
......@@ -19,6 +19,7 @@ package prober
import (
"fmt"
"net"
"net/http"
"net/url"
"strconv"
"strings"
......@@ -126,6 +127,16 @@ func (pb *prober) runProbeWithRetries(p *api.Probe, pod *api.Pod, status api.Pod
return result, output, err
}
// buildHeaderMap takes a list of HTTPHeader <name, value> string
// pairs and returns a a populated string->[]string http.Header map.
func buildHeader(headerList []api.HTTPHeader) http.Header {
headers := make(http.Header)
for _, header := range headerList {
headers[header.Name] = append(headers[header.Name], header.Value)
}
return headers
}
func (pb *prober) runProbe(p *api.Probe, pod *api.Pod, status api.PodStatus, container api.Container, containerID kubecontainer.ContainerID) (probe.Result, string, error) {
timeout := time.Duration(p.TimeoutSeconds) * time.Second
if p.Exec != nil {
......@@ -145,7 +156,9 @@ func (pb *prober) runProbe(p *api.Probe, pod *api.Pod, status api.PodStatus, con
path := p.HTTPGet.Path
glog.V(4).Infof("HTTP-Probe Host: %v://%v, Port: %v, Path: %v", scheme, host, port, path)
url := formatURL(scheme, host, port, path)
return pb.http.Probe(url, timeout)
headers := buildHeader(p.HTTPGet.HTTPHeaders)
glog.V(4).Infof("HTTP-Probe Headers: %v", headers)
return pb.http.Probe(url, headers, timeout)
}
if p.TCPSocket != nil {
port, err := extractPort(p.TCPSocket.Port, container)
......
......@@ -19,6 +19,8 @@ package prober
import (
"errors"
"fmt"
"net/http"
"reflect"
"testing"
"k8s.io/kubernetes/pkg/api"
......@@ -164,6 +166,33 @@ func TestGetTCPAddrParts(t *testing.T) {
}
}
func TestHTTPHeaders(t *testing.T) {
testCases := []struct {
input []api.HTTPHeader
output http.Header
}{
{[]api.HTTPHeader{}, http.Header{}},
{[]api.HTTPHeader{
{"X-Muffins-Or-Cupcakes", "Muffins"},
}, http.Header{"X-Muffins-Or-Cupcakes": {"Muffins"}}},
{[]api.HTTPHeader{
{"X-Muffins-Or-Cupcakes", "Muffins"},
{"X-Muffins-Or-Plumcakes", "Muffins!"},
}, http.Header{"X-Muffins-Or-Cupcakes": {"Muffins"},
"X-Muffins-Or-Plumcakes": {"Muffins!"}}},
{[]api.HTTPHeader{
{"X-Muffins-Or-Cupcakes", "Muffins"},
{"X-Muffins-Or-Cupcakes", "Cupcakes, too"},
}, http.Header{"X-Muffins-Or-Cupcakes": {"Muffins", "Cupcakes, too"}}},
}
for _, test := range testCases {
headers := buildHeader(test.input)
if !reflect.DeepEqual(test.output, headers) {
t.Errorf("Expected %#v, got %#v", test.output, headers)
}
}
}
func TestProbe(t *testing.T) {
prober := &prober{
refManager: kubecontainer.NewRefManager(),
......
......@@ -36,7 +36,7 @@ func New() HTTPProber {
}
type HTTPProber interface {
Probe(url *url.URL, timeout time.Duration) (probe.Result, string, error)
Probe(url *url.URL, headers http.Header, timeout time.Duration) (probe.Result, string, error)
}
type httpProber struct {
......@@ -44,20 +44,26 @@ type httpProber struct {
}
// Probe returns a ProbeRunner capable of running an http check.
func (pr httpProber) Probe(url *url.URL, timeout time.Duration) (probe.Result, string, error) {
return DoHTTPProbe(url, &http.Client{Timeout: timeout, Transport: pr.transport})
func (pr httpProber) Probe(url *url.URL, headers http.Header, timeout time.Duration) (probe.Result, string, error) {
return DoHTTPProbe(url, headers, &http.Client{Timeout: timeout, Transport: pr.transport})
}
type HTTPGetInterface interface {
Get(u string) (*http.Response, error)
Do(req *http.Request) (*http.Response, error)
}
// DoHTTPProbe checks if a GET request to the url succeeds.
// If the HTTP response code is successful (i.e. 400 > code >= 200), it returns Success.
// If the HTTP response code is unsuccessful or HTTP communication fails, it returns Failure.
// This is exported because some other packages may want to do direct HTTP probes.
func DoHTTPProbe(url *url.URL, client HTTPGetInterface) (probe.Result, string, error) {
res, err := client.Get(url.String())
func DoHTTPProbe(url *url.URL, headers http.Header, client HTTPGetInterface) (probe.Result, string, error) {
req, err := http.NewRequest("GET", url.String(), nil)
if err != nil {
// Convert errors into failures to catch timeouts.
return probe.Failure, err.Error(), nil
}
req.Header = headers
res, err := client.Do(req)
if err != nil {
// Convert errors into failures to catch timeouts.
return probe.Failure, err.Error(), nil
......
......@@ -42,8 +42,8 @@ func containsAny(s string, substrs []string) bool {
}
func TestHTTPProbeChecker(t *testing.T) {
handleReq := func(s int, body string) func(w http.ResponseWriter) {
return func(w http.ResponseWriter) {
handleReq := func(s int, body string) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(s)
w.Write([]byte(body))
}
......@@ -51,8 +51,9 @@ func TestHTTPProbeChecker(t *testing.T) {
prober := New()
testCases := []struct {
handler func(w http.ResponseWriter)
health probe.Result
handler func(w http.ResponseWriter, r *http.Request)
reqHeaders http.Header
health probe.Result
// go1.5: error message changed for timeout, need to support
// both old and new
accBodies []string
......@@ -60,18 +61,41 @@ func TestHTTPProbeChecker(t *testing.T) {
// The probe will be filled in below. This is primarily testing that an HTTP GET happens.
{
handleReq(http.StatusOK, "ok body"),
nil,
probe.Success,
[]string{"ok body"},
},
{
// Echo handler that returns the contents of request headers in the body
func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
output := ""
for k, arr := range r.Header {
for _, v := range arr {
output += fmt.Sprintf("%s: %s\n", k, v)
}
}
w.Write([]byte(output))
},
http.Header{
"X-Muffins-Or-Cupcakes": {"muffins"},
},
probe.Success,
[]string{
"X-Muffins-Or-Cupcakes: muffins",
},
},
{
handleReq(FailureCode, "fail body"),
nil,
probe.Failure,
[]string{fmt.Sprintf("HTTP probe failed with statuscode: %d", FailureCode)},
},
{
func(w http.ResponseWriter) {
func(w http.ResponseWriter, r *http.Request) {
time.Sleep(3 * time.Second)
},
nil,
probe.Failure,
[]string{
"use of closed network connection",
......@@ -82,7 +106,7 @@ func TestHTTPProbeChecker(t *testing.T) {
for _, test := range testCases {
// TODO: Close() this when fix #19254
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
test.handler(w)
test.handler(w, r)
}))
u, err := url.Parse(server.URL)
if err != nil {
......@@ -96,7 +120,7 @@ func TestHTTPProbeChecker(t *testing.T) {
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
health, output, err := prober.Probe(u, 1*time.Second)
health, output, err := prober.Probe(u, test.reqHeaders, 1*time.Second)
if test.health == probe.Unknown && err == nil {
t.Errorf("Expected error")
}
......
......@@ -26,6 +26,7 @@ import (
"k8s.io/kubernetes/pkg/apiserver"
"k8s.io/kubernetes/pkg/probe"
"k8s.io/kubernetes/pkg/util"
"net/http"
"net/url"
"time"
)
......@@ -36,7 +37,7 @@ type fakeHttpProber struct {
err error
}
func (f *fakeHttpProber) Probe(*url.URL, time.Duration) (probe.Result, string, error) {
func (f *fakeHttpProber) Probe(*url.URL, http.Header, time.Duration) (probe.Result, string, error) {
return f.result, f.body, f.err
}
......
......@@ -167,3 +167,13 @@ var percentRegexp = regexp.MustCompile("^" + percentFmt + "$")
func IsValidPercent(percent string) bool {
return percentRegexp.MatchString(percent)
}
const HTTPHeaderNameFmt string = "[-A-Za-z0-9]+"
var httpHeaderNameRegexp = regexp.MustCompile("^" + HTTPHeaderNameFmt + "$")
// IsHTTPHeaderName checks that a string conforms to the Go HTTP library's
// definition of a valid header field name (a stricter subset than RFC7230).
func IsHTTPHeaderName(value string) bool {
return httpHeaderNameRegexp.MatchString(value)
}
......@@ -308,3 +308,30 @@ func TestIsValidIP(t *testing.T) {
}
}
}
func TestIsHTTPHeaderName(t *testing.T) {
goodValues := []string{
// Common ones
"Accept-Encoding", "Host", "If-Modified-Since", "X-Forwarded-For",
// Weirdo, but still conforming names
"a", "ab", "abc", "a1", "-a", "a-", "a-b", "a-1", "a--1--2--b", "--abc-123",
"A", "AB", "AbC", "A1", "-A", "A-", "A-B", "A-1", "A--1--2--B", "--123-ABC",
}
for _, val := range goodValues {
if !IsHTTPHeaderName(val) {
t.Errorf("expected true for '%s'", val)
}
}
badValues := []string{
"Host:", "X-Forwarded-For:", "X-@Home",
"", "_", "a_", "_a", "1_", "1_2", ".", "a.", ".a", "a.b", "1.", ".1", "1.2",
" ", "a ", " a", "a b", "1 ", " 1", "1 2", "#a#", "^", ",", ";", "=", "<",
"?", "@", "{",
}
for _, val := range badValues {
if IsHTTPHeaderName(val) {
t.Errorf("expected false for '%s'", val)
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment