From 3f18a6349c1fa47a67b1cf2b1d37090b30cf44fc Mon Sep 17 00:00:00 2001 From: ashing Date: Mon, 31 Mar 2025 12:05:21 +0800 Subject: [PATCH 01/10] feat: support ingress translator Signed-off-by: ashing --- internal/controller/indexer/indexer.go | 2 +- internal/controller/ingress_controller.go | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/internal/controller/indexer/indexer.go b/internal/controller/indexer/indexer.go index 05ab2f59..4ad2bdc9 100644 --- a/internal/controller/indexer/indexer.go +++ b/internal/controller/indexer/indexer.go @@ -14,8 +14,8 @@ const ( ExtensionRef = "extensionRef" ParametersRef = "parametersRef" ParentRefs = "parentRefs" - IngressClass = "ingressClass" SecretIndexRef = "secretRefs" + IngressClass = "ingressClass" IngressClassRef = "ingressClassRef" ) diff --git a/internal/controller/ingress_controller.go b/internal/controller/ingress_controller.go index d5fc617f..48055503 100644 --- a/internal/controller/ingress_controller.go +++ b/internal/controller/ingress_controller.go @@ -283,19 +283,16 @@ func (r *IngressReconciler) processBackends(ctx context.Context, tctx *provider. if rule.HTTP == nil { continue } - for _, path := range rule.HTTP.Paths { if path.Backend.Service == nil { continue } - service := path.Backend.Service if err := r.processBackendService(ctx, tctx, ingress.Namespace, service); err != nil { terr = err } } } - return terr } @@ -383,6 +380,7 @@ func (r *IngressReconciler) updateStatus(ctx context.Context, ingress *networkin if err != nil { return fmt.Errorf("invalid ingress-publish-service format: %s, expected format: namespace/name", publishService) } + // if the namespace is not specified, use the ingress namespace if namespace == "" { namespace = ingress.Namespace } From a2f6e64693a18fe9c62f091f24054d776a2e3f5f Mon Sep 17 00:00:00 2001 From: ashing Date: Mon, 31 Mar 2025 15:54:31 +0800 Subject: [PATCH 02/10] feat: add translator Signed-off-by: ashing --- internal/provider/adc/adc.go | 3 + internal/provider/adc/translator/ingress.go | 185 +++++++++++++++++++- 2 files changed, 187 insertions(+), 1 deletion(-) diff --git a/internal/provider/adc/adc.go b/internal/provider/adc/adc.go index 5d1674d9..88d07898 100644 --- a/internal/provider/adc/adc.go +++ b/internal/provider/adc/adc.go @@ -96,6 +96,9 @@ func (d *adcClient) Delete(ctx context.Context, obj client.Object) error { labels = label.GenLabel(obj) case *gatewayv1.Gateway: // delete all resources + case *networkingv1.Ingress: + resourceTypes = append(resourceTypes, "service", "ssl") + labels = label.GenLabel(obj) } return d.sync(Task{ diff --git a/internal/provider/adc/translator/ingress.go b/internal/provider/adc/translator/ingress.go index 38b3d5e7..ed6a842c 100644 --- a/internal/provider/adc/translator/ingress.go +++ b/internal/provider/adc/translator/ingress.go @@ -1,10 +1,193 @@ package translator import ( + "fmt" + + adctypes "github.com/api7/api7-ingress-controller/api/adc" + "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" + "github.com/api7/gopkg/pkg/log" + "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + discoveryv1 "k8s.io/api/discovery/v1" networkingv1 "k8s.io/api/networking/v1" + "k8s.io/apimachinery/pkg/types" ) +func (t *Translator) translateIngressTLS(ingressTLS *networkingv1.IngressTLS, secret *corev1.Secret, labels map[string]string) (*adctypes.SSL, error) { + // extract the key pair from the secret + cert, key, err := extractKeyPair(secret, true) + if err != nil { + return nil, err + } + + hosts := ingressTLS.Hosts + certHosts, err := extractHost(cert) + if err != nil { + return nil, err + } + hosts = append(hosts, certHosts...) + if len(hosts) == 0 { + return nil, fmt.Errorf("no hosts found in ingress TLS") + } + + ssl := &adctypes.SSL{ + Metadata: adctypes.Metadata{ + Labels: labels, + }, + Certificates: []adctypes.Certificate{ + { + Certificate: string(cert), + Key: string(key), + }, + }, + Snis: hosts, + } + ssl.ID = id.GenID(string(cert)) + + return ssl, nil +} + func (t *Translator) TranslateIngress(tctx *provider.TranslateContext, obj *networkingv1.Ingress) (*TranslateResult, error) { - return nil, nil + result := &TranslateResult{} + + labels := label.GenLabel(obj) + + // handle TLS configuration, convert to SSL objects + for _, tls := range obj.Spec.TLS { + if tls.SecretName == "" { + continue + } + secret := tctx.Secrets[types.NamespacedName{ + Namespace: obj.Namespace, + Name: tls.SecretName, + }] + if secret == nil { + continue + } + if secret.Data == nil { + log.Warnw("secret data is nil", zap.String("secret", secret.Namespace+"/"+secret.Name)) + continue + } + ssl, err := t.translateIngressTLS(&tls, secret, labels) + if err != nil { + return nil, err + } + + result.SSL = append(result.SSL, ssl) + } + + // process Ingress rules, convert to Service and Route objects + for i, rule := range obj.Spec.Rules { + // extract hostnames + var hosts []string + if rule.Host != "" { + hosts = append(hosts, rule.Host) + } + // if there is no HTTP path, skip + if rule.HTTP == nil { + continue + } + + // create a service for each path + for j, path := range rule.HTTP.Paths { + if path.Backend.Service == nil { + continue + } + + service := adctypes.NewDefaultService() + service.Labels = labels + service.Name = adctypes.ComposeServiceNameWithRule(obj.Namespace, obj.Name, fmt.Sprintf("%d-%d", i, j)) + service.ID = id.GenID(service.Name) + service.Hosts = hosts + + // create an upstream + upstream := adctypes.NewDefaultUpstream() + + // get the EndpointSlice of the backend service + backendService := path.Backend.Service + endpointSlices := tctx.EndpointSlices[types.NamespacedName{ + Namespace: obj.Namespace, + Name: backendService.Name, + }] + + // get the service port configuration + var servicePort int32 = 0 + var servicePortName string + if backendService.Port.Number != 0 { + servicePort = backendService.Port.Number + } else if backendService.Port.Name != "" { + servicePortName = backendService.Port.Name + } + + // convert the EndpointSlice to upstream nodes + if len(endpointSlices) > 0 { + upstream.Nodes = t.translateEndpointSliceForIngress(1, endpointSlices, servicePort, servicePortName) + } + + // if there is no upstream node, create a placeholder node + if len(upstream.Nodes) == 0 { + upstream.Nodes = adctypes.UpstreamNodes{ + { + Host: "0.0.0.0", + Port: int(servicePort), + Weight: 1, + }, + } + } + + service.Upstream = upstream + + // create a route + route := adctypes.NewDefaultRoute() + route.Name = adctypes.ComposeRouteName(obj.Namespace, obj.Name, fmt.Sprintf("%d-%d", i, j)) + route.ID = id.GenID(route.Name) + route.Labels = labels + + // set the path matching rule + switch *path.PathType { + case networkingv1.PathTypeExact: + route.Uris = []string{path.Path} + case networkingv1.PathTypePrefix: + route.Uris = []string{path.Path + "*"} + case networkingv1.PathTypeImplementationSpecific: + route.Uris = []string{path.Path + "*"} + } + + service.Routes = []*adctypes.Route{route} + result.Services = append(result.Services, service) + } + } + + return result, nil +} + +// translateEndpointSliceForIngress create upstream nodes from EndpointSlice +func (t *Translator) translateEndpointSliceForIngress(weight int, endpointSlices []discoveryv1.EndpointSlice, portNumber int32, portName string) adctypes.UpstreamNodes { + var nodes adctypes.UpstreamNodes + if len(endpointSlices) == 0 { + return nodes + } + + for _, endpointSlice := range endpointSlices { + for _, port := range endpointSlice.Ports { + // if the port number or port name is specified, only use the matching port + if (portNumber != 0 && *port.Port != portNumber) || (portName != "" && *port.Name != portName) { + continue + } + for _, endpoint := range endpointSlice.Endpoints { + for _, addr := range endpoint.Addresses { + node := adctypes.UpstreamNode{ + Host: addr, + Port: int(*port.Port), + Weight: weight, + } + nodes = append(nodes, node) + } + } + } + } + + return nodes } From 822d167a233274e6d632000dad5443249d56129b Mon Sep 17 00:00:00 2001 From: ashing Date: Mon, 31 Mar 2025 17:54:12 +0800 Subject: [PATCH 03/10] fix: r Signed-off-by: ashing --- api/adc/types.go | 2 +- internal/controller/ingress_controller.go | 6 ++ internal/provider/adc/translator/gateway.go | 13 +++-- internal/provider/adc/translator/ingress.go | 65 ++++++++++++--------- 4 files changed, 50 insertions(+), 36 deletions(-) diff --git a/api/adc/types.go b/api/adc/types.go index 978b844d..18b72e68 100644 --- a/api/adc/types.go +++ b/api/adc/types.go @@ -148,7 +148,7 @@ type Upstream struct { HashOn string `json:"hash_on,omitempty" yaml:"hash_on,omitempty"` Key string `json:"key,omitempty" yaml:"key,omitempty"` - Nodes UpstreamNodes `json:"nodes,omitempty" yaml:"nodes,omitempty"` + Nodes UpstreamNodes `json:"nodes" yaml:"nodes"` PassHost *PassHost `json:"pass_host,omitempty" yaml:"pass_host,omitempty"` Retries *int64 `json:"retries,omitempty" yaml:"retries,omitempty"` RetryTimeout *float64 `json:"retry_timeout,omitempty" yaml:"retry_timeout,omitempty"` diff --git a/internal/controller/ingress_controller.go b/internal/controller/ingress_controller.go index 48055503..d28753a7 100644 --- a/internal/controller/ingress_controller.go +++ b/internal/controller/ingress_controller.go @@ -10,6 +10,7 @@ import ( "github.com/api7/api7-ingress-controller/internal/provider" "github.com/api7/gopkg/pkg/log" "github.com/go-logr/logr" + "go.uber.org/zap" corev1 "k8s.io/api/core/v1" discoveryv1 "k8s.io/api/discovery/v1" networkingv1 "k8s.io/api/networking/v1" @@ -267,6 +268,11 @@ func (r *IngressReconciler) processTLS(ctx context.Context, tctx *provider.Trans return err } + if secret.Data == nil { + log.Warnw("secret data is nil", zap.String("secret", secret.Namespace+"/"+secret.Name)) + continue + } + // add the secret to the translate context tctx.Secrets[types.NamespacedName{Namespace: ingress.Namespace, Name: tls.SecretName}] = &secret } diff --git a/internal/provider/adc/translator/gateway.go b/internal/provider/adc/translator/gateway.go index 64835c7a..62e2af51 100644 --- a/internal/provider/adc/translator/gateway.go +++ b/internal/provider/adc/translator/gateway.go @@ -85,16 +85,17 @@ func (t *Translator) translateSecret(tctx *provider.TranslateContext, listener g // Dashboard doesn't allow wildcard hostname if listener.Hostname != nil && *listener.Hostname != "" { sslObj.Snis = append(sslObj.Snis, string(*listener.Hostname)) + } else { + hosts, err := extractHost(cert) + if err != nil { + return nil, err + } + sslObj.Snis = append(sslObj.Snis, hosts...) } - hosts, err := extractHost(cert) - if err != nil { - return nil, err - } - if len(hosts) == 0 { + if len(sslObj.Snis) == 0 { log.Warnw("no valid hostname found in certificate", zap.String("secret", secret.Namespace+"/"+secret.Name)) continue } - sslObj.Snis = append(sslObj.Snis, hosts...) // Note: Dashboard doesn't allow duplicate certificate across ssl objects sslObj.ID = id.GenID(string(cert)) sslObj.Labels = label.GenLabel(obj) diff --git a/internal/provider/adc/translator/ingress.go b/internal/provider/adc/translator/ingress.go index ed6a842c..7419ccde 100644 --- a/internal/provider/adc/translator/ingress.go +++ b/internal/provider/adc/translator/ingress.go @@ -2,13 +2,12 @@ package translator import ( "fmt" + "strings" adctypes "github.com/api7/api7-ingress-controller/api/adc" "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" - "github.com/api7/gopkg/pkg/log" - "go.uber.org/zap" corev1 "k8s.io/api/core/v1" discoveryv1 "k8s.io/api/discovery/v1" networkingv1 "k8s.io/api/networking/v1" @@ -23,11 +22,13 @@ func (t *Translator) translateIngressTLS(ingressTLS *networkingv1.IngressTLS, se } hosts := ingressTLS.Hosts - certHosts, err := extractHost(cert) - if err != nil { - return nil, err + if len(hosts) == 0 { + certHosts, err := extractHost(cert) + if err != nil { + return nil, err + } + hosts = append(hosts, certHosts...) } - hosts = append(hosts, certHosts...) if len(hosts) == 0 { return nil, fmt.Errorf("no hosts found in ingress TLS") } @@ -66,10 +67,6 @@ func (t *Translator) TranslateIngress(tctx *provider.TranslateContext, obj *netw if secret == nil { continue } - if secret.Data == nil { - log.Warnw("secret data is nil", zap.String("secret", secret.Namespace+"/"+secret.Name)) - continue - } ssl, err := t.translateIngressTLS(&tls, secret, labels) if err != nil { return nil, err @@ -120,21 +117,16 @@ func (t *Translator) TranslateIngress(tctx *provider.TranslateContext, obj *netw } else if backendService.Port.Name != "" { servicePortName = backendService.Port.Name } + _ = servicePort // convert the EndpointSlice to upstream nodes if len(endpointSlices) > 0 { - upstream.Nodes = t.translateEndpointSliceForIngress(1, endpointSlices, servicePort, servicePortName) + upstream.Nodes = t.translateEndpointSliceForIngress(1, endpointSlices, servicePortName) } // if there is no upstream node, create a placeholder node if len(upstream.Nodes) == 0 { - upstream.Nodes = adctypes.UpstreamNodes{ - { - Host: "0.0.0.0", - Port: int(servicePort), - Weight: 1, - }, - } + upstream.Nodes = adctypes.UpstreamNodes{} } service.Upstream = upstream @@ -145,15 +137,30 @@ func (t *Translator) TranslateIngress(tctx *provider.TranslateContext, obj *netw route.ID = id.GenID(route.Name) route.Labels = labels - // set the path matching rule - switch *path.PathType { - case networkingv1.PathTypeExact: - route.Uris = []string{path.Path} - case networkingv1.PathTypePrefix: - route.Uris = []string{path.Path + "*"} - case networkingv1.PathTypeImplementationSpecific: - route.Uris = []string{path.Path + "*"} + uris := []string{path.Path} + if path.PathType != nil { + if *path.PathType == 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 := path.Path + if strings.HasSuffix(prefix, "/") { + prefix += "*" + } else { + prefix += "/*" + } + uris = append(uris, prefix) + } else if *path.PathType == networkingv1.PathTypeImplementationSpecific { + uris = []string{"/*"} + } } + route.Uris = uris service.Routes = []*adctypes.Route{route} result.Services = append(result.Services, service) @@ -164,7 +171,7 @@ func (t *Translator) TranslateIngress(tctx *provider.TranslateContext, obj *netw } // translateEndpointSliceForIngress create upstream nodes from EndpointSlice -func (t *Translator) translateEndpointSliceForIngress(weight int, endpointSlices []discoveryv1.EndpointSlice, portNumber int32, portName string) adctypes.UpstreamNodes { +func (t *Translator) translateEndpointSliceForIngress(weight int, endpointSlices []discoveryv1.EndpointSlice, portName string) adctypes.UpstreamNodes { var nodes adctypes.UpstreamNodes if len(endpointSlices) == 0 { return nodes @@ -172,8 +179,8 @@ func (t *Translator) translateEndpointSliceForIngress(weight int, endpointSlices for _, endpointSlice := range endpointSlices { for _, port := range endpointSlice.Ports { - // if the port number or port name is specified, only use the matching port - if (portNumber != 0 && *port.Port != portNumber) || (portName != "" && *port.Name != portName) { + // if the port name is specified, only use the matching port + if portName != "" && *port.Name != portName { continue } for _, endpoint := range endpointSlice.Endpoints { From 6b5add7b426851831fea10a0218f4e639441a8be Mon Sep 17 00:00:00 2001 From: ashing Date: Mon, 31 Mar 2025 18:28:48 +0800 Subject: [PATCH 04/10] fix: r Signed-off-by: ashing --- internal/controller/ingress_controller.go | 5 ++++ internal/provider/adc/translator/ingress.go | 30 +++++++++++++++++---- internal/provider/provider.go | 1 + 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/internal/controller/ingress_controller.go b/internal/controller/ingress_controller.go index d28753a7..a8ee72f9 100644 --- a/internal/controller/ingress_controller.go +++ b/internal/controller/ingress_controller.go @@ -359,6 +359,11 @@ func (r *IngressReconciler) processBackendService(ctx context.Context, tctx *pro Name: backendService.Name, }] = endpointSliceList.Items + tctx.Services[client.ObjectKey{ + Namespace: namespace, + Name: backendService.Name, + }] = &service + return nil } diff --git a/internal/provider/adc/translator/ingress.go b/internal/provider/adc/translator/ingress.go index 7419ccde..b104e0d6 100644 --- a/internal/provider/adc/translator/ingress.go +++ b/internal/provider/adc/translator/ingress.go @@ -117,11 +117,31 @@ func (t *Translator) TranslateIngress(tctx *provider.TranslateContext, obj *netw } else if backendService.Port.Name != "" { servicePortName = backendService.Port.Name } - _ = servicePort + + getService := tctx.Services[types.NamespacedName{ + Namespace: obj.Namespace, + Name: backendService.Name, + }] + 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 servicePortName != "" && port.Name == servicePortName { + getServicePort = &port + break + } + } // convert the EndpointSlice to upstream nodes if len(endpointSlices) > 0 { - upstream.Nodes = t.translateEndpointSliceForIngress(1, endpointSlices, servicePortName) + upstream.Nodes = t.translateEndpointSliceForIngress(1, endpointSlices, getServicePort) } // if there is no upstream node, create a placeholder node @@ -171,7 +191,7 @@ func (t *Translator) TranslateIngress(tctx *provider.TranslateContext, obj *netw } // translateEndpointSliceForIngress create upstream nodes from EndpointSlice -func (t *Translator) translateEndpointSliceForIngress(weight int, endpointSlices []discoveryv1.EndpointSlice, portName string) adctypes.UpstreamNodes { +func (t *Translator) translateEndpointSliceForIngress(weight int, endpointSlices []discoveryv1.EndpointSlice, servciePort *corev1.ServicePort) adctypes.UpstreamNodes { var nodes adctypes.UpstreamNodes if len(endpointSlices) == 0 { return nodes @@ -179,8 +199,8 @@ func (t *Translator) translateEndpointSliceForIngress(weight int, endpointSlices for _, endpointSlice := range endpointSlices { for _, port := range endpointSlice.Ports { - // if the port name is specified, only use the matching port - if portName != "" && *port.Name != portName { + // if the port number is specified, only use the matching port + if servciePort != nil && *port.Name != servciePort.Name { continue } for _, endpoint := range endpointSlice.Endpoints { diff --git a/internal/provider/provider.go b/internal/provider/provider.go index a8fd9e23..1e1ea093 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -24,6 +24,7 @@ type TranslateContext struct { EndpointSlices map[types.NamespacedName][]discoveryv1.EndpointSlice Secrets map[types.NamespacedName]*corev1.Secret PluginConfigs map[types.NamespacedName]*v1alpha1.PluginConfig + Services map[types.NamespacedName]*corev1.Service } func NewDefaultTranslateContext() *TranslateContext { From cb8a115b1dcd711837f03ef6615a97a294ebf16d Mon Sep 17 00:00:00 2001 From: ashing Date: Tue, 1 Apr 2025 12:13:34 +0800 Subject: [PATCH 05/10] fix: r Signed-off-by: ashing --- internal/provider/adc/translator/gateway.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/internal/provider/adc/translator/gateway.go b/internal/provider/adc/translator/gateway.go index 62e2af51..64835c7a 100644 --- a/internal/provider/adc/translator/gateway.go +++ b/internal/provider/adc/translator/gateway.go @@ -85,17 +85,16 @@ func (t *Translator) translateSecret(tctx *provider.TranslateContext, listener g // Dashboard doesn't allow wildcard hostname if listener.Hostname != nil && *listener.Hostname != "" { sslObj.Snis = append(sslObj.Snis, string(*listener.Hostname)) - } else { - hosts, err := extractHost(cert) - if err != nil { - return nil, err - } - sslObj.Snis = append(sslObj.Snis, hosts...) } - if len(sslObj.Snis) == 0 { + hosts, err := extractHost(cert) + if err != nil { + return nil, err + } + if len(hosts) == 0 { log.Warnw("no valid hostname found in certificate", zap.String("secret", secret.Namespace+"/"+secret.Name)) continue } + sslObj.Snis = append(sslObj.Snis, hosts...) // Note: Dashboard doesn't allow duplicate certificate across ssl objects sslObj.ID = id.GenID(string(cert)) sslObj.Labels = label.GenLabel(obj) From 2010ba8bd230cb6b97e62c90c95435173343e8ce Mon Sep 17 00:00:00 2001 From: ashing Date: Tue, 1 Apr 2025 18:26:23 +0800 Subject: [PATCH 06/10] fix: r Signed-off-by: ashing --- Makefile | 23 +- config/samples/config.yaml | 8 +- internal/controller/ingress_controller.go | 93 +++++-- internal/controller/utils.go | 41 +++ internal/provider/provider.go | 1 + test/e2e/e2e_test.go | 1 + test/e2e/framework/consts.go | 7 + test/e2e/framework/manifests/cert.pem | 32 +++ test/e2e/framework/manifests/key.pem | 51 ++++ test/e2e/gatewayapi/gateway.go | 86 +----- test/e2e/ingress/ingress.go | 309 ++++++++++++++++++++++ test/e2e/ingress/ingress_backend.go | 162 ++++++++++++ 12 files changed, 697 insertions(+), 117 deletions(-) create mode 100644 test/e2e/framework/manifests/cert.pem create mode 100644 test/e2e/framework/manifests/key.pem create mode 100644 test/e2e/ingress/ingress.go create mode 100644 test/e2e/ingress/ingress_backend.go diff --git a/Makefile b/Makefile index 2d1280ed..dce3fac1 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,20 @@ else GOBIN=$(shell go env GOBIN) endif +GOOS ?= linux +GOARCH ?= amd64 + +ifeq ($(shell uname -s),Darwin) + GOOS = darwin +endif + +ifeq ($(shell uname -m),arm64) + GOARCH = arm64 +endif +ifeq ($(shell uname -m), aarch64) + GOARCH = arm64 +endif + # CONTAINER_TOOL defines the container tool to be used for building images. # Be aware that the target commands are only tested with Docker which is # scaffolded by default. However, you might want to replace it to use other @@ -142,7 +156,7 @@ pull-infra-images: .PHONY: build build: manifests generate fmt vet ## Build manager binary. - CGO_ENABLED=0 go build -o bin/api7-ingress-controller -ldflags $(GO_LDFLAGS) cmd/main.go + GOOS=$(GOOS) GOARCH=$(GOARCH) CGO_ENABLED=0 go build -o bin/api7-ingress-controller -ldflags $(GO_LDFLAGS) cmd/main.go linux-build: GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o bin/api7-ingress-controller -ldflags $(GO_LDFLAGS) cmd/main.go @@ -158,7 +172,7 @@ run: manifests generate fmt vet ## Run a controller from your host. # (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it. # More info: https://docs.docker.com/develop/develop-images/build_enhancements/ .PHONY: docker-build -docker-build: build ## Build docker image with the manager. +docker-build: set-e2e-goos build ## Build docker image with the manager. $(CONTAINER_TOOL) build -t ${IMG} -f Dockerfile . .PHONY: docker-push @@ -268,6 +282,11 @@ gofmt: ## Apply go fmt @go fmt ./... .PHONY: gofmt +set-e2e-goos: + $(eval GOOS=linux) + @echo "e2e GOOS: $(GOOS)" +.PHONY: set-e2e-goos + # go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist # $1 - target path with name of binary # $2 - package url which can be installed diff --git a/config/samples/config.yaml b/config/samples/config.yaml index 2543d85f..8ab7728e 100644 --- a/config/samples/config.yaml +++ b/config/samples/config.yaml @@ -1,4 +1,4 @@ -log_level: "info" # The log level of the API7 Ingress Controller. +log_level: "debug" # The log level of the API7 Ingress Controller. # the default value is "info". controller_name: gateway.api7.io/api7-ingress-controller # The controller name of the API7 Ingress Controller, @@ -15,9 +15,9 @@ ingress_status_address: [] # The status address of the ingress. gateway_configs: # The configuration of the API7 Gateway. - name: api7 # The name of the Gateway in the Gateway API. control_plane: - admin_key: "${ADMIN_KEY}" # The admin key of the control plane. + admin_key: "a7adm-2BU3prqKv68BU8DNTaZJIOnIhkH0RJ1xCY3tXGOiEneBfqVF5F-0a0f8eb4d7d94fc6aed43566a6f2989a" # The admin key of the control plane. endpoints: - - ${ENDPOINT} # The endpoint of the control plane. + - "https://172.18.0.5:7443" # The endpoint of the control plane. tls_verify: false addresses: # record the status address of the gateway-api gateway - - "172.18.0.4" # The LB IP of the gateway service. + - "172.18.0.5" # The LB IP of the gateway service. diff --git a/internal/controller/ingress_controller.go b/internal/controller/ingress_controller.go index a8ee72f9..e326e8cd 100644 --- a/internal/controller/ingress_controller.go +++ b/internal/controller/ingress_controller.go @@ -42,7 +42,7 @@ func (r *IngressReconciler) SetupWithManager(mgr ctrl.Manager) error { predicate.NewPredicateFuncs(r.checkIngressClass), ), ). - WithEventFilter(predicate.GenerationChangedPredicate{}). + WithEventFilter(CombinedPredicate{}). Watches( &networkingv1.IngressClass{}, handler.EnqueueRequestsFromMapFunc(r.listIngressForIngressClass), @@ -135,16 +135,19 @@ func (r *IngressReconciler) checkIngressClass(obj client.Object) bool { // find the ingress class that is marked as default for _, ic := range ingressClassList.Items { if IsDefaultIngressClass(&ic) && matchesController(ic.Spec.Controller) { + log.Debugw("match the default ingress class") return true } } + log.Debugw("no default ingress class found") return false } configuredClass := config.GetIngressClass() // if the ingress class name matches the configured ingress class name, return true if *ingress.Spec.IngressClassName == configuredClass { + log.Debugw("match the configured ingress class name") return true } @@ -169,25 +172,55 @@ func (r *IngressReconciler) matchesIngressController(obj client.Object) bool { } // listIngressForIngressClass list all ingresses that use a specific ingress class -func (r *IngressReconciler) listIngressForIngressClass(ctx context.Context, ingressClass client.Object) []reconcile.Request { - ingressList := &networkingv1.IngressList{} - if err := r.List(ctx, ingressList, client.MatchingFields{ - indexer.IngressClassRef: ingressClass.GetName(), - }); err != nil { - r.Log.Error(err, "failed to list ingresses for ingress class", "ingressclass", ingressClass.GetName()) +func (r *IngressReconciler) listIngressForIngressClass(ctx context.Context, obj client.Object) []reconcile.Request { + ingressClass, ok := obj.(*networkingv1.IngressClass) + if !ok { + r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to IngressClass") return nil } - requests := make([]reconcile.Request, 0, len(ingressList.Items)) - for _, ingress := range ingressList.Items { - requests = append(requests, reconcile.Request{ - NamespacedName: client.ObjectKey{ - Namespace: ingress.Namespace, - Name: ingress.Name, - }, - }) + // check if the ingress class is the default ingress class + if IsDefaultIngressClass(ingressClass) { + ingressList := &networkingv1.IngressList{} + if err := r.List(ctx, ingressList); err != nil { + r.Log.Error(err, "failed to list ingresses for ingress class", "ingressclass", ingressClass.GetName()) + return nil + } + + requests := make([]reconcile.Request, 0, len(ingressList.Items)) + for _, ingress := range ingressList.Items { + if ingress.Spec.IngressClassName == nil || *ingress.Spec.IngressClassName == "" || + *ingress.Spec.IngressClassName == ingressClass.GetName() { + requests = append(requests, reconcile.Request{ + NamespacedName: client.ObjectKey{ + Namespace: ingress.Namespace, + Name: ingress.Name, + }, + }) + } + } + return requests + } else { + ingressList := &networkingv1.IngressList{} + if err := r.List(ctx, ingressList, client.MatchingFields{ + indexer.IngressClassRef: ingressClass.GetName(), + }); err != nil { + r.Log.Error(err, "failed to list ingresses for ingress class", "ingressclass", ingressClass.GetName()) + return nil + } + + requests := make([]reconcile.Request, 0, len(ingressList.Items)) + for _, ingress := range ingressList.Items { + requests = append(requests, reconcile.Request{ + NamespacedName: client.ObjectKey{ + Namespace: ingress.Namespace, + Name: ingress.Name, + }, + }) + } + + return requests } - return requests } // listIngressesByService list all ingresses that use a specific service @@ -211,12 +244,14 @@ func (r *IngressReconciler) listIngressesByService(ctx context.Context, obj clie requests := make([]reconcile.Request, 0, len(ingressList.Items)) for _, ingress := range ingressList.Items { - requests = append(requests, reconcile.Request{ - NamespacedName: client.ObjectKey{ - Namespace: ingress.Namespace, - Name: ingress.Name, - }, - }) + if r.checkIngressClass(&ingress) { + requests = append(requests, reconcile.Request{ + NamespacedName: client.ObjectKey{ + Namespace: ingress.Namespace, + Name: ingress.Name, + }, + }) + } } return requests } @@ -242,12 +277,14 @@ func (r *IngressReconciler) listIngressesBySecret(ctx context.Context, obj clien requests := make([]reconcile.Request, 0, len(ingressList.Items)) for _, ingress := range ingressList.Items { - requests = append(requests, reconcile.Request{ - NamespacedName: client.ObjectKey{ - Namespace: ingress.Namespace, - Name: ingress.Name, - }, - }) + if r.checkIngressClass(&ingress) { + requests = append(requests, reconcile.Request{ + NamespacedName: client.ObjectKey{ + Namespace: ingress.Namespace, + Name: ingress.Name, + }, + }) + } } return requests } diff --git a/internal/controller/utils.go b/internal/controller/utils.go index 9282117e..e3f32a20 100644 --- a/internal/controller/utils.go +++ b/internal/controller/utils.go @@ -3,15 +3,19 @@ package controller import ( "context" "fmt" + "reflect" "strings" "github.com/api7/api7-ingress-controller/internal/controller/config" + "github.com/api7/gopkg/pkg/log" "github.com/samber/lo" corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" ) @@ -759,3 +763,40 @@ func SplitMetaNamespaceKey(key string) (namespace, name string, err error) { return "", "", fmt.Errorf("unexpected key format: %q", key) } + +type CombinedPredicate struct { + predicate.GenerationChangedPredicate +} + +func (c CombinedPredicate) Update(e event.UpdateEvent) bool { + if isNil(e.ObjectOld) { + log.Debugw("Update event has no old object for update") + return false + } + if isNil(e.ObjectNew) { + log.Debugw("Update event has no new object for update") + return false + } + + _, ok := e.ObjectNew.(*networkingv1.IngressClass) + if ok { + log.Debugw("IngressClass update event") + return true + } + + log.Debugw("other update event") + // if the object is not IngressClass, return the result of GenerationChangedPredicate.Update + return c.GenerationChangedPredicate.Update(e) +} + +func isNil(arg any) bool { + if v := reflect.ValueOf(arg); !v.IsValid() || ((v.Kind() == reflect.Ptr || + v.Kind() == reflect.Interface || + v.Kind() == reflect.Slice || + v.Kind() == reflect.Map || + v.Kind() == reflect.Chan || + v.Kind() == reflect.Func) && v.IsNil()) { + return true + } + return false +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 1e1ea093..64cbf7d0 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -32,5 +32,6 @@ func NewDefaultTranslateContext() *TranslateContext { EndpointSlices: make(map[types.NamespacedName][]discoveryv1.EndpointSlice), Secrets: make(map[types.NamespacedName]*corev1.Secret), PluginConfigs: make(map[types.NamespacedName]*v1alpha1.PluginConfig), + Services: make(map[types.NamespacedName]*corev1.Service), } } diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index daa720c3..d0f8ede6 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -26,6 +26,7 @@ import ( _ "github.com/api7/api7-ingress-controller/test/e2e/adminapi" "github.com/api7/api7-ingress-controller/test/e2e/framework" _ "github.com/api7/api7-ingress-controller/test/e2e/gatewayapi" + // _ "github.com/api7/api7-ingress-controller/test/e2e/ingress" ) // Run e2e tests using the Ginkgo runner. diff --git a/test/e2e/framework/consts.go b/test/e2e/framework/consts.go index 68e8aa68..33fd3361 100644 --- a/test/e2e/framework/consts.go +++ b/test/e2e/framework/consts.go @@ -31,6 +31,13 @@ var ( DefaultGatewayGroupKeyPrefix = fmt.Sprintf("/gateway_groups/%s", "default") ) +var ( + //go:embed manifests/cert.pem + TestServerCert string + //go:embed manifests/key.pem + TestServerKey string +) + const ( TestCACert = `-----BEGIN CERTIFICATE----- MIIDdzCCAl+gAwIBAgIUBB5PHXyymeboPDVdYeYihYnm5XIwDQYJKoZIhvcNAQEL diff --git a/test/e2e/framework/manifests/cert.pem b/test/e2e/framework/manifests/cert.pem new file mode 100644 index 00000000..2dbe89dd --- /dev/null +++ b/test/e2e/framework/manifests/cert.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFcjCCA1qgAwIBAgIJALDqPppBVXQ3MA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNV +BAYTAkNOMRAwDgYDVQQIDAdKaWFuZ3N1MQ8wDQYDVQQHDAZTdXpob3UxEDAOBgNV +BAoMB2FwaTcuYWkxEDAOBgNVBAsMB2FwaTcuYWkxDzANBgNVBAMMBmp3LmNvbTAg +Fw0yMTA0MDkwNzEyMDBaGA8yMDUxMDQwMjA3MTIwMFowZTELMAkGA1UEBhMCQ04x +EDAOBgNVBAgMB0ppYW5nc3UxDzANBgNVBAcMBlN1emhvdTEQMA4GA1UECgwHYXBp +Ny5haTEQMA4GA1UECwwHYXBpNy5haTEPMA0GA1UEAwwGancuY29tMIICIjANBgkq +hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuEPPnUMlSw41CTdxUNxkQ4gAZ7cPotwY +Y6sVLGtWoR8gKFSZImQIor3UF+8HhN/ZOFRv5tSeeQ/MTE72cs2T5mp6eRU8OrSV +0npk/TpZfaCx7zobsfXB4YU1NZcVWKthFF5X8p//l5gxUMU8V4a01P0aDTmZ67wG +3Fhr0AC067GVYvdwp1yRt6TUUk8ha7JsiySchUIFhX5QMWmrSNhc1bDnHetejMFl +itFFPZkeYG89O/7Ca1K3ca/VVu+/IJ4h7fbF3rt4uP182cJdHl1L94dQSKCJukaW +v+xauWnm4hxOzBK7ImpYB/2eP2D33tmuCLeSv4S+bTG1l7hIN9C/xrYPzfun415h +M2jMK69aAkQL71xa+66kVxioJyNYogYz3ss5ruzDL8K/7bkdO0Zzqldd2+j8lkTl +X4csA+aMHF3v/U7hL/4Wdwi8ziwToRMq9KK9vuh+mPgcdtFGFml+sU+NQfJNm/BN +7fRMZKDIHLacSPE0GUkfW+x3dXOP2lWSZe/iOBZ0NOGNdrOnxTRTr7IH7DYU8aXF +w2GqfAFEQbD4wazCh1AI8lkZr6mPGB1q+HnF2IF7kkgXBHtI5U2KErgcX5BirIVe +v0Yg/OxbbymeTh/hNCcY1kJ1YUCbm9U3U6ZV+d8lj7dQHtugcAzWxSTwpBLVUPrO +eFuhSMLVblUCAwEAAaMjMCEwHwYDVR0RBBgwFoIIYXBpNi5jb22CCiouYXBpNi5j +b20wDQYJKoZIhvcNAQELBQADggIBAFgeSuMPpriYUrQByO3taiY1+56s+KoAmsyA +LH15n2+thAgorusX2g1Zd7UNBHBtVO8c+ihpQb+2PnmgTTGT70ndpRbV5F6124Mt +Hui/X0kjm76RYd1QKN1VFp0Zo+JVdRa+VhDsXWjO0VetINmFhNINFEJctyeHB8oi +aaDL0wZrevHh47hBqtnrmLl+QVG34aLBRhZ5953leiNvXHUJNaT0nLgf0j9p4esS +b2bx9uP4pFI1T9wcv/TE3K0rQbu/uqGY6MgznXHyi4qIK/I+WCa3fF2UZ5P/5EUM +k2ptQneYkLLUVwwmj8C04bYhYe7Z6jkYYp17ojxIP+ejOY1eiE8SYKNjKinmphrM +5aceqIyfbE4TPqvicNzIggA4yEMPbTA0PC/wqbCf61oMc15hwacdeIaQGiwsM+pf +DTk+GBxp3megx/+0XwTQbguleTlHnaaES+ma0vbl6a1rUK0YAUDUrcfFLv6EFtGD +6EHxFf7gH9sTfc2RiGhKxUhRbyEree+6INAvXy+AymVYmQmKuAFqkDQJ+09bTfm8 +bDs+00FijzHFBvC8cIhNffj0qqiv35g+9FTwnE9qpunlrtKG/sMgEXX6m8kL1YQ8 +m5DPGhyEZyt5Js2kzzo8TyINPKmrqriYuiD4p4EH13eSRs3ayanQh6ckC7lb+WXq +3IrSc5hO +-----END CERTIFICATE----- diff --git a/test/e2e/framework/manifests/key.pem b/test/e2e/framework/manifests/key.pem new file mode 100644 index 00000000..b70cfbbf --- /dev/null +++ b/test/e2e/framework/manifests/key.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKAIBAAKCAgEAuEPPnUMlSw41CTdxUNxkQ4gAZ7cPotwYY6sVLGtWoR8gKFSZ +ImQIor3UF+8HhN/ZOFRv5tSeeQ/MTE72cs2T5mp6eRU8OrSV0npk/TpZfaCx7zob +sfXB4YU1NZcVWKthFF5X8p//l5gxUMU8V4a01P0aDTmZ67wG3Fhr0AC067GVYvdw +p1yRt6TUUk8ha7JsiySchUIFhX5QMWmrSNhc1bDnHetejMFlitFFPZkeYG89O/7C +a1K3ca/VVu+/IJ4h7fbF3rt4uP182cJdHl1L94dQSKCJukaWv+xauWnm4hxOzBK7 +ImpYB/2eP2D33tmuCLeSv4S+bTG1l7hIN9C/xrYPzfun415hM2jMK69aAkQL71xa ++66kVxioJyNYogYz3ss5ruzDL8K/7bkdO0Zzqldd2+j8lkTlX4csA+aMHF3v/U7h +L/4Wdwi8ziwToRMq9KK9vuh+mPgcdtFGFml+sU+NQfJNm/BN7fRMZKDIHLacSPE0 +GUkfW+x3dXOP2lWSZe/iOBZ0NOGNdrOnxTRTr7IH7DYU8aXFw2GqfAFEQbD4wazC +h1AI8lkZr6mPGB1q+HnF2IF7kkgXBHtI5U2KErgcX5BirIVev0Yg/OxbbymeTh/h +NCcY1kJ1YUCbm9U3U6ZV+d8lj7dQHtugcAzWxSTwpBLVUPrOeFuhSMLVblUCAwEA +AQKCAgApTupoMvlVTiYNnuREYGQJz59noN5cgELndR8WCiotjLDE2dJKp2pYMX4u +r2NcImKsAiHj+Z5dPXFrWfhd3EBf01cJdf0+m+VKfi3NpxsQ0smQ+9Hhn1qLmDVJ +gklCy4jD7DKDLeM6tN+5X74bUROQ+/yvIk6jTk+rbhcdVks422LGAPq8SkBQjx8a +JKs1XZZ/ywFbzmU2fA62RR4lAnwtW680QeO8Yk7FRAzltkHdFJMBtCcZsD13uxd0 +meKbCVhJ5JyPRi/WKN2oY65EdF3na+pPnc3CeLiq5e2gy2D7J6VyknBpUrXRdMXZ +J3/p8ZrWUXEQhk26ZP50uNdXy/Bx1jYe+U8mpkTMYVYxgu5K4Zea3yJyRn2piiE/ +9LnKNy/KsINt/0QE55ldvtciyP8RDA/08eQX0gvtKWWC/UFVRZCeL48bpqLmdAfE +cMwlk1b0Lmo2PxULFLMAjaTKmcMAbwl53YRit0MtvaIOwiZBUVHE0blRiKC2DMKi +SA6xLbaYJVDaMcfix8kZkKbC0xBNmL4123qX4RF6IUTPufyUTz/tpjzH6eKDw/88 +LmSx227d7/qWp5gROCDhZOPhtr4rj70JKNqcXUX9iFga+dhloOwfHYjdKndKOLUI +Gp3K9YkPT/fCfesrguUx8BoleO5pC6RQJhRsemkRGlSY8U1ZsQKCAQEA5FepCn1f +A46GsBSQ+/pbaHlbsR2syN3J5RmAFLFozYUrqyHE/cbNUlaYq397Ax7xwQkiN3F2 +z/whTxOh4Sk/HdDF4d+I0PZeoxINxgfzyYkx8Xpzn2loxsRO8fb3g+mOxZidjjXv +vxqUBaj3Y01Ig0UXuw7YqCwys+xg3ELtvcGLBW/7NWMo8zqk2nUjhfcwp4e4AwBt +Xcsc2aShUlr/RUrJH4adjha6Yaqc/8xTXHW8gZi5L2lucwB0LA+CBe4ES9BZLZdX +N575/ohXRdjadHKYceYHiamt2326DzaxVJu2EIXU8dgdgOZ/6krITzuePRQHLPHX +6bDfdg/WSpFrtwKCAQEAzpVqBcJ1fAI7bOkt89j2zZb1r5uD2f9sp/lA/Dj65QKV +ShWR7Y6Jr4ShXmFvIenWtjwsl86PflMgtsJefOmLyv8o7PL154XD8SnNbBlds6IM +MyNKkOJFa5NOrsal7TitaTvtYdKq8Zpqtxe+2kg80wi+tPVQNQS/drOpR3rDiLIE +La/ty8XDYZsSowlzBX+uoFq7GuMct1Uh2T0/I4Kf0ZLXwYjkRlRk4LrU0BRPhRMu +MHugOTYFKXShE2a3OEcxqCgvQ/3pn2TV92pPVKBIBGL6uKUwmXQYKaV3G4u10pJ4 +axq/avBOErcKZOReb0SNiOjiIsth8o+hnpYPU5COUwKCAQBksVNV0NtpUhyK4Ube +FxTgCUQp4pAjM8qoQIp+lY1FtAgBuy6HSneYa59/YQP56FdrbH+uO1bNeL2nhVzJ +UcsHdt0MMeq/WyV4e6mfPjp/EQT5G6qJDY6quD6n7ORRQ1k2QYqY/6fteeb0aAJP +w/DKElnYnz9jSbpCJWbBOrJkD0ki6LK6ZDPWrnGr9CPqG4tVFUBL8pBH4B2kzDhn +fME86TGvuUkZM2SVVQtOsefAyhqKe7KN+cw+4mBYXa5UtxUl6Yap2CcZ2/0aBT2X +C32qBC69a1a/mheUxuiZdODWEqRCvQGedFLuWLbntnqGlh+9h2tyomM4JkskYO96 +io4ZAoIBAFouLW9AOUseKlTb4dx+DRcoXC4BpGhIsWUOUQkJ0rSgEQ2bJu3d+Erv +igYKYJocW0eIMys917Qck75UUS0UQpsmEfaGBUTBRw0C45LZ6+abydmVAVsH+6f/ +USzIuOw6frDeoTy/2zHG5+jva7gcKrkxKxcRs6bBYNdvjGkQtUT5+Qr8rsDyntz/ +9f3IBTcUSuXjVaRiGkoJ1tHfg617u0qgYKEyofv1oWfdB0Oiaig8fEBb51CyPUSg +jiRLBZaCtbGjgSacNB0JxsHP3buikG2hy7NJIVMLs/SSL9GNhpzapciTj5YeOua+ +ksICUxsdgO+QQg9QW3yoqLPy69Pd2dMCggEBANDLoUf3ZE7Dews6cfIa0WC33NCV +FkyECaF2MNOp5Q9y/T35FyeA7UeDsTZ6Dy++XGW4uNStrSI6dCyQARqJw+i7gCst +2m5lIde01ptzDQ9iO1Dv1XasxX/589CyLq6CxLfRgPMJPDeUEg0X7+a0lBT5Hpwk +gNnZmws4l3i7RlVMtACCenmof9VtOcMK/9Qr502WHEoGkQR1r6HZFb25841cehL2 +do+oXlr8db++r87a8QQUkizzc6wXD9JffBNo9AO9Ed4HVOukpEA0gqVGBu85N3xW +jW4KB95bGOTa7r7DM1Up0MbAIwWoeLBGhOIXk7inurZGg+FNjZMA5Lzm6qo= +-----END RSA PRIVATE KEY----- diff --git a/test/e2e/gatewayapi/gateway.go b/test/e2e/gatewayapi/gateway.go index cee455dc..a79d0194 100644 --- a/test/e2e/gatewayapi/gateway.go +++ b/test/e2e/gatewayapi/gateway.go @@ -9,95 +9,15 @@ import ( . "github.com/onsi/gomega" "github.com/stretchr/testify/assert" + "github.com/api7/api7-ingress-controller/test/e2e/framework" "github.com/api7/api7-ingress-controller/test/e2e/scaffold" ) const _secretName = "test-apisix-tls" -var Cert = `-----BEGIN CERTIFICATE----- -MIIFcjCCA1qgAwIBAgIJALDqPppBVXQ3MA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNV -BAYTAkNOMRAwDgYDVQQIDAdKaWFuZ3N1MQ8wDQYDVQQHDAZTdXpob3UxEDAOBgNV -BAoMB2FwaTcuYWkxEDAOBgNVBAsMB2FwaTcuYWkxDzANBgNVBAMMBmp3LmNvbTAg -Fw0yMTA0MDkwNzEyMDBaGA8yMDUxMDQwMjA3MTIwMFowZTELMAkGA1UEBhMCQ04x -EDAOBgNVBAgMB0ppYW5nc3UxDzANBgNVBAcMBlN1emhvdTEQMA4GA1UECgwHYXBp -Ny5haTEQMA4GA1UECwwHYXBpNy5haTEPMA0GA1UEAwwGancuY29tMIICIjANBgkq -hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuEPPnUMlSw41CTdxUNxkQ4gAZ7cPotwY -Y6sVLGtWoR8gKFSZImQIor3UF+8HhN/ZOFRv5tSeeQ/MTE72cs2T5mp6eRU8OrSV -0npk/TpZfaCx7zobsfXB4YU1NZcVWKthFF5X8p//l5gxUMU8V4a01P0aDTmZ67wG -3Fhr0AC067GVYvdwp1yRt6TUUk8ha7JsiySchUIFhX5QMWmrSNhc1bDnHetejMFl -itFFPZkeYG89O/7Ca1K3ca/VVu+/IJ4h7fbF3rt4uP182cJdHl1L94dQSKCJukaW -v+xauWnm4hxOzBK7ImpYB/2eP2D33tmuCLeSv4S+bTG1l7hIN9C/xrYPzfun415h -M2jMK69aAkQL71xa+66kVxioJyNYogYz3ss5ruzDL8K/7bkdO0Zzqldd2+j8lkTl -X4csA+aMHF3v/U7hL/4Wdwi8ziwToRMq9KK9vuh+mPgcdtFGFml+sU+NQfJNm/BN -7fRMZKDIHLacSPE0GUkfW+x3dXOP2lWSZe/iOBZ0NOGNdrOnxTRTr7IH7DYU8aXF -w2GqfAFEQbD4wazCh1AI8lkZr6mPGB1q+HnF2IF7kkgXBHtI5U2KErgcX5BirIVe -v0Yg/OxbbymeTh/hNCcY1kJ1YUCbm9U3U6ZV+d8lj7dQHtugcAzWxSTwpBLVUPrO -eFuhSMLVblUCAwEAAaMjMCEwHwYDVR0RBBgwFoIIYXBpNi5jb22CCiouYXBpNi5j -b20wDQYJKoZIhvcNAQELBQADggIBAFgeSuMPpriYUrQByO3taiY1+56s+KoAmsyA -LH15n2+thAgorusX2g1Zd7UNBHBtVO8c+ihpQb+2PnmgTTGT70ndpRbV5F6124Mt -Hui/X0kjm76RYd1QKN1VFp0Zo+JVdRa+VhDsXWjO0VetINmFhNINFEJctyeHB8oi -aaDL0wZrevHh47hBqtnrmLl+QVG34aLBRhZ5953leiNvXHUJNaT0nLgf0j9p4esS -b2bx9uP4pFI1T9wcv/TE3K0rQbu/uqGY6MgznXHyi4qIK/I+WCa3fF2UZ5P/5EUM -k2ptQneYkLLUVwwmj8C04bYhYe7Z6jkYYp17ojxIP+ejOY1eiE8SYKNjKinmphrM -5aceqIyfbE4TPqvicNzIggA4yEMPbTA0PC/wqbCf61oMc15hwacdeIaQGiwsM+pf -DTk+GBxp3megx/+0XwTQbguleTlHnaaES+ma0vbl6a1rUK0YAUDUrcfFLv6EFtGD -6EHxFf7gH9sTfc2RiGhKxUhRbyEree+6INAvXy+AymVYmQmKuAFqkDQJ+09bTfm8 -bDs+00FijzHFBvC8cIhNffj0qqiv35g+9FTwnE9qpunlrtKG/sMgEXX6m8kL1YQ8 -m5DPGhyEZyt5Js2kzzo8TyINPKmrqriYuiD4p4EH13eSRs3ayanQh6ckC7lb+WXq -3IrSc5hO ------END CERTIFICATE-----` +var Cert = framework.TestServerCert -var Key = `-----BEGIN RSA PRIVATE KEY----- -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 ------END RSA PRIVATE KEY-----` +var Key = framework.TestServerKey func createSecret(s *scaffold.Scaffold, secretName string) { err := s.NewKubeTlsSecret(secretName, Cert, Key) diff --git a/test/e2e/ingress/ingress.go b/test/e2e/ingress/ingress.go new file mode 100644 index 00000000..3c04ea89 --- /dev/null +++ b/test/e2e/ingress/ingress.go @@ -0,0 +1,309 @@ +package ingress + +import ( + "context" + "fmt" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/stretchr/testify/assert" + + "github.com/api7/api7-ingress-controller/test/e2e/framework" + "github.com/api7/api7-ingress-controller/test/e2e/scaffold" +) + +const _secretName = "test-ingress-tls" + +var Cert = framework.TestServerCert + +var Key = framework.TestServerKey + +func createSecret(s *scaffold.Scaffold, secretName string) { + err := s.NewKubeTlsSecret(secretName, Cert, Key) + assert.Nil(GinkgoT(), err, "create secret error") +} + +var _ = Describe("Test Ingress", func() { + s := scaffold.NewScaffold(&scaffold.Options{ + ControllerName: "gateway.api7.io/api7-ingress-controller", + }) + + Context("Basic Ingress Functionality", func() { + var defaultIngressClass = ` +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: api7 +spec: + controller: "gateway.api7.io/api7-ingress-controller" +` + + var defaultIngress = ` +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: api7-ingress +spec: + ingressClassName: api7 + rules: + - host: example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: example-service + port: + number: 80 +` + + It("Create Ingress", func() { + By("create IngressClass") + err := s.CreateResourceFromStringWithNamespace(defaultIngressClass, "") + Expect(err).NotTo(HaveOccurred(), "creating IngressClass") + time.Sleep(5 * time.Second) + + By("create Ingress") + err = s.CreateResourceFromString(defaultIngress) + Expect(err).NotTo(HaveOccurred(), "creating Ingress") + time.Sleep(5 * time.Second) + + By("check Ingress status") + ingressYaml, err := s.GetResourceYaml("Ingress", "api7-ingress") + Expect(err).NotTo(HaveOccurred(), "getting Ingress yaml") + Expect(ingressYaml).To(ContainSubstring("example.com"), "checking Ingress host") + }) + }) + + Context("Ingress TLS", func() { + It("Check if SSL resource was created", func() { + secretName := _secretName + host := "secure.example.com" + createSecret(s, secretName) + + var defaultIngressClass = ` +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: api7 +spec: + controller: "gateway.api7.io/api7-ingress-controller" +` + + var tlsIngress = fmt.Sprintf(` +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: api7-ingress-tls +spec: + ingressClassName: api7 + tls: + - hosts: + - %s + secretName: %s + rules: + - host: %s + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: secure-service + port: + number: 80 +`, host, secretName, host) + + By("create IngressClass") + err := s.CreateResourceFromStringWithNamespace(defaultIngressClass, "") + Expect(err).NotTo(HaveOccurred(), "creating IngressClass") + time.Sleep(5 * time.Second) + + By("create Ingress with TLS") + err = s.CreateResourceFromString(tlsIngress) + Expect(err).NotTo(HaveOccurred(), "creating Ingress with TLS") + time.Sleep(5 * time.Second) + + By("check TLS configuration") + tls, err := s.DefaultDataplaneResource().SSL().List(context.Background()) + assert.Nil(GinkgoT(), err, "list tls error") + assert.NotEmpty(GinkgoT(), tls, "tls list should not be empty") + + // At least one TLS certificate should contain our host + foundHost := false + for _, sslObj := range tls { + for _, sni := range sslObj.Snis { + if sni == host { + foundHost = true + break + } + } + if foundHost { + break + } + } + assert.True(GinkgoT(), foundHost, "host not found in any SSL configuration") + }) + }) + + Context("Multiple Paths and Backends", func() { + var defaultIngressClass = ` +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: api7 +spec: + controller: "gateway.api7.io/api7-ingress-controller" +` + + var multiPathIngress = ` +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: api7-ingress-multi +spec: + ingressClassName: api7 + rules: + - host: multi.example.com + http: + paths: + - path: /api + pathType: Prefix + backend: + service: + name: api-service + port: + number: 80 + - path: /web + pathType: Prefix + backend: + service: + name: web-service + port: + number: 80 + - path: /admin + pathType: Prefix + backend: + service: + name: admin-service + port: + number: 80 +` + + It("Create Multi-path Ingress", func() { + By("create IngressClass") + err := s.CreateResourceFromStringWithNamespace(defaultIngressClass, "") + Expect(err).NotTo(HaveOccurred(), "creating IngressClass") + time.Sleep(5 * time.Second) + + By("create Multi-path Ingress") + err = s.CreateResourceFromString(multiPathIngress) + Expect(err).NotTo(HaveOccurred(), "creating Multi-path Ingress") + time.Sleep(5 * time.Second) + + By("check Ingress status") + ingressYaml, err := s.GetResourceYaml("Ingress", "api7-ingress-multi") + Expect(err).NotTo(HaveOccurred(), "getting Ingress yaml") + Expect(ingressYaml).To(ContainSubstring("multi.example.com"), "checking Ingress host") + Expect(ingressYaml).To(ContainSubstring("/api"), "checking path /api") + Expect(ingressYaml).To(ContainSubstring("/web"), "checking path /web") + Expect(ingressYaml).To(ContainSubstring("/admin"), "checking path /admin") + }) + }) + + Context("IngressClass Selection", func() { + var defaultIngressClass = ` +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: api7-default + annotations: + ingressclass.kubernetes.io/is-default-class: "true" +spec: + controller: "gateway.api7.io/api7-ingress-controller" +` + + var secondaryIngressClass = ` +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: api7-secondary +spec: + controller: "gateway.api7.io/api7-ingress-controller" +` + + var defaultIngress = ` +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: api7-ingress-default +spec: + rules: + - host: default.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: default-service + port: + number: 80 +` + + var specificIngress = ` +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: api7-ingress-specific +spec: + ingressClassName: api7-secondary + rules: + - host: specific.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: specific-service + port: + number: 80 +` + + It("Test IngressClass Selection", func() { + By("create Default IngressClass") + err := s.CreateResourceFromStringWithNamespace(defaultIngressClass, "") + Expect(err).NotTo(HaveOccurred(), "creating Default IngressClass") + time.Sleep(5 * time.Second) + + By("create Secondary IngressClass") + err = s.CreateResourceFromStringWithNamespace(secondaryIngressClass, "") + Expect(err).NotTo(HaveOccurred(), "creating Secondary IngressClass") + time.Sleep(5 * time.Second) + + By("create Ingress without IngressClass") + err = s.CreateResourceFromString(defaultIngress) + Expect(err).NotTo(HaveOccurred(), "creating Ingress without IngressClass") + time.Sleep(5 * time.Second) + + By("check Default Ingress") + ingressYaml, err := s.GetResourceYaml("Ingress", "api7-ingress-default") + Expect(err).NotTo(HaveOccurred(), "getting Default Ingress yaml") + Expect(ingressYaml).To(ContainSubstring("default.example.com"), "checking Default Ingress host") + + By("create Ingress with specific IngressClass") + err = s.CreateResourceFromString(specificIngress) + Expect(err).NotTo(HaveOccurred(), "creating Ingress with specific IngressClass") + time.Sleep(5 * time.Second) + + By("check Specific Ingress") + ingressYaml, err = s.GetResourceYaml("Ingress", "api7-ingress-specific") + Expect(err).NotTo(HaveOccurred(), "getting Specific Ingress yaml") + Expect(ingressYaml).To(ContainSubstring("specific.example.com"), "checking Specific Ingress host") + Expect(ingressYaml).To(ContainSubstring("api7-secondary"), "checking Specific Ingress class") + }) + }) +}) diff --git a/test/e2e/ingress/ingress_backend.go b/test/e2e/ingress/ingress_backend.go new file mode 100644 index 00000000..38885d97 --- /dev/null +++ b/test/e2e/ingress/ingress_backend.go @@ -0,0 +1,162 @@ +package ingress + +import ( + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/api7/api7-ingress-controller/test/e2e/scaffold" +) + +var _ = Describe("Test Ingress Backend Services", func() { + s := scaffold.NewScaffold(&scaffold.Options{ + ControllerName: "gateway.api7.io/api7-ingress-controller", + }) + + Context("Handling Non-existent Backend Services", func() { + var defaultIngressClass = ` +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: api7 +spec: + controller: "gateway.api7.io/api7-ingress-controller" +` + + var nonExistentServiceIngress = ` +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: api7-ingress-non-existent +spec: + ingressClassName: api7 + rules: + - host: non-existent.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: non-existent-service + port: + number: 80 +` + + It("Creates Ingress with Non-existent Backend Service", func() { + By("create IngressClass") + err := s.CreateResourceFromStringWithNamespace(defaultIngressClass, "") + Expect(err).NotTo(HaveOccurred(), "creating IngressClass") + time.Sleep(5 * time.Second) + + By("create Ingress with Non-existent Backend Service") + err = s.CreateResourceFromString(nonExistentServiceIngress) + Expect(err).NotTo(HaveOccurred(), "creating Ingress with Non-existent Backend Service") + time.Sleep(5 * time.Second) + + By("check Ingress status") + ingressYaml, err := s.GetResourceYaml("Ingress", "api7-ingress-non-existent") + Expect(err).NotTo(HaveOccurred(), "getting Ingress yaml") + Expect(ingressYaml).To(ContainSubstring("non-existent.example.com"), "checking Ingress host") + }) + }) + + Context("Update Backend Service", func() { + var defaultIngressClass = ` +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: api7 +spec: + controller: "gateway.api7.io/api7-ingress-controller" +` + + var serviceYaml = ` +apiVersion: v1 +kind: Service +metadata: + name: backend-service +spec: + ports: + - port: 80 + targetPort: 8080 + protocol: TCP + name: http + selector: + app: backend +` + + var updatedServiceYaml = ` +apiVersion: v1 +kind: Service +metadata: + name: backend-service +spec: + ports: + - port: 80 + targetPort: 9090 + protocol: TCP + name: http + - port: 443 + targetPort: 8443 + protocol: TCP + name: https + selector: + app: backend-updated +` + + var ingressYaml = ` +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: api7-ingress-backend +spec: + ingressClassName: api7 + rules: + - host: backend.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: backend-service + port: + number: 80 +` + + It("Updates Backend Service for Ingress", func() { + By("create IngressClass") + err := s.CreateResourceFromStringWithNamespace(defaultIngressClass, "") + Expect(err).NotTo(HaveOccurred(), "creating IngressClass") + time.Sleep(5 * time.Second) + + By("create Backend Service") + err = s.CreateResourceFromString(serviceYaml) + Expect(err).NotTo(HaveOccurred(), "creating Backend Service") + time.Sleep(5 * time.Second) + + By("create Ingress with Backend Service") + err = s.CreateResourceFromString(ingressYaml) + Expect(err).NotTo(HaveOccurred(), "creating Ingress with Backend Service") + time.Sleep(5 * time.Second) + + By("check Ingress status") + ingressYaml, err := s.GetResourceYaml("Ingress", "api7-ingress-backend") + Expect(err).NotTo(HaveOccurred(), "getting Ingress yaml") + Expect(ingressYaml).To(ContainSubstring("backend.example.com"), "checking Ingress host") + + By("update Backend Service") + err = s.CreateResourceFromString(updatedServiceYaml) + Expect(err).NotTo(HaveOccurred(), "updating Backend Service") + time.Sleep(10 * time.Second) + + By("check Service after update") + serviceYaml, err := s.GetResourceYaml("Service", "backend-service") + Expect(err).NotTo(HaveOccurred(), "getting Service yaml") + Expect(serviceYaml).To(ContainSubstring("9090"), "checking updated Service targetPort") + Expect(serviceYaml).To(ContainSubstring("443"), "checking added Service port") + }) + }) +}) From d7ab9103b9237ad87ef1f82f2b271b2edfbcde5a Mon Sep 17 00:00:00 2001 From: ashing Date: Tue, 1 Apr 2025 19:25:16 +0800 Subject: [PATCH 07/10] fix: r Signed-off-by: ashing --- config/samples/config.yaml | 11 +- test/e2e/e2e_test.go | 2 +- test/e2e/gatewayapi/gateway.go | 5 +- test/e2e/ingress/ingress.go | 154 ++++---------------------- test/e2e/ingress/ingress_backend.go | 162 ---------------------------- 5 files changed, 31 insertions(+), 303 deletions(-) delete mode 100644 test/e2e/ingress/ingress_backend.go diff --git a/config/samples/config.yaml b/config/samples/config.yaml index 8ab7728e..3bfe2f3a 100644 --- a/config/samples/config.yaml +++ b/config/samples/config.yaml @@ -1,23 +1,20 @@ -log_level: "debug" # The log level of the API7 Ingress Controller. +log_level: "info" # The log level of the API7 Ingress Controller. # the default value is "info". controller_name: gateway.api7.io/api7-ingress-controller # The controller name of the API7 Ingress Controller, # which is used to identify the controller in the GatewayClass. # The default value is "gateway.api7.io/api7-ingress-controller". - leader_election_id: "api7-ingress-controller-leader" # The leader election ID for the API7 Ingress Controller. # The default value is "api7-ingress-controller-leader". - ingress_class: api7 # The ingress class name of the API7 Ingress Controller. ingress_publish_service: "" # The service name of the ingress publish service. ingress_status_address: [] # The status address of the ingress. - gateway_configs: # The configuration of the API7 Gateway. - name: api7 # The name of the Gateway in the Gateway API. control_plane: - admin_key: "a7adm-2BU3prqKv68BU8DNTaZJIOnIhkH0RJ1xCY3tXGOiEneBfqVF5F-0a0f8eb4d7d94fc6aed43566a6f2989a" # The admin key of the control plane. + admin_key: "${ADMIN_KEY}" # The admin key of the control plane. endpoints: - - "https://172.18.0.5:7443" # The endpoint of the control plane. + - ${ENDPOINT} # The endpoint of the control plane. tls_verify: false addresses: # record the status address of the gateway-api gateway - - "172.18.0.5" # The LB IP of the gateway service. + - "172.18.0.4" # The LB IP of the gateway service. diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index d0f8ede6..7ad530e0 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -26,7 +26,7 @@ import ( _ "github.com/api7/api7-ingress-controller/test/e2e/adminapi" "github.com/api7/api7-ingress-controller/test/e2e/framework" _ "github.com/api7/api7-ingress-controller/test/e2e/gatewayapi" - // _ "github.com/api7/api7-ingress-controller/test/e2e/ingress" + _ "github.com/api7/api7-ingress-controller/test/e2e/ingress" ) // Run e2e tests using the Ginkgo runner. diff --git a/test/e2e/gatewayapi/gateway.go b/test/e2e/gatewayapi/gateway.go index a79d0194..6320e5c9 100644 --- a/test/e2e/gatewayapi/gateway.go +++ b/test/e2e/gatewayapi/gateway.go @@ -3,6 +3,7 @@ package gatewayapi import ( "context" "fmt" + "strings" "time" . "github.com/onsi/ginkgo/v2" @@ -15,9 +16,9 @@ import ( const _secretName = "test-apisix-tls" -var Cert = framework.TestServerCert +var Cert = strings.TrimSpace(framework.TestServerCert) -var Key = framework.TestServerKey +var Key = strings.TrimSpace(framework.TestServerKey) func createSecret(s *scaffold.Scaffold, secretName string) { err := s.NewKubeTlsSecret(secretName, Cert, Key) diff --git a/test/e2e/ingress/ingress.go b/test/e2e/ingress/ingress.go index 3c04ea89..ecd5660b 100644 --- a/test/e2e/ingress/ingress.go +++ b/test/e2e/ingress/ingress.go @@ -3,6 +3,7 @@ package ingress import ( "context" "fmt" + "strings" "time" . "github.com/onsi/ginkgo/v2" @@ -15,9 +16,9 @@ import ( const _secretName = "test-ingress-tls" -var Cert = framework.TestServerCert +var Cert = strings.TrimSpace(framework.TestServerCert) -var Key = framework.TestServerKey +var Key = strings.TrimSpace(framework.TestServerKey) func createSecret(s *scaffold.Scaffold, secretName string) { err := s.NewKubeTlsSecret(secretName, Cert, Key) @@ -54,7 +55,7 @@ spec: pathType: Prefix backend: service: - name: example-service + name: httpbin-service-e2e-test port: number: 80 ` @@ -74,13 +75,20 @@ spec: ingressYaml, err := s.GetResourceYaml("Ingress", "api7-ingress") Expect(err).NotTo(HaveOccurred(), "getting Ingress yaml") Expect(ingressYaml).To(ContainSubstring("example.com"), "checking Ingress host") + + By("verify HTTP request") + s.NewAPISIXClient(). + GET("/get"). + WithHost("example.com"). + Expect(). + Status(200) }) }) Context("Ingress TLS", func() { It("Check if SSL resource was created", func() { secretName := _secretName - host := "secure.example.com" + host := "api6.com" createSecret(s, secretName) var defaultIngressClass = ` @@ -111,7 +119,7 @@ spec: pathType: Prefix backend: service: - name: secure-service + name: httpbin-service-e2e-test port: number: 80 `, host, secretName, host) @@ -129,87 +137,9 @@ spec: By("check TLS configuration") tls, err := s.DefaultDataplaneResource().SSL().List(context.Background()) assert.Nil(GinkgoT(), err, "list tls error") - assert.NotEmpty(GinkgoT(), tls, "tls list should not be empty") - - // At least one TLS certificate should contain our host - foundHost := false - for _, sslObj := range tls { - for _, sni := range sslObj.Snis { - if sni == host { - foundHost = true - break - } - } - if foundHost { - break - } - } - assert.True(GinkgoT(), foundHost, "host not found in any SSL configuration") - }) - }) - - Context("Multiple Paths and Backends", func() { - var defaultIngressClass = ` -apiVersion: networking.k8s.io/v1 -kind: IngressClass -metadata: - name: api7 -spec: - controller: "gateway.api7.io/api7-ingress-controller" -` - - var multiPathIngress = ` -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: api7-ingress-multi -spec: - ingressClassName: api7 - rules: - - host: multi.example.com - http: - paths: - - path: /api - pathType: Prefix - backend: - service: - name: api-service - port: - number: 80 - - path: /web - pathType: Prefix - backend: - service: - name: web-service - port: - number: 80 - - path: /admin - pathType: Prefix - backend: - service: - name: admin-service - port: - number: 80 -` - - It("Create Multi-path Ingress", func() { - By("create IngressClass") - err := s.CreateResourceFromStringWithNamespace(defaultIngressClass, "") - Expect(err).NotTo(HaveOccurred(), "creating IngressClass") - time.Sleep(5 * time.Second) - - By("create Multi-path Ingress") - err = s.CreateResourceFromString(multiPathIngress) - Expect(err).NotTo(HaveOccurred(), "creating Multi-path Ingress") - time.Sleep(5 * time.Second) - - By("check Ingress status") - ingressYaml, err := s.GetResourceYaml("Ingress", "api7-ingress-multi") - Expect(err).NotTo(HaveOccurred(), "getting Ingress yaml") - Expect(ingressYaml).To(ContainSubstring("multi.example.com"), "checking Ingress host") - Expect(ingressYaml).To(ContainSubstring("/api"), "checking path /api") - Expect(ingressYaml).To(ContainSubstring("/web"), "checking path /web") - Expect(ingressYaml).To(ContainSubstring("/admin"), "checking path /admin") + assert.Len(GinkgoT(), tls, 1, "tls number not expect") + assert.Equal(GinkgoT(), Cert, tls[0].Cert, "tls cert not expect") + assert.ElementsMatch(GinkgoT(), []string{host}, tls[0].Snis) }) }) @@ -225,15 +155,6 @@ spec: controller: "gateway.api7.io/api7-ingress-controller" ` - var secondaryIngressClass = ` -apiVersion: networking.k8s.io/v1 -kind: IngressClass -metadata: - name: api7-secondary -spec: - controller: "gateway.api7.io/api7-ingress-controller" -` - var defaultIngress = ` apiVersion: networking.k8s.io/v1 kind: Ingress @@ -248,27 +169,7 @@ spec: pathType: Prefix backend: service: - name: default-service - port: - number: 80 -` - - var specificIngress = ` -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: api7-ingress-specific -spec: - ingressClassName: api7-secondary - rules: - - host: specific.example.com - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: specific-service + name: httpbin-service-e2e-test port: number: 80 ` @@ -279,11 +180,6 @@ spec: Expect(err).NotTo(HaveOccurred(), "creating Default IngressClass") time.Sleep(5 * time.Second) - By("create Secondary IngressClass") - err = s.CreateResourceFromStringWithNamespace(secondaryIngressClass, "") - Expect(err).NotTo(HaveOccurred(), "creating Secondary IngressClass") - time.Sleep(5 * time.Second) - By("create Ingress without IngressClass") err = s.CreateResourceFromString(defaultIngress) Expect(err).NotTo(HaveOccurred(), "creating Ingress without IngressClass") @@ -294,16 +190,12 @@ spec: Expect(err).NotTo(HaveOccurred(), "getting Default Ingress yaml") Expect(ingressYaml).To(ContainSubstring("default.example.com"), "checking Default Ingress host") - By("create Ingress with specific IngressClass") - err = s.CreateResourceFromString(specificIngress) - Expect(err).NotTo(HaveOccurred(), "creating Ingress with specific IngressClass") - time.Sleep(5 * time.Second) - - By("check Specific Ingress") - ingressYaml, err = s.GetResourceYaml("Ingress", "api7-ingress-specific") - Expect(err).NotTo(HaveOccurred(), "getting Specific Ingress yaml") - Expect(ingressYaml).To(ContainSubstring("specific.example.com"), "checking Specific Ingress host") - Expect(ingressYaml).To(ContainSubstring("api7-secondary"), "checking Specific Ingress class") + By("verify default ingress") + s.NewAPISIXClient(). + GET("/get"). + WithHost("default.example.com"). + Expect(). + Status(200) }) }) }) diff --git a/test/e2e/ingress/ingress_backend.go b/test/e2e/ingress/ingress_backend.go deleted file mode 100644 index 38885d97..00000000 --- a/test/e2e/ingress/ingress_backend.go +++ /dev/null @@ -1,162 +0,0 @@ -package ingress - -import ( - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/api7/api7-ingress-controller/test/e2e/scaffold" -) - -var _ = Describe("Test Ingress Backend Services", func() { - s := scaffold.NewScaffold(&scaffold.Options{ - ControllerName: "gateway.api7.io/api7-ingress-controller", - }) - - Context("Handling Non-existent Backend Services", func() { - var defaultIngressClass = ` -apiVersion: networking.k8s.io/v1 -kind: IngressClass -metadata: - name: api7 -spec: - controller: "gateway.api7.io/api7-ingress-controller" -` - - var nonExistentServiceIngress = ` -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: api7-ingress-non-existent -spec: - ingressClassName: api7 - rules: - - host: non-existent.example.com - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: non-existent-service - port: - number: 80 -` - - It("Creates Ingress with Non-existent Backend Service", func() { - By("create IngressClass") - err := s.CreateResourceFromStringWithNamespace(defaultIngressClass, "") - Expect(err).NotTo(HaveOccurred(), "creating IngressClass") - time.Sleep(5 * time.Second) - - By("create Ingress with Non-existent Backend Service") - err = s.CreateResourceFromString(nonExistentServiceIngress) - Expect(err).NotTo(HaveOccurred(), "creating Ingress with Non-existent Backend Service") - time.Sleep(5 * time.Second) - - By("check Ingress status") - ingressYaml, err := s.GetResourceYaml("Ingress", "api7-ingress-non-existent") - Expect(err).NotTo(HaveOccurred(), "getting Ingress yaml") - Expect(ingressYaml).To(ContainSubstring("non-existent.example.com"), "checking Ingress host") - }) - }) - - Context("Update Backend Service", func() { - var defaultIngressClass = ` -apiVersion: networking.k8s.io/v1 -kind: IngressClass -metadata: - name: api7 -spec: - controller: "gateway.api7.io/api7-ingress-controller" -` - - var serviceYaml = ` -apiVersion: v1 -kind: Service -metadata: - name: backend-service -spec: - ports: - - port: 80 - targetPort: 8080 - protocol: TCP - name: http - selector: - app: backend -` - - var updatedServiceYaml = ` -apiVersion: v1 -kind: Service -metadata: - name: backend-service -spec: - ports: - - port: 80 - targetPort: 9090 - protocol: TCP - name: http - - port: 443 - targetPort: 8443 - protocol: TCP - name: https - selector: - app: backend-updated -` - - var ingressYaml = ` -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: api7-ingress-backend -spec: - ingressClassName: api7 - rules: - - host: backend.example.com - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: backend-service - port: - number: 80 -` - - It("Updates Backend Service for Ingress", func() { - By("create IngressClass") - err := s.CreateResourceFromStringWithNamespace(defaultIngressClass, "") - Expect(err).NotTo(HaveOccurred(), "creating IngressClass") - time.Sleep(5 * time.Second) - - By("create Backend Service") - err = s.CreateResourceFromString(serviceYaml) - Expect(err).NotTo(HaveOccurred(), "creating Backend Service") - time.Sleep(5 * time.Second) - - By("create Ingress with Backend Service") - err = s.CreateResourceFromString(ingressYaml) - Expect(err).NotTo(HaveOccurred(), "creating Ingress with Backend Service") - time.Sleep(5 * time.Second) - - By("check Ingress status") - ingressYaml, err := s.GetResourceYaml("Ingress", "api7-ingress-backend") - Expect(err).NotTo(HaveOccurred(), "getting Ingress yaml") - Expect(ingressYaml).To(ContainSubstring("backend.example.com"), "checking Ingress host") - - By("update Backend Service") - err = s.CreateResourceFromString(updatedServiceYaml) - Expect(err).NotTo(HaveOccurred(), "updating Backend Service") - time.Sleep(10 * time.Second) - - By("check Service after update") - serviceYaml, err := s.GetResourceYaml("Service", "backend-service") - Expect(err).NotTo(HaveOccurred(), "getting Service yaml") - Expect(serviceYaml).To(ContainSubstring("9090"), "checking updated Service targetPort") - Expect(serviceYaml).To(ContainSubstring("443"), "checking added Service port") - }) - }) -}) From 5edce5ba4ef7680fb93506aa60df7c84ba2cdf71 Mon Sep 17 00:00:00 2001 From: ashing Date: Mon, 7 Apr 2025 18:10:28 +0800 Subject: [PATCH 08/10] fix: review Signed-off-by: ashing --- internal/controller/ingress_controller.go | 7 +++- internal/controller/utils.go | 41 ----------------------- test/e2e/ingress/ingress.go | 10 ------ 3 files changed, 6 insertions(+), 52 deletions(-) diff --git a/internal/controller/ingress_controller.go b/internal/controller/ingress_controller.go index e326e8cd..2e1636ee 100644 --- a/internal/controller/ingress_controller.go +++ b/internal/controller/ingress_controller.go @@ -42,7 +42,12 @@ func (r *IngressReconciler) SetupWithManager(mgr ctrl.Manager) error { predicate.NewPredicateFuncs(r.checkIngressClass), ), ). - WithEventFilter(CombinedPredicate{}). + WithEventFilter( + predicate.Or( + predicate.GenerationChangedPredicate{}, + predicate.AnnotationChangedPredicate{}, + ), + ). Watches( &networkingv1.IngressClass{}, handler.EnqueueRequestsFromMapFunc(r.listIngressForIngressClass), diff --git a/internal/controller/utils.go b/internal/controller/utils.go index e3f32a20..9282117e 100644 --- a/internal/controller/utils.go +++ b/internal/controller/utils.go @@ -3,19 +3,15 @@ package controller import ( "context" "fmt" - "reflect" "strings" "github.com/api7/api7-ingress-controller/internal/controller/config" - "github.com/api7/gopkg/pkg/log" "github.com/samber/lo" corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" ) @@ -763,40 +759,3 @@ func SplitMetaNamespaceKey(key string) (namespace, name string, err error) { return "", "", fmt.Errorf("unexpected key format: %q", key) } - -type CombinedPredicate struct { - predicate.GenerationChangedPredicate -} - -func (c CombinedPredicate) Update(e event.UpdateEvent) bool { - if isNil(e.ObjectOld) { - log.Debugw("Update event has no old object for update") - return false - } - if isNil(e.ObjectNew) { - log.Debugw("Update event has no new object for update") - return false - } - - _, ok := e.ObjectNew.(*networkingv1.IngressClass) - if ok { - log.Debugw("IngressClass update event") - return true - } - - log.Debugw("other update event") - // if the object is not IngressClass, return the result of GenerationChangedPredicate.Update - return c.GenerationChangedPredicate.Update(e) -} - -func isNil(arg any) bool { - if v := reflect.ValueOf(arg); !v.IsValid() || ((v.Kind() == reflect.Ptr || - v.Kind() == reflect.Interface || - v.Kind() == reflect.Slice || - v.Kind() == reflect.Map || - v.Kind() == reflect.Chan || - v.Kind() == reflect.Func) && v.IsNil()) { - return true - } - return false -} diff --git a/test/e2e/ingress/ingress.go b/test/e2e/ingress/ingress.go index ecd5660b..c5ebb119 100644 --- a/test/e2e/ingress/ingress.go +++ b/test/e2e/ingress/ingress.go @@ -71,11 +71,6 @@ spec: Expect(err).NotTo(HaveOccurred(), "creating Ingress") time.Sleep(5 * time.Second) - By("check Ingress status") - ingressYaml, err := s.GetResourceYaml("Ingress", "api7-ingress") - Expect(err).NotTo(HaveOccurred(), "getting Ingress yaml") - Expect(ingressYaml).To(ContainSubstring("example.com"), "checking Ingress host") - By("verify HTTP request") s.NewAPISIXClient(). GET("/get"). @@ -185,11 +180,6 @@ spec: Expect(err).NotTo(HaveOccurred(), "creating Ingress without IngressClass") time.Sleep(5 * time.Second) - By("check Default Ingress") - ingressYaml, err := s.GetResourceYaml("Ingress", "api7-ingress-default") - Expect(err).NotTo(HaveOccurred(), "getting Default Ingress yaml") - Expect(ingressYaml).To(ContainSubstring("default.example.com"), "checking Default Ingress host") - By("verify default ingress") s.NewAPISIXClient(). GET("/get"). From 31a5dca26425074f17f1906ceb50d7b7fca7049e Mon Sep 17 00:00:00 2001 From: ashing Date: Mon, 7 Apr 2025 18:11:53 +0800 Subject: [PATCH 09/10] fix: r Signed-off-by: ashing --- internal/provider/adc/translator/ingress.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/provider/adc/translator/ingress.go b/internal/provider/adc/translator/ingress.go index b104e0d6..f3e11c1f 100644 --- a/internal/provider/adc/translator/ingress.go +++ b/internal/provider/adc/translator/ingress.go @@ -191,7 +191,7 @@ func (t *Translator) TranslateIngress(tctx *provider.TranslateContext, obj *netw } // translateEndpointSliceForIngress create upstream nodes from EndpointSlice -func (t *Translator) translateEndpointSliceForIngress(weight int, endpointSlices []discoveryv1.EndpointSlice, servciePort *corev1.ServicePort) adctypes.UpstreamNodes { +func (t *Translator) translateEndpointSliceForIngress(weight int, endpointSlices []discoveryv1.EndpointSlice, servicePort *corev1.ServicePort) adctypes.UpstreamNodes { var nodes adctypes.UpstreamNodes if len(endpointSlices) == 0 { return nodes @@ -200,7 +200,7 @@ func (t *Translator) translateEndpointSliceForIngress(weight int, endpointSlices for _, endpointSlice := range endpointSlices { for _, port := range endpointSlice.Ports { // if the port number is specified, only use the matching port - if servciePort != nil && *port.Name != servciePort.Name { + if servicePort != nil && *port.Name != servicePort.Name { continue } for _, endpoint := range endpointSlice.Endpoints { From 59e65c9f9eda37540653f085ce894276089ac9b3 Mon Sep 17 00:00:00 2001 From: Ashing Zheng Date: Mon, 7 Apr 2025 23:07:15 +0800 Subject: [PATCH 10/10] Update internal/provider/adc/translator/ingress.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/provider/adc/translator/ingress.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/adc/translator/ingress.go b/internal/provider/adc/translator/ingress.go index f3e11c1f..19cc0903 100644 --- a/internal/provider/adc/translator/ingress.go +++ b/internal/provider/adc/translator/ingress.go @@ -200,7 +200,7 @@ func (t *Translator) translateEndpointSliceForIngress(weight int, endpointSlices for _, endpointSlice := range endpointSlices { for _, port := range endpointSlice.Ports { // if the port number is specified, only use the matching port - if servicePort != nil && *port.Name != servicePort.Name { + if servicePort != nil && port.Name != nil && *port.Name != servicePort.Name { continue } for _, endpoint := range endpointSlice.Endpoints {