Add websocket-services annotation

This PR is to add websocket-services annotation, user can
specify a comma separated services list in the annotation
and websocket is enabled on these backend services.
This commit is contained in:
Jian Qiu 2017-10-30 13:33:28 +08:00
parent e02b5e3876
commit f48d134469
8 changed files with 174 additions and 4 deletions

View file

@ -47,6 +47,7 @@ import (
"k8s.io/ingress-nginx/internal/ingress/annotations/upstreamhashby" "k8s.io/ingress-nginx/internal/ingress/annotations/upstreamhashby"
"k8s.io/ingress-nginx/internal/ingress/annotations/upstreamvhost" "k8s.io/ingress-nginx/internal/ingress/annotations/upstreamvhost"
"k8s.io/ingress-nginx/internal/ingress/annotations/vtsfilterkey" "k8s.io/ingress-nginx/internal/ingress/annotations/vtsfilterkey"
"k8s.io/ingress-nginx/internal/ingress/annotations/websocket"
"k8s.io/ingress-nginx/internal/ingress/annotations/xforwardedprefix" "k8s.io/ingress-nginx/internal/ingress/annotations/xforwardedprefix"
"k8s.io/ingress-nginx/internal/ingress/errors" "k8s.io/ingress-nginx/internal/ingress/errors"
"k8s.io/ingress-nginx/internal/ingress/resolver" "k8s.io/ingress-nginx/internal/ingress/resolver"
@ -83,6 +84,7 @@ type Ingress struct {
VtsFilterKey string VtsFilterKey string
Whitelist ipwhitelist.SourceRange Whitelist ipwhitelist.SourceRange
XForwardedPrefix bool XForwardedPrefix bool
EnableWebSocket bool
} }
// Extractor defines the annotation parsers to be used in the extraction of annotations // Extractor defines the annotation parsers to be used in the extraction of annotations
@ -118,6 +120,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
"VtsFilterKey": vtsfilterkey.NewParser(cfg), "VtsFilterKey": vtsfilterkey.NewParser(cfg),
"Whitelist": ipwhitelist.NewParser(cfg), "Whitelist": ipwhitelist.NewParser(cfg),
"XForwardedPrefix": xforwardedprefix.NewParser(cfg), "XForwardedPrefix": xforwardedprefix.NewParser(cfg),
"EnableWebSocket": websocket.NewParser(cfg),
}, },
} }
} }

View file

