test: add Gateway API v1.4 conformance CI (#4135)

Signed-off-by: EndlessSeeker <1766508902@qq.com>
This commit is contained in:
EndlessSeeker
2026-07-14 19:49:58 +08:00
committed by GitHub
parent 152f14b171
commit afb72b81cb
22 changed files with 454 additions and 59 deletions

View File

@@ -93,8 +93,77 @@ jobs:
gateway-conformance-test:
runs-on: ubuntu-22.04
needs: [build]
env:
GATEWAY_API_TEST_NAMESPACE: gateway-conformance-infra
HIGRESS_TEST_IMAGE_TAG: ${{ github.event.pull_request.head.sha || github.sha }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
ref: ${{ env.HIGRESS_TEST_IMAGE_TAG }}
- name: Disable containerd image store
run: |
sudo bash -c 'cat > /etc/docker/daemon.json << EOF
{
"features": {
"containerd-snapshotter": false
}
}
EOF'
sudo systemctl restart docker
docker info -f '{{ .DriverStatus }}'
- name: Free Up GitHub Actions Ubuntu Runner Disk Space 🔧
uses: jlumbroso/free-disk-space@main
with:
tool-cache: false
android: true
dotnet: true
haskell: true
large-packages: true
swap-storage: true
- name: "Setup Go"
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- name: Setup Golang Caches
uses: actions/cache@v4
with:
path: |-
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ github.run_id }}
restore-keys: ${{ runner.os }}-go
- name: Run Higress Gateway API Tests
run: |-
GOPROXY="https://proxy.golang.org,direct" make gateway-conformance-test \
TAG="${HIGRESS_TEST_IMAGE_TAG}" \
HIGRESS_CONFORMANCE_VERSION="${HIGRESS_TEST_IMAGE_TAG}"
- name: Collect Gateway API Test Diagnostics
if: always()
run: |-
mkdir -p out/gateway-api-conformance/diagnostics
kubectl get gatewayclass,gateway,httproute,referencegrant -A -o yaml > out/gateway-api-conformance/diagnostics/resources.yaml 2>&1 || true
kubectl get events -A --sort-by=.lastTimestamp > out/gateway-api-conformance/diagnostics/events.txt 2>&1 || true
kubectl logs -n "${GATEWAY_API_TEST_NAMESPACE}" deployment/higress-controller --all-containers > out/gateway-api-conformance/diagnostics/controller.log 2>&1 || true
kubectl logs -n "${GATEWAY_API_TEST_NAMESPACE}" deployment/higress-gateway --all-containers > out/gateway-api-conformance/diagnostics/gateway.log 2>&1 || true
tools/bin/kind-gateway-api export logs out/gateway-api-conformance/diagnostics/kind --name higress || true
- name: Upload Gateway API Test Results
if: always()
uses: actions/upload-artifact@v4
with:
name: gateway-conformance-test
path: out/gateway-api-conformance/
if-no-files-found: warn
- name: Clean Gateway API Test Environment
if: always()
run: make gateway-conformance-test-clean
higress-conformance-test:
runs-on: ubuntu-22.04

View File

