Paramaterize stickyMaxAgeMinutes for service in API

pull/6/head
m1093782566 2017-08-02 00:09:37 +08:00
parent 41aeccd088
commit c355a2ac96
16 changed files with 349 additions and 69 deletions

View File

@ -15,6 +15,11 @@
"name": "meteor" "name": "meteor"
}, },
"sessionAffinity": "ClientIP", "sessionAffinity": "ClientIP",
"sessionAffinityConfig": {
"clientIP": {
"timeoutSeconds": 90
}
},
"type": "LoadBalancer" "type": "LoadBalancer"
} }
} }

View File

@ -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 {
// +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."

View File

@ -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 (
"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 {
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 {
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"))

View File

@ -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) {
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) {
} }
} }
} }
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)
}
}
}

View File

@ -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
ClusterIP: serviceIP.String(), ClusterIP: serviceIP.String(),
SessionAffinity: api.ServiceAffinityClientIP, SessionAffinity: api.ServiceAffinityClientIP,
Type: serviceType, Type: serviceType,
SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
}, },
} }

View File

@ -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,7 +571,12 @@ 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,
Type: api.ServiceTypeClusterIP, SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP,
}, },
}, },
}, },
@ -625,7 +631,12 @@ 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,
Type: api.ServiceTypeClusterIP, SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP,
}, },
}, },
expectUpdate: &api.Service{ expectUpdate: &api.Service{
@ -637,7 +648,12 @@ 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,
Type: api.ServiceTypeClusterIP, SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP,
}, },
}, },
}, },
@ -658,7 +674,12 @@ 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,
Type: api.ServiceTypeClusterIP, SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP,
}, },
}, },
expectUpdate: &api.Service{ expectUpdate: &api.Service{
@ -671,7 +692,12 @@ 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,
Type: api.ServiceTypeClusterIP, SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP,
}, },
}, },
}, },
@ -691,7 +717,12 @@ 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,
Type: api.ServiceTypeClusterIP, SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP,
}, },
}, },
expectUpdate: &api.Service{ expectUpdate: &api.Service{
@ -703,7 +734,12 @@ 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,
Type: api.ServiceTypeClusterIP, SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP,
}, },
}, },
}, },
@ -723,7 +759,12 @@ 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,
Type: api.ServiceTypeClusterIP, SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP,
}, },
}, },
expectUpdate: &api.Service{ expectUpdate: &api.Service{
@ -735,7 +776,12 @@ 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,
Type: api.ServiceTypeClusterIP, SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP,
}, },
}, },
}, },
@ -755,7 +801,12 @@ 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,
Type: api.ServiceTypeClusterIP, SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP,
}, },
}, },
expectUpdate: &api.Service{ expectUpdate: &api.Service{
@ -767,7 +818,12 @@ 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,
Type: api.ServiceTypeClusterIP, SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP,
}, },
}, },
}, },
@ -787,7 +843,12 @@ 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,
Type: api.ServiceTypeClusterIP, SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP,
}, },
}, },
expectUpdate: &api.Service{ expectUpdate: &api.Service{
@ -799,7 +860,12 @@ 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,
Type: api.ServiceTypeClusterIP, SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP,
}, },
}, },
}, },
@ -819,7 +885,12 @@ 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,
Type: api.ServiceTypeNodePort, SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeNodePort,
}, },
}, },
expectUpdate: &api.Service{ expectUpdate: &api.Service{
@ -831,7 +902,12 @@ 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,
Type: api.ServiceTypeClusterIP, SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP,
}, },
}, },
}, },
@ -851,7 +927,12 @@ 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,
Type: api.ServiceTypeClusterIP, SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP,
}, },
}, },
expectUpdate: nil, expectUpdate: nil,
@ -910,7 +991,12 @@ 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,
Type: api.ServiceTypeClusterIP, SessionAffinityConfig: &api.SessionAffinityConfig{
ClientIP: &api.ClientIPConfig{
TimeoutSeconds: &timeoutSeconds,
},
},
Type: api.ServiceTypeClusterIP,
}, },
}, },
expectUpdate: nil, expectUpdate: nil,

View File

@ -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
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
// 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)
@ -1440,7 +1445,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))
} }
} }

View File

@ -178,8 +178,8 @@ 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,

View File

@ -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

View File

@ -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
} }
@ -378,15 +378,13 @@ func (proxier *Proxier) addServiceOnPort(service proxy.ServicePortName, protocol
return nil, err return nil, err
} }
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 {
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

View File

@ -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 {
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 {
} }
} }
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
} }
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
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)
} }

View File

@ -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) {
}, },
} }
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) {
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) {
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) {
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) {
}, },
} }
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) {
} }
// 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{

View File

@ -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
}, },
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)
} }
} }

View File

@ -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 {
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 {
} }
} }
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
} }
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
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)
} }

View File

@ -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) {
}, },
} }
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) {
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) {
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) {
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) {
}, },
} }
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) {
} }
// 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{

View File

@ -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 {
// 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.