Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions internal/adc/translator/ingress.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ import (
discoveryv1 "k8s.io/api/discovery/v1"
networkingv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/types"
<<<<<<< HEAD
=======
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/utils/ptr"
>>>>>>> 18882b92 (fix(ingress): port.name matching failure for ExternalName Services (#2604))

adctypes "github.com/apache/apisix-ingress-controller/api/adc"
"github.com/apache/apisix-ingress-controller/internal/controller/label"
Expand Down Expand Up @@ -94,6 +99,7 @@ func (t *Translator) TranslateIngress(tctx *provider.TranslateContext, obj *netw
result.SSL = append(result.SSL, ssl)
}

<<<<<<< HEAD
// process Ingress rules, convert to Service and Route objects
for i, rule := range obj.Spec.Rules {
// extract hostnames
Expand Down Expand Up @@ -219,6 +225,129 @@ func (t *Translator) TranslateIngress(tctx *provider.TranslateContext, obj *netw
}

return result, nil
=======
func (t *Translator) buildServiceFromIngressPath(
tctx *provider.TranslateContext,
obj *networkingv1.Ingress,
path *networkingv1.HTTPIngressPath,
ruleIndex, pathIndex int,
hosts []string,
labels map[string]string,
) *adctypes.Service {
if path.Backend.Service == nil {
return nil
}

service := adctypes.NewDefaultService()
service.Labels = labels
service.Name = adctypes.ComposeServiceNameWithRule(obj.Namespace, obj.Name, fmt.Sprintf("%d-%d", ruleIndex, pathIndex))
service.ID = id.GenID(service.Name)
service.Hosts = hosts

upstream := adctypes.NewDefaultUpstream()
protocol := t.resolveIngressUpstream(tctx, obj, path.Backend.Service, upstream)
service.Upstream = upstream

route := buildRouteFromIngressPath(obj, path, ruleIndex, pathIndex, labels)
if protocol == internaltypes.AppProtocolWS || protocol == internaltypes.AppProtocolWSS {
route.EnableWebsocket = ptr.To(true)
}
service.Routes = []*adctypes.Route{route}

t.fillHTTPRoutePoliciesForIngress(tctx, service.Routes)
return service
}

func (t *Translator) resolveIngressUpstream(
tctx *provider.TranslateContext,
obj *networkingv1.Ingress,
backendService *networkingv1.IngressServiceBackend,
upstream *adctypes.Upstream,
) string {
backendRef := convertBackendRef(obj.Namespace, backendService.Name, internaltypes.KindService)
t.AttachBackendTrafficPolicyToUpstream(backendRef, tctx.BackendTrafficPolicies, upstream)
// determine service port/port name
var protocol string
var port intstr.IntOrString
if backendService.Port.Number != 0 {
port = intstr.FromInt32(backendService.Port.Number)
} else if backendService.Port.Name != "" {
port = intstr.FromString(backendService.Port.Name)
}

getService := tctx.Services[types.NamespacedName{
Namespace: obj.Namespace,
Name: backendService.Name,
}]
if getService == nil {
return protocol
}
getServicePort, _ := findMatchingServicePort(getService, port)
if getServicePort != nil && getServicePort.AppProtocol != nil {
protocol = *getServicePort.AppProtocol
if upstream.Scheme == "" {
upstream.Scheme = appProtocolToUpstreamScheme(*getServicePort.AppProtocol)
}
}
if getService.Spec.Type == corev1.ServiceTypeExternalName {
servicePort := 80
if getServicePort != nil {
servicePort = int(getServicePort.Port)
}
upstream.Nodes = adctypes.UpstreamNodes{
{
Host: getService.Spec.ExternalName,
Port: servicePort,
Weight: 1,
},
}
return protocol
}

endpointSlices := tctx.EndpointSlices[types.NamespacedName{
Namespace: obj.Namespace,
Name: backendService.Name,
}]
if len(endpointSlices) > 0 {
upstream.Nodes = t.translateEndpointSliceForIngress(1, endpointSlices, getServicePort)
}

return protocol
}

func buildRouteFromIngressPath(
obj *networkingv1.Ingress,
path *networkingv1.HTTPIngressPath,
ruleIndex, pathIndex int,
labels map[string]string,
) *adctypes.Route {
route := adctypes.NewDefaultRoute()
route.Name = adctypes.ComposeRouteName(obj.Namespace, obj.Name, fmt.Sprintf("%d-%d", ruleIndex, pathIndex))
route.ID = id.GenID(route.Name)
route.Labels = labels

uris := []string{path.Path}
if path.PathType != nil {
switch *path.PathType {
case networkingv1.PathTypePrefix:
// As per the specification of Ingress path matching rule:
// if the last element of the path is a substring of the
// last element in request path, it is not a match, e.g. /foo/bar
// matches /foo/bar/baz, but does not match /foo/barbaz.
// While in APISIX, /foo/bar matches both /foo/bar/baz and
// /foo/barbaz.
// In order to be conformant with Ingress specification, here
// we create two paths here, the first is the path itself
// (exact match), the other is path + "/*" (prefix match).
prefix := strings.TrimSuffix(path.Path, "/") + "/*"
uris = append(uris, prefix)
case networkingv1.PathTypeImplementationSpecific:
uris = []string{"/*"}
}
}
route.Uris = uris
return route
>>>>>>> 18882b92 (fix(ingress): port.name matching failure for ExternalName Services (#2604))
}

// translateEndpointSliceForIngress create upstream nodes from EndpointSlice
Expand Down
50 changes: 50 additions & 0 deletions test/e2e/ingress/ingress.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,36 @@ spec:
name: httpbin-external-domain
port:
number: 80
`
var ingressWithExternalNamePortName = `
apiVersion: v1
kind: Service
metadata:
name: httpbin-external-domain
spec:
type: ExternalName
externalName: httpbin-service-e2e-test
ports:
- name: http
port: 8080
protocol: TCP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-external-name-port-name
spec:
rules:
- host: httpbin.external
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: httpbin-external-domain
port:
name: http
`
BeforeEach(func() {
By("create GatewayProxy")
Expand Down Expand Up @@ -290,6 +320,26 @@ spec:
})
})

It("Mathch Service Port by Name", func() {
By("create Ingress")
err := s.CreateResourceFromString(ingressWithExternalNamePortName)
Expect(err).NotTo(HaveOccurred(), "creating Ingress without IngressClass")

By("checking the external service response")
s.RequestAssert(&scaffold.RequestAssert{
Method: "GET",
Path: "/get",
Host: "httpbin.external",
Check: scaffold.WithExpectedStatus(http.StatusOK),
})

upstreams, err := s.DefaultDataplaneResource().Upstream().List(context.Background())
Expect(err).NotTo(HaveOccurred(), "listing Upstream")
Expect(upstreams).To(HaveLen(1), "the number of Upstream")
Expect(upstreams[0].Nodes).To(HaveLen(1), "the number of Upstream nodes")
Expect(upstreams[0].Nodes[0].Port).To(Equal(8080), "the port of Upstream node")
})

It("Delete Ingress during restart", func() {
By("create Ingress with ExternalName")
ingressName := s.Namespace() + "-external"
Expand Down
4 changes: 4 additions & 0 deletions test/e2e/scaffold/httpbin.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ spec:
port: 80
protocol: TCP
targetPort: 80
- name: http-v2
port: 8080
protocol: TCP
targetPort: 80
type: ClusterIP
`
)
Expand Down