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 6c7452ff feat: support gateway API HTTPRoute (#1037)
6c7452ff is described below

commit 6c7452ffab358c47e25882d802e4cc891e95eb37
Author: Sarasa Kisaragi <[email protected]>
AuthorDate: Tue May 31 16:52:46 2022 +0800

    feat: support gateway API HTTPRoute (#1037)
---
 go.mod                                         |   1 +
 pkg/ingress/controller.go                      |  29 +-
 pkg/ingress/gateway_httproute.go               | 216 +++++++++++++
 pkg/kube/translation/gateway_httproute.go      | 235 ++++++++++++++
 pkg/kube/translation/gateway_httproute_test.go | 406 +++++++++++++++++++++++++
 pkg/kube/translation/translator.go             |   5 +-
 pkg/types/event.go                             |   2 +
 7 files changed, 886 insertions(+), 8 deletions(-)

diff --git a/go.mod b/go.mod
index bf2394ac..aefa0e0f 100644
--- a/go.mod
+++ b/go.mod
@@ -6,6 +6,7 @@ require (
        github.com/gin-gonic/gin v1.7.7
        github.com/hashicorp/go-memdb v1.3.2
        github.com/hashicorp/go-multierror v1.1.1
+       github.com/pkg/errors v0.9.1
        github.com/prometheus/client_golang v1.11.0
        github.com/prometheus/client_model v0.2.0
        github.com/slok/kubewebhook/v2 v2.2.0
diff --git a/pkg/ingress/controller.go b/pkg/ingress/controller.go
index 33c273c8..7ab245c6 100644
--- a/pkg/ingress/controller.go
+++ b/pkg/ingress/controller.go
@@ -119,15 +119,18 @@ type Controller struct {
        apisixPluginConfigLister    kube.ApisixPluginConfigLister
        gatewayInformer             cache.SharedIndexInformer
        gatewayLister               gatewaylistersv1alpha2.GatewayLister
+       gatewayHttpRouteInformer    cache.SharedIndexInformer
+       gatewayHttpRouteLister      gatewaylistersv1alpha2.HTTPRouteLister
 
        // resource controllers
-       namespaceController     *namespaceController
-       podController           *podController
-       endpointsController     *endpointsController
-       endpointSliceController *endpointSliceController
-       ingressController       *ingressController
-       secretController        *secretController
-       gatewayController       *gatewayController
+       namespaceController        *namespaceController
+       podController              *podController
+       endpointsController        *endpointsController
+       endpointSliceController    *endpointSliceController
+       ingressController          *ingressController
+       secretController           *secretController
+       gatewayController          *gatewayController
+       gatewayHTTPRouteController *gatewayHTTPRouteController
 
        apisixUpstreamController      *apisixUpstreamController
        apisixRouteController         *apisixRouteController
@@ -264,6 +267,9 @@ func (c *Controller) initWhenStartLeading() {
        c.gatewayLister = 
gatewayFactory.Gateway().V1alpha2().Gateways().Lister()
        c.gatewayInformer = 
gatewayFactory.Gateway().V1alpha2().Gateways().Informer()
 
+       c.gatewayHttpRouteLister = 
gatewayFactory.Gateway().V1alpha2().HTTPRoutes().Lister()
+       c.gatewayHttpRouteInformer = 
gatewayFactory.Gateway().V1alpha2().HTTPRoutes().Informer()
+
        switch c.cfg.Kubernetes.ApisixRouteVersion {
        case config.ApisixRouteV2beta2:
                apisixRouteInformer = 
apisixFactory.Apisix().V2beta2().ApisixRoutes().Informer()
@@ -328,6 +334,7 @@ func (c *Controller) initWhenStartLeading() {
        c.apisixConsumerController = c.newApisixConsumerController()
        c.apisixPluginConfigController = c.newApisixPluginConfigController()
        c.gatewayController = c.newGatewayController()
+       c.gatewayHTTPRouteController = c.newGatewayHTTPRouteController()
 }
 
 // recorderEvent recorder events for resources
@@ -550,9 +557,17 @@ func (c *Controller) run(ctx context.Context) {
                        c.gatewayInformer.Run(ctx.Done())
                })
 
+               c.goAttach(func() {
+                       c.gatewayHttpRouteInformer.Run(ctx.Done())
+               })
+
                c.goAttach(func() {
                        c.gatewayController.run(ctx)
                })
+
+               c.goAttach(func() {
+                       c.gatewayHTTPRouteController.run(ctx)
+               })
        }
 
        c.goAttach(func() {
diff --git a/pkg/ingress/gateway_httproute.go b/pkg/ingress/gateway_httproute.go
new file mode 100644
index 00000000..667bcb22
--- /dev/null
+++ b/pkg/ingress/gateway_httproute.go
@@ -0,0 +1,216 @@
+// 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 ingress
+
+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/kube/translation"
+       "github.com/apache/apisix-ingress-controller/pkg/log"
+       "github.com/apache/apisix-ingress-controller/pkg/types"
+)
+
+type gatewayHTTPRouteController struct {
+       controller *Controller
+       workqueue  workqueue.RateLimitingInterface
+       workers    int
+}
+
+func (c *Controller) newGatewayHTTPRouteController() 
*gatewayHTTPRouteController {
+       ctrl := &gatewayHTTPRouteController{
+               controller: c,
+               workqueue:  
workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(1*time.Second,
 60*time.Second, 5), "GatewayHTTPRoute"),
+               workers:    1,
+       }
+
+       
ctrl.controller.gatewayHttpRouteInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
+               AddFunc:    ctrl.onAdd,
+               UpdateFunc: ctrl.onUpdate,
+               DeleteFunc: ctrl.OnDelete,
+       })
+       return ctrl
+}
+
+func (c *gatewayHTTPRouteController) run(ctx context.Context) {
+       log.Info("gateway HTTPRoute controller started")
+       defer log.Info("gateway HTTPRoute controller exited")
+       defer c.workqueue.ShutDown()
+
+       if !cache.WaitForCacheSync(ctx.Done(), 
c.controller.gatewayHttpRouteInformer.HasSynced) {
+               log.Error("sync Gateway HTTPRoute cache failed")
+               return
+       }
+
+       for i := 0; i < c.workers; i++ {
+               go c.runWorker(ctx)
+       }
+       <-ctx.Done()
+}
+
+func (c *gatewayHTTPRouteController) 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 *gatewayHTTPRouteController) 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 HTTPRoute resource with invalid key",
+                       zap.Error(err),
+                       zap.String("key", key),
+               )
+               return err
+       }
+
+       httpRoute, err := 
c.controller.gatewayHttpRouteLister.HTTPRoutes(namespace).Get(name)
+       if err != nil {
+               if !k8serrors.IsNotFound(err) {
+                       log.Errorw("failed to get Gateway HTTPRoute",
+                               zap.Error(err),
+                               zap.String("key", key),
+                       )
+                       return err
+               }
+               if ev.Type != types.EventDelete {
+                       log.Warnw("Gateway HTTPRoute was deleted before 
process",
+                               zap.String("key", key),
+                       )
+                       // Don't need to retry.
+                       return nil
+               }
+       }
+
+       if ev.Type == types.EventDelete {
+               if httpRoute != 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.Warnf("discard the stale Gateway delete event since 
the %s exists", key)
+                       return nil
+               }
+               httpRoute = ev.Tombstone.(*gatewayv1alpha2.HTTPRoute)
+       }
+
+       tctx, err := 
c.controller.translator.TranslateGatewayHTTPRouteV1Alpha2(httpRoute)
+
+       if err != nil {
+               log.Errorw("failed to translate gateway HTTPRoute",
+                       zap.Error(err),
+                       zap.Any("object", httpRoute),
+               )
+               return err
+       }
+
+       log.Debugw("translated HTTPRoute",
+               zap.Any("routes", tctx.Routes),
+               zap.Any("upstreams", tctx.Upstreams),
+       )
+       m := &manifest{
+               routes:    tctx.Routes,
+               upstreams: tctx.Upstreams,
+       }
+
+       var (
+               added   *manifest
+               updated *manifest
+               deleted *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.HTTPRoute)
+               oldCtx, err = 
c.controller.translator.TranslateGatewayHTTPRouteV1Alpha2(oldObj)
+               if err != nil {
+                       log.Errorw("failed to translate old HTTPRoute",
+                               zap.String("version", oldObj.APIVersion),
+                               zap.String("event_type", "update"),
+                               zap.Any("HTTPRoute", oldObj),
+                               zap.Error(err),
+                       )
+                       return err
+               }
+
+               om := &manifest{
+                       routes:    oldCtx.Routes,
+                       upstreams: oldCtx.Upstreams,
+               }
+               added, updated, deleted = m.diff(om)
+       }
+
+       return c.controller.syncManifests(ctx, added, updated, deleted)
+}
+
+func (c *gatewayHTTPRouteController) handleSyncErr(obj interface{}, err error) 
{
+       if err == nil {
+               c.workqueue.Forget(obj)
+               
c.controller.MetricsCollector.IncrSyncOperation("gateway_httproute", "success")
+               return
+       }
+       event := obj.(*types.Event)
+       if k8serrors.IsNotFound(err) && event.Type != types.EventDelete {
+               log.Infow("sync gateway HTTPRoute but not found, ignore",
+                       zap.String("event_type", event.Type.String()),
+                       zap.String("HTTPRoute ", event.Object.(string)),
+               )
+               c.workqueue.Forget(event)
+               return
+       }
+       log.Warnw("sync gateway HTTPRoute failed, will retry",
+               zap.Any("object", obj),
+               zap.Error(err),
+       )
+       c.workqueue.AddRateLimited(obj)
+       c.controller.MetricsCollector.IncrSyncOperation("gateway_httproute", 
"failure")
+}
+
+func (c *gatewayHTTPRouteController) onAdd(obj interface{}) {
+       key, err := cache.MetaNamespaceKeyFunc(obj)
+       if err != nil {
+               log.Errorf("found gateway HTTPRoute resource with bad meta 
namespace key: %s", err)
+               return
+       }
+       if !c.controller.isWatchingNamespace(key) {
+               return
+       }
+       log.Debugw("gateway HTTPRoute add event arrived",
+               zap.Any("object", obj),
+       )
+
+       c.workqueue.Add(&types.Event{
+               Type:   types.EventAdd,
+               Object: key,
+       })
+}
+func (c *gatewayHTTPRouteController) onUpdate(oldObj, newObj interface{}) {}
+func (c *gatewayHTTPRouteController) OnDelete(obj interface{})            {}
diff --git a/pkg/kube/translation/gateway_httproute.go 
b/pkg/kube/translation/gateway_httproute.go
new file mode 100644
index 00000000..e096f096
--- /dev/null
+++ b/pkg/kube/translation/gateway_httproute.go
@@ -0,0 +1,235 @@
+// 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"
+       "strings"
+
+       "github.com/pkg/errors"
+       "go.uber.org/zap"
+       gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
+
+       "github.com/apache/apisix-ingress-controller/pkg/id"
+       "github.com/apache/apisix-ingress-controller/pkg/log"
+       apisixv1 
"github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
+)
+
+func (t *translator) TranslateGatewayHTTPRouteV1Alpha2(httpRoute 
*gatewayv1alpha2.HTTPRoute) (*TranslateContext, error) {
+       ctx := defaultEmptyTranslateContext()
+
+       var hosts []string
+       for _, hostname := range httpRoute.Spec.Hostnames {
+               hosts = append(hosts, string(hostname))
+       }
+
+       rules := httpRoute.Spec.Rules
+
+       for i, rule := range rules {
+               backends := rule.BackendRefs
+               if len(backends) == 0 {
+                       continue
+               }
+
+               var ruleUpstreams []*apisixv1.Upstream
+               var weightedUpstreams 
[]apisixv1.TrafficSplitConfigRuleWeightedUpstream
+
+               for j, backend := range backends {
+                       //TODO: Support filters
+                       //filters := backend.Filters
+                       kind := strings.ToLower(string(*backend.Kind))
+                       if kind != "service" {
+                               log.Warnw(fmt.Sprintf("ignore non-service kind 
at Rules[%v].BackendRefs[%v]", i, j),
+                                       zap.String("kind", kind),
+                               )
+                               continue
+                       }
+
+                       ns := string(*backend.Namespace)
+                       //if ns != httpRoute.Namespace {
+                       // TODO: check gatewayv1alpha2.ReferencePolicy
+                       //}
+
+                       ups, err := t.TranslateUpstream(ns, 
string(backend.Name), "", int32(*backend.Port))
+                       if err != nil {
+                               return nil, errors.Wrap(err, 
fmt.Sprintf("failed to translate Rules[%v].BackendRefs[%v]", i, j))
+                       }
+                       name := apisixv1.ComposeUpstreamName(ns, 
string(backend.Name), "", int32(*backend.Port))
+                       ups.Labels["id-name"] = name
+                       ups.ID = id.GenID(name)
+                       ctx.addUpstream(ups)
+                       ruleUpstreams = append(ruleUpstreams, ups)
+
+                       if backend.Weight == nil {
+                               weightedUpstreams = append(weightedUpstreams, 
apisixv1.TrafficSplitConfigRuleWeightedUpstream{
+                                       UpstreamID: ups.ID,
+                                       Weight:     1, // 1 is default value of 
BackendRef
+                               })
+                       } else {
+                               weightedUpstreams = append(weightedUpstreams, 
apisixv1.TrafficSplitConfigRuleWeightedUpstream{
+                                       UpstreamID: ups.ID,
+                                       Weight:     int(*backend.Weight),
+                               })
+                       }
+               }
+               if len(ruleUpstreams) == 0 {
+                       log.Warnw(fmt.Sprintf("ignore all-failed backend refs 
at Rules[%v]", i),
+                               zap.Any("BackendRefs", rule.BackendRefs),
+                       )
+                       continue
+               }
+
+               matches := rule.Matches
+               if len(matches) == 0 {
+                       defaultType := gatewayv1alpha2.PathMatchPathPrefix
+                       defaultValue := "/"
+                       matches = []gatewayv1alpha2.HTTPRouteMatch{
+                               {
+                                       Path: &gatewayv1alpha2.HTTPPathMatch{
+                                               Type:  &defaultType,
+                                               Value: &defaultValue,
+                                       },
+                               },
+                       }
+               }
+
+               for j, match := range matches {
+                       route, err := t.translateGatewayHTTPRouteMatch(&match)
+                       if err != nil {
+                               return nil, errors.Wrap(err, 
fmt.Sprintf("failed to translate Rules[%v].Matches[%v]", i, j))
+                       }
+
+                       route.Hosts = hosts
+
+                       // Bind Upstream
+                       if len(ruleUpstreams) == 1 {
+                               route.UpstreamId = ruleUpstreams[0].ID
+                       } else if len(ruleUpstreams) > 0 {
+                               route.Plugins["traffic-split"] = 
&apisixv1.TrafficSplitConfig{
+                                       Rules: 
[]apisixv1.TrafficSplitConfigRule{
+                                               {
+                                                       WeightedUpstreams: 
weightedUpstreams,
+                                               },
+                                       },
+                               }
+                       }
+
+                       ctx.addRoute(route)
+               }
+
+               //TODO: Support filters
+               //filters := rule.Filters
+       }
+
+       return ctx, nil
+}
+
+func (t *translator) translateGatewayHTTPRouteMatch(match 
*gatewayv1alpha2.HTTPRouteMatch) (*apisixv1.Route, error) {
+       route := apisixv1.NewDefaultRoute()
+
+       if match.Path != nil {
+               switch *match.Path.Type {
+               case gatewayv1alpha2.PathMatchExact:
+                       route.Uri = *match.Path.Value
+               case gatewayv1alpha2.PathMatchPathPrefix:
+                       route.Uri = *match.Path.Value + "*"
+               case gatewayv1alpha2.PathMatchRegularExpression:
+                       var this []apisixv1.StringOrSlice
+                       this = append(this, apisixv1.StringOrSlice{
+                               StrVal: "uri",
+                       })
+                       this = append(this, apisixv1.StringOrSlice{
+                               StrVal: "~~",
+                       })
+                       this = append(this, apisixv1.StringOrSlice{
+                               StrVal: *match.Path.Value,
+                       })
+
+                       route.Vars = append(route.Vars, this)
+               default:
+                       return nil, errors.New("unknown path match type " + 
string(*match.Path.Type))
+               }
+       }
+
+       if match.Headers != nil && len(match.Headers) > 0 {
+               for _, header := range match.Headers {
+                       name := strings.ToLower(string(header.Name))
+                       name = strings.ReplaceAll(name, "-", "_")
+
+                       var this []apisixv1.StringOrSlice
+                       this = append(this, apisixv1.StringOrSlice{
+                               StrVal: "http_" + name,
+                       })
+
+                       switch *header.Type {
+                       case gatewayv1alpha2.HeaderMatchExact:
+                               this = append(this, apisixv1.StringOrSlice{
+                                       StrVal: "==",
+                               })
+                       case gatewayv1alpha2.HeaderMatchRegularExpression:
+                               this = append(this, apisixv1.StringOrSlice{
+                                       StrVal: "~~",
+                               })
+                       default:
+                               return nil, errors.New("unknown header match 
type " + string(*header.Type))
+                       }
+
+                       this = append(this, apisixv1.StringOrSlice{
+                               StrVal: header.Value,
+                       })
+
+                       route.Vars = append(route.Vars, this)
+               }
+       }
+
+       if match.QueryParams != nil && len(match.QueryParams) > 0 {
+               for _, query := range match.QueryParams {
+                       var this []apisixv1.StringOrSlice
+                       this = append(this, apisixv1.StringOrSlice{
+                               StrVal: "arg_" + strings.ToLower(query.Name),
+                       })
+
+                       switch *query.Type {
+                       case gatewayv1alpha2.QueryParamMatchExact:
+                               this = append(this, apisixv1.StringOrSlice{
+                                       StrVal: "==",
+                               })
+                       case gatewayv1alpha2.QueryParamMatchRegularExpression:
+                               this = append(this, apisixv1.StringOrSlice{
+                                       StrVal: "~~",
+                               })
+                       default:
+                               return nil, errors.New("unknown query match 
type " + string(*query.Type))
+                       }
+
+                       this = append(this, apisixv1.StringOrSlice{
+                               StrVal: query.Value,
+                       })
+
+                       route.Vars = append(route.Vars, this)
+               }
+       }
+
+       if match.Method != nil {
+               route.Methods = []string{
+                       string(*match.Method),
+               }
+       }
+
+       return route, nil
+}
diff --git a/pkg/kube/translation/gateway_httproute_test.go 
b/pkg/kube/translation/gateway_httproute_test.go
new file mode 100644
index 00000000..9b8d98f2
--- /dev/null
+++ b/pkg/kube/translation/gateway_httproute_test.go
@@ -0,0 +1,406 @@
+// 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/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"
+       v1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
+)
+
+func mockHTTPRouteTranslator(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{
+                       EndpointLister:       epLister,
+                       ServiceLister:        svcLister,
+                       ApisixUpstreamLister: 
apisixInformersFactory.Apisix().V2beta3().ApisixUpstreams().Lister(),
+               },
+       }
+
+       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)
+
+       return tr, processCh
+}
+
+func TestTranslateGatewayHTTPRouteExactMatch(t *testing.T) {
+       refStr := func(str string) *string {
+               return &str
+       }
+       refKind := func(str gatewayv1alpha2.Kind) *gatewayv1alpha2.Kind {
+               return &str
+       }
+       refNamespace := func(str gatewayv1alpha2.Namespace) 
*gatewayv1alpha2.Namespace {
+               return &str
+       }
+       refMethod := func(str gatewayv1alpha2.HTTPMethod) 
*gatewayv1alpha2.HTTPMethod {
+               return &str
+       }
+       refPathMatchType := func(str gatewayv1alpha2.PathMatchType) 
*gatewayv1alpha2.PathMatchType {
+               return &str
+       }
+       refHeaderMatchType := func(str gatewayv1alpha2.HeaderMatchType) 
*gatewayv1alpha2.HeaderMatchType {
+               return &str
+       }
+       refQueryParamMatchType := func(str gatewayv1alpha2.QueryParamMatchType) 
*gatewayv1alpha2.QueryParamMatchType {
+               return &str
+       }
+       refInt32 := func(i int32) *int32 {
+               return &i
+       }
+       refPortNumber := func(i gatewayv1alpha2.PortNumber) 
*gatewayv1alpha2.PortNumber {
+               return &i
+       }
+
+       tr, processCh := mockHTTPRouteTranslator(t)
+       <-processCh
+       <-processCh
+
+       httpRoute := &gatewayv1alpha2.HTTPRoute{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      "http_route",
+                       Namespace: "test",
+               },
+               Spec: gatewayv1alpha2.HTTPRouteSpec{
+                       Hostnames: []gatewayv1alpha2.Hostname{
+                               "example.com",
+                       },
+                       Rules: []gatewayv1alpha2.HTTPRouteRule{
+                               {
+                                       Matches: 
[]gatewayv1alpha2.HTTPRouteMatch{
+                                               {
+                                                       Path: 
&gatewayv1alpha2.HTTPPathMatch{
+                                                               Type:  
refPathMatchType(gatewayv1alpha2.PathMatchPathPrefix),
+                                                               Value: 
refStr("/path"),
+                                                       },
+                                                       Headers: 
[]gatewayv1alpha2.HTTPHeaderMatch{
+                                                               {
+                                                                       Type:  
refHeaderMatchType(gatewayv1alpha2.HeaderMatchExact),
+                                                                       Name:  
"REFERER",
+                                                                       Value: 
"api7.com",
+                                                               },
+                                                       },
+                                                       QueryParams: 
[]gatewayv1alpha2.HTTPQueryParamMatch{
+                                                               {
+                                                                       Type:  
refQueryParamMatchType(gatewayv1alpha2.QueryParamMatchExact),
+                                                                       Name:  
"user",
+                                                                       Value: 
"api7",
+                                                               },
+                                                               {
+                                                                       Type:  
refQueryParamMatchType(gatewayv1alpha2.QueryParamMatchExact),
+                                                                       Name:  
"title",
+                                                                       Value: 
"ingress",
+                                                               },
+                                                       },
+                                                       Method: 
refMethod(gatewayv1alpha2.HTTPMethodGet),
+                                               },
+                                       },
+                                       Filters: 
[]gatewayv1alpha2.HTTPRouteFilter{
+                                               // TODO
+                                       },
+                                       BackendRefs: 
[]gatewayv1alpha2.HTTPBackendRef{
+                                               {
+                                                       BackendRef: 
gatewayv1alpha2.BackendRef{
+                                                               
BackendObjectReference: gatewayv1alpha2.BackendObjectReference{
+                                                                       Kind:   
   refKind("Service"),
+                                                                       Name:   
   "svc",
+                                                                       
Namespace: refNamespace("test"),
+                                                                       Port:   
   refPortNumber(80),
+                                                               },
+                                                               Weight: 
refInt32(100), // TODO
+                                                       },
+                                                       Filters: 
[]gatewayv1alpha2.HTTPRouteFilter{
+                                                               // TODO
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+               },
+       }
+
+       tctx, err := tr.TranslateGatewayHTTPRouteV1Alpha2(httpRoute)
+       assert.Nil(t, err)
+
+       assert.Equal(t, 1, len(tctx.Routes))
+       assert.Equal(t, 1, len(tctx.Upstreams))
+
+       r := tctx.Routes[0]
+       u := tctx.Upstreams[0]
+
+       // Metadata
+       // FIXME
+       assert.NotEqual(t, "", u.ID)
+       assert.Equal(t, u.ID, r.UpstreamId)
+
+       // hosts
+       assert.Equal(t, 1, len(r.Hosts))
+       assert.Equal(t, "example.com", r.Hosts[0])
+
+       // matches
+       assert.Equal(t, "/path*", r.Uri)
+
+       assert.Equal(t, 3, len(r.Vars))
+       referer := r.Vars[0]
+       argUser := r.Vars[1]
+       argTitle := r.Vars[2]
+       assert.Equal(t, []v1.StringOrSlice{{StrVal: "http_referer"}, {StrVal: 
"=="}, {StrVal: "api7.com"}}, referer)
+       assert.Equal(t, []v1.StringOrSlice{{StrVal: "arg_user"}, {StrVal: 
"=="}, {StrVal: "api7"}}, argUser)
+       assert.Equal(t, []v1.StringOrSlice{{StrVal: "arg_title"}, {StrVal: 
"=="}, {StrVal: "ingress"}}, argTitle)
+
+       assert.Equal(t, 1, len(r.Methods))
+       assert.Equal(t, "GET", r.Methods[0])
+
+       // backend refs
+       assert.Equal(t, "http", u.Scheme) // FIXME
+       assert.Equal(t, 2, len(u.Nodes))
+       assert.Equal(t, "192.168.1.1", u.Nodes[0].Host)
+       assert.Equal(t, 9080, u.Nodes[0].Port)
+       assert.Equal(t, "192.168.1.2", u.Nodes[1].Host)
+       assert.Equal(t, 9080, u.Nodes[0].Port)
+}
+
+func TestTranslateGatewayHTTPRouteRegexMatch(t *testing.T) {
+       refStr := func(str string) *string {
+               return &str
+       }
+       refKind := func(str gatewayv1alpha2.Kind) *gatewayv1alpha2.Kind {
+               return &str
+       }
+       refNamespace := func(str gatewayv1alpha2.Namespace) 
*gatewayv1alpha2.Namespace {
+               return &str
+       }
+       refMethod := func(str gatewayv1alpha2.HTTPMethod) 
*gatewayv1alpha2.HTTPMethod {
+               return &str
+       }
+       refPathMatchType := func(str gatewayv1alpha2.PathMatchType) 
*gatewayv1alpha2.PathMatchType {
+               return &str
+       }
+       refHeaderMatchType := func(str gatewayv1alpha2.HeaderMatchType) 
*gatewayv1alpha2.HeaderMatchType {
+               return &str
+       }
+       refQueryParamMatchType := func(str gatewayv1alpha2.QueryParamMatchType) 
*gatewayv1alpha2.QueryParamMatchType {
+               return &str
+       }
+       refInt32 := func(i int32) *int32 {
+               return &i
+       }
+       refPortNumber := func(i gatewayv1alpha2.PortNumber) 
*gatewayv1alpha2.PortNumber {
+               return &i
+       }
+
+       tr, processCh := mockHTTPRouteTranslator(t)
+       <-processCh
+       <-processCh
+
+       httpRoute := &gatewayv1alpha2.HTTPRoute{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      "http_route",
+                       Namespace: "test",
+               },
+               Spec: gatewayv1alpha2.HTTPRouteSpec{
+                       Hostnames: []gatewayv1alpha2.Hostname{
+                               "example.com",
+                       },
+                       Rules: []gatewayv1alpha2.HTTPRouteRule{
+                               {
+                                       Matches: 
[]gatewayv1alpha2.HTTPRouteMatch{
+                                               {
+                                                       Path: 
&gatewayv1alpha2.HTTPPathMatch{
+                                                               Type:  
refPathMatchType(gatewayv1alpha2.PathMatchRegularExpression),
+                                                               Value: 
refStr("/path"),
+                                                       },
+                                                       Headers: 
[]gatewayv1alpha2.HTTPHeaderMatch{
+                                                               {
+                                                                       Type:  
refHeaderMatchType(gatewayv1alpha2.HeaderMatchRegularExpression),
+                                                                       Name:  
"REFERER",
+                                                                       Value: 
"api7.com",
+                                                               },
+                                                       },
+                                                       QueryParams: 
[]gatewayv1alpha2.HTTPQueryParamMatch{
+                                                               {
+                                                                       Type:  
refQueryParamMatchType(gatewayv1alpha2.QueryParamMatchRegularExpression),
+                                                                       Name:  
"user",
+                                                                       Value: 
"api7",
+                                                               },
+                                                               {
+                                                                       Type:  
refQueryParamMatchType(gatewayv1alpha2.QueryParamMatchRegularExpression),
+                                                                       Name:  
"title",
+                                                                       Value: 
"ingress",
+                                                               },
+                                                       },
+                                                       Method: 
refMethod(gatewayv1alpha2.HTTPMethodGet),
+                                               },
+                                       },
+                                       Filters: 
[]gatewayv1alpha2.HTTPRouteFilter{
+                                               // TODO
+                                       },
+                                       BackendRefs: 
[]gatewayv1alpha2.HTTPBackendRef{
+                                               {
+                                                       BackendRef: 
gatewayv1alpha2.BackendRef{
+                                                               
BackendObjectReference: gatewayv1alpha2.BackendObjectReference{
+                                                                       Kind:   
   refKind("Service"),
+                                                                       Name:   
   "svc",
+                                                                       
Namespace: refNamespace("test"),
+                                                                       Port:   
   refPortNumber(80),
+                                                               },
+                                                               Weight: 
refInt32(100), // TODO
+                                                       },
+                                                       Filters: 
[]gatewayv1alpha2.HTTPRouteFilter{
+                                                               // TODO
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+               },
+       }
+
+       tctx, err := tr.TranslateGatewayHTTPRouteV1Alpha2(httpRoute)
+       assert.Nil(t, err)
+
+       assert.Equal(t, 1, len(tctx.Routes))
+       assert.Equal(t, 1, len(tctx.Upstreams))
+
+       r := tctx.Routes[0]
+       u := tctx.Upstreams[0]
+
+       // Metadata
+       // FIXME
+       assert.NotEqual(t, "", u.ID)
+       assert.Equal(t, u.ID, r.UpstreamId)
+
+       // hosts
+       assert.Equal(t, 1, len(r.Hosts))
+       assert.Equal(t, "example.com", r.Hosts[0])
+
+       // matches
+       assert.Equal(t, 4, len(r.Vars))
+       uri := r.Vars[0]
+       referer := r.Vars[1]
+       argUser := r.Vars[2]
+       argTitle := r.Vars[3]
+       assert.Equal(t, []v1.StringOrSlice{{StrVal: "uri"}, {StrVal: "~~"}, 
{StrVal: "/path"}}, uri)
+       assert.Equal(t, []v1.StringOrSlice{{StrVal: "http_referer"}, {StrVal: 
"~~"}, {StrVal: "api7.com"}}, referer)
+       assert.Equal(t, []v1.StringOrSlice{{StrVal: "arg_user"}, {StrVal: 
"~~"}, {StrVal: "api7"}}, argUser)
+       assert.Equal(t, []v1.StringOrSlice{{StrVal: "arg_title"}, {StrVal: 
"~~"}, {StrVal: "ingress"}}, argTitle)
+
+       assert.Equal(t, 1, len(r.Methods))
+       assert.Equal(t, "GET", r.Methods[0])
+
+       // backend refs
+       assert.Equal(t, "http", u.Scheme) // FIXME
+       assert.Equal(t, 2, len(u.Nodes))
+       assert.Equal(t, "192.168.1.1", u.Nodes[0].Host)
+       assert.Equal(t, 9080, u.Nodes[0].Port)
+       assert.Equal(t, "192.168.1.2", u.Nodes[1].Host)
+       assert.Equal(t, 9080, u.Nodes[0].Port)
+}
+
+// TODO: Multiple BackendRefs, Multiple Rules, Multiple Matches
diff --git a/pkg/kube/translation/translator.go 
b/pkg/kube/translation/translator.go
index d0291abe..8ec4cbb7 100644
--- a/pkg/kube/translation/translator.go
+++ b/pkg/kube/translation/translator.go
@@ -20,6 +20,7 @@ import (
        corev1 "k8s.io/api/core/v1"
        k8serrors "k8s.io/apimachinery/pkg/api/errors"
        listerscorev1 "k8s.io/client-go/listers/core/v1"
+       gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
 
        "github.com/apache/apisix-ingress-controller/pkg/kube"
        configv2 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2"
@@ -108,6 +109,8 @@ type Translator interface {
        // ExtractKeyPair extracts certificate and private key pair from secret
        // Supports APISIX style ("cert" and "key") and Kube style ("tls.crt" 
and "tls.key)
        ExtractKeyPair(s *corev1.Secret, hasPrivateKey bool) ([]byte, []byte, 
error)
+       // TranslateGatewayHTTPRouteV1Alpha2 translates Gateway API HTTPRoute 
to APISIX resources
+       TranslateGatewayHTTPRouteV1Alpha2(httpRoute *gatewayv1alpha2.HTTPRoute) 
(*TranslateContext, error)
 }
 
 // TranslatorOptions contains options to help Translator
@@ -173,7 +176,7 @@ func (t *translator) TranslateUpstream(namespace, name, 
subset string, port int3
        ups := apisixv1.NewDefaultUpstream()
        if err != nil {
                if k8serrors.IsNotFound(err) {
-                       // If subset in ApisixRoute is not empty but the 
ApisixUpstream resouce not found,
+                       // If subset in ApisixRoute is not empty but the 
ApisixUpstream resource not found,
                        // just set an empty node list.
                        if subset != "" {
                                ups.Nodes = apisixv1.UpstreamNodes{}
diff --git a/pkg/types/event.go b/pkg/types/event.go
index 1d68553d..7d523bcd 100644
--- a/pkg/types/event.go
+++ b/pkg/types/event.go
@@ -46,6 +46,8 @@ type Event struct {
        Type EventType
        // Object is the event subject.
        Object interface{}
+       // OldObject is the old object in update event.
+       OldObject interface{}
        // Tombstone is the final state before object was delete,
        // it's useful for DELETE event.
        Tombstone interface{}

Reply via email to