@@ -2369,6 +2369,13 @@ var _ = SIGDescribe("Services", func() {
2369
2369
execAffinityTestForNonLBService (f , cs , svc )
2370
2370
})
2371
2371
2372
+ // [LinuxOnly]: Windows does not support session affinity.
2373
+ ginkgo .It ("should have session affinity timeout work for service with type clusterIP [LinuxOnly]" , func () {
2374
+ svc := getServeHostnameService ("affinity-clusterip-timeout" )
2375
+ svc .Spec .Type = v1 .ServiceTypeClusterIP
2376
+ execAffinityTestForSessionAffinityTimeout (f , cs , svc )
2377
+ })
2378
+
2372
2379
// [LinuxOnly]: Windows does not support session affinity.
2373
2380
ginkgo .It ("should be able to switch session affinity for service with type clusterIP [LinuxOnly]" , func () {
2374
2381
svc := getServeHostnameService ("affinity-clusterip-transition" )
@@ -2383,6 +2390,13 @@ var _ = SIGDescribe("Services", func() {
2383
2390
execAffinityTestForNonLBService (f , cs , svc )
2384
2391
})
2385
2392
2393
+ // [LinuxOnly]: Windows does not support session affinity.
2394
+ ginkgo .It ("should have session affinity timeout work for NodePort service [LinuxOnly]" , func () {
2395
+ svc := getServeHostnameService ("affinity-nodeport-timeout" )
2396
+ svc .Spec .Type = v1 .ServiceTypeNodePort
2397
+ execAffinityTestForSessionAffinityTimeout (f , cs , svc )
2398
+ })
2399
+
2386
2400
// [LinuxOnly]: Windows does not support session affinity.
2387
2401
ginkgo .It ("should be able to switch session affinity for NodePort service [LinuxOnly]" , func () {
2388
2402
svc := getServeHostnameService ("affinity-nodeport-transition" )
@@ -3042,6 +3056,69 @@ func execSourceipTest(pausePod v1.Pod, serviceAddress string) (string, string) {
3042
3056
return pausePod .Status .PodIP , host
3043
3057
}
3044
3058
3059
+ // execAffinityTestForSessionAffinityTimeout is a helper function that wrap the logic of
3060
+ // affinity test for non-load-balancer services. Session afinity will be
3061
+ // enabled when the service is created and a short timeout will be configured so
3062
+ // session affinity must change after the timeout expirese.
3063
+ func execAffinityTestForSessionAffinityTimeout (f * framework.Framework , cs clientset.Interface , svc * v1.Service ) {
3064
+ ns := f .Namespace .Name
3065
+ numPods , servicePort , serviceName := 3 , defaultServeHostnameServicePort , svc .ObjectMeta .Name
3066
+ ginkgo .By ("creating service in namespace " + ns )
3067
+ serviceType := svc .Spec .Type
3068
+ // set an affinity timeout equal to the number of connection requests
3069
+ svcSessionAffinityTimeout := int32 (AffinityConfirmCount )
3070
+ svc .Spec .SessionAffinity = v1 .ServiceAffinityClientIP
3071
+ svc .Spec .SessionAffinityConfig = & v1.SessionAffinityConfig {
3072
+ ClientIP : & v1.ClientIPConfig {TimeoutSeconds : & svcSessionAffinityTimeout },
3073
+ }
3074
+ _ , _ , err := StartServeHostnameService (cs , svc , ns , numPods )
3075
+ framework .ExpectNoError (err , "failed to create replication controller with service in the namespace: %s" , ns )
3076
+ defer func () {
3077
+ StopServeHostnameService (cs , ns , serviceName )
3078
+ }()
3079
+ jig := e2eservice .NewTestJig (cs , ns , serviceName )
3080
+ svc , err = jig .Client .CoreV1 ().Services (ns ).Get (context .TODO (), serviceName , metav1.GetOptions {})
3081
+ framework .ExpectNoError (err , "failed to fetch service: %s in namespace: %s" , serviceName , ns )
3082
+ var svcIP string
3083
+ if serviceType == v1 .ServiceTypeNodePort {
3084
+ nodes , err := e2enode .GetReadySchedulableNodes (cs )
3085
+ framework .ExpectNoError (err )
3086
+ addrs := e2enode .CollectAddresses (nodes , v1 .NodeInternalIP )
3087
+ gomega .Expect (len (addrs )).To (gomega .BeNumerically (">" , 0 ), "ginkgo.Failed to get Node internal IP" )
3088
+ svcIP = addrs [0 ]
3089
+ servicePort = int (svc .Spec .Ports [0 ].NodePort )
3090
+ } else {
3091
+ svcIP = svc .Spec .ClusterIP
3092
+ }
3093
+
3094
+ execPod := e2epod .CreateExecPodOrFail (cs , ns , "execpod-affinity" , nil )
3095
+ defer func () {
3096
+ framework .Logf ("Cleaning up the exec pod" )
3097
+ err := cs .CoreV1 ().Pods (ns ).Delete (context .TODO (), execPod .Name , nil )
3098
+ framework .ExpectNoError (err , "failed to delete pod: %s in namespace: %s" , execPod .Name , ns )
3099
+ }()
3100
+ err = jig .CheckServiceReachability (svc , execPod )
3101
+ framework .ExpectNoError (err )
3102
+
3103
+ // the service should be sticky until the timeout expires
3104
+ framework .ExpectEqual (checkAffinity (execPod , svcIP , servicePort , true ), true )
3105
+ // but it should return different hostnames after the timeout expires
3106
+ // try several times to avoid the probability that we hit the same pod twice
3107
+ hosts := sets .NewString ()
3108
+ cmd := fmt .Sprintf (`curl -q -s --connect-timeout 2 http://%s/` , net .JoinHostPort (svcIP , strconv .Itoa (servicePort )))
3109
+ for i := 0 ; i < 10 ; i ++ {
3110
+ hostname , err := framework .RunHostCmd (execPod .Namespace , execPod .Name , cmd )
3111
+ if err == nil {
3112
+ hosts .Insert (hostname )
3113
+ if hosts .Len () > 1 {
3114
+ return
3115
+ }
3116
+ time .Sleep (time .Duration (svcSessionAffinityTimeout ) * time .Second )
3117
+ }
3118
+ }
3119
+ framework .Fail ("Session is sticky after reaching the timeout" )
3120
+ }
3121
+
3045
3122
func execAffinityTestForNonLBServiceWithTransition (f * framework.Framework , cs clientset.Interface , svc * v1.Service ) {
3046
3123
execAffinityTestForNonLBServiceWithOptionalTransition (f , cs , svc , true )
3047
3124
}
0 commit comments