174 lines
6.4 KiB
Go
174 lines
6.4 KiB
Go
![]() |
/*
|
||
|
Copyright 2020 The Kubernetes Authors.
|
||
|
|
||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
you may not use this file except in compliance with the License.
|
||
|
You may obtain a copy of the License at
|
||
|
|
||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
||
|
Unless required by applicable law or agreed to in writing, software
|
||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
See the License for the specific language governing permissions and
|
||
|
limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package annotations
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"net/http"
|
||
|
"reflect"
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
"github.com/onsi/ginkgo"
|
||
|
"github.com/stretchr/testify/assert"
|
||
|
|
||
|
"k8s.io/ingress-nginx/test/e2e/framework"
|
||
|
)
|
||
|
|
||
|
var _ = framework.DescribeAnnotation("affinitymode", func() {
|
||
|
f := framework.NewDefaultFramework("affinity")
|
||
|
|
||
|
ginkgo.It("Balanced affinity mode should balance", func() {
|
||
|
deploymentName := "affinitybalanceecho"
|
||
|
replicas := 5
|
||
|
f.NewEchoDeploymentWithNameAndReplicas(deploymentName, replicas)
|
||
|
|
||
|
host := "affinity-mode-balance.com"
|
||
|
annotations := make(map[string]string)
|
||
|
annotations["nginx.ingress.kubernetes.io/affinity"] = "cookie"
|
||
|
annotations["ginx.ingress.kubernetes.io/session-cookie-name"] = "hello-cookie"
|
||
|
annotations["nginx.ingress.kubernetes.io/session-cookie-expires"] = "172800"
|
||
|
annotations["nginx.ingress.kubernetes.io/session-cookie-max-age"] = "172800"
|
||
|
annotations["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
|
||
|
annotations["nginx.ingress.kubernetes.io/affinity-mode"] = "balanced"
|
||
|
annotations["nginx.ingress.kubernetes.io/session-cookie-hash"] = "sha1"
|
||
|
|
||
|
ing := framework.NewSingleIngress(host, "/", host, f.Namespace, deploymentName, 80, annotations)
|
||
|
f.EnsureIngress(ing)
|
||
|
|
||
|
f.WaitForNginxServer(host,
|
||
|
func(server string) bool {
|
||
|
return strings.Contains(server, fmt.Sprintf("server_name %s ;", host))
|
||
|
})
|
||
|
|
||
|
// Check configuration
|
||
|
ingress := f.GetIngress(f.Namespace, host)
|
||
|
returnedAnnotations := ingress.GetAnnotations()
|
||
|
isItEqual := reflect.DeepEqual(annotations, returnedAnnotations)
|
||
|
assert.Equal(ginkgo.GinkgoT(), isItEqual, true)
|
||
|
})
|
||
|
|
||
|
ginkgo.It("Check persistent affinity mode", func() {
|
||
|
deploymentName := "affinitypersistentecho"
|
||
|
replicas := 5
|
||
|
f.NewEchoDeploymentWithNameAndReplicas(deploymentName, replicas)
|
||
|
|
||
|
host := "affinity-mode-persistent.com"
|
||
|
annotations := make(map[string]string)
|
||
|
annotations["nginx.ingress.kubernetes.io/affinity"] = "cookie"
|
||
|
annotations["ginx.ingress.kubernetes.io/session-cookie-name"] = "hello-cookie"
|
||
|
annotations["nginx.ingress.kubernetes.io/session-cookie-expires"] = "172800"
|
||
|
annotations["nginx.ingress.kubernetes.io/session-cookie-max-age"] = "172800"
|
||
|
annotations["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
|
||
|
annotations["nginx.ingress.kubernetes.io/affinity-mode"] = "persistent"
|
||
|
annotations["nginx.ingress.kubernetes.io/session-cookie-hash"] = "sha1"
|
||
|
|
||
|
ing := framework.NewSingleIngress(host, "/", host, f.Namespace, deploymentName, 80, annotations)
|
||
|
f.EnsureIngress(ing)
|
||
|
|
||
|
f.WaitForNginxServer(host,
|
||
|
func(server string) bool {
|
||
|
return strings.Contains(server, fmt.Sprintf("server_name %s ;", host))
|
||
|
})
|
||
|
|
||
|
// Check configuration
|
||
|
ingress := f.GetIngress(f.Namespace, host)
|
||
|
returnedAnnotations := ingress.GetAnnotations()
|
||
|
isItEqual := reflect.DeepEqual(annotations, returnedAnnotations)
|
||
|
assert.Equal(ginkgo.GinkgoT(), isItEqual, true)
|
||
|
|
||
|
// Make a request
|
||
|
request := f.HTTPTestClient().GET("/").WithHeader("Host", host)
|
||
|
response := request.Expect()
|
||
|
|
||
|
// Get the responder host name
|
||
|
originalHostName := getHostnameFromResponseBody(response.Body().Raw())
|
||
|
|
||
|
// Send new requests and add new backends. Check which backend responded to the sent request
|
||
|
cookies := getCookiesFromHeader(response.Header("Set-Cookie").Raw())
|
||
|
for sendRequestNumber := 0; sendRequestNumber < 10; sendRequestNumber++ {
|
||
|
replicas = replicas + 1
|
||
|
err := framework.UpdateDeployment(f.KubeClientSet, f.Namespace, deploymentName, replicas, nil)
|
||
|
assert.Nil(ginkgo.GinkgoT(), err)
|
||
|
time.Sleep(3 * time.Second)
|
||
|
response = request.WithCookies(cookies).Expect()
|
||
|
newHostName := getHostnameFromResponseBody(response.Body().Raw())
|
||
|
assert.Equal(ginkgo.GinkgoT(), originalHostName, newHostName,
|
||
|
fmt.Sprintf("Response number %v is not from the same host. Original host: %s, response returned: %s", sendRequestNumber, originalHostName, newHostName))
|
||
|
|
||
|
}
|
||
|
|
||
|
// remove all backends
|
||
|
replicas = 0
|
||
|
err := framework.UpdateDeployment(f.KubeClientSet, f.Namespace, deploymentName, replicas, nil)
|
||
|
assert.Nil(ginkgo.GinkgoT(), err)
|
||
|
time.Sleep(5 * time.Second)
|
||
|
|
||
|
// validate, there is no backend to serve the request
|
||
|
response = request.WithCookies(cookies).Expect().Status(http.StatusServiceUnavailable)
|
||
|
|
||
|
// create brand new backends
|
||
|
replicas = 2
|
||
|
err = framework.UpdateDeployment(f.KubeClientSet, f.Namespace, deploymentName, replicas, nil)
|
||
|
assert.Nil(ginkgo.GinkgoT(), err)
|
||
|
time.Sleep(5 * time.Second)
|
||
|
|
||
|
// wait brand new backends to spawn
|
||
|
response = request.WithCookies(cookies).Expect()
|
||
|
try := 0
|
||
|
for (response.Raw().StatusCode == http.StatusServiceUnavailable) && (try < 30) {
|
||
|
time.Sleep(5 * time.Second)
|
||
|
response = request.WithCookies(cookies).Expect()
|
||
|
try++
|
||
|
}
|
||
|
assert.LessOrEqual(ginkgo.GinkgoT(), try, 29, "Tries reached it's maximum, backends did not deployed in time.")
|
||
|
|
||
|
// brand new backends equals new hostname
|
||
|
newHostName := getHostnameFromResponseBody(response.Body().Raw())
|
||
|
assert.NotEqual(ginkgo.GinkgoT(), originalHostName, newHostName,
|
||
|
fmt.Sprintf("Response is from the same host (That should not be possible). Original host: %s, response returned: %s", originalHostName, newHostName))
|
||
|
})
|
||
|
})
|
||
|
|
||
|
func getHostnameFromResponseBody(rawResponseBody string) string {
|
||
|
lines := strings.Split(strings.TrimSpace(rawResponseBody), "\n")
|
||
|
for _, line := range lines {
|
||
|
if strings.Contains(line, "Hostname") {
|
||
|
hostnameParts := strings.Split(strings.TrimSpace(line), ":")
|
||
|
if len(hostnameParts) == 2 {
|
||
|
return strings.TrimSpace(hostnameParts[1])
|
||
|
}
|
||
|
return ""
|
||
|
}
|
||
|
}
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
func getCookiesFromHeader(rawheader string) map[string]string {
|
||
|
cookies := make(map[string]string)
|
||
|
parts := strings.Split(strings.TrimSpace(rawheader), ";")
|
||
|
for _, part := range parts {
|
||
|
subparts := strings.Split(strings.TrimSpace(part), "=")
|
||
|
if len(subparts) == 2 {
|
||
|
cookies[subparts[0]] = subparts[1]
|
||
|
} else {
|
||
|
cookies[subparts[0]] = ""
|
||
|
}
|
||
|
}
|
||
|
return cookies
|
||
|
}
|