@@ -21,6 +21,21 @@ GO ?= go
export GOPROXY ?= https://proxy.golang.org,direct
GATEWAY_API_VERSION ?= v1.4.0
GATEWAY_CONFORMANCE_PROFILE ?= GATEWAY-HTTP
GATEWAY_CONFORMANCE_SUPPORTED_FEATURES ?= Gateway,HTTPRoute,ReferenceGrant
GATEWAY_CONFORMANCE_REPORT ?= out/gateway-api-conformance/report.yaml
GATEWAY_CONFORMANCE_CONTACT ?= https://github.com/alibaba/higress/issues
GATEWAY_CONFORMANCE_RUN_TEST ?=
GATEWAY_CONFORMANCE_ALLOW_CRDS_MISMATCH ?= false
GATEWAY_API_TEST_NAMESPACE ?= gateway-conformance-infra
GATEWAY_API_GATEWAY_SERVICE_TYPE ?= ClusterIP
GATEWAY_API_DIAL_LOCALHOST ?= true
GATEWAY_API_LOCAL_HTTP_PORT ?= 80
GATEWAY_API_LOCAL_HTTPS_PORT ?= 443
GATEWAY_API_KIND_NODE_TAG ?= v1.34.0@sha256:7416a61b42b1662ca6ca89f02028ac133a309a2a30ba309614e8ec94d976dc5a
HIGRESS_CONFORMANCE_VERSION ?= $(shell git rev-parse HEAD)
TARGET_ARCH ?= amd64
VALID_ARCHS := amd64 arm64
@@ -210,6 +225,10 @@ ISTIO_LATEST_IMAGE_TAG ?= de2c9628294f51b13c4a70b3a862b4372890797a
install-dev: pre-install
helm install higress helm/core -n higress-system --create-namespace --set 'controller.tag=$(TAG)' --set 'gateway.replicas=1' --set 'pilot.tag=$(ISTIO_LATEST_IMAGE_TAG)' --set 'gateway.tag=$(ENVOY_LATEST_IMAGE_TAG)' --set 'global.local=true'
install-dev-gateway-api: pre-install
helm install higress helm/core -n $(GATEWAY_API_TEST_NAMESPACE) --create-namespace --set 'controller.tag=$(TAG)' --set 'gateway.replicas=1' --set 'pilot.tag=$(ISTIO_LATEST_IMAGE_TAG)' --set 'gateway.tag=$(ENVOY_LATEST_IMAGE_TAG)' --set 'global.local=true' --set 'gateway.service.type=$(GATEWAY_API_GATEWAY_SERVICE_TYPE)'
install-dev-wasmplugin: build-wasmplugins pre-install
helm install higress helm/core -n higress-system --create-namespace --set 'controller.tag=$(TAG)' --set 'gateway.replicas=1' --set 'pilot.tag=$(ISTIO_LATEST_IMAGE_TAG)' --set 'gateway.tag=$(ENVOY_LATEST_IMAGE_TAG)' --set 'global.local=true' --set 'global.volumeWasmPlugins=true' --set 'global.onlyPushRouteCluster=false'
@@ -262,9 +281,71 @@ clean: clean-higress clean-gateway clean-istio clean-env clean-tool
include tools/tools.mk
include tools/lint.mk
# gateway-conformance-test runs gateway api conformance tests.
# install-gateway-api-crds installs the Gateway API CRDs used by the conformance suite.
.PHONY: install-gateway-api-crds
install-gateway-api-crds:
kubectl apply --server-side=true -f https://github.com/kubernetes-sigs/gateway-api/releases/download/$(GATEWAY_API_VERSION)/standard-install.yaml
kubectl wait --for=condition=Established crd/gatewayclasses.gateway.networking.k8s.io --timeout=120s
kubectl wait --for=condition=Established crd/gateways.gateway.networking.k8s.io --timeout=120s
kubectl wait --for=condition=Established crd/httproutes.gateway.networking.k8s.io --timeout=120s
kubectl wait --for=condition=Established crd/referencegrants.gateway.networking.k8s.io --timeout=120s
# create-gateway-api-cluster creates the Kubernetes version used by Gateway API v1.4.0 tests.
.PHONY: create-gateway-api-cluster
create-gateway-api-cluster: $(tools/kind-gateway-api)
KIND=$(tools/kind-gateway-api) KIND_NODE_TAG=$(GATEWAY_API_KIND_NODE_TAG) tools/hack/create-cluster.sh
# delete-gateway-api-cluster deletes the Gateway API test cluster.
.PHONY: delete-gateway-api-cluster
delete-gateway-api-cluster: $(tools/kind-gateway-api)
$(tools/kind-gateway-api) delete cluster --name higress
# kube-load-gateway-api-images loads only the images required by the Gateway API tests.
.PHONY: kube-load-gateway-api-images
kube-load-gateway-api-images: $(tools/kind-gateway-api)
KIND=$(tools/kind-gateway-api) tools/hack/kind-load-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/higress $(TAG)
tools/hack/docker-pull-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/pilot $(ISTIO_LATEST_IMAGE_TAG)
tools/hack/docker-pull-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/gateway $(ENVOY_LATEST_IMAGE_TAG)
KIND=$(tools/kind-gateway-api) tools/hack/kind-load-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/pilot $(ISTIO_LATEST_IMAGE_TAG)
KIND=$(tools/kind-gateway-api) tools/hack/kind-load-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/gateway $(ENVOY_LATEST_IMAGE_TAG)
# gateway-conformance-test-prepare prepares a kind cluster for Gateway API tests.
.PHONY: gateway-conformance-test-prepare
gateway-conformance-test-prepare: delete-gateway-api-cluster create-gateway-api-cluster install-gateway-api-crds docker-build kube-load-gateway-api-images install-dev-gateway-api
kubectl wait --timeout=10m -n $(GATEWAY_API_TEST_NAMESPACE) deployment/higress-controller --for=condition=Available
kubectl wait --timeout=10m -n $(GATEWAY_API_TEST_NAMESPACE) deployment/higress-gateway --for=condition=Available
kubectl wait --timeout=10m gatewayclass/higress --for=condition=Accepted
# run-gateway-conformance-test runs the upstream Gateway API Conformance Suite.
.PHONY: run-gateway-conformance-test
run-gateway-conformance-test:
mkdir -p $(dir $(GATEWAY_CONFORMANCE_REPORT))
HIGRESS_GATEWAY_API_TEST_DIAL_LOCALHOST=$(GATEWAY_API_DIAL_LOCALHOST) \
HIGRESS_GATEWAY_API_TEST_LOCAL_HTTP_PORT=$(GATEWAY_API_LOCAL_HTTP_PORT) \
HIGRESS_GATEWAY_API_TEST_LOCAL_HTTPS_PORT=$(GATEWAY_API_LOCAL_HTTPS_PORT) go test -v ./test/gateway \
-run '^TestGatewayAPIConformance$$' \
-args \
--gateway-class=higress \
--supported-features=$(GATEWAY_CONFORMANCE_SUPPORTED_FEATURES) \
--conformance-profiles=$(GATEWAY_CONFORMANCE_PROFILE) \
--organization=alibaba \
--project=higress \
--url=https://github.com/alibaba/higress \
--version=$(HIGRESS_CONFORMANCE_VERSION) \
--contact=$(GATEWAY_CONFORMANCE_CONTACT) \
--mode=default \
--cleanup-base-resources=false \
--allow-crds-mismatch=$(GATEWAY_CONFORMANCE_ALLOW_CRDS_MISMATCH) \
$(if $(GATEWAY_CONFORMANCE_RUN_TEST),--run-test=$(GATEWAY_CONFORMANCE_RUN_TEST),) \
--report-output=$(abspath $(GATEWAY_CONFORMANCE_REPORT))
# gateway-conformance-test runs Gateway API tests as a standard Higress integration test.
.PHONY: gateway-conformance-test
gateway-conformance-test:
gateway-conformance-test: gateway-conformance-test-prepare run-gateway-conformance-test
# gateway-conformance-test-clean deletes the Gateway API test cluster.
.PHONY: gateway-conformance-test-clean
gateway-conformance-test-clean: delete-gateway-api-cluster
# higress-conformance-test-prepare prepares the environment for higress conformance tests.
.PHONY: higress-conformance-test-prepare

