This commit is contained in:
Qiu Jian 2018-01-29 14:44:22 +00:00 committed by GitHub
commit 9ea09ef121
9 changed files with 315 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/upstreamvhost"
"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/errors"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -83,6 +84,7 @@ type Ingress struct {
VtsFilterKey string
Whitelist ipwhitelist.SourceRange
XForwardedPrefix bool
EnableWebSocket bool
}
// 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),
"Whitelist": ipwhitelist.NewParser(cfg),
"XForwardedPrefix": xforwardedprefix.NewParser(cfg),
"EnableWebSocket": websocket.NewParser(cfg),
},
}
}

View file

@ -46,6 +46,7 @@ var (
annotationAffinityCookieName = parser.GetAnnotationWithPrefix("session-cookie-name")
annotationAffinityCookieHash = parser.GetAnnotationWithPrefix("session-cookie-hash")
annotationUpstreamHashBy = parser.GetAnnotationWithPrefix("upstream-hash-by")
annotationEnableWebSocket = parser.GetAnnotationWithPrefix("enable-websocket")
)
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) {
// 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.XForwardedPrefix = anns.XForwardedPrefix
loc.UsePortInRedirects = anns.UsePortInRedirects
loc.EnableWebSocket = anns.EnableWebSocket
if loc.Redirect.FromToWWW {
server.RedirectFromToWWW = true
@ -458,6 +459,7 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([]
Denied: anns.Denied,
XForwardedPrefix: anns.XForwardedPrefix,
UsePortInRedirects: anns.UsePortInRedirects,
EnableWebSocket: anns.EnableWebSocket,
}
if loc.Redirect.FromToWWW {

View file

@ -251,6 +251,9 @@ type Location struct {
// original location.
// +optional
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

View file

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

View file

@ -119,7 +119,7 @@ http {
include /etc/nginx/mime.types;
default_type text/html;
{{ if $cfg.EnableBrotli }}
brotli on;
brotli_comp_level {{ $cfg.BrotliLevel }};
@ -181,7 +181,11 @@ http {
# Retain the default nginx handling of requests without a "Connection" header
map $http_upgrade $connection_upgrade {
default upgrade;
{{ if (and (not ($location.EnableWebSocket)) (gt $all.Cfg.UpstreamKeepaliveConnections 0)) }}
'' keep-alive
{{ else }}
'' close;
{{ end }}
}
map {{ buildForwardedFor $cfg.ForwardedForHeader }} $the_real_ip {
@ -294,7 +298,7 @@ http {
{{ range $header := $cfg.HideHeaders }}proxy_hide_header {{ $header }};
{{ end }}
{{ if not (empty $cfg.HTTPSnippet) }}
# Custom code snippet configured in the configuration configmap
{{ $cfg.HTTPSnippet }}
@ -700,10 +704,10 @@ stream {
{{/* redirect to HTTPS can be achieved forcing the redirect or having a SSL Certificate configured for the server */}}
{{ if (or $location.Rewrite.ForceSSLRedirect (and (not (empty $server.SSLCertificate)) $location.Rewrite.SSLRedirect)) }}
# enforce ssl on server side
if ($redirect_to_https) {
if ($redirect_to_https) {
{{ if $location.UsePortInRedirects }}
# using custom ports require a different rewrite directive
{{ $redirect_port := (printf ":%v" $all.ListenPorts.HTTPS) }}
{{ $redirect_port := (printf ":%v" $all.ListenPorts.HTTPS) }}
error_page 497 ={{ $all.Cfg.HTTPRedirectCode }} https://$host{{ $redirect_port }}$request_uri;
return 497;

View file

@ -0,0 +1,141 @@
/*
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 annotations
import (
"net/http"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/parnurzeal/gorequest"
v1beta1 "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/test/e2e/framework"
)
var _ = framework.IngressNginxDescribe("Annotations - EnableWebSocket", func() {
f := framework.NewDefaultFramework("websocket")
BeforeEach(func() {
err := f.NewEchoDeployment()
Expect(err).NotTo(HaveOccurred())
})
AfterEach(func() {
})
It("should set 'Close' in connection header for host 'foo'", func() {
host := "foo"
ing, err := f.EnsureIngress(&v1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: host,
Namespace: f.Namespace.Name,
},
Spec: v1beta1.IngressSpec{
Rules: []v1beta1.IngressRule{
{
Host: host,
IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []v1beta1.HTTPIngressPath{
{
Path: "/",
Backend: v1beta1.IngressBackend{
ServiceName: "http-svc",
ServicePort: intstr.FromInt(80),
},
},
},
},
},
},
},
},
})
Expect(err).NotTo(HaveOccurred())
Expect(ing).NotTo(BeNil())
err = f.WaitForNginxServer(host,
func(server string) bool {
return Expect(server).Should(ContainSubstring("'' close"))
})
Expect(err).NotTo(HaveOccurred())
resp, _, errs := gorequest.New().
Get(f.NginxHTTPURL).
Set("Host", host).
End()
Expect(len(errs)).Should(BeNumerically("==", 0))
Expect(resp.StatusCode).Should(Equal(http.StatusOK))
})
It("should set 'keep-alive' in connection header for host 'foo'", func() {
host := "foo"
ing, err := f.EnsureIngress(&v1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: host,
Namespace: f.Namespace.Name,
Annotations: map[string]string{
"nginx.ingress.kubernetes.io/enable-websocket": "false",
},
},
Spec: v1beta1.IngressSpec{
Rules: []v1beta1.IngressRule{
{
Host: host,
IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []v1beta1.HTTPIngressPath{
{
Path: "/",
Backend: v1beta1.IngressBackend{
ServiceName: "http-svc",
ServicePort: intstr.FromInt(80),
},
},
},
},
},
},
},
},
})
Expect(err).NotTo(HaveOccurred())
Expect(ing).NotTo(BeNil())
err = f.WaitForNginxServer(host,
func(server string) bool {
return Expect(server).Should(ContainSubstring("'' keep-alive"))
})
Expect(err).NotTo(HaveOccurred())
resp, _, errs := gorequest.New().
Get(f.NginxHTTPURL).
Set("Host", host).
End()
Expect(len(errs)).Should(BeNumerically("==", 0))
Expect(resp.StatusCode).Should(Equal(http.StatusOK))
})
})