@ -46,6 +46,7 @@ var (
annotationAffinityCookieName = parser.GetAnnotationWithPrefix("session-cookie-name") annotationAffinityCookieName = parser.GetAnnotationWithPrefix("session-cookie-name")
annotationAffinityCookieHash = parser.GetAnnotationWithPrefix("session-cookie-hash") annotationAffinityCookieHash = parser.GetAnnotationWithPrefix("session-cookie-hash")
annotationUpstreamHashBy = parser.GetAnnotationWithPrefix("upstream-hash-by") annotationUpstreamHashBy = parser.GetAnnotationWithPrefix("upstream-hash-by")
annotationEnableWebSocket = parser.GetAnnotationWithPrefix("enable-websocket")
) )
type mockCfg struct { type mockCfg struct {
@ -328,6 +329,27 @@ func TestCors(t *testing.T) {
} }
} }
func TestEnableWebSocket(t *testing.T) {
ec := NewAnnotationExtractor(mockCfg{})
ing := buildIngress()
fooAnns := []struct {
annotations map[string]string
er bool
}{
{map[string]string{annotationEnableWebSocket: "true"}, true},
{map[string]string{annotationEnableWebSocket: "false"}, false},
}
for _, foo := range fooAnns {
ing.SetAnnotations(foo.annotations)
r := ec.Extract(ing).EnableWebSocket
if r != foo.er {
t.Errorf("Returned %v but expected %v", r, foo.er)
}
}
}
/* /*
func TestMergeLocationAnnotations(t *testing.T) { func TestMergeLocationAnnotations(t *testing.T) {
// initial parameters // initial parameters

View file

@ -0,0 +1,46 @@
/*
Copyright 2017 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 websocket
import (
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
type websocket struct {
r resolver.Resolver
}
// NewParser creates a new Alias annotation parser
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
return websocket{r}
}
// Parse parses the annotations contained in the ingress rule
// used to add an alias to the provided hosts
func (a websocket) Parse(ing *extensions.Ingress) (interface{}, error) {
enabled, err := parser.GetBoolAnnotation("enable-websocket", ing)
// If annotation is not set, enable websocket by default
if err != nil {
return true, err
}
return enabled, nil
}

View file

@ -0,0 +1,87 @@
/*
Copyright 2017 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 websocket
import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
func TestParse(t *testing.T) {
annotation := parser.GetAnnotationWithPrefix("enable-websocket")
ap := NewParser(&resolver.Mock{})
if ap == nil {
t.Fatalf("expected a parser.IngressAnnotation but returned nil")
}
testCases := []struct {
annotations map[string]string
expected bool
}{
{map[string]string{annotation: "true"}, true},
{map[string]string{annotation: "false"}, false},
{map[string]string{annotation: ""}, true},
{map[string]string{}, true},
{nil, true},
}
ing := &extensions.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Rules: []extensions.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
{
Path: "/foo",
Backend: extensions.IngressBackend{
ServiceName: "test1",
ServicePort: intstr.FromInt(80),
},
},
},
},
},
},
},
},
}
for _, testCase := range testCases {
ing.SetAnnotations(testCase.annotations)
result, _ := ap.Parse(ing)
ws, ok := result.(bool)
if !ok {
t.Errorf("expected a Config type")
}
if !ws == testCase.expected {
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, ws, testCase.annotations)
}
}
}

View file

@ -426,6 +426,7 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([]
loc.Denied = anns.Denied loc.Denied = anns.Denied
loc.XForwardedPrefix = anns.XForwardedPrefix loc.XForwardedPrefix = anns.XForwardedPrefix
loc.UsePortInRedirects = anns.UsePortInRedirects loc.UsePortInRedirects = anns.UsePortInRedirects
loc.EnableWebSocket = anns.EnableWebSocket
if loc.Redirect.FromToWWW { if loc.Redirect.FromToWWW {
server.RedirectFromToWWW = true server.RedirectFromToWWW = true
@ -458,6 +459,7 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([]
Denied: anns.Denied, Denied: anns.Denied,
XForwardedPrefix: anns.XForwardedPrefix, XForwardedPrefix: anns.XForwardedPrefix,
UsePortInRedirects: anns.UsePortInRedirects, UsePortInRedirects: anns.UsePortInRedirects,
EnableWebSocket: anns.EnableWebSocket,
} }
if loc.Redirect.FromToWWW { if loc.Redirect.FromToWWW {

View file

@ -251,6 +251,9 @@ type Location struct {
// original location. // original location.
// +optional // +optional
XForwardedPrefix bool `json:"xForwardedPrefix,omitempty"` XForwardedPrefix bool `json:"xForwardedPrefix,omitempty"`
// EnableWebSocket indicates if websocket is enabled for the location
// +optional
EnableWebSocket bool `json:"webSocket,omitempty"`
} }
// SSLPassthroughBackend describes a SSL upstream server configured // SSLPassthroughBackend describes a SSL upstream server configured

View file

@ -370,6 +370,9 @@ func (l1 *Location) Equal(l2 *Location) bool {
if l1.XForwardedPrefix != l2.XForwardedPrefix { if l1.XForwardedPrefix != l2.XForwardedPrefix {
return false return false
} }
if l1.EnableWebSocket != l2.EnableWebSocket {
return false
}
return true return true
} }

View file

@ -181,7 +181,11 @@ http {
# Retain the default nginx handling of requests without a "Connection" header # Retain the default nginx handling of requests without a "Connection" header
map $http_upgrade $connection_upgrade { map $http_upgrade $connection_upgrade {
default upgrade; default upgrade;
{{ if (and (not ($location.EnableWebSocket)) (gt $all.Cfg.UpstreamKeepaliveConnections 0)) }}
'' keep-alive
{{ else }}
'' close; '' close;
{{ end }}
} }
map {{ buildForwardedFor $cfg.ForwardedForHeader }} $the_real_ip { map {{ buildForwardedFor $cfg.ForwardedForHeader }} $the_real_ip {