Skip to content

Commit

Permalink
Merge branch 'cookie-affinity'
Browse files Browse the repository at this point in the history
  • Loading branch information
tatsuhiro-t committed Nov 27, 2017
2 parents b643002 + 4d576c7 commit de32c08
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 31 deletions.
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,9 +267,26 @@ is the JSON dictionary, and can contain the following key value pairs:
dynamically.

* `affinity`: Specify session affinity method. Specifying `ip`
enables client IP based session affinity. Specifying `none` or
enables client IP based session affinity. Specifying `cookie`
enables cookie-based session affinity. Specifying `none` or
omitting this key disables session affinity.

If `cookie` is specified, additional configuration is required. See
`affinityCookieName`, `affinityCookiePath`, and
`affinityCookieSecure` fields.

* `affinityCookieName`: Specify a name of cookie to use. This is
required field if `cookie` is set in `affinity` field.

* `affinityCookiePath`: Specify a path of cookie path. This is
optional, and if not set, cookie path is not set.

* `affinityCookieSecure`: Specify whether Secure attribute of cookie
is added, or not. Omitting this field, specifying empty string, or
specifying "auto" sets Secure attribute if client connection is TLS
encrypted. If "yes" is specified, Secure attribute is always added.
If "no" is specified, Secure attribute is always omitted.

The following example specifies HTTP/2 as backend connection for
service "greeter", and service port "50051":

