This is an automated email from the ASF dual-hosted git repository.
zhangjintao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git
The following commit(s) were added to refs/heads/master by this push:
new aae2105e feat: ingress annotations support enable websocket (#1101)
aae2105e is described below
commit aae2105e123008a0170b68a0133432695ee230c9
Author: seven dickens <[email protected]>
AuthorDate: Tue Jul 5 02:05:40 2022 +0800
feat: ingress annotations support enable websocket (#1101)
---
docs/en/latest/concepts/annotations.md | 33 ++++
pkg/kube/translation/ingress.go | 8 +
pkg/kube/translation/ingress_test.go | 261 +++++++++++++++++++++++++++
test/e2e/suite-annotations/websocket.go | 306 ++++++++++++++++++++++++++++++++
4 files changed, 608 insertions(+)
diff --git a/docs/en/latest/concepts/annotations.md
b/docs/en/latest/concepts/annotations.md
index ff215ccd..338c7562 100644
--- a/docs/en/latest/concepts/annotations.md
+++ b/docs/en/latest/concepts/annotations.md
@@ -171,3 +171,36 @@ spec:
port:
number: 80
```
+
+Enable websocket
+---------
+
+You can use the follow annotations to enable websocket
+
+* `k8s.apisix.apache.org/enable-websocket`
+
+If this annotations set to `true` the route will enable websoket
+
+For example, the following Ingress, if we set
`k8s.apisix.apache.org/enable-websocket: "true"`. `/api/*` route will enable
websocket
+
+```yaml
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ annotations:
+ kubernetes.io/ingress.class: apisix
+ k8s.apisix.apache.org/enable-websocket: "true"
+ name: ingress-v1
+spec:
+ rules:
+ - host: httpbin.org
+ http:
+ paths:
+ - path: /api/*
+ pathType: ImplementationSpecific
+ backend:
+ service:
+ name: service1
+ port:
+ number: 80
+```
diff --git a/pkg/kube/translation/ingress.go b/pkg/kube/translation/ingress.go
index 76fb1e07..8b0ab9d6 100644
--- a/pkg/kube/translation/ingress.go
+++ b/pkg/kube/translation/ingress.go
@@ -44,6 +44,8 @@ func (t *translator) translateIngressV1(ing
*networkingv1.Ingress, skipVerify bo
plugins := t.translateAnnotations(ing.Annotations)
annoExtractor := annotations.NewExtractor(ing.Annotations)
useRegex :=
annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix + "use-regex")
+ enableWebsocket :=
annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix +
"enable-websocket")
+
// add https
for _, tls := range ing.Spec.TLS {
apisixTls := kubev2.ApisixTls{
@@ -132,6 +134,7 @@ func (t *translator) translateIngressV1(ing
*networkingv1.Ingress, skipVerify bo
route.ID = id.GenID(route.Name)
route.Host = rule.Host
route.Uris = uris
+ route.EnableWebsocket = enableWebsocket
if len(nginxVars) > 0 {
routeVars, err :=
t.translateRouteMatchExprs(nginxVars)
if err != nil {
@@ -165,6 +168,8 @@ func (t *translator) translateIngressV1beta1(ing
*networkingv1beta1.Ingress, ski
plugins := t.translateAnnotations(ing.Annotations)
annoExtractor := annotations.NewExtractor(ing.Annotations)
useRegex :=
annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix + "use-regex")
+ enableWebsocket :=
annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix +
"enable-websocket")
+
// add https
for _, tls := range ing.Spec.TLS {
apisixTls := kubev2beta3.ApisixTls{
@@ -253,6 +258,7 @@ func (t *translator) translateIngressV1beta1(ing
*networkingv1beta1.Ingress, ski
route.ID = id.GenID(route.Name)
route.Host = rule.Host
route.Uris = uris
+ route.EnableWebsocket = enableWebsocket
if len(nginxVars) > 0 {
routeVars, err :=
t.translateRouteMatchExprs(nginxVars)
if err != nil {
@@ -340,6 +346,7 @@ func (t *translator) translateIngressExtensionsV1beta1(ing
*extensionsv1beta1.In
plugins := t.translateAnnotations(ing.Annotations)
annoExtractor := annotations.NewExtractor(ing.Annotations)
useRegex :=
annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix + "use-regex")
+ enableWebsocket :=
annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix +
"enable-websocket")
for _, rule := range ing.Spec.Rules {
for _, pathRule := range rule.HTTP.Paths {
@@ -400,6 +407,7 @@ func (t *translator) translateIngressExtensionsV1beta1(ing
*extensionsv1beta1.In
route.ID = id.GenID(route.Name)
route.Host = rule.Host
route.Uris = uris
+ route.EnableWebsocket = enableWebsocket
if len(nginxVars) > 0 {
routeVars, err :=
t.translateRouteMatchExprs(nginxVars)
if err != nil {
diff --git a/pkg/kube/translation/ingress_test.go
b/pkg/kube/translation/ingress_test.go
index a2365c66..fed2ae28 100644
--- a/pkg/kube/translation/ingress_test.go
+++ b/pkg/kube/translation/ingress_test.go
@@ -1048,3 +1048,264 @@ func TestTranslateIngressExtensionsV1beta1WithRegex(t
*testing.T) {
assert.Equal(t, []string{"/*"}, ctx.Routes[0].Uris)
assert.Equal(t, expectedVars, ctx.Routes[0].Vars)
}
+
+func TestTranslateIngressV1WithWebsocket(t *testing.T) {
+ prefix := networkingv1.PathTypeImplementationSpecific
+ regexPath := "/foo/*/bar"
+ ing := &networkingv1.Ingress{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test",
+ Namespace: "default",
+ Annotations: map[string]string{
+ "k8s.apisix.apache.org/enable-websocket":
"true",
+ },
+ },
+ Spec: networkingv1.IngressSpec{
+ Rules: []networkingv1.IngressRule{
+ {
+ Host: "apisix.apache.org",
+ IngressRuleValue:
networkingv1.IngressRuleValue{
+ HTTP:
&networkingv1.HTTPIngressRuleValue{
+ Paths:
[]networkingv1.HTTPIngressPath{
+ {
+ Path:
regexPath,
+
PathType: &prefix,
+
Backend: networkingv1.IngressBackend{
+
Service: &networkingv1.IngressServiceBackend{
+
Name: "test-service",
+
Port: networkingv1.ServiceBackendPort{
+
Name: "port1",
+
},
+
},
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+ client := fake.NewSimpleClientset()
+ informersFactory := informers.NewSharedInformerFactory(client, 0)
+ svcInformer := informersFactory.Core().V1().Services().Informer()
+ svcLister := informersFactory.Core().V1().Services().Lister()
+ epLister, epInformer :=
kube.NewEndpointListerAndInformer(informersFactory, false)
+ apisixClient := fakeapisix.NewSimpleClientset()
+ apisixInformersFactory :=
apisixinformers.NewSharedInformerFactory(apisixClient, 0)
+ processCh := make(chan struct{})
+ svcInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
+ AddFunc: func(obj interface{}) {
+ processCh <- struct{}{}
+ },
+ })
+ epInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
+ AddFunc: func(obj interface{}) {
+ processCh <- struct{}{}
+ },
+ })
+
+ stopCh := make(chan struct{})
+ defer close(stopCh)
+ go svcInformer.Run(stopCh)
+ go epInformer.Run(stopCh)
+ cache.WaitForCacheSync(stopCh, svcInformer.HasSynced)
+
+ _, err :=
client.CoreV1().Services("default").Create(context.Background(), _testSvc,
metav1.CreateOptions{})
+ assert.Nil(t, err)
+ _, err =
client.CoreV1().Endpoints("default").Create(context.Background(), _testEp,
metav1.CreateOptions{})
+ assert.Nil(t, err)
+
+ tr := &translator{
+ TranslatorOptions: &TranslatorOptions{
+ ServiceLister: svcLister,
+ EndpointLister: epLister,
+ ApisixUpstreamLister:
apisixInformersFactory.Apisix().V2beta3().ApisixUpstreams().Lister(),
+ },
+ }
+
+ <-processCh
+ <-processCh
+ ctx, err := tr.translateIngressV1(ing, false)
+ assert.Nil(t, err)
+ assert.Len(t, ctx.Routes, 1)
+ assert.Len(t, ctx.Upstreams, 1)
+ // the number of the PluginConfigs should be zero, cause there no
available Annotations matched te rule
+ assert.Len(t, ctx.PluginConfigs, 0)
+
+ assert.Equal(t, true, ctx.Routes[0].EnableWebsocket)
+}
+
+func TestTranslateIngressV1beta1WithWebsocket(t *testing.T) {
+ prefix := networkingv1beta1.PathTypeImplementationSpecific
+ // no backend.
+ regexPath := "/foo/*/bar"
+ ing := &networkingv1beta1.Ingress{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test",
+ Namespace: "default",
+ Annotations: map[string]string{
+ "k8s.apisix.apache.org/enable-websocket":
"true",
+ },
+ },
+ Spec: networkingv1beta1.IngressSpec{
+ Rules: []networkingv1beta1.IngressRule{
+ {
+ Host: "apisix.apache.org",
+ IngressRuleValue:
networkingv1beta1.IngressRuleValue{
+ HTTP:
&networkingv1beta1.HTTPIngressRuleValue{
+ Paths:
[]networkingv1beta1.HTTPIngressPath{
+ {
+ Path:
regexPath,
+
PathType: &prefix,
+
Backend: networkingv1beta1.IngressBackend{
+
ServiceName: "test-service",
+
ServicePort: intstr.IntOrString{
+
Type: intstr.String,
+
StrVal: "port1",
+
},
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+ client := fake.NewSimpleClientset()
+ informersFactory := informers.NewSharedInformerFactory(client, 0)
+ svcInformer := informersFactory.Core().V1().Services().Informer()
+ svcLister := informersFactory.Core().V1().Services().Lister()
+ epLister, epInformer :=
kube.NewEndpointListerAndInformer(informersFactory, false)
+ apisixClient := fakeapisix.NewSimpleClientset()
+ apisixInformersFactory :=
apisixinformers.NewSharedInformerFactory(apisixClient, 0)
+ processCh := make(chan struct{})
+ svcInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
+ AddFunc: func(obj interface{}) {
+ processCh <- struct{}{}
+ },
+ })
+ epInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
+ AddFunc: func(obj interface{}) {
+ processCh <- struct{}{}
+ },
+ })
+
+ stopCh := make(chan struct{})
+ defer close(stopCh)
+ go svcInformer.Run(stopCh)
+ go epInformer.Run(stopCh)
+ cache.WaitForCacheSync(stopCh, svcInformer.HasSynced)
+
+ _, err :=
client.CoreV1().Services("default").Create(context.Background(), _testSvc,
metav1.CreateOptions{})
+ assert.Nil(t, err)
+ _, err =
client.CoreV1().Endpoints("default").Create(context.Background(), _testEp,
metav1.CreateOptions{})
+ assert.Nil(t, err)
+
+ tr := &translator{
+ TranslatorOptions: &TranslatorOptions{
+ ServiceLister: svcLister,
+ EndpointLister: epLister,
+ ApisixUpstreamLister:
apisixInformersFactory.Apisix().V2beta3().ApisixUpstreams().Lister(),
+ },
+ }
+
+ <-processCh
+ <-processCh
+ ctx, err := tr.translateIngressV1beta1(ing, false)
+ assert.Nil(t, err)
+ assert.Len(t, ctx.Routes, 1)
+ assert.Len(t, ctx.Upstreams, 1)
+ // the number of the PluginConfigs should be zero, cause there no
available Annotations matched te rule
+ assert.Len(t, ctx.PluginConfigs, 0)
+
+ assert.Nil(t, err)
+ assert.Equal(t, true, ctx.Routes[0].EnableWebsocket)
+}
+
+func TestTranslateIngressExtensionsV1beta1WithWebsocket(t *testing.T) {
+ prefix := extensionsv1beta1.PathTypeImplementationSpecific
+ regexPath := "/foo/*/bar"
+ ing := &extensionsv1beta1.Ingress{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test",
+ Namespace: "default",
+ Annotations: map[string]string{
+ "k8s.apisix.apache.org/enable-websocket":
"true",
+ },
+ },
+ Spec: extensionsv1beta1.IngressSpec{
+ Rules: []extensionsv1beta1.IngressRule{
+ {
+ Host: "apisix.apache.org",
+ IngressRuleValue:
extensionsv1beta1.IngressRuleValue{
+ HTTP:
&extensionsv1beta1.HTTPIngressRuleValue{
+ Paths:
[]extensionsv1beta1.HTTPIngressPath{
+ {
+ Path:
regexPath,
+
PathType: &prefix,
+
Backend: extensionsv1beta1.IngressBackend{
+
ServiceName: "test-service",
+
ServicePort: intstr.IntOrString{
+
Type: intstr.String,
+
StrVal: "port1",
+
},
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+ client := fake.NewSimpleClientset()
+ informersFactory := informers.NewSharedInformerFactory(client, 0)
+ svcInformer := informersFactory.Core().V1().Services().Informer()
+ svcLister := informersFactory.Core().V1().Services().Lister()
+ epLister, epInformer :=
kube.NewEndpointListerAndInformer(informersFactory, false)
+ apisixClient := fakeapisix.NewSimpleClientset()
+ apisixInformersFactory :=
apisixinformers.NewSharedInformerFactory(apisixClient, 0)
+ processCh := make(chan struct{})
+ svcInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
+ AddFunc: func(obj interface{}) {
+ processCh <- struct{}{}
+ },
+ })
+ epInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
+ AddFunc: func(obj interface{}) {
+ processCh <- struct{}{}
+ },
+ })
+
+ stopCh := make(chan struct{})
+ defer close(stopCh)
+ go svcInformer.Run(stopCh)
+ go epInformer.Run(stopCh)
+ cache.WaitForCacheSync(stopCh, svcInformer.HasSynced)
+
+ _, err :=
client.CoreV1().Services("default").Create(context.Background(), _testSvc,
metav1.CreateOptions{})
+ assert.Nil(t, err)
+ _, err =
client.CoreV1().Endpoints("default").Create(context.Background(), _testEp,
metav1.CreateOptions{})
+ assert.Nil(t, err)
+
+ tr := &translator{
+ TranslatorOptions: &TranslatorOptions{
+ ServiceLister: svcLister,
+ EndpointLister: epLister,
+ ApisixUpstreamLister:
apisixInformersFactory.Apisix().V2beta3().ApisixUpstreams().Lister(),
+ },
+ }
+
+ <-processCh
+ <-processCh
+ ctx, err := tr.translateIngressExtensionsV1beta1(ing, false)
+ assert.Nil(t, err)
+ assert.Len(t, ctx.Routes, 1)
+ assert.Len(t, ctx.Upstreams, 1)
+ // the number of the PluginConfigs should be zero, cause there no
available Annotations matched te rule
+ assert.Len(t, ctx.PluginConfigs, 0)
+
+ assert.Equal(t, true, ctx.Routes[0].EnableWebsocket)
+}
diff --git a/test/e2e/suite-annotations/websocket.go
b/test/e2e/suite-annotations/websocket.go
new file mode 100644
index 00000000..3ffb5920
--- /dev/null
+++ b/test/e2e/suite-annotations/websocket.go
@@ -0,0 +1,306 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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"
+ "net/url"
+ "time"
+
+ "github.com/gorilla/websocket"
+ ginkgo "github.com/onsi/ginkgo/v2"
+ "github.com/stretchr/testify/assert"
+
+ "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
+)
+
+var _ = ginkgo.Describe("suite-annotations: annotations.networking/v1
websocket", func() {
+ opts := &scaffold.Options{
+ Name: "default",
+ Kubeconfig: scaffold.GetKubeconfig(),
+ APISIXConfigPath: "testdata/apisix-gw-config.yaml",
+ IngressAPISIXReplicas: 1,
+ HTTPBinServicePort: 80,
+ APISIXRouteVersion: "apisix.apache.org/v2beta3",
+ }
+ s := scaffold.NewScaffold(opts)
+ ginkgo.It("sanity", func() {
+ resources := `
+apiVersion: v1
+kind: Pod
+metadata:
+ name: websocket-server
+ labels:
+ app: websocket-server
+spec:
+ containers:
+ - name: websocket-server
+ image: localhost:5000/jmalloc/echo-server:latest
+ ports:
+ - containerPort: 8080
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: websocket-server-service
+spec:
+ selector:
+ app: websocket-server
+ ports:
+ - name: ws
+ port: 48733
+ protocol: TCP
+ targetPort: 8080
+`
+ err := s.CreateResourceFromString(resources)
+ assert.Nil(ginkgo.GinkgoT(), err)
+ time.Sleep(5 * time.Second)
+
+ ing := `
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ annotations:
+ kubernetes.io/ingress.class: apisix
+ k8s.apisix.apache.org/enable-websocket: 'true'
+ name: ingress-v1
+spec:
+ rules:
+ - host: httpbin.org
+ http:
+ paths:
+ - path: /echo
+ pathType: ImplementationSpecific
+ backend:
+ service:
+ name: websocket-server-service
+ port:
+ number: 48733
+`
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ing))
+ err = s.EnsureNumApisixUpstreamsCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
upstreams")
+ err = s.EnsureNumApisixRoutesCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
+
+ dialer := websocket.Dialer{}
+ u := url.URL{
+ Scheme: "ws",
+ Host: s.APISIXGatewayServiceEndpoint(),
+ Path: "/echo",
+ }
+ header := http.Header{
+ "Host": []string{"httpbin.org"},
+ }
+ conn, resp, err := dialer.Dial(u.String(), header)
+ assert.Nil(ginkgo.GinkgoT(), err, "websocket handshake failure")
+ assert.Equal(ginkgo.GinkgoT(), resp.StatusCode,
http.StatusSwitchingProtocols)
+
+ assert.Nil(ginkgo.GinkgoT(),
conn.WriteMessage(websocket.TextMessage, []byte("hello, I'm gorilla")),
"writing message")
+ msgType, buf, err := conn.ReadMessage()
+ assert.Nil(ginkgo.GinkgoT(), err, "reading message")
+ assert.Equal(ginkgo.GinkgoT(), string(buf), "Request served by
websocket-server")
+ msgType, buf, err = conn.ReadMessage()
+ assert.Nil(ginkgo.GinkgoT(), err, "reading message")
+ assert.Equal(ginkgo.GinkgoT(), msgType, websocket.TextMessage)
+ assert.Equal(ginkgo.GinkgoT(), string(buf), "hello, I'm
gorilla")
+ assert.Nil(ginkgo.GinkgoT(), conn.Close(), "closing ws
connection")
+ })
+})
+
+var _ = ginkgo.Describe("suite-annotations: annotations.networking/v1beta1
with websocket", func() {
+ opts := &scaffold.Options{
+ Name: "default",
+ Kubeconfig: scaffold.GetKubeconfig(),
+ APISIXConfigPath: "testdata/apisix-gw-config.yaml",
+ IngressAPISIXReplicas: 1,
+ HTTPBinServicePort: 80,
+ APISIXRouteVersion: "apisix.apache.org/v2beta3",
+ }
+ s := scaffold.NewScaffold(opts)
+ ginkgo.It("sanity", func() {
+ resources := `
+apiVersion: v1
+kind: Pod
+metadata:
+ name: websocket-server
+ labels:
+ app: websocket-server
+spec:
+ containers:
+ - name: websocket-server
+ image: localhost:5000/jmalloc/echo-server:latest
+ ports:
+ - containerPort: 8080
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: websocket-server-service
+spec:
+ selector:
+ app: websocket-server
+ ports:
+ - name: ws
+ port: 48733
+ protocol: TCP
+ targetPort: 8080
+`
+ err := s.CreateResourceFromString(resources)
+ assert.Nil(ginkgo.GinkgoT(), err)
+ time.Sleep(5 * time.Second)
+
+ ing := `
+apiVersion: networking.k8s.io/v1beta1
+kind: Ingress
+metadata:
+ name: ingress-v1beta1
+ annotations:
+ kubernetes.io/ingress.class: apisix
+ k8s.apisix.apache.org/enable-websocket: 'true'
+spec:
+ rules:
+ - host: httpbin.org
+ http:
+ paths:
+ - path: /echo
+ pathType: Exact
+ backend:
+ serviceName: websocket-server-service
+ servicePort: 48733
+`
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ing))
+ err = s.EnsureNumApisixUpstreamsCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
upstreams")
+ err = s.EnsureNumApisixRoutesCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
+
+ dialer := websocket.Dialer{}
+ u := url.URL{
+ Scheme: "ws",
+ Host: s.APISIXGatewayServiceEndpoint(),
+ Path: "/echo",
+ }
+ header := http.Header{
+ "Host": []string{"httpbin.org"},
+ }
+ conn, resp, err := dialer.Dial(u.String(), header)
+ assert.Nil(ginkgo.GinkgoT(), err, "websocket handshake failure")
+ assert.Equal(ginkgo.GinkgoT(), resp.StatusCode,
http.StatusSwitchingProtocols)
+
+ assert.Nil(ginkgo.GinkgoT(),
conn.WriteMessage(websocket.TextMessage, []byte("hello, I'm gorilla")),
"writing message")
+ msgType, buf, err := conn.ReadMessage()
+ assert.Nil(ginkgo.GinkgoT(), err, "reading message")
+ assert.Equal(ginkgo.GinkgoT(), string(buf), "Request served by
websocket-server")
+ msgType, buf, err = conn.ReadMessage()
+ assert.Nil(ginkgo.GinkgoT(), err, "reading message")
+ assert.Equal(ginkgo.GinkgoT(), msgType, websocket.TextMessage)
+ assert.Equal(ginkgo.GinkgoT(), string(buf), "hello, I'm
gorilla")
+ assert.Nil(ginkgo.GinkgoT(), conn.Close(), "closing ws
connection")
+ })
+})
+
+var _ = ginkgo.Describe("suite-annotations: ingress.networking/v1beta1 with
websocket", func() {
+ opts := &scaffold.Options{
+ Name: "default",
+ Kubeconfig: scaffold.GetKubeconfig(),
+ APISIXConfigPath: "testdata/apisix-gw-config.yaml",
+ IngressAPISIXReplicas: 1,
+ HTTPBinServicePort: 80,
+ APISIXRouteVersion: "apisix.apache.org/v2beta3",
+ }
+ s := scaffold.NewScaffold(opts)
+ ginkgo.It("sanity", func() {
+ resources := `
+apiVersion: v1
+kind: Pod
+metadata:
+ name: websocket-server
+ labels:
+ app: websocket-server
+spec:
+ containers:
+ - name: websocket-server
+ image: localhost:5000/jmalloc/echo-server:latest
+ ports:
+ - containerPort: 8080
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: websocket-server-service
+spec:
+ selector:
+ app: websocket-server
+ ports:
+ - name: ws
+ port: 48733
+ protocol: TCP
+ targetPort: 8080
+`
+ err := s.CreateResourceFromString(resources)
+ assert.Nil(ginkgo.GinkgoT(), err)
+ time.Sleep(5 * time.Second)
+
+ ing := `
+apiVersion: extensions/v1beta1
+kind: Ingress
+metadata:
+ name: ingress-ext-v1beta1
+ annotations:
+ kubernetes.io/ingress.class: apisix
+ k8s.apisix.apache.org/enable-websocket: 'true'
+spec:
+ rules:
+ - host: httpbin.org
+ http:
+ paths:
+ - path: /echo
+ pathType: Exact
+ backend:
+ serviceName: websocket-server-service
+ servicePort: 48733
+`
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ing))
+ err = s.EnsureNumApisixUpstreamsCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
upstreams")
+ err = s.EnsureNumApisixRoutesCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
+
+ dialer := websocket.Dialer{}
+ u := url.URL{
+ Scheme: "ws",
+ Host: s.APISIXGatewayServiceEndpoint(),
+ Path: "/echo",
+ }
+ header := http.Header{
+ "Host": []string{"httpbin.org"},
+ }
+ conn, resp, err := dialer.Dial(u.String(), header)
+ assert.Nil(ginkgo.GinkgoT(), err, "websocket handshake failure")
+ assert.Equal(ginkgo.GinkgoT(), resp.StatusCode,
http.StatusSwitchingProtocols)
+
+ assert.Nil(ginkgo.GinkgoT(),
conn.WriteMessage(websocket.TextMessage, []byte("hello, I'm gorilla")),
"writing message")
+ msgType, buf, err := conn.ReadMessage()
+ assert.Nil(ginkgo.GinkgoT(), err, "reading message")
+ assert.Equal(ginkgo.GinkgoT(), string(buf), "Request served by
websocket-server")
+ msgType, buf, err = conn.ReadMessage()
+ assert.Nil(ginkgo.GinkgoT(), err, "reading message")
+ assert.Equal(ginkgo.GinkgoT(), msgType, websocket.TextMessage)
+ assert.Equal(ginkgo.GinkgoT(), string(buf), "hello, I'm
gorilla")
+ assert.Nil(ginkgo.GinkgoT(), conn.Close(), "closing ws
connection")
+ })
+})