Commit b65f3cc8 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #49850 from m1093782566/service-session-timeout

Automatic merge from submit-queue (batch tested with PRs 49850, 47782, 50595, 50730, 51341) Paramaterize `stickyMaxAgeMinutes` for service in API **What this PR does / why we need it**: Currently I find `stickyMaxAgeMinutes` for a session affinity type service is hard code to 180min. There is a TODO comment, see https://github.com/kubernetes/kubernetes/blob/master/pkg/proxy/iptables/proxier.go#L205 I think the seesion sticky max time varies from service to service and users may not aware of it since it's hard coded in all proxier.go - iptables, userspace and winuserspace. Once we parameterize it in API, users can set/get the values for their different services. Perhaps, we can introduce a new field `api.ClientIPAffinityConfig` in `api.ServiceSpec`. There is an initial discussion about it in sig-network group. See, https://groups.google.com/forum/#!topic/kubernetes-sig-network/i-LkeHrjs80 **Which issue this PR fixes**: fixes #49831 **Special notes for your reviewer**: **Release note**: ```release-note Paramaterize session affinity timeout seconds in service API for Client IP based session affinity. ```
parents 85f96331 ad73fe68
...@@ -58320,6 +58320,16 @@ ...@@ -58320,6 +58320,16 @@
} }
} }
}, },
"io.k8s.api.core.v1.ClientIPConfig": {
"description": "ClientIPConfig represents the configurations of Client IP based session affinity.",
"properties": {
"timeoutSeconds": {
"description": "timeoutSeconds specifies the seconds of ClientIP type session sticky time. The value must be \u003e0 \u0026\u0026 \u003c=86400(for 1 day) if ServiceAffinity == \"ClientIP\". Default value is 10800(for 3 hours).",
"type": "integer",
"format": "int32"
}
}
},
"io.k8s.api.core.v1.ComponentCondition": { "io.k8s.api.core.v1.ComponentCondition": {
"description": "Information about the condition of a component.", "description": "Information about the condition of a component.",
"required": [ "required": [
...@@ -62066,6 +62076,10 @@ ...@@ -62066,6 +62076,10 @@
"description": "Supports \"ClientIP\" and \"None\". Used to maintain session affinity. Enable client IP based session affinity. Must be ClientIP or None. Defaults to None. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies", "description": "Supports \"ClientIP\" and \"None\". Used to maintain session affinity. Enable client IP based session affinity. Must be ClientIP or None. Defaults to None. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies",
"type": "string" "type": "string"
}, },
"sessionAffinityConfig": {
"description": "sessionAffinityConfig contains the configurations of session affinity.",
"$ref": "#/definitions/io.k8s.api.core.v1.SessionAffinityConfig"
},
"type": { "type": {
"description": "type determines how the Service is exposed. Defaults to ClusterIP. Valid options are ExternalName, ClusterIP, NodePort, and LoadBalancer. \"ExternalName\" maps to the specified externalName. \"ClusterIP\" allocates a cluster-internal IP address for load-balancing to endpoints. Endpoints are determined by the selector or if that is not specified, by manual construction of an Endpoints object. If clusterIP is \"None\", no virtual IP is allocated and the endpoints are published as a set of endpoints rather than a stable IP. \"NodePort\" builds on ClusterIP and allocates a port on every node which routes to the clusterIP. \"LoadBalancer\" builds on NodePort and creates an external load-balancer (if supported in the current cloud) which routes to the clusterIP. More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types", "description": "type determines how the Service is exposed. Defaults to ClusterIP. Valid options are ExternalName, ClusterIP, NodePort, and LoadBalancer. \"ExternalName\" maps to the specified externalName. \"ClusterIP\" allocates a cluster-internal IP address for load-balancing to endpoints. Endpoints are determined by the selector or if that is not specified, by manual construction of an Endpoints object. If clusterIP is \"None\", no virtual IP is allocated and the endpoints are published as a set of endpoints rather than a stable IP. \"NodePort\" builds on ClusterIP and allocates a port on every node which routes to the clusterIP. \"LoadBalancer\" builds on NodePort and creates an external load-balancer (if supported in the current cloud) which routes to the clusterIP. More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types",
"type": "string" "type": "string"
...@@ -62081,6 +62095,15 @@ ...@@ -62081,6 +62095,15 @@
} }
} }
}, },
"io.k8s.api.core.v1.SessionAffinityConfig": {
"description": "SessionAffinityConfig represents the configurations of session affinity.",
"properties": {
"clientIP": {
"description": "clientIP contains the configurations of Client IP based session affinity.",
"$ref": "#/definitions/io.k8s.api.core.v1.ClientIPConfig"
}
}
},
"io.k8s.api.core.v1.StorageOSPersistentVolumeSource": { "io.k8s.api.core.v1.StorageOSPersistentVolumeSource": {
"description": "Represents a StorageOS persistent volume resource.", "description": "Represents a StorageOS persistent volume resource.",
"properties": { "properties": {
...@@ -21907,6 +21907,10 @@ ...@@ -21907,6 +21907,10 @@
"publishNotReadyAddresses": { "publishNotReadyAddresses": {
"type": "boolean", "type": "boolean",
"description": "publishNotReadyAddresses, when set to true, indicates that DNS implementations must publish the notReadyAddresses of subsets for the Endpoints associated with the Service. The default value is false. The primary use case for setting this field is to use a StatefulSet's Headless Service to propagate SRV records for its Pods without respect to their readiness for purpose of peer discovery. This field will replace the service.alpha.kubernetes.io/tolerate-unready-endpoints when that annotation is deprecated and all clients have been converted to use this field." "description": "publishNotReadyAddresses, when set to true, indicates that DNS implementations must publish the notReadyAddresses of subsets for the Endpoints associated with the Service. The default value is false. The primary use case for setting this field is to use a StatefulSet's Headless Service to propagate SRV records for its Pods without respect to their readiness for purpose of peer discovery. This field will replace the service.alpha.kubernetes.io/tolerate-unready-endpoints when that annotation is deprecated and all clients have been converted to use this field."
},
"sessionAffinityConfig": {
"$ref": "v1.SessionAffinityConfig",
"description": "sessionAffinityConfig contains the configurations of session affinity."
} }
} }
}, },
...@@ -21941,6 +21945,27 @@ ...@@ -21941,6 +21945,27 @@
} }
} }
}, },
"v1.SessionAffinityConfig": {
"id": "v1.SessionAffinityConfig",
"description": "SessionAffinityConfig represents the configurations of session affinity.",
"properties": {
"clientIP": {
"$ref": "v1.ClientIPConfig",
"description": "clientIP contains the configurations of Client IP based session affinity."
}
}
},
"v1.ClientIPConfig": {
"id": "v1.ClientIPConfig",
"description": "ClientIPConfig represents the configurations of Client IP based session affinity.",
"properties": {
"timeoutSeconds": {
"type": "integer",
"format": "int32",
"description": "timeoutSeconds specifies the seconds of ClientIP type session sticky time. The value must be \u003e0 \u0026\u0026 \u003c=86400(for 1 day) if ServiceAffinity == \"ClientIP\". Default value is 10800(for 3 hours)."
}
}
},
"v1.ServiceStatus": { "v1.ServiceStatus": {
"id": "v1.ServiceStatus", "id": "v1.ServiceStatus",
"description": "ServiceStatus represents the current status of a service.", "description": "ServiceStatus represents the current status of a service.",
......
...@@ -6146,6 +6146,40 @@ Examples:<br> ...@@ -6146,6 +6146,40 @@ Examples:<br>
</div> </div>
<div class="sect2"> <div class="sect2">
<h3 id="_v1_sessionaffinityconfig">v1.SessionAffinityConfig</h3>
<div class="paragraph">
<p>SessionAffinityConfig represents the configurations of session affinity.</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">clientIP</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">clientIP contains the configurations of Client IP based session affinity.</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_clientipconfig">v1.ClientIPConfig</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_podcondition">v1.PodCondition</h3> <h3 id="_v1_podcondition">v1.PodCondition</h3>
<div class="paragraph"> <div class="paragraph">
<p>PodCondition contains details for the current condition of this pod.</p> <p>PodCondition contains details for the current condition of this pod.</p>
...@@ -7997,6 +8031,40 @@ Examples:<br> ...@@ -7997,6 +8031,40 @@ Examples:<br>
</div> </div>
<div class="sect2"> <div class="sect2">
<h3 id="_v1_clientipconfig">v1.ClientIPConfig</h3>
<div class="paragraph">
<p>ClientIPConfig represents the configurations of Client IP based session affinity.</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">timeoutSeconds</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">timeoutSeconds specifies the seconds of ClientIP type session sticky time. The value must be &gt;0 &amp;&amp; &#8656;86400(for 1 day) if ServiceAffinity == "ClientIP". Default value is 10800(for 3 hours).</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">integer (int32)</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_apiresource">v1.APIResource</h3> <h3 id="_v1_apiresource">v1.APIResource</h3>
<div class="paragraph"> <div class="paragraph">
<p>APIResource specifies the name of a resource and whether it is namespaced.</p> <p>APIResource specifies the name of a resource and whether it is namespaced.</p>
...@@ -10224,6 +10292,13 @@ Examples:<br> ...@@ -10224,6 +10292,13 @@ Examples:<br>
<td class="tableblock halign-left valign-top"><p class="tableblock">boolean</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">boolean</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">false</p></td>
</tr> </tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">sessionAffinityConfig</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">sessionAffinityConfig contains the configurations of session affinity.</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_sessionaffinityconfig">v1.SessionAffinityConfig</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody> </tbody>
</table> </table>
......
...@@ -15,6 +15,11 @@ ...@@ -15,6 +15,11 @@
"name": "meteor" "name": "meteor"
}, },
"sessionAffinity": "ClientIP", "sessionAffinity": "ClientIP",
"sessionAffinityConfig": {
"clientIP": {
"timeoutSeconds": 90
}
},
"type": "LoadBalancer" "type": "LoadBalancer"
} }
} }
...@@ -9844,6 +9844,16 @@ ...@@ -9844,6 +9844,16 @@
} }
} }
}, },
"io.k8s.api.core.v1.ClientIPConfig": {
"description": "ClientIPConfig represents the configurations of Client IP based session affinity.",
"properties": {
"timeoutSeconds": {
"description": "timeoutSeconds specifies the seconds of ClientIP type session sticky time. The value must be \u003e0 \u0026\u0026 \u003c=86400(for 1 day) if ServiceAffinity == \"ClientIP\". Default value is 10800(for 3 hours).",
"type": "integer",
"format": "int32"
}
}
},
"io.k8s.api.core.v1.ConfigMap": { "io.k8s.api.core.v1.ConfigMap": {
"description": "ConfigMap holds configuration data for pods to consume.", "description": "ConfigMap holds configuration data for pods to consume.",
"properties": { "properties": {
...@@ -11874,6 +11884,10 @@ ...@@ -11874,6 +11884,10 @@
"description": "Supports \"ClientIP\" and \"None\". Used to maintain session affinity. Enable client IP based session affinity. Must be ClientIP or None. Defaults to None. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies", "description": "Supports \"ClientIP\" and \"None\". Used to maintain session affinity. Enable client IP based session affinity. Must be ClientIP or None. Defaults to None. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies",
"type": "string" "type": "string"
}, },
"sessionAffinityConfig": {
"description": "sessionAffinityConfig contains the configurations of session affinity.",
"$ref": "#/definitions/io.k8s.api.core.v1.SessionAffinityConfig"
},
"type": { "type": {
"description": "type determines how the Service is exposed. Defaults to ClusterIP. Valid options are ExternalName, ClusterIP, NodePort, and LoadBalancer. \"ExternalName\" maps to the specified externalName. \"ClusterIP\" allocates a cluster-internal IP address for load-balancing to endpoints. Endpoints are determined by the selector or if that is not specified, by manual construction of an Endpoints object. If clusterIP is \"None\", no virtual IP is allocated and the endpoints are published as a set of endpoints rather than a stable IP. \"NodePort\" builds on ClusterIP and allocates a port on every node which routes to the clusterIP. \"LoadBalancer\" builds on NodePort and creates an external load-balancer (if supported in the current cloud) which routes to the clusterIP. More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types", "description": "type determines how the Service is exposed. Defaults to ClusterIP. Valid options are ExternalName, ClusterIP, NodePort, and LoadBalancer. \"ExternalName\" maps to the specified externalName. \"ClusterIP\" allocates a cluster-internal IP address for load-balancing to endpoints. Endpoints are determined by the selector or if that is not specified, by manual construction of an Endpoints object. If clusterIP is \"None\", no virtual IP is allocated and the endpoints are published as a set of endpoints rather than a stable IP. \"NodePort\" builds on ClusterIP and allocates a port on every node which routes to the clusterIP. \"LoadBalancer\" builds on NodePort and creates an external load-balancer (if supported in the current cloud) which routes to the clusterIP. More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types",
"type": "string" "type": "string"
...@@ -11889,6 +11903,15 @@ ...@@ -11889,6 +11903,15 @@
} }
} }
}, },
"io.k8s.api.core.v1.SessionAffinityConfig": {
"description": "SessionAffinityConfig represents the configurations of session affinity.",
"properties": {
"clientIP": {
"description": "clientIP contains the configurations of Client IP based session affinity.",
"$ref": "#/definitions/io.k8s.api.core.v1.ClientIPConfig"
}
}
},
"io.k8s.api.core.v1.StorageOSVolumeSource": { "io.k8s.api.core.v1.StorageOSVolumeSource": {
"description": "Represents a StorageOS persistent volume resource.", "description": "Represents a StorageOS persistent volume resource.",
"properties": { "properties": {
......
...@@ -5128,6 +5128,10 @@ ...@@ -5128,6 +5128,10 @@
"publishNotReadyAddresses": { "publishNotReadyAddresses": {
"type": "boolean", "type": "boolean",
"description": "publishNotReadyAddresses, when set to true, indicates that DNS implementations must publish the notReadyAddresses of subsets for the Endpoints associated with the Service. The default value is false. The primary use case for setting this field is to use a StatefulSet's Headless Service to propagate SRV records for its Pods without respect to their readiness for purpose of peer discovery. This field will replace the service.alpha.kubernetes.io/tolerate-unready-endpoints when that annotation is deprecated and all clients have been converted to use this field." "description": "publishNotReadyAddresses, when set to true, indicates that DNS implementations must publish the notReadyAddresses of subsets for the Endpoints associated with the Service. The default value is false. The primary use case for setting this field is to use a StatefulSet's Headless Service to propagate SRV records for its Pods without respect to their readiness for purpose of peer discovery. This field will replace the service.alpha.kubernetes.io/tolerate-unready-endpoints when that annotation is deprecated and all clients have been converted to use this field."
},
"sessionAffinityConfig": {
"$ref": "v1.SessionAffinityConfig",
"description": "sessionAffinityConfig contains the configurations of session affinity."
} }
} }
}, },
...@@ -5162,6 +5166,27 @@ ...@@ -5162,6 +5166,27 @@
} }
} }
}, },
"v1.SessionAffinityConfig": {
"id": "v1.SessionAffinityConfig",
"description": "SessionAffinityConfig represents the configurations of session affinity.",
"properties": {
"clientIP": {
"$ref": "v1.ClientIPConfig",
"description": "clientIP contains the configurations of Client IP based session affinity."
}
}
},
"v1.ClientIPConfig": {
"id": "v1.ClientIPConfig",
"description": "ClientIPConfig represents the configurations of Client IP based session affinity.",
"properties": {
"timeoutSeconds": {
"type": "integer",
"format": "int32",
"description": "timeoutSeconds specifies the seconds of ClientIP type session sticky time. The value must be \u003e0 \u0026\u0026 \u003c=86400(for 1 day) if ServiceAffinity == \"ClientIP\". Default value is 10800(for 3 hours)."
}
}
},
"v1.ServiceStatus": { "v1.ServiceStatus": {
"id": "v1.ServiceStatus", "id": "v1.ServiceStatus",
"description": "ServiceStatus represents the current status of a service.", "description": "ServiceStatus represents the current status of a service.",
......
...@@ -2051,6 +2051,40 @@ When an object is created, the system will populate this list with the current s ...@@ -2051,6 +2051,40 @@ When an object is created, the system will populate this list with the current s
</div> </div>
<div class="sect2"> <div class="sect2">
<h3 id="_v1_clientipconfig">v1.ClientIPConfig</h3>
<div class="paragraph">
<p>ClientIPConfig represents the configurations of Client IP based session affinity.</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">timeoutSeconds</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">timeoutSeconds specifies the seconds of ClientIP type session sticky time. The value must be &gt;0 &amp;&amp; &#8656;86400(for 1 day) if ServiceAffinity == "ClientIP". Default value is 10800(for 3 hours).</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">integer (int32)</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_apiresource">v1.APIResource</h3> <h3 id="_v1_apiresource">v1.APIResource</h3>
<div class="paragraph"> <div class="paragraph">
<p>APIResource specifies the name of a resource and whether it is namespaced.</p> <p>APIResource specifies the name of a resource and whether it is namespaced.</p>
...@@ -2233,6 +2267,13 @@ When an object is created, the system will populate this list with the current s ...@@ -2233,6 +2267,13 @@ When an object is created, the system will populate this list with the current s
<td class="tableblock halign-left valign-top"><p class="tableblock">boolean</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">boolean</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">false</p></td>
</tr> </tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">sessionAffinityConfig</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">sessionAffinityConfig contains the configurations of session affinity.</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_sessionaffinityconfig">v1.SessionAffinityConfig</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody> </tbody>
</table> </table>
...@@ -2335,6 +2376,40 @@ Examples:<br> ...@@ -2335,6 +2376,40 @@ Examples:<br>
</div> </div>
<div class="sect2"> <div class="sect2">
<h3 id="_v1_sessionaffinityconfig">v1.SessionAffinityConfig</h3>
<div class="paragraph">
<p>SessionAffinityConfig represents the configurations of session affinity.</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">clientIP</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">clientIP contains the configurations of Client IP based session affinity.</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_clientipconfig">v1.ClientIPConfig</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_any">any</h3> <h3 id="_any">any</h3>
<div class="paragraph"> <div class="paragraph">
<p>Represents an untyped JSON map - see the description of the field for more info about the structure of this object.</p> <p>Represents an untyped JSON map - see the description of the field for more info about the structure of this object.</p>
......
...@@ -2660,6 +2660,31 @@ const ( ...@@ -2660,6 +2660,31 @@ const (
ServiceAffinityNone ServiceAffinity = "None" ServiceAffinityNone ServiceAffinity = "None"
) )
const (
// DefaultClientIPServiceAffinitySeconds is the default timeout seconds
// of Client IP based session affinity - 3 hours.
DefaultClientIPServiceAffinitySeconds int32 = 10800
// MaxClientIPServiceAffinitySeconds is the max timeout seconds
// of Client IP based session affinity - 1 day.
MaxClientIPServiceAffinitySeconds int32 = 86400
)
// SessionAffinityConfig represents the configurations of session affinity.
type SessionAffinityConfig struct {
// clientIP contains the configurations of Client IP based session affinity.
// +optional
ClientIP *ClientIPConfig
}
// ClientIPConfig represents the configurations of Client IP based session affinity.
type ClientIPConfig struct {
// timeoutSeconds specifies the seconds of ClientIP type session sticky time.
// The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP".
// Default value is 10800(for 3 hours).
// +optional
TimeoutSeconds *int32
}
// Service Type string describes ingress methods for a service // Service Type string describes ingress methods for a service
type ServiceType string type ServiceType string
...@@ -2787,6 +2812,10 @@ type ServiceSpec struct { ...@@ -2787,6 +2812,10 @@ type ServiceSpec struct {
// +optional // +optional
SessionAffinity ServiceAffinity SessionAffinity ServiceAffinity
// sessionAffinityConfig contains the configurations of session affinity.
// +optional
SessionAffinityConfig *SessionAffinityConfig
// Optional: If specified and supported by the platform, this will restrict traffic through the cloud-provider // Optional: If specified and supported by the platform, this will restrict traffic through the cloud-provider
// load-balancer will be restricted to the specified client IPs. This field will be ignored if the // load-balancer will be restricted to the specified client IPs. This field will be ignored if the
// cloud-provider does not support the feature." // cloud-provider does not support the feature."
......
...@@ -62,6 +62,8 @@ func RegisterConversions(scheme *runtime.Scheme) error { ...@@ -62,6 +62,8 @@ func RegisterConversions(scheme *runtime.Scheme) error {
Convert_api_CephFSVolumeSource_To_v1_CephFSVolumeSource, Convert_api_CephFSVolumeSource_To_v1_CephFSVolumeSource,
Convert_v1_CinderVolumeSource_To_api_CinderVolumeSource, Convert_v1_CinderVolumeSource_To_api_CinderVolumeSource,
Convert_api_CinderVolumeSource_To_v1_CinderVolumeSource, Convert_api_CinderVolumeSource_To_v1_CinderVolumeSource,
Convert_v1_ClientIPConfig_To_api_ClientIPConfig,
Convert_api_ClientIPConfig_To_v1_ClientIPConfig,
Convert_v1_ComponentCondition_To_api_ComponentCondition, Convert_v1_ComponentCondition_To_api_ComponentCondition,
Convert_api_ComponentCondition_To_v1_ComponentCondition, Convert_api_ComponentCondition_To_v1_ComponentCondition,
Convert_v1_ComponentStatus_To_api_ComponentStatus, Convert_v1_ComponentStatus_To_api_ComponentStatus,
...@@ -364,6 +366,8 @@ func RegisterConversions(scheme *runtime.Scheme) error { ...@@ -364,6 +366,8 @@ func RegisterConversions(scheme *runtime.Scheme) error {
Convert_api_ServiceSpec_To_v1_ServiceSpec, Convert_api_ServiceSpec_To_v1_ServiceSpec,
Convert_v1_ServiceStatus_To_api_ServiceStatus, Convert_v1_ServiceStatus_To_api_ServiceStatus,
Convert_api_ServiceStatus_To_v1_ServiceStatus, Convert_api_ServiceStatus_To_v1_ServiceStatus,
Convert_v1_SessionAffinityConfig_To_api_SessionAffinityConfig,
Convert_api_SessionAffinityConfig_To_v1_SessionAffinityConfig,
Convert_v1_StorageOSPersistentVolumeSource_To_api_StorageOSPersistentVolumeSource, Convert_v1_StorageOSPersistentVolumeSource_To_api_StorageOSPersistentVolumeSource,
Convert_api_StorageOSPersistentVolumeSource_To_v1_StorageOSPersistentVolumeSource, Convert_api_StorageOSPersistentVolumeSource_To_v1_StorageOSPersistentVolumeSource,
Convert_v1_StorageOSVolumeSource_To_api_StorageOSVolumeSource, Convert_v1_StorageOSVolumeSource_To_api_StorageOSVolumeSource,
...@@ -703,6 +707,26 @@ func Convert_api_CinderVolumeSource_To_v1_CinderVolumeSource(in *api.CinderVolum ...@@ -703,6 +707,26 @@ func Convert_api_CinderVolumeSource_To_v1_CinderVolumeSource(in *api.CinderVolum
return autoConvert_api_CinderVolumeSource_To_v1_CinderVolumeSource(in, out, s) return autoConvert_api_CinderVolumeSource_To_v1_CinderVolumeSource(in, out, s)
} }
func autoConvert_v1_ClientIPConfig_To_api_ClientIPConfig(in *v1.ClientIPConfig, out *api.ClientIPConfig, s conversion.Scope) error {
out.TimeoutSeconds = (*int32)(unsafe.Pointer(in.TimeoutSeconds))
return nil
}
// Convert_v1_ClientIPConfig_To_api_ClientIPConfig is an autogenerated conversion function.
func Convert_v1_ClientIPConfig_To_api_ClientIPConfig(in *v1.ClientIPConfig, out *api.ClientIPConfig, s conversion.Scope) error {
return autoConvert_v1_ClientIPConfig_To_api_ClientIPConfig(in, out, s)
}
func autoConvert_api_ClientIPConfig_To_v1_ClientIPConfig(in *api.ClientIPConfig, out *v1.ClientIPConfig, s conversion.Scope) error {
out.TimeoutSeconds = (*int32)(unsafe.Pointer(in.TimeoutSeconds))
return nil
}
// Convert_api_ClientIPConfig_To_v1_ClientIPConfig is an autogenerated conversion function.
func Convert_api_ClientIPConfig_To_v1_ClientIPConfig(in *api.ClientIPConfig, out *v1.ClientIPConfig, s conversion.Scope) error {
return autoConvert_api_ClientIPConfig_To_v1_ClientIPConfig(in, out, s)
}
func autoConvert_v1_ComponentCondition_To_api_ComponentCondition(in *v1.ComponentCondition, out *api.ComponentCondition, s conversion.Scope) error { func autoConvert_v1_ComponentCondition_To_api_ComponentCondition(in *v1.ComponentCondition, out *api.ComponentCondition, s conversion.Scope) error {
out.Type = api.ComponentConditionType(in.Type) out.Type = api.ComponentConditionType(in.Type)
out.Status = api.ConditionStatus(in.Status) out.Status = api.ConditionStatus(in.Status)
...@@ -4930,6 +4954,7 @@ func autoConvert_v1_ServiceSpec_To_api_ServiceSpec(in *v1.ServiceSpec, out *api. ...@@ -4930,6 +4954,7 @@ func autoConvert_v1_ServiceSpec_To_api_ServiceSpec(in *v1.ServiceSpec, out *api.
out.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyType(in.ExternalTrafficPolicy) out.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyType(in.ExternalTrafficPolicy)
out.HealthCheckNodePort = in.HealthCheckNodePort out.HealthCheckNodePort = in.HealthCheckNodePort
out.PublishNotReadyAddresses = in.PublishNotReadyAddresses out.PublishNotReadyAddresses = in.PublishNotReadyAddresses
out.SessionAffinityConfig = (*api.SessionAffinityConfig)(unsafe.Pointer(in.SessionAffinityConfig))
return nil return nil
} }
...@@ -4947,6 +4972,7 @@ func autoConvert_api_ServiceSpec_To_v1_ServiceSpec(in *api.ServiceSpec, out *v1. ...@@ -4947,6 +4972,7 @@ func autoConvert_api_ServiceSpec_To_v1_ServiceSpec(in *api.ServiceSpec, out *v1.
out.ExternalIPs = *(*[]string)(unsafe.Pointer(&in.ExternalIPs)) out.ExternalIPs = *(*[]string)(unsafe.Pointer(&in.ExternalIPs))
out.LoadBalancerIP = in.LoadBalancerIP out.LoadBalancerIP = in.LoadBalancerIP
out.SessionAffinity = v1.ServiceAffinity(in.SessionAffinity) out.SessionAffinity = v1.ServiceAffinity(in.SessionAffinity)
out.SessionAffinityConfig = (*v1.SessionAffinityConfig)(unsafe.Pointer(in.SessionAffinityConfig))
out.LoadBalancerSourceRanges = *(*[]string)(unsafe.Pointer(&in.LoadBalancerSourceRanges)) out.LoadBalancerSourceRanges = *(*[]string)(unsafe.Pointer(&in.LoadBalancerSourceRanges))
out.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyType(in.ExternalTrafficPolicy) out.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyType(in.ExternalTrafficPolicy)
out.HealthCheckNodePort = in.HealthCheckNodePort out.HealthCheckNodePort = in.HealthCheckNodePort
...@@ -4983,6 +5009,26 @@ func Convert_api_ServiceStatus_To_v1_ServiceStatus(in *api.ServiceStatus, out *v ...@@ -4983,6 +5009,26 @@ func Convert_api_ServiceStatus_To_v1_ServiceStatus(in *api.ServiceStatus, out *v
return autoConvert_api_ServiceStatus_To_v1_ServiceStatus(in, out, s) return autoConvert_api_ServiceStatus_To_v1_ServiceStatus(in, out, s)
} }
func autoConvert_v1_SessionAffinityConfig_To_api_SessionAffinityConfig(in *v1.SessionAffinityConfig, out *api.SessionAffinityConfig, s conversion.Scope) error {
out.ClientIP = (*api.ClientIPConfig)(unsafe.Pointer(in.ClientIP))
return nil
}
// Convert_v1_SessionAffinityConfig_To_api_SessionAffinityConfig is an autogenerated conversion function.
func Convert_v1_SessionAffinityConfig_To_api_SessionAffinityConfig(in *v1.SessionAffinityConfig, out *api.SessionAffinityConfig, s conversion.Scope) error {
return autoConvert_v1_SessionAffinityConfig_To_api_SessionAffinityConfig(in, out, s)
}
func autoConvert_api_SessionAffinityConfig_To_v1_SessionAffinityConfig(in *api.SessionAffinityConfig, out *v1.SessionAffinityConfig, s conversion.Scope) error {
out.ClientIP = (*v1.ClientIPConfig)(unsafe.Pointer(in.ClientIP))
return nil
}
// Convert_api_SessionAffinityConfig_To_v1_SessionAffinityConfig is an autogenerated conversion function.
func Convert_api_SessionAffinityConfig_To_v1_SessionAffinityConfig(in *api.SessionAffinityConfig, out *v1.SessionAffinityConfig, s conversion.Scope) error {
return autoConvert_api_SessionAffinityConfig_To_v1_SessionAffinityConfig(in, out, s)
}
func autoConvert_v1_StorageOSPersistentVolumeSource_To_api_StorageOSPersistentVolumeSource(in *v1.StorageOSPersistentVolumeSource, out *api.StorageOSPersistentVolumeSource, s conversion.Scope) error { func autoConvert_v1_StorageOSPersistentVolumeSource_To_api_StorageOSPersistentVolumeSource(in *v1.StorageOSPersistentVolumeSource, out *api.StorageOSPersistentVolumeSource, s conversion.Scope) error {
out.VolumeName = in.VolumeName out.VolumeName = in.VolumeName
out.VolumeNamespace = in.VolumeNamespace out.VolumeNamespace = in.VolumeNamespace
......
...@@ -19,6 +19,7 @@ package validation ...@@ -19,6 +19,7 @@ package validation
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"math"
"net" "net"
"path" "path"
"path/filepath" "path/filepath"
...@@ -28,8 +29,6 @@ import ( ...@@ -28,8 +29,6 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
"math"
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality" apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
...@@ -1873,6 +1872,33 @@ func validateProbe(probe *api.Probe, fldPath *field.Path) field.ErrorList { ...@@ -1873,6 +1872,33 @@ func validateProbe(probe *api.Probe, fldPath *field.Path) field.ErrorList {
return allErrs return allErrs
} }
func validateClientIPAffinityConfig(config *api.SessionAffinityConfig, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if config == nil {
allErrs = append(allErrs, field.Required(fldPath, fmt.Sprintf("when session affinity type is %s", api.ServiceAffinityClientIP)))
return allErrs
}
if config.ClientIP == nil {
allErrs = append(allErrs, field.Required(fldPath.Child("clientIP"), fmt.Sprintf("when session affinity type is %s", api.ServiceAffinityClientIP)))
return allErrs
}
if config.ClientIP.TimeoutSeconds == nil {
allErrs = append(allErrs, field.Required(fldPath.Child("clientIP").Child("timeoutSeconds"), fmt.Sprintf("when session affinity type is %s", api.ServiceAffinityClientIP)))
return allErrs
}
allErrs = append(allErrs, validateAffinityTimeout(config.ClientIP.TimeoutSeconds, fldPath.Child("clientIP").Child("timeoutSeconds"))...)
return allErrs
}
func validateAffinityTimeout(timeout *int32, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if *timeout <= 0 || *timeout > api.MaxClientIPServiceAffinitySeconds {
allErrs = append(allErrs, field.Invalid(fldPath, timeout, fmt.Sprintf("must be greater than 0 and less than %d", api.MaxClientIPServiceAffinitySeconds)))
}
return allErrs
}
// AccumulateUniqueHostPorts extracts each HostPort of each Container, // AccumulateUniqueHostPorts extracts each HostPort of each Container,
// accumulating the results and returning an error if any ports conflict. // accumulating the results and returning an error if any ports conflict.
func AccumulateUniqueHostPorts(containers []api.Container, accumulator *sets.String, fldPath *field.Path) field.ErrorList { func AccumulateUniqueHostPorts(containers []api.Container, accumulator *sets.String, fldPath *field.Path) field.ErrorList {
...@@ -2914,6 +2940,14 @@ func ValidateService(service *api.Service) field.ErrorList { ...@@ -2914,6 +2940,14 @@ func ValidateService(service *api.Service) field.ErrorList {
allErrs = append(allErrs, field.NotSupported(specPath.Child("sessionAffinity"), service.Spec.SessionAffinity, supportedSessionAffinityType.List())) allErrs = append(allErrs, field.NotSupported(specPath.Child("sessionAffinity"), service.Spec.SessionAffinity, supportedSessionAffinityType.List()))
} }
if service.Spec.SessionAffinity == api.ServiceAffinityClientIP {
allErrs = append(allErrs, validateClientIPAffinityConfig(service.Spec.SessionAffinityConfig, specPath.Child("sessionAffinityConfig"))...)
} else if service.Spec.SessionAffinity == api.ServiceAffinityNone {
if service.Spec.SessionAffinityConfig != nil {
allErrs = append(allErrs, field.Forbidden(specPath.Child("sessionAffinityConfig"), fmt.Sprintf("must not be set when session affinity is %s", string(api.ServiceAffinityNone))))
}
}
if helper.IsServiceIPSet(service) { if helper.IsServiceIPSet(service) {
if ip := net.ParseIP(service.Spec.ClusterIP); ip == nil { if ip := net.ParseIP(service.Spec.ClusterIP); ip == nil {
allErrs = append(allErrs, field.Invalid(specPath.Child("clusterIP"), service.Spec.ClusterIP, "must be empty, 'None', or a valid IP address")) allErrs = append(allErrs, field.Invalid(specPath.Child("clusterIP"), service.Spec.ClusterIP, "must be empty, 'None', or a valid IP address"))
......
...@@ -6792,6 +6792,32 @@ func TestValidateService(t *testing.T) { ...@@ -6792,6 +6792,32 @@ func TestValidateService(t *testing.T) {
numErrs: 0, numErrs: 0,
}, },
// ESIPP section ends. // ESIPP section ends.
{
name: "invalid timeoutSeconds field",
tweakSvc: func(s *api.Service) {
s.Spec.Type = api.ServiceTypeClusterIP
s.Spec.SessionAffinity = api.ServiceAffinityClientIP
s.Spec.SessionAffinityConfig = &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: newInt32(-1),
},
}
},
numErrs: 1,
},
{
name: "sessionAffinityConfig can't be set when session affinity is None",
tweakSvc: func(s *api.Service) {
s.Spec.Type = api.ServiceTypeLoadBalancer
s.Spec.SessionAffinity = api.ServiceAffinityNone
s.Spec.SessionAffinityConfig = &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: newInt32(90),
},
}
},
numErrs: 1,
},
} }
for _, tc := range testCases { for _, tc := range testCases {
...@@ -8193,6 +8219,11 @@ func TestValidateServiceUpdate(t *testing.T) { ...@@ -8193,6 +8219,11 @@ func TestValidateServiceUpdate(t *testing.T) {
name: "change affinity", name: "change affinity",
tweakSvc: func(oldSvc, newSvc *api.Service) { tweakSvc: func(oldSvc, newSvc *api.Service) {
newSvc.Spec.SessionAffinity = "ClientIP" newSvc.Spec.SessionAffinity = "ClientIP"
newSvc.Spec.SessionAffinityConfig = &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: newInt32(90),
},
}
}, },
numErrs: 0, numErrs: 0,
}, },
...@@ -10314,3 +10345,62 @@ func TestValidateFlexVolumeSource(t *testing.T) { ...@@ -10314,3 +10345,62 @@ func TestValidateFlexVolumeSource(t *testing.T) {
} }
} }
} }
func TestValidateOrSetClientIPAffinityConfig(t *testing.T) {
successCases := map[string]*api.SessionAffinityConfig{
"non-empty config, valid timeout: 1": {
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: newInt32(1),
},
},
"non-empty config, valid timeout: api.MaxClientIPServiceAffinitySeconds-1": {
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: newInt32(int(api.MaxClientIPServiceAffinitySeconds - 1)),
},
},
"non-empty config, valid timeout: api.MaxClientIPServiceAffinitySeconds": {
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: newInt32(int(api.MaxClientIPServiceAffinitySeconds)),
},
},
}
for name, test := range successCases {
if errs := validateClientIPAffinityConfig(test, field.NewPath("field")); len(errs) != 0 {
t.Errorf("case: %s, expected success: %v", name, errs)
}
}
errorCases := map[string]*api.SessionAffinityConfig{
"empty session affinity config": nil,
"empty client IP config": {
ClientIP: nil,
},
"empty timeoutSeconds": {
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: nil,
},
},
"non-empty config, invalid timeout: api.MaxClientIPServiceAffinitySeconds+1": {
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: newInt32(int(api.MaxClientIPServiceAffinitySeconds + 1)),
},
},
"non-empty config, invalid timeout: -1": {
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: newInt32(-1),
},
},
"non-empty config, invalid timeout: 0": {
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: newInt32(0),
},
},
}
for name, test := range errorCases {
if errs := validateClientIPAffinityConfig(test, field.NewPath("field")); len(errs) == 0 {
t.Errorf("case: %v, expected failures: %v", name, errs)
}
}
}
...@@ -87,6 +87,10 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error { ...@@ -87,6 +87,10 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
return nil return nil
}, InType: reflect.TypeOf(&CinderVolumeSource{})}, }, InType: reflect.TypeOf(&CinderVolumeSource{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*ClientIPConfig).DeepCopyInto(out.(*ClientIPConfig))
return nil
}, InType: reflect.TypeOf(&ClientIPConfig{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*ComponentCondition).DeepCopyInto(out.(*ComponentCondition)) in.(*ComponentCondition).DeepCopyInto(out.(*ComponentCondition))
return nil return nil
}, InType: reflect.TypeOf(&ComponentCondition{})}, }, InType: reflect.TypeOf(&ComponentCondition{})},
...@@ -691,6 +695,10 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error { ...@@ -691,6 +695,10 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
return nil return nil
}, InType: reflect.TypeOf(&ServiceStatus{})}, }, InType: reflect.TypeOf(&ServiceStatus{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*SessionAffinityConfig).DeepCopyInto(out.(*SessionAffinityConfig))
return nil
}, InType: reflect.TypeOf(&SessionAffinityConfig{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*StorageOSPersistentVolumeSource).DeepCopyInto(out.(*StorageOSPersistentVolumeSource)) in.(*StorageOSPersistentVolumeSource).DeepCopyInto(out.(*StorageOSPersistentVolumeSource))
return nil return nil
}, InType: reflect.TypeOf(&StorageOSPersistentVolumeSource{})}, }, InType: reflect.TypeOf(&StorageOSPersistentVolumeSource{})},
...@@ -1063,6 +1071,31 @@ func (in *CinderVolumeSource) DeepCopy() *CinderVolumeSource { ...@@ -1063,6 +1071,31 @@ func (in *CinderVolumeSource) DeepCopy() *CinderVolumeSource {
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClientIPConfig) DeepCopyInto(out *ClientIPConfig) {
*out = *in
if in.TimeoutSeconds != nil {
in, out := &in.TimeoutSeconds, &out.TimeoutSeconds
if *in == nil {
*out = nil
} else {
*out = new(int32)
**out = **in
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientIPConfig.
func (in *ClientIPConfig) DeepCopy() *ClientIPConfig {
if in == nil {
return nil
}
out := new(ClientIPConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ComponentCondition) DeepCopyInto(out *ComponentCondition) { func (in *ComponentCondition) DeepCopyInto(out *ComponentCondition) {
*out = *in *out = *in
return return
...@@ -5670,6 +5703,15 @@ func (in *ServiceSpec) DeepCopyInto(out *ServiceSpec) { ...@@ -5670,6 +5703,15 @@ func (in *ServiceSpec) DeepCopyInto(out *ServiceSpec) {
*out = make([]string, len(*in)) *out = make([]string, len(*in))
copy(*out, *in) copy(*out, *in)
} }
if in.SessionAffinityConfig != nil {
in, out := &in.SessionAffinityConfig, &out.SessionAffinityConfig
if *in == nil {
*out = nil
} else {
*out = new(SessionAffinityConfig)
(*in).DeepCopyInto(*out)
}
}
if in.LoadBalancerSourceRanges != nil { if in.LoadBalancerSourceRanges != nil {
in, out := &in.LoadBalancerSourceRanges, &out.LoadBalancerSourceRanges in, out := &in.LoadBalancerSourceRanges, &out.LoadBalancerSourceRanges
*out = make([]string, len(*in)) *out = make([]string, len(*in))
...@@ -5706,6 +5748,31 @@ func (in *ServiceStatus) DeepCopy() *ServiceStatus { ...@@ -5706,6 +5748,31 @@ func (in *ServiceStatus) DeepCopy() *ServiceStatus {
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SessionAffinityConfig) DeepCopyInto(out *SessionAffinityConfig) {
*out = *in
if in.ClientIP != nil {
in, out := &in.ClientIP, &out.ClientIP
if *in == nil {
*out = nil
} else {
*out = new(ClientIPConfig)
(*in).DeepCopyInto(*out)
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SessionAffinityConfig.
func (in *SessionAffinityConfig) DeepCopy() *SessionAffinityConfig {
if in == nil {
return nil
}
out := new(SessionAffinityConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *StorageOSPersistentVolumeSource) DeepCopyInto(out *StorageOSPersistentVolumeSource) { func (in *StorageOSPersistentVolumeSource) DeepCopyInto(out *StorageOSPersistentVolumeSource) {
*out = *in *out = *in
if in.SecretRef != nil { if in.SecretRef != nil {
......
...@@ -250,6 +250,7 @@ func (c *Controller) CreateOrUpdateMasterServiceIfNeeded(serviceName string, ser ...@@ -250,6 +250,7 @@ func (c *Controller) CreateOrUpdateMasterServiceIfNeeded(serviceName string, ser
} }
return nil return nil
} }
timeoutSeconds := api.DefaultClientIPServiceAffinitySeconds
svc := &api.Service{ svc := &api.Service{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: serviceName, Name: serviceName,
...@@ -263,6 +264,11 @@ func (c *Controller) CreateOrUpdateMasterServiceIfNeeded(serviceName string, ser ...@@ -263,6 +264,11 @@ func (c *Controller) CreateOrUpdateMasterServiceIfNeeded(serviceName string, ser
ClusterIP: serviceIP.String(), ClusterIP: serviceIP.String(),
SessionAffinity: api.ServiceAffinityClientIP, SessionAffinity: api.ServiceAffinityClientIP,
Type: serviceType, Type: serviceType,
SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
}, },
} }
......
...@@ -546,6 +546,7 @@ func TestCreateOrUpdateMasterService(t *testing.T) { ...@@ -546,6 +546,7 @@ func TestCreateOrUpdateMasterService(t *testing.T) {
om := func(name string) metav1.ObjectMeta { om := func(name string) metav1.ObjectMeta {
return metav1.ObjectMeta{Namespace: ns, Name: name} return metav1.ObjectMeta{Namespace: ns, Name: name}
} }
timeoutSeconds := api.DefaultClientIPServiceAffinitySeconds
create_tests := []struct { create_tests := []struct {
testName string testName string
...@@ -570,6 +571,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) { ...@@ -570,6 +571,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) {
Selector: nil, Selector: nil,
ClusterIP: "1.2.3.4", ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP, SessionAffinity: api.ServiceAffinityClientIP,
SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP, Type: api.ServiceTypeClusterIP,
}, },
}, },
...@@ -625,6 +631,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) { ...@@ -625,6 +631,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) {
Selector: nil, Selector: nil,
ClusterIP: "1.2.3.4", ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP, SessionAffinity: api.ServiceAffinityClientIP,
SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP, Type: api.ServiceTypeClusterIP,
}, },
}, },
...@@ -637,6 +648,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) { ...@@ -637,6 +648,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) {
Selector: nil, Selector: nil,
ClusterIP: "1.2.3.4", ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP, SessionAffinity: api.ServiceAffinityClientIP,
SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP, Type: api.ServiceTypeClusterIP,
}, },
}, },
...@@ -658,6 +674,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) { ...@@ -658,6 +674,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) {
Selector: nil, Selector: nil,
ClusterIP: "1.2.3.4", ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP, SessionAffinity: api.ServiceAffinityClientIP,
SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP, Type: api.ServiceTypeClusterIP,
}, },
}, },
...@@ -671,6 +692,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) { ...@@ -671,6 +692,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) {
Selector: nil, Selector: nil,
ClusterIP: "1.2.3.4", ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP, SessionAffinity: api.ServiceAffinityClientIP,
SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP, Type: api.ServiceTypeClusterIP,
}, },
}, },
...@@ -691,6 +717,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) { ...@@ -691,6 +717,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) {
Selector: nil, Selector: nil,
ClusterIP: "1.2.3.4", ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP, SessionAffinity: api.ServiceAffinityClientIP,
SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP, Type: api.ServiceTypeClusterIP,
}, },
}, },
...@@ -703,6 +734,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) { ...@@ -703,6 +734,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) {
Selector: nil, Selector: nil,
ClusterIP: "1.2.3.4", ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP, SessionAffinity: api.ServiceAffinityClientIP,
SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP, Type: api.ServiceTypeClusterIP,
}, },
}, },
...@@ -723,6 +759,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) { ...@@ -723,6 +759,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) {
Selector: nil, Selector: nil,
ClusterIP: "1.2.3.4", ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP, SessionAffinity: api.ServiceAffinityClientIP,
SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP, Type: api.ServiceTypeClusterIP,
}, },
}, },
...@@ -735,6 +776,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) { ...@@ -735,6 +776,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) {
Selector: nil, Selector: nil,
ClusterIP: "1.2.3.4", ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP, SessionAffinity: api.ServiceAffinityClientIP,
SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP, Type: api.ServiceTypeClusterIP,
}, },
}, },
...@@ -755,6 +801,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) { ...@@ -755,6 +801,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) {
Selector: nil, Selector: nil,
ClusterIP: "1.2.3.4", ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP, SessionAffinity: api.ServiceAffinityClientIP,
SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP, Type: api.ServiceTypeClusterIP,
}, },
}, },
...@@ -767,6 +818,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) { ...@@ -767,6 +818,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) {
Selector: nil, Selector: nil,
ClusterIP: "1.2.3.4", ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP, SessionAffinity: api.ServiceAffinityClientIP,
SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP, Type: api.ServiceTypeClusterIP,
}, },
}, },
...@@ -787,6 +843,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) { ...@@ -787,6 +843,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) {
Selector: nil, Selector: nil,
ClusterIP: "1.2.3.4", ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP, SessionAffinity: api.ServiceAffinityClientIP,
SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP, Type: api.ServiceTypeClusterIP,
}, },
}, },
...@@ -799,6 +860,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) { ...@@ -799,6 +860,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) {
Selector: nil, Selector: nil,
ClusterIP: "1.2.3.4", ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP, SessionAffinity: api.ServiceAffinityClientIP,
SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP, Type: api.ServiceTypeClusterIP,
}, },
}, },
...@@ -819,6 +885,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) { ...@@ -819,6 +885,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) {
Selector: nil, Selector: nil,
ClusterIP: "1.2.3.4", ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP, SessionAffinity: api.ServiceAffinityClientIP,
SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeNodePort, Type: api.ServiceTypeNodePort,
}, },
}, },
...@@ -831,6 +902,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) { ...@@ -831,6 +902,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) {
Selector: nil, Selector: nil,
ClusterIP: "1.2.3.4", ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP, SessionAffinity: api.ServiceAffinityClientIP,
SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP, Type: api.ServiceTypeClusterIP,
}, },
}, },
...@@ -851,6 +927,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) { ...@@ -851,6 +927,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) {
Selector: nil, Selector: nil,
ClusterIP: "1.2.3.4", ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP, SessionAffinity: api.ServiceAffinityClientIP,
SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP, Type: api.ServiceTypeClusterIP,
}, },
}, },
...@@ -910,6 +991,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) { ...@@ -910,6 +991,11 @@ func TestCreateOrUpdateMasterService(t *testing.T) {
Selector: nil, Selector: nil,
ClusterIP: "1.2.3.4", ClusterIP: "1.2.3.4",
SessionAffinity: api.ServiceAffinityClientIP, SessionAffinity: api.ServiceAffinityClientIP,
SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP, Type: api.ServiceTypeClusterIP,
}, },
}, },
......
...@@ -143,7 +143,7 @@ type serviceInfo struct { ...@@ -143,7 +143,7 @@ type serviceInfo struct {
nodePort int nodePort int
loadBalancerStatus api.LoadBalancerStatus loadBalancerStatus api.LoadBalancerStatus
sessionAffinityType api.ServiceAffinity sessionAffinityType api.ServiceAffinity
stickyMaxAgeMinutes int stickyMaxAgeSeconds int
externalIPs []string externalIPs []string
loadBalancerSourceRanges []string loadBalancerSourceRanges []string
onlyNodeLocalEndpoints bool onlyNodeLocalEndpoints bool
...@@ -194,6 +194,10 @@ func newServiceInfo(svcPortName proxy.ServicePortName, port *api.ServicePort, se ...@@ -194,6 +194,10 @@ func newServiceInfo(svcPortName proxy.ServicePortName, port *api.ServicePort, se
apiservice.RequestsOnlyLocalTraffic(service) { apiservice.RequestsOnlyLocalTraffic(service) {
onlyNodeLocalEndpoints = true onlyNodeLocalEndpoints = true
} }
var stickyMaxAgeSeconds int
if service.Spec.SessionAffinity == api.ServiceAffinityClientIP {
stickyMaxAgeSeconds = int(*service.Spec.SessionAffinityConfig.ClientIP.TimeoutSeconds)
}
info := &serviceInfo{ info := &serviceInfo{
clusterIP: net.ParseIP(service.Spec.ClusterIP), clusterIP: net.ParseIP(service.Spec.ClusterIP),
port: int(port.Port), port: int(port.Port),
...@@ -202,11 +206,12 @@ func newServiceInfo(svcPortName proxy.ServicePortName, port *api.ServicePort, se ...@@ -202,11 +206,12 @@ func newServiceInfo(svcPortName proxy.ServicePortName, port *api.ServicePort, se
// Deep-copy in case the service instance changes // Deep-copy in case the service instance changes
loadBalancerStatus: *helper.LoadBalancerStatusDeepCopy(&service.Status.LoadBalancer), loadBalancerStatus: *helper.LoadBalancerStatusDeepCopy(&service.Status.LoadBalancer),
sessionAffinityType: service.Spec.SessionAffinity, sessionAffinityType: service.Spec.SessionAffinity,
stickyMaxAgeMinutes: 180, // TODO: paramaterize this in the API. stickyMaxAgeSeconds: stickyMaxAgeSeconds,
externalIPs: make([]string, len(service.Spec.ExternalIPs)), externalIPs: make([]string, len(service.Spec.ExternalIPs)),
loadBalancerSourceRanges: make([]string, len(service.Spec.LoadBalancerSourceRanges)), loadBalancerSourceRanges: make([]string, len(service.Spec.LoadBalancerSourceRanges)),
onlyNodeLocalEndpoints: onlyNodeLocalEndpoints, onlyNodeLocalEndpoints: onlyNodeLocalEndpoints,
} }
copy(info.loadBalancerSourceRanges, service.Spec.LoadBalancerSourceRanges) copy(info.loadBalancerSourceRanges, service.Spec.LoadBalancerSourceRanges)
copy(info.externalIPs, service.Spec.ExternalIPs) copy(info.externalIPs, service.Spec.ExternalIPs)
...@@ -1419,7 +1424,7 @@ func (proxier *Proxier) syncProxyRules() { ...@@ -1419,7 +1424,7 @@ func (proxier *Proxier) syncProxyRules() {
"-A", string(svcChain), "-A", string(svcChain),
"-m", "comment", "--comment", svcNameString, "-m", "comment", "--comment", svcNameString,
"-m", "recent", "--name", string(endpointChain), "-m", "recent", "--name", string(endpointChain),
"--rcheck", "--seconds", strconv.Itoa(svcInfo.stickyMaxAgeMinutes*60), "--reap", "--rcheck", "--seconds", strconv.Itoa(svcInfo.stickyMaxAgeSeconds), "--reap",
"-j", string(endpointChain)) "-j", string(endpointChain))
} }
} }
......
...@@ -180,7 +180,7 @@ func TestGetChainLinesMultipleTables(t *testing.T) { ...@@ -180,7 +180,7 @@ func TestGetChainLinesMultipleTables(t *testing.T) {
func newFakeServiceInfo(service proxy.ServicePortName, ip net.IP, port int, protocol api.Protocol, onlyNodeLocalEndpoints bool) *serviceInfo { func newFakeServiceInfo(service proxy.ServicePortName, ip net.IP, port int, protocol api.Protocol, onlyNodeLocalEndpoints bool) *serviceInfo {
return &serviceInfo{ return &serviceInfo{
sessionAffinityType: api.ServiceAffinityNone, // default sessionAffinityType: api.ServiceAffinityNone, // default
stickyMaxAgeMinutes: 180, // TODO: paramaterize this in the API. stickyMaxAgeSeconds: int(api.DefaultClientIPServiceAffinitySeconds), // default
clusterIP: ip, clusterIP: ip,
port: port, port: port,
protocol: protocol, protocol: protocol,
......
...@@ -28,7 +28,7 @@ type LoadBalancer interface { ...@@ -28,7 +28,7 @@ type LoadBalancer interface {
// NextEndpoint returns the endpoint to handle a request for the given // NextEndpoint returns the endpoint to handle a request for the given
// service-port and source address. // service-port and source address.
NextEndpoint(service proxy.ServicePortName, srcAddr net.Addr, sessionAffinityReset bool) (string, error) NextEndpoint(service proxy.ServicePortName, srcAddr net.Addr, sessionAffinityReset bool) (string, error)
NewService(service proxy.ServicePortName, sessionAffinityType api.ServiceAffinity, stickyMaxAgeMinutes int) error NewService(service proxy.ServicePortName, sessionAffinityType api.ServiceAffinity, stickyMaxAgeSeconds int) error
DeleteService(service proxy.ServicePortName) DeleteService(service proxy.ServicePortName)
CleanupStaleStickySessions(service proxy.ServicePortName) CleanupStaleStickySessions(service proxy.ServicePortName)
ServiceHasEndpoints(service proxy.ServicePortName) bool ServiceHasEndpoints(service proxy.ServicePortName) bool
......
...@@ -61,7 +61,7 @@ type ServiceInfo struct { ...@@ -61,7 +61,7 @@ type ServiceInfo struct {
nodePort int nodePort int
loadBalancerStatus api.LoadBalancerStatus loadBalancerStatus api.LoadBalancerStatus
sessionAffinityType api.ServiceAffinity sessionAffinityType api.ServiceAffinity
stickyMaxAgeMinutes int stickyMaxAgeSeconds int
// Deprecated, but required for back-compat (including e2e) // Deprecated, but required for back-compat (including e2e)
externalIPs []string externalIPs []string
} }
...@@ -380,13 +380,11 @@ func (proxier *Proxier) addServiceOnPort(service proxy.ServicePortName, protocol ...@@ -380,13 +380,11 @@ func (proxier *Proxier) addServiceOnPort(service proxy.ServicePortName, protocol
si := &ServiceInfo{ si := &ServiceInfo{
Timeout: timeout, Timeout: timeout,
ActiveClients: newClientCache(), ActiveClients: newClientCache(),
isAliveAtomic: 1, isAliveAtomic: 1,
proxyPort: portNum, proxyPort: portNum,
protocol: protocol, protocol: protocol,
socket: sock, socket: sock,
sessionAffinityType: api.ServiceAffinityNone, // default sessionAffinityType: api.ServiceAffinityNone, // default
stickyMaxAgeMinutes: 180, // TODO: parameterize this in the API.
} }
proxier.setServiceInfo(service, si) proxier.setServiceInfo(service, si)
...@@ -450,12 +448,17 @@ func (proxier *Proxier) mergeService(service *api.Service) sets.String { ...@@ -450,12 +448,17 @@ func (proxier *Proxier) mergeService(service *api.Service) sets.String {
info.loadBalancerStatus = *helper.LoadBalancerStatusDeepCopy(&service.Status.LoadBalancer) info.loadBalancerStatus = *helper.LoadBalancerStatusDeepCopy(&service.Status.LoadBalancer)
info.nodePort = int(servicePort.NodePort) info.nodePort = int(servicePort.NodePort)
info.sessionAffinityType = service.Spec.SessionAffinity info.sessionAffinityType = service.Spec.SessionAffinity
// Set session affinity timeout value when sessionAffinity==ClientIP
if service.Spec.SessionAffinity == api.ServiceAffinityClientIP {
info.stickyMaxAgeSeconds = int(*service.Spec.SessionAffinityConfig.ClientIP.TimeoutSeconds)
}
glog.V(4).Infof("info: %#v", info) glog.V(4).Infof("info: %#v", info)
if err := proxier.openPortal(serviceName, info); err != nil { if err := proxier.openPortal(serviceName, info); err != nil {
glog.Errorf("Failed to open portal for %q: %v", serviceName, err) glog.Errorf("Failed to open portal for %q: %v", serviceName, err)
} }
proxier.loadBalancer.NewService(serviceName, info.sessionAffinityType, info.stickyMaxAgeMinutes) proxier.loadBalancer.NewService(serviceName, info.sessionAffinityType, info.stickyMaxAgeSeconds)
} }
return existingPorts return existingPorts
......
...@@ -48,7 +48,7 @@ type affinityState struct { ...@@ -48,7 +48,7 @@ type affinityState struct {
type affinityPolicy struct { type affinityPolicy struct {
affinityType api.ServiceAffinity affinityType api.ServiceAffinity
affinityMap map[string]*affinityState // map client IP -> affinity info affinityMap map[string]*affinityState // map client IP -> affinity info
ttlMinutes int ttlSeconds int
} }
// LoadBalancerRR is a round-robin load balancer. // LoadBalancerRR is a round-robin load balancer.
...@@ -66,11 +66,11 @@ type balancerState struct { ...@@ -66,11 +66,11 @@ type balancerState struct {
affinity affinityPolicy affinity affinityPolicy
} }
func newAffinityPolicy(affinityType api.ServiceAffinity, ttlMinutes int) *affinityPolicy { func newAffinityPolicy(affinityType api.ServiceAffinity, ttlSeconds int) *affinityPolicy {
return &affinityPolicy{ return &affinityPolicy{
affinityType: affinityType, affinityType: affinityType,
affinityMap: make(map[string]*affinityState), affinityMap: make(map[string]*affinityState),
ttlMinutes: ttlMinutes, ttlSeconds: ttlSeconds,
} }
} }
...@@ -81,22 +81,22 @@ func NewLoadBalancerRR() *LoadBalancerRR { ...@@ -81,22 +81,22 @@ func NewLoadBalancerRR() *LoadBalancerRR {
} }
} }
func (lb *LoadBalancerRR) NewService(svcPort proxy.ServicePortName, affinityType api.ServiceAffinity, ttlMinutes int) error { func (lb *LoadBalancerRR) NewService(svcPort proxy.ServicePortName, affinityType api.ServiceAffinity, ttlSeconds int) error {
glog.V(4).Infof("LoadBalancerRR NewService %q", svcPort) glog.V(4).Infof("LoadBalancerRR NewService %q", svcPort)
lb.lock.Lock() lb.lock.Lock()
defer lb.lock.Unlock() defer lb.lock.Unlock()
lb.newServiceInternal(svcPort, affinityType, ttlMinutes) lb.newServiceInternal(svcPort, affinityType, ttlSeconds)
return nil return nil
} }
// This assumes that lb.lock is already held. // This assumes that lb.lock is already held.
func (lb *LoadBalancerRR) newServiceInternal(svcPort proxy.ServicePortName, affinityType api.ServiceAffinity, ttlMinutes int) *balancerState { func (lb *LoadBalancerRR) newServiceInternal(svcPort proxy.ServicePortName, affinityType api.ServiceAffinity, ttlSeconds int) *balancerState {
if ttlMinutes == 0 { if ttlSeconds == 0 {
ttlMinutes = 180 //default to 3 hours if not specified. Should 0 be unlimited instead???? ttlSeconds = int(api.DefaultClientIPServiceAffinitySeconds) //default to 3 hours if not specified. Should 0 be unlimited instead????
} }
if _, exists := lb.services[svcPort]; !exists { if _, exists := lb.services[svcPort]; !exists {
lb.services[svcPort] = &balancerState{affinity: *newAffinityPolicy(affinityType, ttlMinutes)} lb.services[svcPort] = &balancerState{affinity: *newAffinityPolicy(affinityType, ttlSeconds)}
glog.V(4).Infof("LoadBalancerRR service %q did not exist, created", svcPort) glog.V(4).Infof("LoadBalancerRR service %q did not exist, created", svcPort)
} else if affinityType != "" { } else if affinityType != "" {
lb.services[svcPort].affinity.affinityType = affinityType lb.services[svcPort].affinity.affinityType = affinityType
...@@ -159,7 +159,7 @@ func (lb *LoadBalancerRR) NextEndpoint(svcPort proxy.ServicePortName, srcAddr ne ...@@ -159,7 +159,7 @@ func (lb *LoadBalancerRR) NextEndpoint(svcPort proxy.ServicePortName, srcAddr ne
} }
if !sessionAffinityReset { if !sessionAffinityReset {
sessionAffinity, exists := state.affinity.affinityMap[ipaddr] sessionAffinity, exists := state.affinity.affinityMap[ipaddr]
if exists && int(time.Now().Sub(sessionAffinity.lastUsed).Minutes()) < state.affinity.ttlMinutes { if exists && int(time.Now().Sub(sessionAffinity.lastUsed).Seconds()) < state.affinity.ttlSeconds {
// Affinity wins. // Affinity wins.
endpoint := sessionAffinity.endpoint endpoint := sessionAffinity.endpoint
sessionAffinity.lastUsed = time.Now() sessionAffinity.lastUsed = time.Now()
...@@ -378,7 +378,7 @@ func (lb *LoadBalancerRR) CleanupStaleStickySessions(svcPort proxy.ServicePortNa ...@@ -378,7 +378,7 @@ func (lb *LoadBalancerRR) CleanupStaleStickySessions(svcPort proxy.ServicePortNa
return return
} }
for ip, affinity := range state.affinity.affinityMap { for ip, affinity := range state.affinity.affinityMap {
if int(time.Now().Sub(affinity.lastUsed).Minutes()) >= state.affinity.ttlMinutes { if int(time.Now().Sub(affinity.lastUsed).Seconds()) >= state.affinity.ttlSeconds {
glog.V(4).Infof("Removing client %s from affinityMap for service %q", affinity.clientIP, svcPort) glog.V(4).Infof("Removing client %s from affinityMap for service %q", affinity.clientIP, svcPort)
delete(state.affinity.affinityMap, ip) delete(state.affinity.affinityMap, ip)
} }
......
...@@ -357,7 +357,7 @@ func TestStickyLoadBalanceWorksWithNewServiceCalledFirst(t *testing.T) { ...@@ -357,7 +357,7 @@ func TestStickyLoadBalanceWorksWithNewServiceCalledFirst(t *testing.T) {
} }
// Call NewService() before OnEndpointsUpdate() // Call NewService() before OnEndpointsUpdate()
loadBalancer.NewService(service, api.ServiceAffinityClientIP, 0) loadBalancer.NewService(service, api.ServiceAffinityClientIP, int(api.DefaultClientIPServiceAffinitySeconds))
endpoints := &api.Endpoints{ endpoints := &api.Endpoints{
ObjectMeta: metav1.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, ObjectMeta: metav1.ObjectMeta{Name: service.Name, Namespace: service.Namespace},
Subsets: []api.EndpointSubset{ Subsets: []api.EndpointSubset{
...@@ -421,7 +421,7 @@ func TestStickyLoadBalanceWorksWithNewServiceCalledSecond(t *testing.T) { ...@@ -421,7 +421,7 @@ func TestStickyLoadBalanceWorksWithNewServiceCalledSecond(t *testing.T) {
}, },
} }
loadBalancer.OnEndpointsAdd(endpoints) loadBalancer.OnEndpointsAdd(endpoints)
loadBalancer.NewService(service, api.ServiceAffinityClientIP, 0) loadBalancer.NewService(service, api.ServiceAffinityClientIP, int(api.DefaultClientIPServiceAffinitySeconds))
client1 := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0} client1 := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}
client2 := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 2), Port: 0} client2 := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 2), Port: 0}
...@@ -473,7 +473,7 @@ func TestStickyLoadBalanaceWorksWithMultipleEndpointsRemoveOne(t *testing.T) { ...@@ -473,7 +473,7 @@ func TestStickyLoadBalanaceWorksWithMultipleEndpointsRemoveOne(t *testing.T) {
t.Errorf("Didn't fail with non-existent service") t.Errorf("Didn't fail with non-existent service")
} }
loadBalancer.NewService(service, api.ServiceAffinityClientIP, 0) loadBalancer.NewService(service, api.ServiceAffinityClientIP, int(api.DefaultClientIPServiceAffinitySeconds))
endpointsv1 := &api.Endpoints{ endpointsv1 := &api.Endpoints{
ObjectMeta: metav1.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, ObjectMeta: metav1.ObjectMeta{Name: service.Name, Namespace: service.Namespace},
Subsets: []api.EndpointSubset{ Subsets: []api.EndpointSubset{
...@@ -546,7 +546,7 @@ func TestStickyLoadBalanceWorksWithMultipleEndpointsAndUpdates(t *testing.T) { ...@@ -546,7 +546,7 @@ func TestStickyLoadBalanceWorksWithMultipleEndpointsAndUpdates(t *testing.T) {
t.Errorf("Didn't fail with non-existent service") t.Errorf("Didn't fail with non-existent service")
} }
loadBalancer.NewService(service, api.ServiceAffinityClientIP, 0) loadBalancer.NewService(service, api.ServiceAffinityClientIP, int(api.DefaultClientIPServiceAffinitySeconds))
endpointsv1 := &api.Endpoints{ endpointsv1 := &api.Endpoints{
ObjectMeta: metav1.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, ObjectMeta: metav1.ObjectMeta{Name: service.Name, Namespace: service.Namespace},
Subsets: []api.EndpointSubset{ Subsets: []api.EndpointSubset{
...@@ -605,7 +605,7 @@ func TestStickyLoadBalanceWorksWithServiceRemoval(t *testing.T) { ...@@ -605,7 +605,7 @@ func TestStickyLoadBalanceWorksWithServiceRemoval(t *testing.T) {
if err == nil || len(endpoint) != 0 { if err == nil || len(endpoint) != 0 {
t.Errorf("Didn't fail with non-existent service") t.Errorf("Didn't fail with non-existent service")
} }
loadBalancer.NewService(fooService, api.ServiceAffinityClientIP, 0) loadBalancer.NewService(fooService, api.ServiceAffinityClientIP, int(api.DefaultClientIPServiceAffinitySeconds))
endpoints1 := &api.Endpoints{ endpoints1 := &api.Endpoints{
ObjectMeta: metav1.ObjectMeta{Name: fooService.Name, Namespace: fooService.Namespace}, ObjectMeta: metav1.ObjectMeta{Name: fooService.Name, Namespace: fooService.Namespace},
Subsets: []api.EndpointSubset{ Subsets: []api.EndpointSubset{
...@@ -616,7 +616,7 @@ func TestStickyLoadBalanceWorksWithServiceRemoval(t *testing.T) { ...@@ -616,7 +616,7 @@ func TestStickyLoadBalanceWorksWithServiceRemoval(t *testing.T) {
}, },
} }
barService := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "bar"}, Port: ""} barService := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "bar"}, Port: ""}
loadBalancer.NewService(barService, api.ServiceAffinityClientIP, 0) loadBalancer.NewService(barService, api.ServiceAffinityClientIP, int(api.DefaultClientIPServiceAffinitySeconds))
endpoints2 := &api.Endpoints{ endpoints2 := &api.Endpoints{
ObjectMeta: metav1.ObjectMeta{Name: barService.Name, Namespace: barService.Namespace}, ObjectMeta: metav1.ObjectMeta{Name: barService.Name, Namespace: barService.Namespace},
Subsets: []api.EndpointSubset{ Subsets: []api.EndpointSubset{
...@@ -674,7 +674,7 @@ func TestStickyLoadBalanceWorksWithEndpointFails(t *testing.T) { ...@@ -674,7 +674,7 @@ func TestStickyLoadBalanceWorksWithEndpointFails(t *testing.T) {
} }
// Call NewService() before OnEndpointsUpdate() // Call NewService() before OnEndpointsUpdate()
loadBalancer.NewService(service, api.ServiceAffinityClientIP, 0) loadBalancer.NewService(service, api.ServiceAffinityClientIP, int(api.DefaultClientIPServiceAffinitySeconds))
endpoints := &api.Endpoints{ endpoints := &api.Endpoints{
ObjectMeta: metav1.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, ObjectMeta: metav1.ObjectMeta{Name: service.Name, Namespace: service.Namespace},
Subsets: []api.EndpointSubset{ Subsets: []api.EndpointSubset{
......
...@@ -36,7 +36,6 @@ import ( ...@@ -36,7 +36,6 @@ import (
) )
const allAvailableInterfaces string = "" const allAvailableInterfaces string = ""
const stickyMaxAgeMinutes int = 180 // TODO: parameterize this in the API.
type portal struct { type portal struct {
ip string ip string
...@@ -360,7 +359,11 @@ func (proxier *Proxier) mergeService(service *api.Service) map[ServicePortPortal ...@@ -360,7 +359,11 @@ func (proxier *Proxier) mergeService(service *api.Service) map[ServicePortPortal
}, },
Port: servicePort.Name, Port: servicePort.Name,
} }
proxier.loadBalancer.NewService(servicePortName, service.Spec.SessionAffinity, stickyMaxAgeMinutes) timeoutSeconds := 0
if service.Spec.SessionAffinity == api.ServiceAffinityClientIP {
timeoutSeconds = int(*service.Spec.SessionAffinityConfig.ClientIP.TimeoutSeconds)
}
proxier.loadBalancer.NewService(servicePortName, service.Spec.SessionAffinity, timeoutSeconds)
} }
} }
......
...@@ -48,7 +48,7 @@ type affinityState struct { ...@@ -48,7 +48,7 @@ type affinityState struct {
type affinityPolicy struct { type affinityPolicy struct {
affinityType api.ServiceAffinity affinityType api.ServiceAffinity
affinityMap map[string]*affinityState // map client IP -> affinity info affinityMap map[string]*affinityState // map client IP -> affinity info
ttlMinutes int ttlSeconds int
} }
// LoadBalancerRR is a round-robin load balancer. // LoadBalancerRR is a round-robin load balancer.
...@@ -66,11 +66,11 @@ type balancerState struct { ...@@ -66,11 +66,11 @@ type balancerState struct {
affinity affinityPolicy affinity affinityPolicy
} }
func newAffinityPolicy(affinityType api.ServiceAffinity, ttlMinutes int) *affinityPolicy { func newAffinityPolicy(affinityType api.ServiceAffinity, ttlSeconds int) *affinityPolicy {
return &affinityPolicy{ return &affinityPolicy{
affinityType: affinityType, affinityType: affinityType,
affinityMap: make(map[string]*affinityState), affinityMap: make(map[string]*affinityState),
ttlMinutes: ttlMinutes, ttlSeconds: ttlSeconds,
} }
} }
...@@ -81,22 +81,22 @@ func NewLoadBalancerRR() *LoadBalancerRR { ...@@ -81,22 +81,22 @@ func NewLoadBalancerRR() *LoadBalancerRR {
} }
} }
func (lb *LoadBalancerRR) NewService(svcPort proxy.ServicePortName, affinityType api.ServiceAffinity, ttlMinutes int) error { func (lb *LoadBalancerRR) NewService(svcPort proxy.ServicePortName, affinityType api.ServiceAffinity, ttlSeconds int) error {
glog.V(4).Infof("LoadBalancerRR NewService %q", svcPort) glog.V(4).Infof("LoadBalancerRR NewService %q", svcPort)
lb.lock.Lock() lb.lock.Lock()
defer lb.lock.Unlock() defer lb.lock.Unlock()
lb.newServiceInternal(svcPort, affinityType, ttlMinutes) lb.newServiceInternal(svcPort, affinityType, ttlSeconds)
return nil return nil
} }
// This assumes that lb.lock is already held. // This assumes that lb.lock is already held.
func (lb *LoadBalancerRR) newServiceInternal(svcPort proxy.ServicePortName, affinityType api.ServiceAffinity, ttlMinutes int) *balancerState { func (lb *LoadBalancerRR) newServiceInternal(svcPort proxy.ServicePortName, affinityType api.ServiceAffinity, ttlSeconds int) *balancerState {
if ttlMinutes == 0 { if ttlSeconds == 0 {
ttlMinutes = 180 //default to 3 hours if not specified. Should 0 be unlimited instead???? ttlSeconds = int(api.DefaultClientIPServiceAffinitySeconds) //default to 3 hours if not specified. Should 0 be unlimited instead????
} }
if _, exists := lb.services[svcPort]; !exists { if _, exists := lb.services[svcPort]; !exists {
lb.services[svcPort] = &balancerState{affinity: *newAffinityPolicy(affinityType, ttlMinutes)} lb.services[svcPort] = &balancerState{affinity: *newAffinityPolicy(affinityType, ttlSeconds)}
glog.V(4).Infof("LoadBalancerRR service %q did not exist, created", svcPort) glog.V(4).Infof("LoadBalancerRR service %q did not exist, created", svcPort)
} else if affinityType != "" { } else if affinityType != "" {
lb.services[svcPort].affinity.affinityType = affinityType lb.services[svcPort].affinity.affinityType = affinityType
...@@ -149,7 +149,7 @@ func (lb *LoadBalancerRR) NextEndpoint(svcPort proxy.ServicePortName, srcAddr ne ...@@ -149,7 +149,7 @@ func (lb *LoadBalancerRR) NextEndpoint(svcPort proxy.ServicePortName, srcAddr ne
} }
if !sessionAffinityReset { if !sessionAffinityReset {
sessionAffinity, exists := state.affinity.affinityMap[ipaddr] sessionAffinity, exists := state.affinity.affinityMap[ipaddr]
if exists && int(time.Now().Sub(sessionAffinity.lastUsed).Minutes()) < state.affinity.ttlMinutes { if exists && int(time.Now().Sub(sessionAffinity.lastUsed).Seconds()) < state.affinity.ttlSeconds {
// Affinity wins. // Affinity wins.
endpoint := sessionAffinity.endpoint endpoint := sessionAffinity.endpoint
sessionAffinity.lastUsed = time.Now() sessionAffinity.lastUsed = time.Now()
...@@ -366,7 +366,7 @@ func (lb *LoadBalancerRR) CleanupStaleStickySessions(svcPort proxy.ServicePortNa ...@@ -366,7 +366,7 @@ func (lb *LoadBalancerRR) CleanupStaleStickySessions(svcPort proxy.ServicePortNa
return return
} }
for ip, affinity := range state.affinity.affinityMap { for ip, affinity := range state.affinity.affinityMap {
if int(time.Now().Sub(affinity.lastUsed).Minutes()) >= state.affinity.ttlMinutes { if int(time.Now().Sub(affinity.lastUsed).Seconds()) >= state.affinity.ttlSeconds {
glog.V(4).Infof("Removing client %s from affinityMap for service %q", affinity.clientIP, svcPort) glog.V(4).Infof("Removing client %s from affinityMap for service %q", affinity.clientIP, svcPort)
delete(state.affinity.affinityMap, ip) delete(state.affinity.affinityMap, ip)
} }
......
...@@ -357,7 +357,7 @@ func TestStickyLoadBalanceWorksWithNewServiceCalledFirst(t *testing.T) { ...@@ -357,7 +357,7 @@ func TestStickyLoadBalanceWorksWithNewServiceCalledFirst(t *testing.T) {
} }
// Call NewService() before OnEndpointsUpdate() // Call NewService() before OnEndpointsUpdate()
loadBalancer.NewService(service, api.ServiceAffinityClientIP, 0) loadBalancer.NewService(service, api.ServiceAffinityClientIP, int(api.DefaultClientIPServiceAffinitySeconds))
endpoints := &api.Endpoints{ endpoints := &api.Endpoints{
ObjectMeta: metav1.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, ObjectMeta: metav1.ObjectMeta{Name: service.Name, Namespace: service.Namespace},
Subsets: []api.EndpointSubset{ Subsets: []api.EndpointSubset{
...@@ -421,7 +421,7 @@ func TestStickyLoadBalanceWorksWithNewServiceCalledSecond(t *testing.T) { ...@@ -421,7 +421,7 @@ func TestStickyLoadBalanceWorksWithNewServiceCalledSecond(t *testing.T) {
}, },
} }
loadBalancer.OnEndpointsAdd(endpoints) loadBalancer.OnEndpointsAdd(endpoints)
loadBalancer.NewService(service, api.ServiceAffinityClientIP, 0) loadBalancer.NewService(service, api.ServiceAffinityClientIP, int(api.DefaultClientIPServiceAffinitySeconds))
client1 := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0} client1 := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}
client2 := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 2), Port: 0} client2 := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 2), Port: 0}
...@@ -473,7 +473,7 @@ func TestStickyLoadBalanaceWorksWithMultipleEndpointsRemoveOne(t *testing.T) { ...@@ -473,7 +473,7 @@ func TestStickyLoadBalanaceWorksWithMultipleEndpointsRemoveOne(t *testing.T) {
t.Errorf("Didn't fail with non-existent service") t.Errorf("Didn't fail with non-existent service")
} }
loadBalancer.NewService(service, api.ServiceAffinityClientIP, 0) loadBalancer.NewService(service, api.ServiceAffinityClientIP, int(api.DefaultClientIPServiceAffinitySeconds))
endpointsv1 := &api.Endpoints{ endpointsv1 := &api.Endpoints{
ObjectMeta: metav1.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, ObjectMeta: metav1.ObjectMeta{Name: service.Name, Namespace: service.Namespace},
Subsets: []api.EndpointSubset{ Subsets: []api.EndpointSubset{
...@@ -546,7 +546,7 @@ func TestStickyLoadBalanceWorksWithMultipleEndpointsAndUpdates(t *testing.T) { ...@@ -546,7 +546,7 @@ func TestStickyLoadBalanceWorksWithMultipleEndpointsAndUpdates(t *testing.T) {
t.Errorf("Didn't fail with non-existent service") t.Errorf("Didn't fail with non-existent service")
} }
loadBalancer.NewService(service, api.ServiceAffinityClientIP, 0) loadBalancer.NewService(service, api.ServiceAffinityClientIP, int(api.DefaultClientIPServiceAffinitySeconds))
endpointsv1 := &api.Endpoints{ endpointsv1 := &api.Endpoints{
ObjectMeta: metav1.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, ObjectMeta: metav1.ObjectMeta{Name: service.Name, Namespace: service.Namespace},
Subsets: []api.EndpointSubset{ Subsets: []api.EndpointSubset{
...@@ -605,7 +605,7 @@ func TestStickyLoadBalanceWorksWithServiceRemoval(t *testing.T) { ...@@ -605,7 +605,7 @@ func TestStickyLoadBalanceWorksWithServiceRemoval(t *testing.T) {
if err == nil || len(endpoint) != 0 { if err == nil || len(endpoint) != 0 {
t.Errorf("Didn't fail with non-existent service") t.Errorf("Didn't fail with non-existent service")
} }
loadBalancer.NewService(fooService, api.ServiceAffinityClientIP, 0) loadBalancer.NewService(fooService, api.ServiceAffinityClientIP, int(api.DefaultClientIPServiceAffinitySeconds))
endpoints1 := &api.Endpoints{ endpoints1 := &api.Endpoints{
ObjectMeta: metav1.ObjectMeta{Name: fooService.Name, Namespace: fooService.Namespace}, ObjectMeta: metav1.ObjectMeta{Name: fooService.Name, Namespace: fooService.Namespace},
Subsets: []api.EndpointSubset{ Subsets: []api.EndpointSubset{
...@@ -616,7 +616,7 @@ func TestStickyLoadBalanceWorksWithServiceRemoval(t *testing.T) { ...@@ -616,7 +616,7 @@ func TestStickyLoadBalanceWorksWithServiceRemoval(t *testing.T) {
}, },
} }
barService := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "bar"}, Port: ""} barService := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "bar"}, Port: ""}
loadBalancer.NewService(barService, api.ServiceAffinityClientIP, 0) loadBalancer.NewService(barService, api.ServiceAffinityClientIP, int(api.DefaultClientIPServiceAffinitySeconds))
endpoints2 := &api.Endpoints{ endpoints2 := &api.Endpoints{
ObjectMeta: metav1.ObjectMeta{Name: barService.Name, Namespace: barService.Namespace}, ObjectMeta: metav1.ObjectMeta{Name: barService.Name, Namespace: barService.Namespace},
Subsets: []api.EndpointSubset{ Subsets: []api.EndpointSubset{
...@@ -674,7 +674,7 @@ func TestStickyLoadBalanceWorksWithEndpointFails(t *testing.T) { ...@@ -674,7 +674,7 @@ func TestStickyLoadBalanceWorksWithEndpointFails(t *testing.T) {
} }
// Call NewService() before OnEndpointsUpdate() // Call NewService() before OnEndpointsUpdate()
loadBalancer.NewService(service, api.ServiceAffinityClientIP, 0) loadBalancer.NewService(service, api.ServiceAffinityClientIP, int(api.DefaultClientIPServiceAffinitySeconds))
endpoints := &api.Endpoints{ endpoints := &api.Endpoints{
ObjectMeta: metav1.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, ObjectMeta: metav1.ObjectMeta{Name: service.Name, Namespace: service.Namespace},
Subsets: []api.EndpointSubset{ Subsets: []api.EndpointSubset{
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -269,6 +269,15 @@ message CinderVolumeSource { ...@@ -269,6 +269,15 @@ message CinderVolumeSource {
optional bool readOnly = 3; optional bool readOnly = 3;
} }
// ClientIPConfig represents the configurations of Client IP based session affinity.
message ClientIPConfig {
// timeoutSeconds specifies the seconds of ClientIP type session sticky time.
// The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP".
// Default value is 10800(for 3 hours).
// +optional
optional int32 timeoutSeconds = 1;
}
// Information about the condition of a component. // Information about the condition of a component.
message ComponentCondition { message ComponentCondition {
// Type of condition for a component. // Type of condition for a component.
...@@ -3864,6 +3873,10 @@ message ServiceSpec { ...@@ -3864,6 +3873,10 @@ message ServiceSpec {
// field. // field.
// +optional // +optional
optional bool publishNotReadyAddresses = 13; optional bool publishNotReadyAddresses = 13;
// sessionAffinityConfig contains the configurations of session affinity.
// +optional
optional SessionAffinityConfig sessionAffinityConfig = 14;
} }
// ServiceStatus represents the current status of a service. // ServiceStatus represents the current status of a service.
...@@ -3874,6 +3887,13 @@ message ServiceStatus { ...@@ -3874,6 +3887,13 @@ message ServiceStatus {
optional LoadBalancerStatus loadBalancer = 1; optional LoadBalancerStatus loadBalancer = 1;
} }
// SessionAffinityConfig represents the configurations of session affinity.
message SessionAffinityConfig {
// clientIP contains the configurations of Client IP based session affinity.
// +optional
optional ClientIPConfig clientIP = 1;
}
// Represents a StorageOS persistent volume resource. // Represents a StorageOS persistent volume resource.
message StorageOSPersistentVolumeSource { message StorageOSPersistentVolumeSource {
// VolumeName is the human-readable name of the StorageOS volume. Volume // VolumeName is the human-readable name of the StorageOS volume. Volume
......
...@@ -2998,6 +2998,22 @@ const ( ...@@ -2998,6 +2998,22 @@ const (
ServiceAffinityNone ServiceAffinity = "None" ServiceAffinityNone ServiceAffinity = "None"
) )
// SessionAffinityConfig represents the configurations of session affinity.
type SessionAffinityConfig struct {
// clientIP contains the configurations of Client IP based session affinity.
// +optional
ClientIP *ClientIPConfig `json:"clientIP,omitempty" protobuf:"bytes,1,opt,name=clientIP"`
}
// ClientIPConfig represents the configurations of Client IP based session affinity.
type ClientIPConfig struct {
// timeoutSeconds specifies the seconds of ClientIP type session sticky time.
// The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP".
// Default value is 10800(for 3 hours).
// +optional
TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty" protobuf:"varint,1,opt,name=timeoutSeconds"`
}
// Service Type string describes ingress methods for a service // Service Type string describes ingress methods for a service
type ServiceType string type ServiceType string
...@@ -3172,6 +3188,9 @@ type ServiceSpec struct { ...@@ -3172,6 +3188,9 @@ type ServiceSpec struct {
// field. // field.
// +optional // +optional
PublishNotReadyAddresses bool `json:"publishNotReadyAddresses,omitempty" protobuf:"varint,13,opt,name=publishNotReadyAddresses"` PublishNotReadyAddresses bool `json:"publishNotReadyAddresses,omitempty" protobuf:"varint,13,opt,name=publishNotReadyAddresses"`
// sessionAffinityConfig contains the configurations of session affinity.
// +optional
SessionAffinityConfig *SessionAffinityConfig `json:"sessionAffinityConfig,omitempty" protobuf:"bytes,14,opt,name=sessionAffinityConfig"`
} }
// ServicePort contains information on service's port. // ServicePort contains information on service's port.
......
...@@ -165,6 +165,15 @@ func (CinderVolumeSource) SwaggerDoc() map[string]string { ...@@ -165,6 +165,15 @@ func (CinderVolumeSource) SwaggerDoc() map[string]string {
return map_CinderVolumeSource return map_CinderVolumeSource
} }
var map_ClientIPConfig = map[string]string{
"": "ClientIPConfig represents the configurations of Client IP based session affinity.",
"timeoutSeconds": "timeoutSeconds specifies the seconds of ClientIP type session sticky time. The value must be >0 && <=86400(for 1 day) if ServiceAffinity == \"ClientIP\". Default value is 10800(for 3 hours).",
}
func (ClientIPConfig) SwaggerDoc() map[string]string {
return map_ClientIPConfig
}
var map_ComponentCondition = map[string]string{ var map_ComponentCondition = map[string]string{
"": "Information about the condition of a component.", "": "Information about the condition of a component.",
"type": "Type of condition for a component. Valid value: \"Healthy\"", "type": "Type of condition for a component. Valid value: \"Healthy\"",
...@@ -1903,6 +1912,7 @@ var map_ServiceSpec = map[string]string{ ...@@ -1903,6 +1912,7 @@ var map_ServiceSpec = map[string]string{
"externalTrafficPolicy": "externalTrafficPolicy denotes if this Service desires to route external traffic to node-local or cluster-wide endpoints. \"Local\" preserves the client source IP and avoids a second hop for LoadBalancer and Nodeport type services, but risks potentially imbalanced traffic spreading. \"Cluster\" obscures the client source IP and may cause a second hop to another node, but should have good overall load-spreading.", "externalTrafficPolicy": "externalTrafficPolicy denotes if this Service desires to route external traffic to node-local or cluster-wide endpoints. \"Local\" preserves the client source IP and avoids a second hop for LoadBalancer and Nodeport type services, but risks potentially imbalanced traffic spreading. \"Cluster\" obscures the client source IP and may cause a second hop to another node, but should have good overall load-spreading.",
"healthCheckNodePort": "healthCheckNodePort specifies the healthcheck nodePort for the service. If not specified, HealthCheckNodePort is created by the service api backend with the allocated nodePort. Will use user-specified nodePort value if specified by the client. Only effects when Type is set to LoadBalancer and ExternalTrafficPolicy is set to Local.", "healthCheckNodePort": "healthCheckNodePort specifies the healthcheck nodePort for the service. If not specified, HealthCheckNodePort is created by the service api backend with the allocated nodePort. Will use user-specified nodePort value if specified by the client. Only effects when Type is set to LoadBalancer and ExternalTrafficPolicy is set to Local.",
"publishNotReadyAddresses": "publishNotReadyAddresses, when set to true, indicates that DNS implementations must publish the notReadyAddresses of subsets for the Endpoints associated with the Service. The default value is false. The primary use case for setting this field is to use a StatefulSet's Headless Service to propagate SRV records for its Pods without respect to their readiness for purpose of peer discovery. This field will replace the service.alpha.kubernetes.io/tolerate-unready-endpoints when that annotation is deprecated and all clients have been converted to use this field.", "publishNotReadyAddresses": "publishNotReadyAddresses, when set to true, indicates that DNS implementations must publish the notReadyAddresses of subsets for the Endpoints associated with the Service. The default value is false. The primary use case for setting this field is to use a StatefulSet's Headless Service to propagate SRV records for its Pods without respect to their readiness for purpose of peer discovery. This field will replace the service.alpha.kubernetes.io/tolerate-unready-endpoints when that annotation is deprecated and all clients have been converted to use this field.",
"sessionAffinityConfig": "sessionAffinityConfig contains the configurations of session affinity.",
} }
func (ServiceSpec) SwaggerDoc() map[string]string { func (ServiceSpec) SwaggerDoc() map[string]string {
...@@ -1918,6 +1928,15 @@ func (ServiceStatus) SwaggerDoc() map[string]string { ...@@ -1918,6 +1928,15 @@ func (ServiceStatus) SwaggerDoc() map[string]string {
return map_ServiceStatus return map_ServiceStatus
} }
var map_SessionAffinityConfig = map[string]string{
"": "SessionAffinityConfig represents the configurations of session affinity.",
"clientIP": "clientIP contains the configurations of Client IP based session affinity.",
}
func (SessionAffinityConfig) SwaggerDoc() map[string]string {
return map_SessionAffinityConfig
}
var map_StorageOSPersistentVolumeSource = map[string]string{ var map_StorageOSPersistentVolumeSource = map[string]string{
"": "Represents a StorageOS persistent volume resource.", "": "Represents a StorageOS persistent volume resource.",
"volumeName": "VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace.", "volumeName": "VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace.",
......
...@@ -87,6 +87,10 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error { ...@@ -87,6 +87,10 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
return nil return nil
}, InType: reflect.TypeOf(&CinderVolumeSource{})}, }, InType: reflect.TypeOf(&CinderVolumeSource{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*ClientIPConfig).DeepCopyInto(out.(*ClientIPConfig))
return nil
}, InType: reflect.TypeOf(&ClientIPConfig{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*ComponentCondition).DeepCopyInto(out.(*ComponentCondition)) in.(*ComponentCondition).DeepCopyInto(out.(*ComponentCondition))
return nil return nil
}, InType: reflect.TypeOf(&ComponentCondition{})}, }, InType: reflect.TypeOf(&ComponentCondition{})},
...@@ -691,6 +695,10 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error { ...@@ -691,6 +695,10 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
return nil return nil
}, InType: reflect.TypeOf(&ServiceStatus{})}, }, InType: reflect.TypeOf(&ServiceStatus{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*SessionAffinityConfig).DeepCopyInto(out.(*SessionAffinityConfig))
return nil
}, InType: reflect.TypeOf(&SessionAffinityConfig{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*StorageOSPersistentVolumeSource).DeepCopyInto(out.(*StorageOSPersistentVolumeSource)) in.(*StorageOSPersistentVolumeSource).DeepCopyInto(out.(*StorageOSPersistentVolumeSource))
return nil return nil
}, InType: reflect.TypeOf(&StorageOSPersistentVolumeSource{})}, }, InType: reflect.TypeOf(&StorageOSPersistentVolumeSource{})},
...@@ -1063,6 +1071,31 @@ func (in *CinderVolumeSource) DeepCopy() *CinderVolumeSource { ...@@ -1063,6 +1071,31 @@ func (in *CinderVolumeSource) DeepCopy() *CinderVolumeSource {
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClientIPConfig) DeepCopyInto(out *ClientIPConfig) {
*out = *in
if in.TimeoutSeconds != nil {
in, out := &in.TimeoutSeconds, &out.TimeoutSeconds
if *in == nil {
*out = nil
} else {
*out = new(int32)
**out = **in
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientIPConfig.
func (in *ClientIPConfig) DeepCopy() *ClientIPConfig {
if in == nil {
return nil
}
out := new(ClientIPConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ComponentCondition) DeepCopyInto(out *ComponentCondition) { func (in *ComponentCondition) DeepCopyInto(out *ComponentCondition) {
*out = *in *out = *in
return return
...@@ -5677,6 +5710,15 @@ func (in *ServiceSpec) DeepCopyInto(out *ServiceSpec) { ...@@ -5677,6 +5710,15 @@ func (in *ServiceSpec) DeepCopyInto(out *ServiceSpec) {
*out = make([]string, len(*in)) *out = make([]string, len(*in))
copy(*out, *in) copy(*out, *in)
} }
if in.SessionAffinityConfig != nil {
in, out := &in.SessionAffinityConfig, &out.SessionAffinityConfig
if *in == nil {
*out = nil
} else {
*out = new(SessionAffinityConfig)
(*in).DeepCopyInto(*out)
}
}
return return
} }
...@@ -5708,6 +5750,31 @@ func (in *ServiceStatus) DeepCopy() *ServiceStatus { ...@@ -5708,6 +5750,31 @@ func (in *ServiceStatus) DeepCopy() *ServiceStatus {
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SessionAffinityConfig) DeepCopyInto(out *SessionAffinityConfig) {
*out = *in
if in.ClientIP != nil {
in, out := &in.ClientIP, &out.ClientIP
if *in == nil {
*out = nil
} else {
*out = new(ClientIPConfig)
(*in).DeepCopyInto(*out)
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SessionAffinityConfig.
func (in *SessionAffinityConfig) DeepCopy() *SessionAffinityConfig {
if in == nil {
return nil
}
out := new(SessionAffinityConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *StorageOSPersistentVolumeSource) DeepCopyInto(out *StorageOSPersistentVolumeSource) { func (in *StorageOSPersistentVolumeSource) DeepCopyInto(out *StorageOSPersistentVolumeSource) {
*out = *in *out = *in
if in.SecretRef != nil { if in.SecretRef != nil {
......
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