Expand Down
2 changes: 1 addition & 1 deletion nghttpx-backend.tmpl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{{ range $upstream := .Upstreams -}}
# {{ $upstream.Name }}
{{ range $backend := $upstream.Backends -}}
backend={{ $backend.Address }},{{ $backend.Port }};{{ $upstream.Host }}{{ $upstream.Path }};proto={{ $backend.Protocol }}{{ if $backend.TLS }};tls{{ end }}{{ if $backend.SNI }};sni={{ $backend.SNI }}{{ end }}{{ if $backend.DNS }};dns{{ end }};affinity={{ $backend.Affinity }}{{ if $upstream.RedirectIfNotTLS }};redirect-if-not-tls{{ end}}
backend={{ $backend.Address }},{{ $backend.Port }};{{ $upstream.Host }}{{ $upstream.Path }};proto={{ $backend.Protocol }}{{ if $backend.TLS }};tls{{ end }}{{ if $backend.SNI }};sni={{ $backend.SNI }}{{ end }}{{ if $backend.DNS }};dns{{ end }};affinity={{ $backend.Affinity }}{{ if eq $backend.Affinity "cookie" }}{{ if $backend.AffinityCookieName }};affinity-cookie-name={{ $backend.AffinityCookieName }}{{ end }}{{ if $backend.AffinityCookiePath }};affinity-cookie-path={{ $backend.AffinityCookiePath }}{{ end }};affinity-cookie-secure={{ $backend.AffinityCookieSecure }}{{ end }}{{ if $upstream.RedirectIfNotTLS }};redirect-if-not-tls{{ end}}
{{ end -}}
{{ end }}
19 changes: 12 additions & 7 deletions pkg/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -1148,20 +1148,25 @@ func (lbc *LoadBalancerController) getEndpoints(s *v1.Service, servicePort *v1.S
for i, _ := range ss.Addresses {
epAddress := &ss.Addresses[i]
ups := nghttpx.UpstreamServer{
Address: epAddress.IP,
Port: strconv.Itoa(int(targetPort)),
Protocol: portBackendConfig.GetProto(),
TLS: portBackendConfig.GetTLS(),
SNI: portBackendConfig.GetSNI(),
DNS: portBackendConfig.GetDNS(),
Affinity: portBackendConfig.GetAffinity(),
Address: epAddress.IP,
Port: strconv.Itoa(int(targetPort)),
Protocol: portBackendConfig.GetProto(),
TLS: portBackendConfig.GetTLS(),
SNI: portBackendConfig.GetSNI(),
DNS: portBackendConfig.GetDNS(),
Affinity: portBackendConfig.GetAffinity(),
AffinityCookieName: portBackendConfig.GetAffinityCookieName(),
AffinityCookiePath: portBackendConfig.GetAffinityCookiePath(),
AffinityCookieSecure: portBackendConfig.GetAffinityCookieSecure(),
}
// Set Protocol and Affinity here if they are empty. Template expects them.
if ups.Protocol == "" {
ups.Protocol = nghttpx.ProtocolH1
}
if ups.Affinity == "" {
ups.Affinity = nghttpx.AffinityNone
} else if ups.Affinity == nghttpx.AffinityCookie && ups.AffinityCookieSecure == "" {
ups.AffinityCookieSecure = nghttpx.AffinityCookieSecureAuto
}
upsServers = append(upsServers, ups)
}
Expand Down
72 changes: 63 additions & 9 deletions pkg/nghttpx/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,17 @@ type Upstream struct {
type Affinity string

const (
AffinityNone Affinity = "none"
AffinityIP Affinity = "ip"
AffinityNone Affinity = "none"
AffinityIP Affinity = "ip"
AffinityCookie Affinity = "cookie"
)

type AffinityCookieSecure string

const (
AffinityCookieSecureAuto AffinityCookieSecure = "auto"
AffinityCookieSecureYes AffinityCookieSecure = "yes"
AffinityCookieSecureNo AffinityCookieSecure = "no"
)

type Protocol string
Expand All @@ -104,13 +113,16 @@ const (

// UpstreamServer describes a server in an nghttpx upstream
type UpstreamServer struct {
Address string
Port string
Protocol Protocol
TLS bool
SNI string
DNS bool
Affinity Affinity
Address string
Port string
Protocol Protocol
TLS bool
SNI string
DNS bool
Affinity Affinity
AffinityCookieName string
AffinityCookiePath string
AffinityCookieSecure AffinityCookieSecure
}

// TLS server private key, certificate file path, and optionally OCSP response. OCSP response must be DER encoded byte string.
Expand Down Expand Up @@ -142,6 +154,12 @@ type PortBackendConfig struct {
DNS *bool `json:"dns,omitempty"`
// Affinity is session affinity method nghttpx supports. See affinity parameter in backend option of nghttpx.
Affinity *Affinity `json:"affinity,omitempty"`
// AffinityCookieName is a name of cookie to use for cookie-based session affinity.
AffinityCookieName *string `json:"affinityCookieName,omitempty"`
// AffinityCookiePath is a path of cookie for cookie-based session affinity.
AffinityCookiePath *string `json:"affinityCookiePath,omitempty"`
// AffinityCookieSecure controls whether Secure attribute is added to session affinity cookie.
AffinityCookieSecure *AffinityCookieSecure `json:"affinityCookieSecure,omitempty"`
}

func (pbc *PortBackendConfig) GetProto() Protocol {
Expand Down Expand Up @@ -204,6 +222,42 @@ func (pbc *PortBackendConfig) SetAffinity(affinity Affinity) {
*pbc.Affinity = affinity
}

func (pbc *PortBackendConfig) GetAffinityCookieName() string {
if pbc.AffinityCookieName == nil {
return ""
}
return *pbc.AffinityCookieName
}

func (pbc *PortBackendConfig) SetAffinityCookieName(affinityCookieName string) {
pbc.AffinityCookieName = new(string)
*pbc.AffinityCookieName = affinityCookieName
}

func (pbc *PortBackendConfig) GetAffinityCookiePath() string {
if pbc.AffinityCookiePath == nil {
return ""
}
return *pbc.AffinityCookiePath
}

func (pbc *PortBackendConfig) SetAffinityCookiePath(affinityCookiePath string) {
pbc.AffinityCookiePath = new(string)
*pbc.AffinityCookiePath = affinityCookiePath
}

func (pbc *PortBackendConfig) GetAffinityCookieSecure() AffinityCookieSecure {
if pbc.AffinityCookieSecure == nil {
return ""
}
return *pbc.AffinityCookieSecure
}

func (pbc *PortBackendConfig) SetAffinityCookieSecure(affinityCookieSecure AffinityCookieSecure) {
pbc.AffinityCookieSecure = new(AffinityCookieSecure)
*pbc.AffinityCookieSecure = affinityCookieSecure
}

// ChecksumFile represents a file with path, its arbitrary content, and its checksum.
type ChecksumFile struct {
Path string
Expand Down
18 changes: 17 additions & 1 deletion pkg/nghttpx/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,19 @@ func FixupPortBackendConfig(config *PortBackendConfig) {
config.SetProto(ProtocolH1)
}
switch config.GetAffinity() {
case AffinityNone, AffinityIP, "":
case AffinityNone, AffinityIP, AffinityCookie, "":
// OK
default:
glog.Errorf("unsupported affinity method %v", config.GetAffinity())
config.SetAffinity(AffinityNone)
}
switch config.GetAffinityCookieSecure() {
case AffinityCookieSecureAuto, AffinityCookieSecureYes, AffinityCookieSecureNo, "":
// OK
default:
glog.Errorf("unsupported affinity cookie secure %v", config.GetAffinityCookieSecure())
config.SetAffinityCookieSecure(AffinityCookieSecureAuto)
}
}

// ApplyDefaultPortBackendConfig applies default field value specified in defaultConfig to config if a corresponding field is missing.
Expand All @@ -159,6 +166,15 @@ func ApplyDefaultPortBackendConfig(config *PortBackendConfig, defaultConfig *Por
if defaultConfig.Affinity != nil && config.Affinity == nil {
config.SetAffinity(*defaultConfig.Affinity)
}
if defaultConfig.AffinityCookieName != nil && config.AffinityCookieName == nil {
config.SetAffinityCookieName(*defaultConfig.AffinityCookieName)
}
if defaultConfig.AffinityCookiePath != nil && config.AffinityCookiePath == nil {
config.SetAffinityCookiePath(*defaultConfig.AffinityCookiePath)
}
if defaultConfig.AffinityCookieSecure != nil && config.AffinityCookieSecure == nil {
config.SetAffinityCookieSecure(*defaultConfig.AffinityCookieSecure)
}
}

func WriteFile(path string, content []byte) error {
Expand Down
64 changes: 52 additions & 12 deletions pkg/nghttpx/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,21 @@ import (
// TestFixupPortBackendConfig validates fixupPortBackendConfig corrects invalid input to the correct default value.
func TestFixupPortBackendConfig(t *testing.T) {
tests := []struct {
inProto Protocol
inAffinity Affinity
outProto Protocol
outAffinity Affinity
inProto Protocol
inAffinity Affinity
inAffinityCookieSecure AffinityCookieSecure
outProto Protocol
outAffinity Affinity
outAffinityCookieSecure AffinityCookieSecure
}{
// 0
{
inProto: "foo",
inAffinity: "bar",
outProto: ProtocolH1,
outAffinity: AffinityNone,
inProto: "foo",
inAffinity: "bar",
inAffinityCookieSecure: "buzz",
outProto: ProtocolH1,
outAffinity: AffinityNone,
outAffinityCookieSecure: AffinityCookieSecureAuto,
},
// 1
{
Expand All @@ -34,24 +38,30 @@ func TestFixupPortBackendConfig(t *testing.T) {
// 2
{
// Correct input must be left unchanged.
inProto: ProtocolH2,
inAffinity: AffinityIP,
outProto: ProtocolH2,
outAffinity: AffinityIP,
inProto: ProtocolH2,
inAffinity: AffinityIP,
inAffinityCookieSecure: AffinityCookieSecureYes,
outProto: ProtocolH2,
outAffinity: AffinityIP,
outAffinityCookieSecure: AffinityCookieSecureYes,
},
}

for i, tt := range tests {
c := &PortBackendConfig{}
c.SetProto(tt.inProto)
c.SetAffinity(tt.inAffinity)
c.SetAffinityCookieSecure(tt.inAffinityCookieSecure)
FixupPortBackendConfig(c)
if got, want := c.GetProto(), tt.outProto; got != want {
t.Errorf("#%v: c.GetProto() = %q, want %q", i, got, want)
}
if got, want := c.GetAffinity(), tt.outAffinity; got != want {
t.Errorf("#%v: c.GetAffinity() = %q, want %q", i, got, want)
}
if got, want := c.GetAffinityCookieSecure(), tt.outAffinityCookieSecure; got != want {
t.Errorf("#%v: c.GetAffinityCookieSecure() = %q, want %q", i, got, want)
}
}
}

Expand Down Expand Up @@ -95,6 +105,27 @@ func TestApplyDefaultPortBackendConfig(t *testing.T) {
return a
}(),
},
{
defaultConf: func() *PortBackendConfig {
a := &PortBackendConfig{}
a.SetAffinityCookieName("lb-cookie")
return a
}(),
},
{
defaultConf: func() *PortBackendConfig {
a := &PortBackendConfig{}
a.SetAffinityCookiePath("/path")
return a
}(),
},
{
defaultConf: func() *PortBackendConfig {
a := &PortBackendConfig{}
a.SetAffinityCookieSecure(AffinityCookieSecureNo)
return a
}(),
},
}

for i, tt := range tests {
Expand All @@ -116,5 +147,14 @@ func TestApplyDefaultPortBackendConfig(t *testing.T) {
if got, want := a.GetAffinity(), tt.defaultConf.GetAffinity(); got != want {
t.Errorf("#%v: a.GetAffinity() = %v, want %v", i, got, want)
}
if got, want := a.GetAffinityCookieName(), tt.defaultConf.GetAffinityCookieName(); got != want {
t.Errorf("#%v: a.GetAffinityCookieName() = %v, want %v", i, got, want)
}
if got, want := a.GetAffinityCookiePath(), tt.defaultConf.GetAffinityCookiePath(); got != want {
t.Errorf("#%v: a.GetAffinityCookiePath() = %v, want %v", i, got, want)
}
if got, want := a.GetAffinityCookieSecure(), tt.defaultConf.GetAffinityCookieSecure(); got != want {
t.Errorf("#%v: a.GetAffinityCookieSecure() = %v, want %v", i, got, want)
}
}
}

0 comments on commit de32c08

Please sign in to comment.