1
go.mod
View File

@@ -128,6 +128,7 @@ require (
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.21.2 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect

4
go.sum
View File

@@ -5345,7 +5345,6 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250219182151-9fdb1cabc7b2/go.
google.golang.org/genproto/googleapis/api v0.0.0-20250227231956-55c901821b1e/go.mod h1:Xsh8gBVxGCcbV8ZeTB9wI5XPyZ5RvC6V3CTeeplHbiA=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8=
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw=
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA=
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA=
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE=
@@ -5475,8 +5474,6 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250227231956-55c901821b1e/go.
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
@@ -5564,7 +5561,6 @@ google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=

View File

@@ -21,6 +21,7 @@ import (
"strings"
"testing"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -31,6 +32,7 @@ import (
networking "istio.io/api/networking/v1alpha3"
"istio.io/istio/pilot/pkg/networking/core"
"istio.io/istio/pilot/pkg/serviceregistry/kube/controller"
"istio.io/istio/pilot/pkg/status"
"istio.io/istio/pkg/config"
"istio.io/istio/pkg/config/constants"
"istio.io/istio/pkg/config/schema/gvk"
@@ -192,6 +194,90 @@ func TestListGatewayResourceType(t *testing.T) {
}
}
func TestListGatewayResourceTypeWithAlternateGatewayClassName(t *testing.T) {
alternateGateway := gatewaySpec.DeepCopy()
alternateGateway.GatewayClassName = "alternate"
controller := setupController(t,
&k8sbeta.GatewayClass{
ObjectMeta: metav1.ObjectMeta{Name: "alternate"},
Spec: *gatewayClassSpec,
},
&k8sbeta.Gateway{
ObjectMeta: metav1.ObjectMeta{Name: "alternate-gw", Namespace: "ns1"},
Spec: *alternateGateway,
})
cfg := controller.List(gvk.Gateway, "ns1")
assert.Equal(t, len(cfg), 1)
assert.Equal(t, cfg[0].Name, "alternate-gw-"+constants.KubernetesGatewayName+"-default")
}
func TestHTTPRouteBackendServiceLifecycle(t *testing.T) {
backendPort := k8s.PortNumber(80)
route := httpRouteSpec.DeepCopy()
route.Rules = []k8s.HTTPRouteRule{{
BackendRefs: []k8s.HTTPBackendRef{{
BackendRef: k8s.BackendRef{
BackendObjectReference: k8s.BackendObjectReference{
Name: "backend",
Port: &backendPort,
},
},
}},
}}
controller := setupController(t,
&k8sbeta.GatewayClass{
ObjectMeta: metav1.ObjectMeta{Name: "higress"},
Spec: *gatewayClassSpec,
},
&k8sbeta.Gateway{
ObjectMeta: metav1.ObjectMeta{Name: "gwspec", Namespace: "ns1"},
Spec: *gatewaySpec,
},
&k8sbeta.HTTPRoute{
ObjectMeta: metav1.ObjectMeta{Name: "http-route", Namespace: "ns1"},
Spec: *route,
},
)
statusQueue := &TestStatusQueue{state: map[status.Resource]any{}}
setAndWait(t, controller, statusQueue)
resolvedRefs := func() string {
for _, rawStatus := range statusQueue.Statuses() {
routeStatus, ok := rawStatus.(*k8s.HTTPRouteStatus)
if !ok {
continue
}
for _, parent := range routeStatus.Parents {
for _, condition := range parent.Conditions {
if condition.Type == string(k8s.RouteConditionResolvedRefs) {
return string(condition.Status) + "/" + condition.Reason
}
}
}
}
return ""
}
assert.EventuallyEqual(t, resolvedRefs, "False/BackendNotFound")
service := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "backend", Namespace: "ns1"},
Spec: corev1.ServiceSpec{Ports: []corev1.ServicePort{{
Name: "http",
Port: int32(backendPort),
}}},
}
if _, err := controller.client.Kube().CoreV1().Services("ns1").Create(t.Context(), service, metav1.CreateOptions{}); err != nil {
t.Fatal(err)
}
assert.EventuallyEqual(t, resolvedRefs, "True/ResolvedRefs")
if err := controller.client.Kube().CoreV1().Services("ns1").Delete(t.Context(), service.Name, metav1.DeleteOptions{}); err != nil {
t.Fatal(err)
}
assert.EventuallyEqual(t, resolvedRefs, "False/BackendNotFound")
}
func TestListGatewayResourceTypeWithCustomGatewayClass(t *testing.T) {
if runInGatewayClassSubprocess(t) {
return

View File

@@ -1064,12 +1064,12 @@ func buildDestination(ctx RouteContext, to k8s.BackendRef, ns string,
return nil, nil, &ConfigError{Reason: InvalidDestination, Message: "service name invalid; the name of the Service must be used, not the hostname."}
}
hostname = fmt.Sprintf("%s.%s.svc.%s", to.Name, namespace, ctx.DomainSuffix)
// Start - Updated by Higress
//key := namespace + "/" + string(to.Name)
//svc := ptr.Flatten(krt.FetchOne(ctx.Krt, ctx.Services, krt.FilterKey(key)))
svc := ctx.LookupHostname(hostname, namespace, "Service")
// End - Updated by Higress
if svc == nil {
// Keep Higress service resolution as the primary lookup so its service naming and
// plugin binding semantics remain unchanged. Always fetch from the informer to
// retain the dependency needed to recompute the Route when the Service is deleted.
key := namespace + "/" + string(to.Name)
kubeSvc := ptr.Flatten(krt.FetchOne(ctx.Krt, ctx.Services, krt.FilterKey(key)))
if ctx.LookupHostname(hostname, namespace, "Service") == nil && kubeSvc == nil {
invalidBackendErr = &ConfigError{Reason: InvalidDestinationNotFound, Message: fmt.Sprintf("backend(%s) not found", hostname)}
}
case config.GroupVersionKind{Group: gvk.ServiceEntry.Group, Kind: "Hostname"}:
@@ -1592,6 +1592,21 @@ func (r routeParentReference) IsMesh() bool {
return r.InternalName == "mesh"
}
func (r routeParentReference) hostnameIntersection(rawRouteHost string) (string, bool) {
routeHost := host.Name(rawRouteHost)
listenerHost := host.Name(r.Hostname)
if len(listenerHost) == 0 || listenerHost == "*" {
return rawRouteHost, true
}
if routeHost.SubsetOf(listenerHost) {
return rawRouteHost, true
}
if listenerHost.SubsetOf(routeHost) {
return r.Hostname, true
}
return "", false
}
func (r routeParentReference) hostnameAllowedByIsolation(rawRouteHost string) bool {
routeHost := host.Name(rawRouteHost)
ourListener := host.Name(r.Hostname)

View File

@@ -1522,6 +1522,36 @@ func TestGatewayReferenceAllowedParentHostnameParsing(t *testing.T) {
}
}
func TestRouteParentReferenceHostnameIntersection(t *testing.T) {
tests := []struct {
name string
listenerHost string
routeHost string
wantHost string
wantMatch bool
}{
{name: "empty listener", listenerHost: "", routeHost: "example.com", wantHost: "example.com", wantMatch: true},
{name: "empty route", listenerHost: "example.com", routeHost: "*", wantHost: "example.com", wantMatch: true},
{name: "exact match", listenerHost: "example.com", routeHost: "example.com", wantHost: "example.com", wantMatch: true},
{name: "exact listener and wildcard route", listenerHost: "foo.example.com", routeHost: "*.example.com", wantHost: "foo.example.com", wantMatch: true},
{name: "wildcard listener and exact route", listenerHost: "*.example.com", routeHost: "foo.example.com", wantHost: "foo.example.com", wantMatch: true},
{name: "narrower route wildcard", listenerHost: "*.example.com", routeHost: "*.foo.example.com", wantHost: "*.foo.example.com", wantMatch: true},
{name: "narrower listener wildcard", listenerHost: "*.foo.example.com", routeHost: "*.example.com", wantHost: "*.foo.example.com", wantMatch: true},
{name: "disjoint exact hosts", listenerHost: "foo.example.com", routeHost: "bar.example.com", wantMatch: false},
{name: "disjoint wildcard hosts", listenerHost: "*.example.com", routeHost: "*.example.net", wantMatch: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
parent := routeParentReference{Hostname: tt.listenerHost}
gotHost, gotMatch := parent.hostnameIntersection(tt.routeHost)
if gotHost != tt.wantHost || gotMatch != tt.wantMatch {
t.Fatalf("hostnameIntersection() = (%q, %v), want (%q, %v)", gotHost, gotMatch, tt.wantHost, tt.wantMatch)
}
})
}
}
func TestReferencePolicy(t *testing.T) {
validator := crdvalidation.NewIstioValidator(t)
type res struct {

View File

@@ -38,8 +38,7 @@ func GatewayClassesCollection(
krt.Collection[GatewayClass],
) {
return krt.NewStatusCollection(gatewayClasses, func(ctx krt.HandlerContext, obj *gateway.GatewayClass) (*gateway.GatewayClassStatus, *GatewayClass) {
if gatewayv1.ObjectName(obj.Name) != gatewayv1.ObjectName(gatewayClassName) ||
obj.Spec.ControllerName != managedGatewayController {
if obj.Spec.ControllerName != managedGatewayController {
return nil, nil
}
if _, known := classInfos[obj.Spec.ControllerName]; !known {

View File

@@ -148,7 +148,12 @@ func HTTPRouteCollection(
}
// Create one VS per hostname with a single hostname.
// This ensures we can treat each hostname independently, as the spec requires
for _, h := range vsHosts {
processedHosts := sets.New[string]()
for _, routeHost := range vsHosts {
h, ok := parent.hostnameIntersection(routeHost)
if !ok || processedHosts.InsertContains(h) {
continue
}
if !parent.hostnameAllowedByIsolation(h) {
// TODO: standardize a status message for this upstream and report
continue

View File

@@ -23,13 +23,13 @@ metadata:
annotations:
internal.istio.io/parents: HTTPRoute/http.higress-system
internal.istio.io/route-semantics: gateway
name: higress-system~gateway-istio-autogenerated-k8s-gateway-default~*
name: higress-system~gateway-istio-autogenerated-k8s-gateway-default~*.domain.example
namespace: higress-system
spec:
gateways:
- higress-system/gateway-istio-autogenerated-k8s-gateway-default
hosts:
- '*'
- '*.domain.example'
http:
- name: http
route:

View File

@@ -42,13 +42,13 @@ metadata:
annotations:
internal.istio.io/parents: HTTPRoute/http.apple
internal.istio.io/route-semantics: gateway
name: higress-system~gateway-istio-autogenerated-k8s-gateway-apple~*
name: higress-system~gateway-istio-autogenerated-k8s-gateway-apple~apple.example
namespace: apple
spec:
gateways:
- higress-system/gateway-istio-autogenerated-k8s-gateway-apple
hosts:
- '*'
- apple.example
http:
- name: apple/http
route:
@@ -63,13 +63,13 @@ metadata:
annotations:
internal.istio.io/parents: HTTPRoute/http.banana
internal.istio.io/route-semantics: gateway
name: higress-system~gateway-istio-autogenerated-k8s-gateway-banana~*
name: higress-system~gateway-istio-autogenerated-k8s-gateway-banana~banana.example
namespace: banana
spec:
gateways:
- higress-system/gateway-istio-autogenerated-k8s-gateway-banana
hosts:
- '*'
- banana.example
http:
- name: banana/http
route:

View File

@@ -23,13 +23,13 @@ metadata:
annotations:
internal.istio.io/parents: HTTPRoute/mirror.default,HTTPRoute/redirect.default,HTTPRoute/rewrite.default
internal.istio.io/route-semantics: gateway
name: higress-system~gateway-istio-autogenerated-k8s-gateway-default~*
name: higress-system~gateway-istio-autogenerated-k8s-gateway-default~*.domain.example
namespace: default
spec:
gateways:
- higress-system/gateway-istio-autogenerated-k8s-gateway-default
hosts:
- '*'
- '*.domain.example'
http:
- match:
- uri:

View File

@@ -142,13 +142,13 @@ metadata:
annotations:
internal.istio.io/parents: HTTPRoute/invalid-mirror.default
internal.istio.io/route-semantics: gateway
name: higress-system~gateway-istio-autogenerated-k8s-gateway-default~*
name: higress-system~gateway-istio-autogenerated-k8s-gateway-default~*.domain.example
namespace: default
spec:
gateways:
- higress-system/gateway-istio-autogenerated-k8s-gateway-default
hosts:
- '*'
- '*.domain.example'
http:
- name: default/invalid-mirror
route:

View File

@@ -305,13 +305,13 @@ metadata:
annotations:
internal.istio.io/parents: HTTPRoute/bind-all.default
internal.istio.io/route-semantics: gateway
name: higress-system~gateway-istio-autogenerated-k8s-gateway-default~*
name: higress-system~gateway-istio-autogenerated-k8s-gateway-default~*.domain.example
namespace: default
spec:
gateways:
- higress-system/gateway-istio-autogenerated-k8s-gateway-default
hosts:
- '*'
- '*.domain.example'
http:
- name: default/bind-all
route:
@@ -326,13 +326,13 @@ metadata:
annotations:
internal.istio.io/parents: HTTPRoute/bind-all.default,HTTPRoute/same-namespace-valid.istio-system
internal.istio.io/route-semantics: gateway
name: higress-system~gateway-istio-autogenerated-k8s-gateway-foobar~*
name: higress-system~gateway-istio-autogenerated-k8s-gateway-foobar~*.foobar.example
namespace: default
spec:
gateways:
- higress-system/gateway-istio-autogenerated-k8s-gateway-foobar
hosts:
- '*'
- '*.foobar.example'
http:
- name: default/bind-all
route:
@@ -374,13 +374,13 @@ metadata:
annotations:
internal.istio.io/parents: HTTPRoute/bind-all.default,HTTPRoute/bind-cross-namespace.group-namespace1
internal.istio.io/route-semantics: gateway
name: higress-system~gateway-istio-autogenerated-k8s-gateway-slctr-expr-dne-no~*
name: higress-system~gateway-istio-autogenerated-k8s-gateway-slctr-expr-dne-no~*.slctr-expr-dne-no.example
namespace: default
spec:
gateways:
- higress-system/gateway-istio-autogenerated-k8s-gateway-slctr-expr-dne-no
hosts:
- '*'
- '*.slctr-expr-dne-no.example'
http:
- name: default/bind-all
route:
@@ -401,13 +401,13 @@ metadata:
annotations:
internal.istio.io/parents: HTTPRoute/bind-all.default,HTTPRoute/bind-cross-namespace.group-namespace1
internal.istio.io/route-semantics: gateway
name: higress-system~gateway-istio-autogenerated-k8s-gateway-slctr-expr-dne-yes~*
name: higress-system~gateway-istio-autogenerated-k8s-gateway-slctr-expr-dne-yes~*.slctr-expr-dne-yes.example
namespace: default
spec:
gateways:
- higress-system/gateway-istio-autogenerated-k8s-gateway-slctr-expr-dne-yes
hosts:
- '*'
- '*.slctr-expr-dne-yes.example'
http:
- name: default/bind-all
route:
@@ -428,13 +428,13 @@ metadata:
annotations:
internal.istio.io/parents: HTTPRoute/bind-all.default,HTTPRoute/bind-cross-namespace.group-namespace1
internal.istio.io/route-semantics: gateway
name: higress-system~gateway-istio-autogenerated-k8s-gateway-slctr-expr-notin-no~*
name: higress-system~gateway-istio-autogenerated-k8s-gateway-slctr-expr-notin-no~*.slctr-expr-notin-no.example
namespace: default
spec:
gateways:
- higress-system/gateway-istio-autogenerated-k8s-gateway-slctr-expr-notin-no
hosts:
- '*'
- '*.slctr-expr-notin-no.example'
http:
- name: default/bind-all
route:
@@ -455,13 +455,13 @@ metadata:
annotations:
internal.istio.io/parents: HTTPRoute/bind-all.default,HTTPRoute/bind-cross-namespace.group-namespace1
internal.istio.io/route-semantics: gateway
name: higress-system~gateway-istio-autogenerated-k8s-gateway-slctr-expr-notin-yes~*
name: higress-system~gateway-istio-autogenerated-k8s-gateway-slctr-expr-notin-yes~*.slctr-expr-notin-yes.example
namespace: default
spec:
gateways:
- higress-system/gateway-istio-autogenerated-k8s-gateway-slctr-expr-notin-yes
hosts:
- '*'
- '*.slctr-expr-notin-yes.example'
http:
- name: default/bind-all
route:

View File

@@ -18,6 +18,7 @@ Higress e2e tests are mainly focusing on two parts for now:
Higress provides make target to run ingress api conformance tests and wasmplugin tests,
+ API Tests: `make higress-conformance-test`
+ Gateway API Tests: `make gateway-conformance-test`
+ WasmPlugin Tests: `make higress-wasmplugin-test`
+ Build all Go WasmPlugins for testing: `make higress-wasmplugin-test`
+ Build tests for a specific Go WasmPlugin only: `PLUGIN_NAME=request-block make higress-wasmplugin-test`
@@ -71,15 +72,6 @@ The test environment reusability is primarily achieved through the following tar
## Gateway APIs Conformance Tests
Gateway API Conformance tests are based on the suite provided by `kubernetes-sig/gateway-api`, we can reuse that,
and decide what conformance tests we need to open. Conformance tests of Gateway API.
Run `make gateway-conformance-test` to execute the upstream Gateway API v1.4.0 Conformance Suite. The default scope is the required `GATEWAY-HTTP` Core profile (`Gateway`, `HTTPRoute`, and `ReferenceGrant`); Extended features are not enabled.
This API covers a broad set of features and use cases and has been implemented widely.
This combination of both a large feature set and variety of implementations requires
clear conformance definitions and tests to ensure the API provides a consistent experience wherever it is used.
Gateway API includes a set of conformance tests. These create a series of Gateways and Routes with the specified
GatewayClass, and test that the implementation matches the API specification.
Each release contains a set of conformance tests, these will continue to expand as the API evolves.
Currently conformance tests cover the majority of Core capabilities in the standard channel, in addition to some Extended capabilities.
The runner imports the upstream suite and its embedded manifests directly, so Higress does not maintain copies of official test cases. Set `GATEWAY_CONFORMANCE_RUN_TEST=<ShortName>` only when debugging one upstream test. The default PR workflow always runs the complete Core profile and stores the generated report as a CI artifact.

View File

@@ -18,6 +18,7 @@ Higress E2E 测试主要关注两个部分:
Higress 提供了运行 Ingress API 一致性测试和 wasmplugin 测试的 make 目标,
+ API 测试: `make higress-conformance-test`
+ Gateway API 测试: `make gateway-conformance-test`
+ WasmPlugin 测试: `make higress-wasmplugin-test`
+ 为测试构建所有 GO WasmPlugins: `make higress-wasmplugin-test`
+ 仅为一个 GO WasmPlugin 构建测试: `PLUGIN_NAME=request-block make higress-wasmplugin-test`
@@ -70,12 +71,6 @@ Higress 提供了运行 Ingress API 一致性测试和 wasmplugin 测试的 make
## Gateway API 一致性测试
Gateway API 一致性测试基于 `kubernetes-sig/gateway-api` 提供的套件,我们可以重复使用它,并决定我们需要打开哪些 Gateway API 的一致性测试
执行 `make gateway-conformance-test` 可运行上游 Gateway API v1.4.0 Conformance Suite。默认范围只包含 `GATEWAY-HTTP` Profile 的必选 Core 能力:`Gateway``HTTPRoute``ReferenceGrant`,不启用 Extended 能力
此 API 包含一系列广泛的功能和用例,并已得到广泛实现
这个大的功能集和各种实现的结合需要明确的一致性定义和测试,以确保在任何地方使用该 API 时都提供一致的体验。
Gateway API 包括一组一致性测试。这些测试创建具有指定 GatewayClass 的一系列 Gateways 和 Routes并测试实现是否符合 API 规范。
每个发布版本都包含一组一致性测试,随着 API 的演进,这些测试将不断扩展。
目前,一致性测试覆盖了标准通道中的大多数核心功能,以及一些扩展功能。
Runner 直接引用上游 Suite 及其内嵌 manifestsHigress 不复制维护官方测试用例。只有调试单个上游用例时才设置 `GATEWAY_CONFORMANCE_RUN_TEST=<ShortName>`;默认 PR 流水线始终执行完整 Core Profile并将官方报告保存为 CI artifact

View File

@@ -13,3 +13,46 @@
// limitations under the License.
package gateway
import (
"context"
"net"
"os"
"testing"
"sigs.k8s.io/gateway-api/conformance"
"sigs.k8s.io/gateway-api/conformance/utils/roundtripper"
)
const dialLocalhostEnv = "HIGRESS_GATEWAY_API_TEST_DIAL_LOCALHOST"
const localHTTPPortEnv = "HIGRESS_GATEWAY_API_TEST_LOCAL_HTTP_PORT"
const localHTTPSPortEnv = "HIGRESS_GATEWAY_API_TEST_LOCAL_HTTPS_PORT"
func TestGatewayAPIConformance(t *testing.T) {
opts := conformance.DefaultOptions(t)
if os.Getenv(dialLocalhostEnv) == "true" {
opts.RoundTripper = &roundtripper.DefaultRoundTripper{
Debug: opts.Debug,
TimeoutConfig: opts.TimeoutConfig,
CustomDialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
_, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
switch port {
case "80":
if localPort := os.Getenv(localHTTPPortEnv); localPort != "" {
port = localPort
}
case "443":
if localPort := os.Getenv(localHTTPSPortEnv); localPort != "" {
port = localPort
}
}
var dialer net.Dialer
return dialer.DialContext(ctx, network, net.JoinHostPort("127.0.0.1", port))
},
}
}
conformance.RunConformanceWithOptions(t, opts)
}

View File

@@ -20,6 +20,7 @@ set -euo pipefail
CLUSTER_NAME=${CLUSTER_NAME:-"higress"}
METALLB_VERSION=${METALLB_VERSION:-"v0.13.7"}
KIND_NODE_TAG=${KIND_NODE_TAG:-"v1.25.3"}
KIND=${KIND:-"tools/bin/kind"}
PROJECT_DIR=$(pwd)
echo ${KIND_NODE_TAG}
@@ -67,7 +68,7 @@ EOF
## Create kind cluster.
if [[ -z "${KIND_NODE_TAG}" ]]; then
tools/bin/kind create cluster --name "${CLUSTER_NAME}" --config=tools/hack/cluster.conf
"${KIND}" create cluster --name "${CLUSTER_NAME}" --config=tools/hack/cluster.conf
else
tools/bin/kind create cluster --image "kindest/node:${KIND_NODE_TAG}" --name "${CLUSTER_NAME}" --config=tools/hack/cluster.conf
"${KIND}" create cluster --image "kindest/node:${KIND_NODE_TAG}" --name "${CLUSTER_NAME}" --config=tools/hack/cluster.conf
fi

View File

@@ -0,0 +1,20 @@
module github.com/alibaba/higress/tools/src/kind-gateway-api
go 1.24.0
require sigs.k8s.io/kind v0.30.0
require (
al.essio.dev/pkg/shellescape v1.5.1 // indirect
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/spf13/cobra v1.8.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/sys v0.6.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)

View File

@@ -0,0 +1,42 @@
al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho=
al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=
github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
sigs.k8s.io/kind v0.30.0 h1:2Xi1KFEfSMm0XDcvKnUt15ZfgRPCT0OnCBbpgh8DztY=
sigs.k8s.io/kind v0.30.0/go.mod h1:FSqriGaoTPruiXWfRnUXNykF8r2t+fHtK0P0m1AbGF8=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=

View File

@@ -0,0 +1,19 @@
// Copyright (c) 2026 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build pin
package ignore
import "sigs.k8s.io/kind"

View File

@@ -16,6 +16,7 @@ tools/controller-gen = $(tools.bindir)/controller-gen
tools/golangci-lint = $(tools.bindir)/golangci-lint
tools/kustomize = $(tools.bindir)/kustomize
tools/kind = $(tools.bindir)/kind
tools/kind-gateway-api = $(tools.bindir)/kind-gateway-api
tools/setup-envtest = $(tools.bindir)/setup-envtest
$(tools.bindir)/%: $(tools.srcdir)/%/pin.go $(tools.srcdir)/%/go.mod
cd $(<D) && GOOS= GOARCH= go build -o $(abspath $@) $$(sed -En 's,^import "(.*)".*,\1,p' pin.go)