From 3877ee843b67bf72b95bfdbd8b24d7fe292a3d1a Mon Sep 17 00:00:00 2001 From: Xin Rong Date: Mon, 26 Sep 2022 12:48:56 +0800 Subject: [PATCH] feat: support Gateway API TCPRoute (#1217) --- pkg/providers/gateway/gateway_tcproute.go | 264 ++++++++++++++++++ pkg/providers/gateway/provider.go | 18 ++ pkg/providers/gateway/translation/gateway.go | 2 +- .../gateway/translation/gateway_httproute.go | 2 +- .../translation/gateway_httproute_test.go | 2 +- .../gateway/translation/gateway_tcproute.go | 57 ++++ .../translation/gateway_tcproute_test.go | 185 ++++++++++++ .../gateway/translation/gateway_tlsroute.go | 2 +- .../gateway/translation/translator.go | 4 +- pkg/providers/utils/generic.go | 21 ++ test/e2e/scaffold/ingress.go | 1 + test/e2e/suite-gateway/gateway_tcproute.go | 123 ++++++++ 12 files changed, 676 insertions(+), 5 deletions(-) create mode 100644 pkg/providers/gateway/gateway_tcproute.go create mode 100644 pkg/providers/gateway/translation/gateway_tcproute.go create mode 100644 pkg/providers/gateway/translation/gateway_tcproute_test.go create mode 100644 pkg/providers/utils/generic.go create mode 100644 test/e2e/suite-gateway/gateway_tcproute.go diff --git a/pkg/providers/gateway/gateway_tcproute.go b/pkg/providers/gateway/gateway_tcproute.go new file mode 100644 index 0000000000..817bd3d56d --- /dev/null +++ b/pkg/providers/gateway/gateway_tcproute.go @@ -0,0 +1,264 @@ +// 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 gateway + +import ( + "context" + "time" + + "go.uber.org/zap" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" + gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + + "github.com/apache/apisix-ingress-controller/pkg/log" + "github.com/apache/apisix-ingress-controller/pkg/providers/translation" + "github.com/apache/apisix-ingress-controller/pkg/providers/utils" + "github.com/apache/apisix-ingress-controller/pkg/types" +) + +type gatewayTCPRouteController struct { + controller *Provider + workqueue workqueue.RateLimitingInterface + workers int +} + +func newGatewayTCPRouteController(c *Provider) *gatewayTCPRouteController { + ctrl := &gatewayTCPRouteController{ + controller: c, + workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(1*time.Second, 60*time.Second, 5), "GatewayTCPRoute"), + workers: 1, + } + + ctrl.controller.gatewayTCPRouteInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: ctrl.onAdd, + UpdateFunc: ctrl.onUpdate, + DeleteFunc: ctrl.OnDelete, + }) + return ctrl +} + +func (c *gatewayTCPRouteController) sync(ctx context.Context, ev *types.Event) error { + key := ev.Object.(string) + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + log.Errorw("found Gateway TCPRoute resource with invalid key", + zap.Error(err), + zap.String("key", key), + ) + return err + } + log.Debugw("sync TCPRoute", zap.String("key", key)) + + tcpRoute, err := c.controller.gatewayTCPRouteLister.TCPRoutes(namespace).Get(name) + if err != nil { + if !k8serrors.IsNotFound(err) { + log.Errorw("failed to get Gateway TCPRoute", + zap.Error(err), + zap.String("key", key), + ) + return err + } + if ev.Type != types.EventDelete { + log.Warnw("Gateway TCPRoute was deleted before process", + zap.String("key", key), + ) + // Don't need to retry. + return nil + } + } + if ev.Type == types.EventDelete { + if tcpRoute != nil { + // We still find the resource while we are processing the DELETE event, + // that means object with same namespace and name was created, discarding + // this stale DELETE event. + log.Warnw("discard the stale Gateway/TCPRoute delete event since it exists", + zap.String("key", key), + ) + return nil + } + tcpRoute = ev.Tombstone.(*gatewayv1alpha2.TCPRoute) + } + tctx, err := c.controller.translator.TranslateGatewayTCPRouteV1Alpha2(tcpRoute) + if err != nil { + log.Errorw("failed to translate gateway TCPRoute", + zap.Error(err), + zap.Any("object", tcpRoute), + ) + return err + } + + log.Debugw("translated TCPRoute", + zap.Any("stream_routes", tctx.StreamRoutes), + zap.Any("upstreams", tctx.Upstreams), + ) + m := &utils.Manifest{ + StreamRoutes: tctx.StreamRoutes, + Upstreams: tctx.Upstreams, + } + + var ( + added *utils.Manifest + updated *utils.Manifest + deleted *utils.Manifest + ) + + if ev.Type == types.EventDelete { + deleted = m + } else if ev.Type == types.EventAdd { + added = m + } else { + var oldCtx *translation.TranslateContext + oldObj := ev.OldObject.(*gatewayv1alpha2.TCPRoute) + oldCtx, err = c.controller.translator.TranslateGatewayTCPRouteV1Alpha2(oldObj) + if err != nil { + log.Errorw("failed to translate old TCPRoute", + zap.String("version", oldObj.APIVersion), + zap.String("event_type", "update"), + zap.Any("TCPRoute", oldObj), + zap.Error(err), + ) + return err + } + + om := &utils.Manifest{ + StreamRoutes: oldCtx.StreamRoutes, + Upstreams: oldCtx.Upstreams, + } + added, updated, deleted = m.Diff(om) + } + + return utils.SyncManifests(ctx, c.controller.APISIX, c.controller.APISIXClusterName, added, updated, deleted) +} + +func (c *gatewayTCPRouteController) run(ctx context.Context) { + log.Info("gateway TCPRoute controller started") + defer log.Info("gateway TCPRoute controller exited") + defer c.workqueue.ShutDown() + + if !cache.WaitForCacheSync(ctx.Done(), c.controller.gatewayTCPRouteInformer.HasSynced) { + log.Error("sync Gateway TCPRoute cache failed") + return + } + for i := 0; i < c.workers; i++ { + go c.runWorker(ctx) + } + <-ctx.Done() +} + +func (c *gatewayTCPRouteController) runWorker(ctx context.Context) { + for { + obj, quit := c.workqueue.Get() + if quit { + return + } + err := c.sync(ctx, obj.(*types.Event)) + c.workqueue.Done(obj) + c.handleSyncErr(obj, err) + } +} + +func (c *gatewayTCPRouteController) handleSyncErr(obj interface{}, err error) { + if err == nil { + c.workqueue.Forget(obj) + c.controller.MetricsCollector.IncrSyncOperation("gateway_tcproute", "success") + return + } + event := obj.(*types.Event) + if k8serrors.IsNotFound(err) && event.Type != types.EventDelete { + log.Infow("sync gateway TCPRoute but not found, ignore", + zap.String("event_type", event.Type.String()), + zap.String("TCPRoute ", event.Object.(string)), + ) + c.workqueue.Forget(event) + return + } + log.Warnw("sync gateway TCPRoute failed, will retry", + zap.Any("object", obj), + zap.Error(err), + ) + c.workqueue.AddRateLimited(obj) + c.controller.MetricsCollector.IncrSyncOperation("gateway_tcproute", "failure") +} + +func (c *gatewayTCPRouteController) onAdd(obj interface{}) { + key, err := cache.MetaNamespaceKeyFunc(obj) + if err != nil { + log.Errorw("found gateway TCPRoute resource with bad meta namespace key", + zap.Error(err), + ) + return + } + if !c.controller.NamespaceProvider.IsWatchingNamespace(key) { + return + } + log.Debugw("gateway TCPRoute add event arrived", + zap.Any("object", obj), + ) + c.workqueue.Add(&types.Event{ + Type: types.EventAdd, + Object: key, + }) +} + +func (c *gatewayTCPRouteController) onUpdate(oldObj, newObj interface{}) { + oldTCPRoute := oldObj.(*gatewayv1alpha2.TCPRoute) + newTCPRoute := newObj.(*gatewayv1alpha2.TCPRoute) + if oldTCPRoute.ResourceVersion >= newTCPRoute.ResourceVersion { + return + } + key, err := cache.MetaNamespaceKeyFunc(oldObj) + if err != nil { + log.Errorw("found gateway TCPRoute resource with bad meta namespace key", + zap.Error(err), + ) + return + } + if !c.controller.NamespaceProvider.IsWatchingNamespace(key) { + return + } + log.Debugw("gateway TCPRoute update event arrived", + zap.Any("old object", oldObj), + zap.Any("new object", newObj), + ) + c.workqueue.Add(&types.Event{ + Type: types.EventUpdate, + Object: key, + }) +} + +func (c *gatewayTCPRouteController) OnDelete(obj interface{}) { + key, err := cache.MetaNamespaceKeyFunc(obj) + if err != nil { + log.Errorw("found gateway TCPRoute resource with bad meta namespace key", + zap.Error(err), + ) + return + } + if !c.controller.NamespaceProvider.IsWatchingNamespace(key) { + return + } + log.Debugw("gateway TCPRoute delete event arrived", + zap.Any("object", obj), + ) + c.workqueue.Add(&types.Event{ + Type: types.EventDelete, + Object: key, + Tombstone: obj, + }) +} diff --git a/pkg/providers/gateway/provider.go b/pkg/providers/gateway/provider.go index 84506db9a1..675c80c149 100644 --- a/pkg/providers/gateway/provider.go +++ b/pkg/providers/gateway/provider.go @@ -76,6 +76,10 @@ type Provider struct { gatewayTLSRouteController *gatewayTLSRouteController gatewayTLSRouteInformer cache.SharedIndexInformer gatewayTLSRouteLister gatewaylistersv1alpha2.TLSRouteLister + + gatewayTCPRouteController *gatewayTCPRouteController + gatewayTCPRouteInformer cache.SharedIndexInformer + gatewayTCPRouteLister gatewaylistersv1alpha2.TCPRouteLister } type ProviderOptions struct { @@ -134,6 +138,9 @@ func NewGatewayProvider(opts *ProviderOptions) (*Provider, error) { p.gatewayTLSRouteLister = gatewayFactory.Gateway().V1alpha2().TLSRoutes().Lister() p.gatewayTLSRouteInformer = gatewayFactory.Gateway().V1alpha2().TLSRoutes().Informer() + p.gatewayTCPRouteLister = gatewayFactory.Gateway().V1alpha2().TCPRoutes().Lister() + p.gatewayTCPRouteInformer = gatewayFactory.Gateway().V1alpha2().TCPRoutes().Informer() + p.gatewayController = newGatewayController(p) p.gatewayClassController, err = newGatewayClassController(p) @@ -142,14 +149,18 @@ func NewGatewayProvider(opts *ProviderOptions) (*Provider, error) { } p.gatewayHTTPRouteController = newGatewayHTTPRouteController(p) + p.gatewayTLSRouteController = newGatewayTLSRouteController(p) + p.gatewayTCPRouteController = newGatewayTCPRouteController(p) + return p, nil } func (p *Provider) Run(ctx context.Context) { e := utils.ParallelExecutor{} + // Run informer e.Add(func() { p.gatewayInformer.Run(ctx.Done()) }) @@ -162,7 +173,11 @@ func (p *Provider) Run(ctx context.Context) { e.Add(func() { p.gatewayTLSRouteInformer.Run(ctx.Done()) }) + e.Add(func() { + p.gatewayTCPRouteInformer.Run(ctx.Done()) + }) + // Run Controller e.Add(func() { p.gatewayController.run(ctx) }) @@ -175,6 +190,9 @@ func (p *Provider) Run(ctx context.Context) { e.Add(func() { p.gatewayTLSRouteController.run(ctx) }) + e.Add(func() { + p.gatewayTCPRouteController.run(ctx) + }) e.Wait() } diff --git a/pkg/providers/gateway/translation/gateway.go b/pkg/providers/gateway/translation/gateway.go index f3b30403a2..95045ea2fc 100644 --- a/pkg/providers/gateway/translation/gateway.go +++ b/pkg/providers/gateway/translation/gateway.go @@ -14,7 +14,7 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. -package gateway_translation +package translation import ( "errors" diff --git a/pkg/providers/gateway/translation/gateway_httproute.go b/pkg/providers/gateway/translation/gateway_httproute.go index cf442afa59..282a6e5745 100644 --- a/pkg/providers/gateway/translation/gateway_httproute.go +++ b/pkg/providers/gateway/translation/gateway_httproute.go @@ -14,7 +14,7 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. -package gateway_translation +package translation import ( "fmt" diff --git a/pkg/providers/gateway/translation/gateway_httproute_test.go b/pkg/providers/gateway/translation/gateway_httproute_test.go index 73033eedf8..52d1c12120 100644 --- a/pkg/providers/gateway/translation/gateway_httproute_test.go +++ b/pkg/providers/gateway/translation/gateway_httproute_test.go @@ -12,7 +12,7 @@ // 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 gateway_translation +package translation import ( "context" diff --git a/pkg/providers/gateway/translation/gateway_tcproute.go b/pkg/providers/gateway/translation/gateway_tcproute.go new file mode 100644 index 0000000000..59d53aeb08 --- /dev/null +++ b/pkg/providers/gateway/translation/gateway_tcproute.go @@ -0,0 +1,57 @@ +// 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 translation + +import ( + "fmt" + + gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + + "github.com/apache/apisix-ingress-controller/pkg/id" + "github.com/apache/apisix-ingress-controller/pkg/providers/translation" + apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1" +) + +func (t *translator) TranslateGatewayTCPRouteV1Alpha2(tcpRoute *gatewayv1alpha2.TCPRoute) (*translation.TranslateContext, error) { + ctx := translation.DefaultEmptyTranslateContext() + var ns string + + for i, rule := range tcpRoute.Spec.Rules { + for _, backend := range rule.BackendRefs { + if backend.Namespace != nil { + ns = string(*backend.Namespace) + } else { + ns = tcpRoute.Namespace + } + sr := apisixv1.NewDefaultStreamRoute() + name := apisixv1.ComposeStreamRouteName(tcpRoute.Namespace, tcpRoute.Name, fmt.Sprintf("%d-%s", i, string(backend.Name))) + sr.ID = id.GenID(name) + ups, err := t.KubeTranslator.TranslateService(ns, string(backend.Name), "", int32(*backend.Port)) + if err != nil { + return nil, err + } + ups.Name = apisixv1.ComposeUpstreamName(ns, string(backend.Name), "", int32(*backend.Port), "") + ups.ID = id.GenID(ups.Name) + sr.UpstreamId = ups.ID + ctx.AddStreamRoute(sr) + if !ctx.CheckUpstreamExist(ups.Name) { + ctx.AddUpstream(ups) + } + } + } + return ctx, nil +} diff --git a/pkg/providers/gateway/translation/gateway_tcproute_test.go b/pkg/providers/gateway/translation/gateway_tcproute_test.go new file mode 100644 index 0000000000..45955e9e20 --- /dev/null +++ b/pkg/providers/gateway/translation/gateway_tcproute_test.go @@ -0,0 +1,185 @@ +// 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 translation + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/tools/cache" + gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + + "github.com/apache/apisix-ingress-controller/pkg/config" + "github.com/apache/apisix-ingress-controller/pkg/kube" + fakeapisix "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/clientset/versioned/fake" + apisixinformers "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/informers/externalversions" + "github.com/apache/apisix-ingress-controller/pkg/providers/translation" + "github.com/apache/apisix-ingress-controller/pkg/providers/utils" +) + +func mockTCPRouteTranslator(t *testing.T) (*translator, <-chan struct{}) { + svc := &corev1.Service{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "svc", + Namespace: "test", + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "port1", + Port: 80, + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 9080, + }, + }, + { + Name: "port2", + Port: 443, + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 9443, + }, + }, + }, + }, + } + endpoints := &corev1.Endpoints{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "svc", + Namespace: "test", + }, + Subsets: []corev1.EndpointSubset{ + { + Ports: []corev1.EndpointPort{ + { + Name: "port1", + Port: 9080, + }, + { + Name: "port2", + Port: 9443, + }, + }, + Addresses: []corev1.EndpointAddress{ + {IP: "192.168.1.1"}, + {IP: "192.168.1.2"}, + }, + }, + }, + } + + 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) + + _, err := client.CoreV1().Endpoints("test").Create(context.Background(), endpoints, metav1.CreateOptions{}) + assert.Nil(t, err) + _, err = client.CoreV1().Services("test").Create(context.Background(), svc, metav1.CreateOptions{}) + assert.Nil(t, err) + + tr := &translator{ + &TranslatorOptions{ + KubeTranslator: translation.NewTranslator(&translation.TranslatorOptions{ + EndpointLister: epLister, + ServiceLister: svcLister, + ApisixUpstreamLister: kube.NewApisixUpstreamLister( + apisixInformersFactory.Apisix().V2beta3().ApisixUpstreams().Lister(), + apisixInformersFactory.Apisix().V2().ApisixUpstreams().Lister(), + ), + APIVersion: config.DefaultAPIVersion, + }), + }, + } + + 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{}, 2) + defer close(stopCh) + go svcInformer.Run(stopCh) + go epInformer.Run(stopCh) + cache.WaitForCacheSync(stopCh, svcInformer.HasSynced) + cache.WaitForCacheSync(stopCh, epInformer.HasSynced) + + return tr, processCh +} + +func TestTranslateGatewayTCPRoute(t *testing.T) { + tr, processCh := mockTCPRouteTranslator(t) + <-processCh + <-processCh + + tcpRoute := &gatewayv1alpha2.TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tcp_route", + Namespace: "test", + }, + Spec: gatewayv1alpha2.TCPRouteSpec{ + Rules: []gatewayv1alpha2.TCPRouteRule{ + { + BackendRefs: []gatewayv1alpha2.BackendRef{ + { + BackendObjectReference: gatewayv1alpha2.BackendObjectReference{ + Kind: utils.PtrOf(gatewayv1alpha2.Kind("Service")), + Name: "svc", + Namespace: utils.PtrOf(gatewayv1alpha2.Namespace("test")), + Port: utils.PtrOf(gatewayv1alpha2.PortNumber(80)), + }, + Weight: utils.PtrOf(int32(100)), + }, + }, + }, + }, + }, + } + + tctx, err := tr.TranslateGatewayTCPRouteV1Alpha2(tcpRoute) + assert.Nil(t, err) + + assert.Equal(t, 1, len(tctx.StreamRoutes)) + assert.Equal(t, 1, len(tctx.Upstreams)) + + r := tctx.StreamRoutes[0] + u := tctx.Upstreams[0] + + assert.Equal(t, u.ID, r.UpstreamId) + assert.Len(t, u.Nodes, 2) + assert.Equal(t, "192.168.1.1", u.Nodes[0].Host) + assert.Equal(t, "192.168.1.2", u.Nodes[1].Host) +} diff --git a/pkg/providers/gateway/translation/gateway_tlsroute.go b/pkg/providers/gateway/translation/gateway_tlsroute.go index ced0f1a7d5..038cedd91a 100644 --- a/pkg/providers/gateway/translation/gateway_tlsroute.go +++ b/pkg/providers/gateway/translation/gateway_tlsroute.go @@ -14,7 +14,7 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. -package gateway_translation +package translation import ( "fmt" diff --git a/pkg/providers/gateway/translation/translator.go b/pkg/providers/gateway/translation/translator.go index ac10368438..da5dab7f1e 100644 --- a/pkg/providers/gateway/translation/translator.go +++ b/pkg/providers/gateway/translation/translator.go @@ -14,7 +14,7 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. -package gateway_translation +package translation import ( gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" @@ -38,6 +38,8 @@ type Translator interface { TranslateGatewayHTTPRouteV1Alpha2(httpRoute *gatewayv1alpha2.HTTPRoute) (*translation.TranslateContext, error) // TranslateGatewayTLSRouteV1Alpha2 translates Gateway API TLSRoute to APISIX resources TranslateGatewayTLSRouteV1Alpha2(tlsRoute *gatewayv1alpha2.TLSRoute) (*translation.TranslateContext, error) + // TranslateGatewayTCPRouteV1Alpha2 translates Gateway API TCPRoute to APISIX resources + TranslateGatewayTCPRouteV1Alpha2(*gatewayv1alpha2.TCPRoute) (*translation.TranslateContext, error) } // NewTranslator initializes a APISIX CRD resources Translator. diff --git a/pkg/providers/utils/generic.go b/pkg/providers/utils/generic.go new file mode 100644 index 0000000000..23754c3632 --- /dev/null +++ b/pkg/providers/utils/generic.go @@ -0,0 +1,21 @@ +// 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 utils + +func PtrOf[T any](v T) *T { + return &v +} diff --git a/test/e2e/scaffold/ingress.go b/test/e2e/scaffold/ingress.go index 42ea50f1b8..944efa7a9e 100644 --- a/test/e2e/scaffold/ingress.go +++ b/test/e2e/scaffold/ingress.go @@ -192,6 +192,7 @@ rules: resources: - httproutes - tlsroutes + - tcproutes - gateways - gatewayclasses verbs: diff --git a/test/e2e/suite-gateway/gateway_tcproute.go b/test/e2e/suite-gateway/gateway_tcproute.go new file mode 100644 index 0000000000..cd70c08821 --- /dev/null +++ b/test/e2e/suite-gateway/gateway_tcproute.go @@ -0,0 +1,123 @@ +// 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 gateway + +import ( + "fmt" + "net/http" + "time" + + "github.com/onsi/ginkgo/v2" + "github.com/stretchr/testify/assert" + + "github.com/apache/apisix-ingress-controller/test/e2e/scaffold" +) + +var _ = ginkgo.Describe("suite-gateway: TCP Route", func() { + s := scaffold.NewDefaultScaffold() + ginkgo.It("create TCPRoute", func() { + backendSvc, backendPorts := s.DefaultHTTPBackend() + tcpRoute := fmt.Sprintf(` +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: TCPRoute +metadata: + name: httpbin-tcp-route +spec: + rules: + - backendRefs: + - name: %s + port: %d +`, backendSvc, backendPorts[0]) + assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(tcpRoute), "creating TCPRoute") + assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixStreamRoutesCreated(1), "Checking number of stream_routes") + assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixUpstreamsCreated(1), "Checking number of upstreams") + + _ = s.NewAPISIXClientWithTCPProxy(). + GET("/get"). + Expect(). + Status(http.StatusOK) + }) + + ginkgo.It("update TCPRoute", func() { + backendSvc, backendPorts := s.DefaultHTTPBackend() + tcpRoute := fmt.Sprintf(` +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: TCPRoute +metadata: + name: httpbin-tcp-route +spec: + rules: + - backendRefs: + - name: %s + port: %d +`, "httpbin", 80) + + assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(tcpRoute), "creating TCPRoute") + + time.Sleep(6 * time.Second) + // Non existent k8s service, service not found + assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixStreamRoutesCreated(0), "The number of stream_routes should be 0") + assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixUpstreamsCreated(0), "The number of upstreams should be 0") + + tcpRoute = fmt.Sprintf(` +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: TCPRoute +metadata: + name: httpbin-tcp-route +spec: + rules: + - backendRefs: + - name: %s + port: %d +`, backendSvc, backendPorts[0]) + assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(tcpRoute), "creating TCPRoute") + assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixStreamRoutesCreated(1), "The number of stream_routes should be 1") + assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixUpstreamsCreated(1), "The number of upstreams should be 1") + + _ = s.NewAPISIXClientWithTCPProxy(). + GET("/get"). + Expect(). + Status(http.StatusOK) + }) + + ginkgo.It("delete TCPRoute", func() { + backendSvc, backendPorts := s.DefaultHTTPBackend() + tcpRoute := fmt.Sprintf(` +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: TCPRoute +metadata: + name: httpbin-tcp-route +spec: + rules: + - backendRefs: + - name: %s + port: %d +`, backendSvc, backendPorts[0]) + assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(tcpRoute), "creating TCPRoute") + assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixStreamRoutesCreated(1), "The number of stream_routes should be 1") + assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixUpstreamsCreated(1), "The number of upstreams should be 1") + + _ = s.NewAPISIXClientWithTCPProxy(). + GET("/get"). + Expect(). + Status(http.StatusOK) + + assert.Nil(ginkgo.GinkgoT(), s.DeleteResourceFromString(tcpRoute), "deleting TCPRoute") + assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixStreamRoutesCreated(0), "The number of stream_routes should be 0") + assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixUpstreamsCreated(0), "The number of upstreams should be 0") + }) +})