commit
b5619cc978
6 changed files with 91 additions and 7 deletions
|
@ -39,10 +39,19 @@ const (
|
||||||
// one isn't supplied and affinity is set to "cookie".
|
// one isn't supplied and affinity is set to "cookie".
|
||||||
annotationAffinityCookieHash = "session-cookie-hash"
|
annotationAffinityCookieHash = "session-cookie-hash"
|
||||||
defaultAffinityCookieHash = "md5"
|
defaultAffinityCookieHash = "md5"
|
||||||
|
|
||||||
|
// This is used to control the cookie expires, its value is a number of seconds until the
|
||||||
|
// cookie expires
|
||||||
|
annotationAffinityCookieExpires = "session-cookie-expires"
|
||||||
|
|
||||||
|
// This is used to control the cookie expires, its value is a number of seconds until the
|
||||||
|
// cookie expires
|
||||||
|
annotationAffinityCookieMaxAge = "session-cookie-max-age"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
affinityCookieHashRegex = regexp.MustCompile(`^(index|md5|sha1)$`)
|
affinityCookieHashRegex = regexp.MustCompile(`^(index|md5|sha1)$`)
|
||||||
|
affinityCookieExpiresRegex = regexp.MustCompile(`(^0|-?[1-9]\d*$)`)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config describes the per ingress session affinity config
|
// Config describes the per ingress session affinity config
|
||||||
|
@ -58,17 +67,23 @@ type Cookie struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
// The hash that will be used to encode the cookie in case of cookie affinity type
|
// The hash that will be used to encode the cookie in case of cookie affinity type
|
||||||
Hash string `json:"hash"`
|
Hash string `json:"hash"`
|
||||||
|
// The time duration to control cookie expires
|
||||||
|
Expires string `json:"expires"`
|
||||||
|
// The number of seconds until the cookie expires
|
||||||
|
MaxAge string `json:"maxage"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// cookieAffinityParse gets the annotation values related to Cookie Affinity
|
// cookieAffinityParse gets the annotation values related to Cookie Affinity
|
||||||
// It also sets default values when no value or incorrect value is found
|
// It also sets default values when no value or incorrect value is found
|
||||||
func (a affinity) cookieAffinityParse(ing *extensions.Ingress) *Cookie {
|
func (a affinity) cookieAffinityParse(ing *extensions.Ingress) *Cookie {
|
||||||
|
cookie := &Cookie{}
|
||||||
sn, err := parser.GetStringAnnotation(annotationAffinityCookieName, ing)
|
sn, err := parser.GetStringAnnotation(annotationAffinityCookieName, ing)
|
||||||
|
|
||||||
if err != nil || sn == "" {
|
if err != nil || sn == "" {
|
||||||
glog.V(3).Infof("Ingress %v: No value found in annotation %v. Using the default %v", ing.Name, annotationAffinityCookieName, defaultAffinityCookieName)
|
glog.V(3).Infof("Ingress %v: No value found in annotation %v. Using the default %v", ing.Name, annotationAffinityCookieName, defaultAffinityCookieName)
|
||||||
sn = defaultAffinityCookieName
|
sn = defaultAffinityCookieName
|
||||||
}
|
}
|
||||||
|
cookie.Name = sn
|
||||||
|
|
||||||
sh, err := parser.GetStringAnnotation(annotationAffinityCookieHash, ing)
|
sh, err := parser.GetStringAnnotation(annotationAffinityCookieHash, ing)
|
||||||
|
|
||||||
|
@ -76,11 +91,22 @@ func (a affinity) cookieAffinityParse(ing *extensions.Ingress) *Cookie {
|
||||||
glog.V(3).Infof("Invalid or no annotation value found in Ingress %v: %v. Setting it to default %v", ing.Name, annotationAffinityCookieHash, defaultAffinityCookieHash)
|
glog.V(3).Infof("Invalid or no annotation value found in Ingress %v: %v. Setting it to default %v", ing.Name, annotationAffinityCookieHash, defaultAffinityCookieHash)
|
||||||
sh = defaultAffinityCookieHash
|
sh = defaultAffinityCookieHash
|
||||||
}
|
}
|
||||||
|
cookie.Hash = sh
|
||||||
|
|
||||||
return &Cookie{
|
se, err := parser.GetStringAnnotation(annotationAffinityCookieExpires, ing)
|
||||||
Name: sn,
|
if err != nil || !affinityCookieExpiresRegex.MatchString(se) {
|
||||||
Hash: sh,
|
glog.V(3).Infof("Invalid or no annotation value found in Ingress %v: %v. Ignoring it", ing.Name, annotationAffinityCookieExpires)
|
||||||
|
se = ""
|
||||||
}
|
}
|
||||||
|
cookie.Expires = se
|
||||||
|
|
||||||
|
sm, err := parser.GetStringAnnotation(annotationAffinityCookieMaxAge, ing)
|
||||||
|
if err != nil || !affinityCookieExpiresRegex.MatchString(sm) {
|
||||||
|
glog.V(3).Infof("Invalid or no annotation value found in Ingress %v: %v. Ignoring it", ing.Name, annotationAffinityCookieMaxAge)
|
||||||
|
sm = ""
|
||||||
|
}
|
||||||
|
cookie.MaxAge = sm
|
||||||
|
return cookie
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewParser creates a new Affinity annotation parser
|
// NewParser creates a new Affinity annotation parser
|
||||||
|
|
|
@ -69,6 +69,8 @@ func TestIngressAffinityCookieConfig(t *testing.T) {
|
||||||
data[parser.GetAnnotationWithPrefix(annotationAffinityType)] = "cookie"
|
data[parser.GetAnnotationWithPrefix(annotationAffinityType)] = "cookie"
|
||||||
data[parser.GetAnnotationWithPrefix(annotationAffinityCookieHash)] = "sha123"
|
data[parser.GetAnnotationWithPrefix(annotationAffinityCookieHash)] = "sha123"
|
||||||
data[parser.GetAnnotationWithPrefix(annotationAffinityCookieName)] = "INGRESSCOOKIE"
|
data[parser.GetAnnotationWithPrefix(annotationAffinityCookieName)] = "INGRESSCOOKIE"
|
||||||
|
data[parser.GetAnnotationWithPrefix(annotationAffinityCookieExpires)] = "4500"
|
||||||
|
data[parser.GetAnnotationWithPrefix(annotationAffinityCookieMaxAge)] = "3000"
|
||||||
ing.SetAnnotations(data)
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
affin, _ := NewParser(&resolver.Mock{}).Parse(ing)
|
affin, _ := NewParser(&resolver.Mock{}).Parse(ing)
|
||||||
|
@ -88,4 +90,12 @@ func TestIngressAffinityCookieConfig(t *testing.T) {
|
||||||
if nginxAffinity.Cookie.Name != "INGRESSCOOKIE" {
|
if nginxAffinity.Cookie.Name != "INGRESSCOOKIE" {
|
||||||
t.Errorf("expected route as sticky-name but returned %v", nginxAffinity.Cookie.Name)
|
t.Errorf("expected route as sticky-name but returned %v", nginxAffinity.Cookie.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if nginxAffinity.Cookie.Expires != "4500" {
|
||||||
|
t.Errorf("expected 1h as sticky-expires but returned %v", nginxAffinity.Cookie.Expires)
|
||||||
|
}
|
||||||
|
|
||||||
|
if nginxAffinity.Cookie.MaxAge != "3000" {
|
||||||
|
t.Errorf("expected 3000 as sticky-max-age but returned %v", nginxAffinity.Cookie.MaxAge)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -406,6 +406,8 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([]
|
||||||
if anns.SessionAffinity.Type == "cookie" {
|
if anns.SessionAffinity.Type == "cookie" {
|
||||||
ups.SessionAffinity.CookieSessionAffinity.Name = anns.SessionAffinity.Cookie.Name
|
ups.SessionAffinity.CookieSessionAffinity.Name = anns.SessionAffinity.Cookie.Name
|
||||||
ups.SessionAffinity.CookieSessionAffinity.Hash = anns.SessionAffinity.Cookie.Hash
|
ups.SessionAffinity.CookieSessionAffinity.Hash = anns.SessionAffinity.Cookie.Hash
|
||||||
|
ups.SessionAffinity.CookieSessionAffinity.Expires = anns.SessionAffinity.Cookie.Expires
|
||||||
|
ups.SessionAffinity.CookieSessionAffinity.MaxAge = anns.SessionAffinity.Cookie.MaxAge
|
||||||
|
|
||||||
locs := ups.SessionAffinity.CookieSessionAffinity.Locations
|
locs := ups.SessionAffinity.CookieSessionAffinity.Locations
|
||||||
if _, ok := locs[host]; !ok {
|
if _, ok := locs[host]; !ok {
|
||||||
|
|
|
@ -109,6 +109,8 @@ type SessionAffinityConfig struct {
|
||||||
type CookieSessionAffinity struct {
|
type CookieSessionAffinity struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Hash string `json:"hash"`
|
Hash string `json:"hash"`
|
||||||
|
Expires string `json:"expires,omitempty"`
|
||||||
|
MaxAge string `json:"maxage,omitempty"`
|
||||||
Locations map[string][]string `json:"locations,omitempty"`
|
Locations map[string][]string `json:"locations,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,8 @@ function _M.new(self, backend)
|
||||||
local o = {
|
local o = {
|
||||||
instance = self.factory:new(nodes),
|
instance = self.factory:new(nodes),
|
||||||
cookie_name = backend["sessionAffinityConfig"]["cookieSessionAffinity"]["name"] or "route",
|
cookie_name = backend["sessionAffinityConfig"]["cookieSessionAffinity"]["name"] or "route",
|
||||||
|
cookie_expires = backend["sessionAffinityConfig"]["cookieSessionAffinity"]["expires"],
|
||||||
|
cookie_max_age = backend["sessionAffinityConfig"]["cookieSessionAffinity"]["maxage"],
|
||||||
digest_func = digest_func,
|
digest_func = digest_func,
|
||||||
}
|
}
|
||||||
setmetatable(o, self)
|
setmetatable(o, self)
|
||||||
|
@ -37,14 +39,24 @@ local function set_cookie(self, value)
|
||||||
ngx.log(ngx.ERR, err)
|
ngx.log(ngx.ERR, err)
|
||||||
end
|
end
|
||||||
|
|
||||||
local ok
|
local cookie_data = {
|
||||||
ok, err = cookie:set({
|
|
||||||
key = self.cookie_name,
|
key = self.cookie_name,
|
||||||
value = value,
|
value = value,
|
||||||
path = ngx.var.location_path,
|
path = ngx.var.location_path,
|
||||||
domain = ngx.var.host,
|
domain = ngx.var.host,
|
||||||
httponly = true,
|
httponly = true,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
if self.cookie_expires and self.cookie_expires ~= "" then
|
||||||
|
cookie_data.expires = ngx.cookie_time(tonumber(self.cookie_expires))
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.cookie_max_age and self.cookie_max_age ~= "" then
|
||||||
|
cookie_data.max_age = tonumber(self.cookie_max_age)
|
||||||
|
end
|
||||||
|
|
||||||
|
local ok
|
||||||
|
ok, err = cookie:set(cookie_data)
|
||||||
if not ok then
|
if not ok then
|
||||||
ngx.log(ngx.ERR, err)
|
ngx.log(ngx.ERR, err)
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
|
@ -191,4 +192,35 @@ var _ = framework.IngressNginxDescribe("Annotations - Affinity/Sticky Sessions",
|
||||||
Expect(resp.StatusCode).Should(Equal(http.StatusOK))
|
Expect(resp.StatusCode).Should(Equal(http.StatusOK))
|
||||||
Expect(resp.Header.Get("Set-Cookie")).Should(ContainSubstring("Path=/somewhereelese;"))
|
Expect(resp.Header.Get("Set-Cookie")).Should(ContainSubstring("Path=/somewhereelese;"))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("should set cookie with expires", func() {
|
||||||
|
host := "cookie.foo.com"
|
||||||
|
annotations := map[string]string{
|
||||||
|
"nginx.ingress.kubernetes.io/affinity": "cookie",
|
||||||
|
"nginx.ingress.kubernetes.io/session-cookie-name": "ExpiresCookie",
|
||||||
|
"nginx.ingress.kubernetes.io/session-cookie-expires": "172800",
|
||||||
|
"nginx.ingress.kubernetes.io/session-cookie-max-age": "259200",
|
||||||
|
}
|
||||||
|
|
||||||
|
ing := framework.NewSingleIngress(host, "/", host, f.IngressController.Namespace, "http-svc", 80, &annotations)
|
||||||
|
f.EnsureIngress(ing)
|
||||||
|
|
||||||
|
f.WaitForNginxServer(host,
|
||||||
|
func(server string) bool {
|
||||||
|
return strings.Contains(server, fmt.Sprintf("server_name %s ;", host))
|
||||||
|
})
|
||||||
|
|
||||||
|
resp, _, errs := gorequest.New().
|
||||||
|
Get(f.IngressController.HTTPURL).
|
||||||
|
Set("Host", host).
|
||||||
|
End()
|
||||||
|
|
||||||
|
Expect(len(errs)).Should(BeNumerically("==", 0))
|
||||||
|
Expect(resp.StatusCode).Should(Equal(http.StatusOK))
|
||||||
|
local, _ := time.LoadLocation("GMT")
|
||||||
|
duration, _ := time.ParseDuration("48h")
|
||||||
|
expected := time.Date(1970, time.January, 1, 0, 0, 0, 0, local).Add(duration).Format("Mon, 02-Jan-06 15:04:05 MST")
|
||||||
|
Expect(resp.Header.Get("Set-Cookie")).Should(ContainSubstring(fmt.Sprintf("Expires=%s", expected)))
|
||||||
|
Expect(resp.Header.Get("Set-Cookie")).Should(ContainSubstring("Max-Age=259200"))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue