From 112d3c315c07dd1b2220b043c7329ac97b87d044 Mon Sep 17 00:00:00 2001 From: Karol Szwaj Date: Wed, 15 Nov 2023 02:41:53 +0100 Subject: [PATCH] Add E2E Tests for UDPRoute (#2140) * add e2e udproute test Signed-off-by: Karol Szwaj * lint Signed-off-by: Karol Szwaj * update simple test Signed-off-by: Karol Szwaj * review update Signed-off-by: Karol Szwaj --------- Signed-off-by: Karol Szwaj --- go.mod | 5 +- go.sum | 19 +- test/e2e/base/manifests.yaml | 86 +++++++ test/e2e/testdata/udproute.yaml | 13 + test/e2e/tests/udproute.go | 235 ++++++++++++++++++ tools/linter/codespell/.codespell.ignorewords | 2 +- 6 files changed, 356 insertions(+), 4 deletions(-) create mode 100644 test/e2e/testdata/udproute.yaml create mode 100644 test/e2e/tests/udproute.go diff --git a/go.mod b/go.mod index b2ec257be8a..191de5508cd 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/golang/protobuf v1.5.3 github.com/google/go-cmp v0.6.0 github.com/grafana/tempo v1.5.0 + github.com/miekg/dns v1.1.46 github.com/pkg/errors v0.9.1 github.com/prometheus/common v0.45.0 github.com/spf13/cobra v1.8.0 @@ -55,7 +56,9 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0 // indirect go.opentelemetry.io/otel/sdk v1.20.0 // indirect go.opentelemetry.io/otel/trace v1.20.0 // indirect - golang.org/x/sync v0.3.0 // indirect + golang.org/x/mod v0.13.0 // indirect + golang.org/x/sync v0.4.0 // indirect + golang.org/x/tools v0.14.0 // indirect ) require ( diff --git a/go.sum b/go.sum index b7dbb77fda0..1438a736934 100644 --- a/go.sum +++ b/go.sum @@ -335,6 +335,8 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= +github.com/miekg/dns v1.1.46 h1:uzwpxRtSVxtcIZmz/4Uz6/Rn7G11DvsaslXoy5LxQio= +github.com/miekg/dns v1.1.46/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= @@ -481,6 +483,7 @@ github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -541,7 +544,10 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -564,6 +570,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= @@ -579,9 +587,10 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -607,8 +616,12 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -624,6 +637,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= @@ -649,6 +663,7 @@ golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= diff --git a/test/e2e/base/manifests.yaml b/test/e2e/base/manifests.yaml index a4f39429e50..552e0eeb42e 100644 --- a/test/e2e/base/manifests.yaml +++ b/test/e2e/base/manifests.yaml @@ -414,3 +414,89 @@ spec: resources: requests: cpu: 10m +--- +apiVersion: v1 +kind: Namespace +metadata: + name: gateway-conformance-udp + labels: + gateway-conformance: udp +--- +apiVersion: v1 +kind: Service +metadata: + name: coredns + namespace: gateway-conformance-udp + labels: + app: udp +spec: + ports: + - name: udp-dns + port: 53 + protocol: UDP + targetPort: 53 + selector: + app: udp +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: coredns + namespace: gateway-conformance-udp + labels: + app: udp +spec: + selector: + matchLabels: + app: udp + template: + metadata: + labels: + app: udp + spec: + containers: + - args: + - -conf + - /root/Corefile + image: coredns/coredns + name: coredns + volumeMounts: + - mountPath: /root + name: conf + volumes: + - configMap: + defaultMode: 420 + name: coredns + name: conf +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: coredns + namespace: gateway-conformance-udp +data: + Corefile: | + .:53 { + forward . 8.8.8.8 9.9.9.9 + log + errors + } + + foo.bar.com:53 { + whoami + } +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: Gateway +metadata: + name: udp-gateway + namespace: gateway-conformance-udp +spec: + gatewayClassName: "{GATEWAY_CLASS_NAME}" + listeners: + - name: coredns + protocol: UDP + port: 5300 + allowedRoutes: + kinds: + - kind: UDPRoute diff --git a/test/e2e/testdata/udproute.yaml b/test/e2e/testdata/udproute.yaml new file mode 100644 index 00000000000..e5ea8d1244f --- /dev/null +++ b/test/e2e/testdata/udproute.yaml @@ -0,0 +1,13 @@ +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: UDPRoute +metadata: + name: udp-coredns + namespace: gateway-conformance-udp +spec: + parentRefs: + - name: udp-gateway + sectionName: coredns + rules: + - backendRefs: + - name: coredns + port: 53 diff --git a/test/e2e/tests/udproute.go b/test/e2e/tests/udproute.go new file mode 100644 index 00000000000..31b0dbf44c4 --- /dev/null +++ b/test/e2e/tests/udproute.go @@ -0,0 +1,235 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +// This file contains code derived from upstream gateway-api, it will be moved to upstream. + +//go:build e2e +// +build e2e + +package tests + +import ( + "context" + "fmt" + "reflect" + "testing" + "time" + + "github.com/miekg/dns" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + "sigs.k8s.io/controller-runtime/pkg/client" + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" + "sigs.k8s.io/gateway-api/apis/v1alpha2" + "sigs.k8s.io/gateway-api/conformance/utils/config" + "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" + "sigs.k8s.io/gateway-api/conformance/utils/suite" +) + +func init() { + ConformanceTests = append(ConformanceTests, UDPRouteTest) +} + +var UDPRouteTest = suite.ConformanceTest{ + ShortName: "UDPRoute", + Description: "Make sure UDPRoute is working", + Manifests: []string{"testdata/udproute.yaml"}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + t.Run("Simple UDP request matching UDPRoute should reach coredns backend", func(t *testing.T) { + namespace := "gateway-conformance-udp" + domain := "foo.bar.com." + routeNN := types.NamespacedName{Name: "udp-coredns", Namespace: namespace} + gwNN := types.NamespacedName{Name: "udp-gateway", Namespace: namespace} + gwAddr := GatewayAndUDPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, NewGatewayRef(gwNN), routeNN) + + msg := new(dns.Msg) + msg.SetQuestion(domain, dns.TypeA) + + if err := wait.PollUntilContextTimeout(context.TODO(), time.Second, time.Minute, true, + func(_ context.Context) (done bool, err error) { + t.Logf("performing DNS query %s on %s", domain, gwAddr) + _, err = dns.Exchange(msg, gwAddr) + if err != nil { + return false, err + } + return true, nil + }); err != nil { + t.Errorf("failed to perform DNS query: %v", err) + } + }) + }, +} + +// GatewayRef is a tiny type for specifying an UDP Route ParentRef without +// relying on a specific api version. +type GatewayRef struct { + types.NamespacedName + listenerNames []*gatewayv1.SectionName +} + +// NewGatewayRef creates a GatewayRef resource. ListenerNames are optional. +func NewGatewayRef(nn types.NamespacedName, listenerNames ...string) GatewayRef { + var listeners []*gatewayv1.SectionName + + if len(listenerNames) == 0 { + listenerNames = append(listenerNames, "") + } + + for _, listener := range listenerNames { + sectionName := gatewayv1.SectionName(listener) + listeners = append(listeners, §ionName) + } + return GatewayRef{ + NamespacedName: nn, + listenerNames: listeners, + } +} + +// GatewayAndUDPRoutesMustBeAccepted waits until the specified Gateway has an IP +// address assigned to it and the UDPRoute has a ParentRef referring to the +// Gateway. The test will fail if these conditions are not met before the +// timeouts. +func GatewayAndUDPRoutesMustBeAccepted(t *testing.T, c client.Client, timeoutConfig config.TimeoutConfig, controllerName string, gw GatewayRef, routeNNs ...types.NamespacedName) string { + t.Helper() + + gwAddr, err := kubernetes.WaitForGatewayAddress(t, c, timeoutConfig, gw.NamespacedName) + require.NoErrorf(t, err, "timed out waiting for Gateway address to be assigned") + + ns := gatewayv1.Namespace(gw.Namespace) + kind := gatewayv1.Kind("Gateway") + + for _, routeNN := range routeNNs { + namespaceRequired := true + if routeNN.Namespace == gw.Namespace { + namespaceRequired = false + } + + var parents []gatewayv1.RouteParentStatus + for _, listener := range gw.listenerNames { + parents = append(parents, gatewayv1.RouteParentStatus{ + ParentRef: gatewayv1.ParentReference{ + Group: (*gatewayv1.Group)(&gatewayv1.GroupVersion.Group), + Kind: &kind, + Name: gatewayv1.ObjectName(gw.Name), + Namespace: &ns, + SectionName: listener, + }, + ControllerName: gatewayv1.GatewayController(controllerName), + Conditions: []metav1.Condition{ + { + Type: string(gatewayv1.RouteConditionAccepted), + Status: metav1.ConditionTrue, + Reason: string(gatewayv1.RouteReasonAccepted), + }, + }, + }) + } + UDPRouteMustHaveParents(t, c, timeoutConfig, routeNN, parents, namespaceRequired) + } + + return gwAddr +} + +func UDPRouteMustHaveParents(t *testing.T, client client.Client, timeoutConfig config.TimeoutConfig, routeName types.NamespacedName, parents []gatewayv1.RouteParentStatus, namespaceRequired bool) { + t.Helper() + + var actual []gatewayv1.RouteParentStatus + waitErr := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, timeoutConfig.RouteMustHaveParents, true, func(ctx context.Context) (bool, error) { + route := &v1alpha2.UDPRoute{} + err := client.Get(ctx, routeName, route) + if err != nil { + return false, fmt.Errorf("error fetching UDPRoute: %w", err) + } + + actual = route.Status.Parents + return parentsForRouteMatch(t, routeName, parents, actual, namespaceRequired), nil + }) + require.NoErrorf(t, waitErr, "error waiting for UDPRoute to have parents matching expectations") +} + +func parentsForRouteMatch(t *testing.T, routeName types.NamespacedName, expected, actual []gatewayv1.RouteParentStatus, namespaceRequired bool) bool { + t.Helper() + + if len(expected) != len(actual) { + t.Logf("Route %s/%s expected %d Parents got %d", routeName.Namespace, routeName.Name, len(expected), len(actual)) + return false + } + + for i, expectedParent := range expected { + actualParent := actual[i] + if actualParent.ControllerName != expectedParent.ControllerName { + t.Logf("Route %s/%s ControllerName doesn't match", routeName.Namespace, routeName.Name) + return false + } + if !reflect.DeepEqual(actualParent.ParentRef.Group, expectedParent.ParentRef.Group) { + t.Logf("Route %s/%s expected ParentReference.Group to be %v, got %v", routeName.Namespace, routeName.Name, expectedParent.ParentRef.Group, actualParent.ParentRef.Group) + return false + } + if !reflect.DeepEqual(actualParent.ParentRef.Kind, expectedParent.ParentRef.Kind) { + t.Logf("Route %s/%s expected ParentReference.Kind to be %v, got %v", routeName.Namespace, routeName.Name, expectedParent.ParentRef.Kind, actualParent.ParentRef.Kind) + return false + } + if actualParent.ParentRef.Name != expectedParent.ParentRef.Name { + t.Logf("Route %s/%s ParentReference.Name doesn't match", routeName.Namespace, routeName.Name) + return false + } + if !reflect.DeepEqual(actualParent.ParentRef.Namespace, expectedParent.ParentRef.Namespace) { + if namespaceRequired || actualParent.ParentRef.Namespace != nil { + t.Logf("Route %s/%s expected ParentReference.Namespace to be %v, got %v", routeName.Namespace, routeName.Name, expectedParent.ParentRef.Namespace, actualParent.ParentRef.Namespace) + return false + } + } + if !conditionsMatch(t, expectedParent.Conditions, actualParent.Conditions) { + return false + } + } + + t.Logf("Route %s/%s Parents matched expectations", routeName.Namespace, routeName.Name) + return true +} + +func conditionsMatch(t *testing.T, expected, actual []metav1.Condition) bool { + t.Helper() + + if len(actual) < len(expected) { + t.Logf("Expected more conditions to be present") + return false + } + for _, condition := range expected { + if !findConditionInList(t, actual, condition.Type, string(condition.Status), condition.Reason) { + return false + } + } + + t.Logf("Conditions matched expectations") + return true +} + +// findConditionInList finds a condition in a list of Conditions, checking +// the Name, Value, and Reason. If an empty reason is passed, any Reason will match. +// If an empty status is passed, any Status will match. +func findConditionInList(t *testing.T, conditions []metav1.Condition, condName, expectedStatus, expectedReason string) bool { + t.Helper() + + for _, cond := range conditions { + if cond.Type == condName { + // an empty Status string means "Match any status". + if expectedStatus == "" || cond.Status == metav1.ConditionStatus(expectedStatus) { + // an empty Reason string means "Match any reason". + if expectedReason == "" || cond.Reason == expectedReason { + return true + } + t.Logf("%s condition Reason set to %s, expected %s", condName, cond.Reason, expectedReason) + } + + t.Logf("%s condition set to Status %s with Reason %v, expected Status %s", condName, cond.Status, cond.Reason, expectedStatus) + } + } + + t.Logf("%s was not in conditions list [%v]", condName, conditions) + return false +} diff --git a/tools/linter/codespell/.codespell.ignorewords b/tools/linter/codespell/.codespell.ignorewords index 254c60d129e..e773f85868e 100644 --- a/tools/linter/codespell/.codespell.ignorewords +++ b/tools/linter/codespell/.codespell.ignorewords @@ -1,4 +1,4 @@ keypair keypairs als -requestor \ No newline at end of file +requestor