diff --git a/internal/controller/httproute_controller.go b/internal/controller/httproute_controller.go index 48aacf94a..f4f9b7f60 100644 --- a/internal/controller/httproute_controller.go +++ b/internal/controller/httproute_controller.go @@ -397,14 +397,20 @@ func (r *HTTPRouteReconciler) processHTTPRouteBackendRefs(tctx *provider.Transla continue } - var service corev1.Service - if err := r.Get(tctx, client.ObjectKey{ + serviceNS := types.NamespacedName{ Namespace: namespace, Name: name, - }, &service); err != nil { + } + + var service corev1.Service + if err := r.Get(tctx, serviceNS, &service); err != nil { terr = err continue } + if service.Spec.Type == corev1.ServiceTypeExternalName { + tctx.Services[serviceNS] = &service + return nil + } portExists := false for _, port := range service.Spec.Ports { @@ -417,10 +423,7 @@ func (r *HTTPRouteReconciler) processHTTPRouteBackendRefs(tctx *provider.Transla terr = fmt.Errorf("port %d not found in service %s", *backend.Port, name) continue } - tctx.Services[client.ObjectKey{ - Namespace: namespace, - Name: name, - }] = &service + tctx.Services[serviceNS] = &service endpointSliceList := new(discoveryv1.EndpointSliceList) if err := r.List(tctx, endpointSliceList, @@ -434,11 +437,7 @@ func (r *HTTPRouteReconciler) processHTTPRouteBackendRefs(tctx *provider.Transla continue } - tctx.EndpointSlices[client.ObjectKey{ - Namespace: namespace, - Name: name, - }] = endpointSliceList.Items - + tctx.EndpointSlices[serviceNS] = endpointSliceList.Items } return terr } diff --git a/internal/controller/ingress_controller.go b/internal/controller/ingress_controller.go index 5c3bd9ded..8a49b00c5 100644 --- a/internal/controller/ingress_controller.go +++ b/internal/controller/ingress_controller.go @@ -522,10 +522,11 @@ func (r *IngressReconciler) processBackends(tctx *provider.TranslateContext, ing func (r *IngressReconciler) processBackendService(tctx *provider.TranslateContext, namespace string, backendService *networkingv1.IngressServiceBackend) error { // get the service var service corev1.Service - if err := r.Get(tctx, client.ObjectKey{ + serviceNS := types.NamespacedName{ Namespace: namespace, Name: backendService.Name, - }, &service); err != nil { + } + if err := r.Get(tctx, serviceNS, &service); err != nil { if client.IgnoreNotFound(err) == nil { r.Log.Info("service not found", "namespace", namespace, "name", backendService.Name) return nil @@ -533,6 +534,11 @@ func (r *IngressReconciler) processBackendService(tctx *provider.TranslateContex return err } + if service.Spec.Type == corev1.ServiceTypeExternalName { + tctx.Services[serviceNS] = &service + return nil + } + // verify if the port exists var portExists bool if backendService.Port.Number != 0 { @@ -570,16 +576,8 @@ func (r *IngressReconciler) processBackendService(tctx *provider.TranslateContex } // save the endpoint slices to the translate context - tctx.EndpointSlices[client.ObjectKey{ - Namespace: namespace, - Name: backendService.Name, - }] = endpointSliceList.Items - - tctx.Services[client.ObjectKey{ - Namespace: namespace, - Name: backendService.Name, - }] = &service - + tctx.EndpointSlices[serviceNS] = endpointSliceList.Items + tctx.Services[serviceNS] = &service return nil } diff --git a/internal/provider/adc/translator/httproute.go b/internal/provider/adc/translator/httproute.go index 7c9b8e2d7..f7af81bb5 100644 --- a/internal/provider/adc/translator/httproute.go +++ b/internal/provider/adc/translator/httproute.go @@ -5,17 +5,17 @@ import ( "fmt" "strings" + "github.com/api7/gopkg/pkg/log" "github.com/pkg/errors" "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" discoveryv1 "k8s.io/api/discovery/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" - "github.com/api7/api7-ingress-controller/api/v1alpha1" - "github.com/api7/gopkg/pkg/log" - adctypes "github.com/api7/api7-ingress-controller/api/adc" + "github.com/api7/api7-ingress-controller/api/v1alpha1" "github.com/api7/api7-ingress-controller/internal/controller/label" "github.com/api7/api7-ingress-controller/internal/id" "github.com/api7/api7-ingress-controller/internal/provider" @@ -289,16 +289,33 @@ func (t *Translator) translateEndpointSlice(weight int, endpointSlices []discove } func (t *Translator) translateBackendRef(tctx *provider.TranslateContext, ref gatewayv1.BackendRef) adctypes.UpstreamNodes { - endpointSlices := tctx.EndpointSlices[types.NamespacedName{ - Namespace: string(*ref.Namespace), - Name: string(ref.Name), - }] - weight := 1 + port := 80 if ref.Weight != nil { weight = int(*ref.Weight) } + if ref.Port != nil { + port = int(*ref.Port) + } + key := types.NamespacedName{ + Namespace: string(*ref.Namespace), + Name: string(ref.Name), + } + service, ok := tctx.Services[key] + if !ok { + return adctypes.UpstreamNodes{} + } + if service.Spec.Type == corev1.ServiceTypeExternalName { + return adctypes.UpstreamNodes{ + { + Host: service.Spec.ExternalName, + Port: port, + Weight: weight, + }, + } + } + endpointSlices := tctx.EndpointSlices[key] return t.translateEndpointSlice(weight, endpointSlices) } diff --git a/internal/provider/adc/translator/ingress.go b/internal/provider/adc/translator/ingress.go index c7e25c431..d1f3818da 100644 --- a/internal/provider/adc/translator/ingress.go +++ b/internal/provider/adc/translator/ingress.go @@ -105,12 +105,7 @@ func (t *Translator) TranslateIngress(tctx *provider.TranslateContext, obj *netw // get the EndpointSlice of the backend service backendService := path.Backend.Service - var endpointSlices []discoveryv1.EndpointSlice if backendService != nil { - endpointSlices = tctx.EndpointSlices[types.NamespacedName{ - Namespace: obj.Namespace, - Name: backendService.Name, - }] backendRef := convertBackendRef(obj.Namespace, backendService.Name, "Service") t.AttachBackendTrafficPolicyToUpstream(backendRef, tctx.BackendTrafficPolicies, upstream) } @@ -131,28 +126,39 @@ func (t *Translator) TranslateIngress(tctx *provider.TranslateContext, obj *netw if getService == nil { continue } - - var getServicePort *corev1.ServicePort - for _, port := range getService.Spec.Ports { - port := port - if servicePort > 0 && port.Port == servicePort { - getServicePort = &port - break + if getService.Spec.Type == corev1.ServiceTypeExternalName { + defaultServicePort := 80 + if servicePort > 0 { + defaultServicePort = int(servicePort) } - if servicePortName != "" && port.Name == servicePortName { - getServicePort = &port - break + upstream.Nodes = adctypes.UpstreamNodes{ + { + Host: getService.Spec.ExternalName, + Port: defaultServicePort, + Weight: 1, + }, + } + } else { + var getServicePort *corev1.ServicePort + for _, port := range getService.Spec.Ports { + port := port + if servicePort > 0 && port.Port == servicePort { + getServicePort = &port + break + } + if servicePortName != "" && port.Name == servicePortName { + getServicePort = &port + break + } + } + endpointSlices := tctx.EndpointSlices[types.NamespacedName{ + Namespace: obj.Namespace, + Name: backendService.Name, + }] + // convert the EndpointSlice to upstream nodes + if len(endpointSlices) > 0 { + upstream.Nodes = t.translateEndpointSliceForIngress(1, endpointSlices, getServicePort) } - } - - // convert the EndpointSlice to upstream nodes - if len(endpointSlices) > 0 { - upstream.Nodes = t.translateEndpointSliceForIngress(1, endpointSlices, getServicePort) - } - - // if there is no upstream node, create a placeholder node - if len(upstream.Nodes) == 0 { - upstream.Nodes = adctypes.UpstreamNodes{} } service.Upstream = upstream diff --git a/test/e2e/gatewayapi/httproute.go b/test/e2e/gatewayapi/httproute.go index be2c9ebad..5838be81b 100644 --- a/test/e2e/gatewayapi/httproute.go +++ b/test/e2e/gatewayapi/httproute.go @@ -365,6 +365,33 @@ spec: }) Context("HTTPRoute Base", func() { + var httprouteWithExternalName = ` +apiVersion: v1 +kind: Service +metadata: + name: httpbin-external-domain +spec: + type: ExternalName + externalName: httpbin.org +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: httpbin +spec: + parentRefs: + - name: api7ee + hostnames: + - httpbin.external + rules: + - matches: + - path: + type: Exact + value: /get + backendRefs: + - name: httpbin-external-domain + port: 80 +` var exactRouteByGet = ` apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute @@ -437,6 +464,18 @@ spec: Expect(). Status(404) }) + + It("Proxy External Service", func() { + By("create HTTPRoute") + ResourceApplied("HTTPRoute", "httpbin", httprouteWithExternalName, 1) + + By("checking the external service response") + s.NewAPISIXClient(). + GET("/get"). + WithHost("httpbin.external"). + Expect(). + Status(200) + }) }) Context("HTTPRoute Rule Match", func() { var exactRouteByGet = ` diff --git a/test/e2e/ingress/ingress.go b/test/e2e/ingress/ingress.go index d0eb27de3..0e810b4da 100644 --- a/test/e2e/ingress/ingress.go +++ b/test/e2e/ingress/ingress.go @@ -158,11 +158,36 @@ spec: number: 80 ` + var ingressWithExternalName = ` +apiVersion: v1 +kind: Service +metadata: + name: httpbin-external-domain +spec: + type: ExternalName + externalName: httpbin.org +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: api7-ingress-default +spec: + rules: + - host: httpbin.external + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: httpbin-external-domain + port: + number: 80 +` + It("Test IngressClass Selection", func() { By("create GatewayProxy") gatewayProxy := fmt.Sprintf(gatewayProxyYaml, framework.DashboardTLSEndpoint, s.AdminKey()) - - By("create GatewayProxy") err := s.CreateResourceFromStringWithNamespace(gatewayProxy, "default") Expect(err).NotTo(HaveOccurred(), "creating GatewayProxy") time.Sleep(5 * time.Second) @@ -184,6 +209,31 @@ spec: Expect(). Status(200) }) + + It("Proxy External Service", func() { + By("create GatewayProxy") + gatewayProxy := fmt.Sprintf(gatewayProxyYaml, framework.DashboardTLSEndpoint, s.AdminKey()) + err := s.CreateResourceFromStringWithNamespace(gatewayProxy, "default") + Expect(err).NotTo(HaveOccurred(), "creating GatewayProxy") + time.Sleep(5 * time.Second) + + By("create Default IngressClass") + err = s.CreateResourceFromStringWithNamespace(defaultIngressClass, "") + Expect(err).NotTo(HaveOccurred(), "creating Default IngressClass") + time.Sleep(5 * time.Second) + + By("create Ingress") + err = s.CreateResourceFromString(ingressWithExternalName) + Expect(err).NotTo(HaveOccurred(), "creating Ingress without IngressClass") + time.Sleep(5 * time.Second) + + By("checking the external service response") + s.NewAPISIXClient(). + GET("/get"). + WithHost("httpbin.external"). + Expect(). + Status(200) + }) }) Context("IngressClass with GatewayProxy", func() {