Repository: Kong/kubernetes-ingress-controller
Pull request #2944 (2022-09-17 06:25:21)

Chore: Refactors `makefile` recipe test.integration.gke to support e2e test
<!-- Thanks for sending a pull request! Here are some tips for you:
1. If this is your first time, read our contributor guidelines https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#the-pull-request-submit-process and developer guide https://github.com/kubernetes/community/blob/master/contributors/devel/development.md
2. If you want *faster* PR reviews, read how: https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#best-practices-for-faster-reviews
3. Follow the instructions for writing a release note: https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md and ensure your changes are being reflected in CHANGELOG.md for the next upcoming release
-->

**What this PR does / why we need it**:
As mentioned in the issue, it refactors the makefile and CI workflow

<!-- Please describe why this particular PR is necessary or why you see it as a nice addition -->

**Which issue this PR fixes**:
Fixes: #2788 

<!--
Here you can add any links to issues that this PR is relevant for.
You can use Github keywords (like: closes, fixes or resolves) to auto-resolve
the linked issue(s) when this PR gets merged.

For example: fixes #<issue number>
-->

**Special notes for your reviewer**:

<!-- Here you can add any open questions or notes that you might have for reviewers -->

**PR Readiness Checklist**:

Complete these before marking the PR as `ready to review`:

- [ ] the `CHANGELOG.md` release notes have been updated to reflect any significant (and particularly user-facing) changes introduced by this PR


afzal442: Any review will be great. 😄 

cc @jrsmroz @shaneutt 
afzal442: / @rainest 

Patch:
diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml
index 56aa7f3d6..713fd7b84 100644
--- a/.github/workflows/nightly.yaml
+++ b/.github/workflows/nightly.yaml
@@ -173,7 +173,7 @@ jobs:
         with:
           fetch-depth: 0
       - name: Kubernetes ${{ matrix.kubernetes-version }} ${{ matrix.dbmode }} Integration Tests
-        run: KONG_CLUSTER_VERSION=${{ matrix.kubernetes-version }} make test.integration.${{ matrix.dbmode }}
+        run: TEST_KONG_CONTROLLER_IMAGE_OVERRIDE="kong/nightly-ingress-controller:nightly" KONG_CLUSTER_VERSION=${{ matrix.kubernetes-version }} make test.integration.${{ matrix.dbmode }}
   # run integration tests in previous versions of kubernetes 
   test-previous-kubernetes-gke:
     name: Run integration tests on GKE
@@ -207,7 +207,7 @@ jobs:
         with:
           fetch-depth: 0
       - name: test ${{ matrix.dbmode }} on GKE v1.${{ matrix.minor }}
-        run: ./hack/e2e/run-tests.sh
+        run: make test.integration.gke
         env:
           KUBERNETES_MAJOR_VERSION: 1
           KUBERNETES_MINOR_VERSION: ${{ matrix.minor }}
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index ab62bf844..485ac1e76 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -225,8 +225,8 @@ jobs:
         uses: actions/checkout@v3
         with:
           fetch-depth: 0
-      - name: Kubernetes ${{ matrix.kubernetes_version }} ${{ matrix.dbmode }} Integration Tests
-        run: KONG_CLUSTER_VERSION=${{ matrix.kubernetes_version }} make test.integration.${{ matrix.dbmode }}
+      - name: Kubernetes ${{ matrix.kubernetes-version }} ${{ matrix.dbmode }} Integration Tests
+        run: KONG_CLUSTER_VERSION=${{ matrix.kubernetes-version }} make test.integration.${{ matrix.dbmode }}
 
   test-previous-kubernetes:
     environment: gcloud
@@ -258,7 +258,7 @@ jobs:
         with:
           fetch-depth: 0
       - name: test ${{ matrix.dbmode }} on GKE v1.${{ matrix.minor }}
-        run: ./hack/e2e/run-tests.sh
+        run: make test.integration.gke
         env:
           KUBERNETES_MAJOR_VERSION: 1
           KUBERNETES_MINOR_VERSION: ${{ matrix.minor }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 58bde149d..8dabf4e01 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,14 @@
 # Table of Contents
 
+<!---
+Adding a new version? You'll need three changes:
+* Add the ToC link, like "[1.2.3](#123]".
+* Add the section header, like "## [1.2.3]".
+* Add the diff link, like "[2.7.0]: https://github.com/kong/kubernetes-ingress-controller/compare/v1.2.2...v1.2.3".
+  This is all the way at the bottom. It's the thing we always forget.
+--->
+
+ - [2.7.0](#270)
  - [2.6.0](#260)
  - [2.5.0](#250)
  - [2.4.2](#242)
@@ -53,7 +62,7 @@
 
 ## [2.7.0]
 
-> Release date: TBD
+> Release date: 2022-09-26
 
 2.7 patches several bugs in 2.6.0. One of these required a breaking change. The
 breaking change is not expected to affect most configurations, but does require
@@ -78,6 +87,12 @@ instructions and the [revised Kong 3.x upgrade instructions](https://docs.konghq
   overriden by setting a `konghq.com/regex-prefix` annotation, for routes that
   need their paths to actually begin with `/~`
   [#2956](https://github.com/Kong/kubernetes-ingress-controller/pull/2956)
+- Prometheus metrics now highlight configuration push failures caused by
+  conflicts. The `ingress_controller_configuration_push_count` Prometheus
+  metric now reports `success="false"` with a `failure_reason="conflict|other"`
+  label, distinguishing configuration conflicts from other errors (transient
+  network errors, Kong offline, Kong reported non-conflict error, etc.).
+  [#2965](https://github.com/Kong/kubernetes-ingress-controller/pull/2965)
 
 ### Fixed
 
diff --git a/Makefile b/Makefile
index 8cd775999..704d38eac 100644
--- a/Makefile
+++ b/Makefile
@@ -229,7 +229,7 @@ test.conformance:
 		./test/conformance
 
 .PHONY: test.integration
-test.integration: test.integration.dbless test.integration.postgres
+test.integration: test.integration.dbless test.integration.postgres test.integration.cp
 
 .PHONY: test.integration.enterprise
 test.integration.enterprise: test.integration.enterprise.postgres
@@ -313,6 +313,27 @@ test.integration.enterprise.postgres.pretty:
 		COVERAGE_OUT=coverage.enterprisepostgres.out 2>/dev/null | \
 		$(GOTESTFMT_CMD)
 
+.PHONY: test.integration.cp
+_test.integration.cp:
+	CLUSTER_NAME="e2e-$(uuidgen)" \
+		KUBERNETES_CLUSTER_NAME="${CLUSTER_NAME}" go run hack/e2e/cluster/deploy/main.go \
+		GOFLAGS="-tags=integration_tests" \
+		KONG_TEST_CLUSTER="${CP}:${CLUSTER_NAME}" \
+		go test -parallel "${NCPU}" -timeout $(INTEGRATION_TEST_TIMEOUT) -v \
+		./test/integration/... \
+    	go run hack/e2e/cluster/cleanup/main.go ${CLUSTER_NAME} \
+		trap cleanup EXIT SIGINT SIGQUIT
+
+.PHONY: test.integration.gke
+test.integration.gke:
+	@$(MAKE) _test.integration.cp \
+		CP="gke"
+
+.PHONY: test.integration.kind
+test.integration.kind:
+	@$(MAKE) _test.integration.cp \
+		CP="kind"
+
 .PHONY: test.e2e
 test.e2e:
 	GOFLAGS="-tags=e2e_tests" go test -v $(GOTESTFLAGS) \
@@ -321,6 +342,16 @@ test.e2e:
 		-timeout $(E2E_TEST_TIMEOUT) \
 		./test/e2e/...
 
+.PHONY: test.istio
+test.istio:
+	ISTIO_TEST_ENABLED="true" \
+	GOFLAGS="-tags=e2e_tests" go test -v $(GOTESTFLAGS) \
+		-race \
+		-parallel $(NCPU) \
+		-timeout $(E2E_TEST_TIMEOUT) \
+		-run "^TestIstio" \
+		./test/e2e/...
+
 # ------------------------------------------------------------------------------
 # Operations - Local Deployment
 # ------------------------------------------------------------------------------
diff --git a/config/image/oss/kustomization.yaml b/config/image/oss/kustomization.yaml
index 615509aee..bb5313ee9 100644
--- a/config/image/oss/kustomization.yaml
+++ b/config/image/oss/kustomization.yaml
@@ -7,4 +7,4 @@ images:
   newTag: '3.0'
 - name: kic-placeholder
   newName: kong/kubernetes-ingress-controller
-  newTag: '2.6.0'
+  newTag: '2.7.0'
diff --git a/deploy/single/all-in-one-dbless-k4k8s-enterprise.yaml b/deploy/single/all-in-one-dbless-k4k8s-enterprise.yaml
index 62be827e1..020134aae 100644
--- a/deploy/single/all-in-one-dbless-k4k8s-enterprise.yaml
+++ b/deploy/single/all-in-one-dbless-k4k8s-enterprise.yaml
@@ -1652,7 +1652,7 @@ spec:
             fieldRef:
               apiVersion: v1
               fieldPath: metadata.namespace
-        image: kong/kubernetes-ingress-controller:2.6.0
+        image: kong/kubernetes-ingress-controller:2.7.0
         imagePullPolicy: IfNotPresent
         livenessProbe:
           failureThreshold: 3
diff --git a/deploy/single/all-in-one-dbless.yaml b/deploy/single/all-in-one-dbless.yaml
index 0b02b64ca..c9d5a6653 100644
--- a/deploy/single/all-in-one-dbless.yaml
+++ b/deploy/single/all-in-one-dbless.yaml
@@ -1647,7 +1647,7 @@ spec:
             fieldRef:
               apiVersion: v1
               fieldPath: metadata.namespace
-        image: kong/kubernetes-ingress-controller:2.6.0
+        image: kong/kubernetes-ingress-controller:2.7.0
         imagePullPolicy: IfNotPresent
         livenessProbe:
           failureThreshold: 3
diff --git a/deploy/single/all-in-one-postgres-enterprise.yaml b/deploy/single/all-in-one-postgres-enterprise.yaml
index 33253437f..c85f9453a 100644
--- a/deploy/single/all-in-one-postgres-enterprise.yaml
+++ b/deploy/single/all-in-one-postgres-enterprise.yaml
@@ -1721,7 +1721,7 @@ spec:
             fieldRef:
               apiVersion: v1
               fieldPath: metadata.namespace
-        image: kong/kubernetes-ingress-controller:2.6.0
+        image: kong/kubernetes-ingress-controller:2.7.0
         imagePullPolicy: IfNotPresent
         livenessProbe:
           failureThreshold: 3
diff --git a/deploy/single/all-in-one-postgres.yaml b/deploy/single/all-in-one-postgres.yaml
index df61a775a..cef40480a 100644
--- a/deploy/single/all-in-one-postgres.yaml
+++ b/deploy/single/all-in-one-postgres.yaml
@@ -1665,7 +1665,7 @@ spec:
             fieldRef:
               apiVersion: v1
               fieldPath: metadata.namespace
-        image: kong/kubernetes-ingress-controller:2.6.0
+        image: kong/kubernetes-ingress-controller:2.7.0
         imagePullPolicy: IfNotPresent
         livenessProbe:
           failureThreshold: 3
diff --git a/go.mod b/go.mod
index 258c4a6d6..2f51c4a28 100644
--- a/go.mod
+++ b/go.mod
@@ -14,9 +14,9 @@ require (
 	github.com/go-logr/logr v1.2.3
 	github.com/google/go-cmp v0.5.9
 	github.com/google/uuid v1.3.0
-	github.com/kong/deck v1.14.0
+	github.com/kong/deck v1.15.1
 	github.com/kong/go-kong v0.32.0
-	github.com/kong/kubernetes-testing-framework v0.21.0
+	github.com/kong/kubernetes-testing-framework v0.22.2
 	github.com/lithammer/dedent v1.1.0
 	github.com/miekg/dns v1.1.50
 	github.com/mitchellh/mapstructure v1.5.0
@@ -29,7 +29,7 @@ require (
 	github.com/stretchr/testify v1.8.0
 	github.com/tidwall/gjson v1.14.3
 	golang.org/x/net v0.0.0-20220909164309-bea034e7d591
-	google.golang.org/api v0.97.0
+	google.golang.org/api v0.98.0
 	google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa
 	k8s.io/api v0.25.2
 	k8s.io/apiextensions-apiserver v0.25.2
@@ -137,7 +137,7 @@ require (
 	github.com/prometheus/procfs v0.8.0 // indirect
 	github.com/russross/blackfriday v1.6.0 // indirect
 	github.com/sergi/go-diff v1.2.0 // indirect
-	github.com/shirou/gopsutil/v3 v3.22.6 // indirect
+	github.com/shirou/gopsutil/v3 v3.22.8 // indirect
 	github.com/ssgelm/cookiejarparser v1.0.1 // indirect
 	github.com/tidwall/match v1.1.1 // indirect
 	github.com/tidwall/pretty v1.2.0 // indirect
@@ -168,11 +168,11 @@ require (
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
-	k8s.io/cli-runtime v0.25.0 // indirect
+	k8s.io/cli-runtime v0.25.2 // indirect
 	k8s.io/klog/v2 v2.70.1 // indirect
 	k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect
-	k8s.io/kubectl v0.25.0 // indirect
+	k8s.io/kubectl v0.25.2 // indirect
 	sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
-	sigs.k8s.io/kind v0.15.0 // indirect
+	sigs.k8s.io/kind v0.16.0 // indirect
 	sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
 )
diff --git a/go.sum b/go.sum
index 515a81e03..bdcd972a8 100644
--- a/go.sum
+++ b/go.sum
@@ -510,12 +510,12 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
-github.com/kong/deck v1.14.0 h1:ZqYgDqOr3KXqBTOMakFUf2EIQjjyXVevQrveUNJMneo=
-github.com/kong/deck v1.14.0/go.mod h1:H26s8ZNZhtouUy9drgLjgsGVIXayVtoClRz46TBrap8=
+github.com/kong/deck v1.15.1 h1:o6l6Pmf1UJEMzV3pCibGy9cU1kpajNonCQ/RTLpk5Oo=
+github.com/kong/deck v1.15.1/go.mod h1:J8ZzHYZDukznK5a/zo/q4NCiPORQ9Nm0Ps3YZapfmH8=
 github.com/kong/go-kong v0.32.0 h1:XmSWmKl5bNfcQHbmw3GBNiT7pRHbuehmeMft0Hjr63U=
 github.com/kong/go-kong v0.32.0/go.mod h1:GzkYwVXXuAZwxr3K6kgAbSEkh2zBDKtliD0xIqMu/9E=
-github.com/kong/kubernetes-testing-framework v0.21.0 h1:fCYLl4bE6LUW3DvS54OKSLXq/NPcWVDNgWpq24vx6e0=
-github.com/kong/kubernetes-testing-framework v0.21.0/go.mod h1:8W/siwjPsJuGLTXal65NU8cukkU58T2wBPHmTnOB9Ho=
+github.com/kong/kubernetes-testing-framework v0.22.2 h1:zcXWHrARzhBSvYcXJoIl4cyn8o/iC8GQCTyoAvhOXAw=
+github.com/kong/kubernetes-testing-framework v0.22.2/go.mod h1:9ePbedKYeTl2v1tY02M/L7XcW4x1+932u5W44gl9ylc=
 github.com/kong/semver/v4 v4.0.1 h1:DIcNR8W3gfx0KabFBADPalxxsp+q/5COwIFkkhrFQ2Y=
 github.com/kong/semver/v4 v4.0.1/go.mod h1:LImQ0oT15pJvSns/hs2laLca2zcYoHu5EsSNY0J6/QA=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -697,8 +697,8 @@ github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
 github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI=
 github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE=
-github.com/shirou/gopsutil/v3 v3.22.6 h1:FnHOFOh+cYAM0C30P+zysPISzlknLC5Z1G4EAElznfQ=
-github.com/shirou/gopsutil/v3 v3.22.6/go.mod h1:EdIubSnZhbAvBS1yJ7Xi+AShB/hxwLHOMz4MCYz7yMs=
+github.com/shirou/gopsutil/v3 v3.22.8 h1:a4s3hXogo5mE2PfdfJIonDbstO/P+9JszdfhAHSzD9Y=
+github.com/shirou/gopsutil/v3 v3.22.8/go.mod h1:s648gW4IywYzUfE/KjXxUsqrqx/T2xO5VqOXxONeRfI=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@@ -741,7 +741,6 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
@@ -1225,8 +1224,8 @@ google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69
 google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
 google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
 google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
-google.golang.org/api v0.97.0 h1:x/vEL1XDF/2V4xzdNgFPaKHluRESo2aTsL7QzHnBtGQ=
-google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
+google.golang.org/api v0.98.0 h1:yxZrcxXESimy6r6mdL5Q6EnZwmewDJK2dVg3g75s5Dg=
+google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1427,8 +1426,8 @@ k8s.io/apimachinery v0.22.5/go.mod h1:xziclGKwuuJ2RM5/rSFQSYAj0zdbci3DH8kj+WvyN0
 k8s.io/apimachinery v0.25.2 h1:WbxfAjCx+AeN8Ilp9joWnyJ6xu9OMeS/fsfjK/5zaQs=
 k8s.io/apimachinery v0.25.2/go.mod h1:hqqA1X0bsgsxI6dXsJ4HnNTBOmJNxyPp8dw3u2fSHwA=
 k8s.io/apiserver v0.22.5/go.mod h1:s2WbtgZAkTKt679sYtSudEQrTGWUSQAPe6MupLnlmaQ=
-k8s.io/cli-runtime v0.25.0 h1:XBnTc2Fi+w818jcJGzhiJKQuXl8479sZ4FhtV5hVJ1Q=
-k8s.io/cli-runtime v0.25.0/go.mod h1:bHOI5ZZInRHhbq12OdUiYZQN8ml8aKZLwQgt9QlLINw=
+k8s.io/cli-runtime v0.25.2 h1:XOx+SKRjBpYMLY/J292BHTkmyDffl/qOx3YSuFZkTuc=
+k8s.io/cli-runtime v0.25.2/go.mod h1:OQx3+/0st6x5YpkkJQlEWLC73V0wHsOFMC1/roxV8Oc=
 k8s.io/client-go v0.22.5/go.mod h1:cs6yf/61q2T1SdQL5Rdcjg9J1ElXSwbjSrW2vFImM4Y=
 k8s.io/client-go v0.25.2 h1:SUPp9p5CwM0yXGQrwYurw9LWz+YtMwhWd0GqOsSiefo=
 k8s.io/client-go v0.25.2/go.mod h1:i7cNU7N+yGQmJkewcRD2+Vuj4iz7b30kI8OcL3horQ4=
@@ -1448,8 +1447,8 @@ k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
 k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
 k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 h1:MQ8BAZPZlWk3S9K4a9NCkIFQtZShWqoha7snGixVgEA=
 k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU=
-k8s.io/kubectl v0.25.0 h1:/Wn1cFqo8ik3iee1EvpxYre3bkWsGLXzLQI6uCCAkQc=
-k8s.io/kubectl v0.25.0/go.mod h1:n16ULWsOl2jmQpzt2o7Dud1t4o0+Y186ICb4O+GwKAU=
+k8s.io/kubectl v0.25.2 h1:2993lTeVimxKSWx/7z2PiJxUILygRa3tmC4QhFaeioA=
+k8s.io/kubectl v0.25.2/go.mod h1:eoBGJtKUj7x38KXelz+dqVtbtbKwCqyKzJWmBHU0prg=
 k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
 k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4=
 k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
@@ -1470,8 +1469,8 @@ sigs.k8s.io/gateway-api v0.5.0 h1:ze+k9fJqvmL8s1t3e4q1ST8RnN+f09dEv+gfacahlAE=
 sigs.k8s.io/gateway-api v0.5.0/go.mod h1:x0AP6gugkFV8fC/oTlnOMU0pnmuzIR8LfIPRVUjxSqA=
 sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k=
 sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
-sigs.k8s.io/kind v0.15.0 h1:Fskj234L4hjQlsScCgeYvCBIRt06cjLzc7+kbr1u8Tg=
-sigs.k8s.io/kind v0.15.0/go.mod h1:cKTqagdRyUQmihhBOd+7p43DpOPRn9rHsUC08K1Jbsk=
+sigs.k8s.io/kind v0.16.0 h1:GFXyyxtPnHFKqXr3ZG8/X0+0K9sl69lejStlPn2WQyM=
+sigs.k8s.io/kind v0.16.0/go.mod h1:cKTqagdRyUQmihhBOd+7p43DpOPRn9rHsUC08K1Jbsk=
 sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM=
 sigs.k8s.io/kustomize/api v0.12.1/go.mod h1:y3JUhimkZkR6sbLNwfJHxvo1TCLwuwm14sCYnkH6S1s=
 sigs.k8s.io/kustomize/kyaml v0.13.9 h1:Qz53EAaFFANyNgyOEJbT/yoIHygK40/ZcvU3rgry2Tk=
diff --git a/grafana.json b/grafana.json
index aba859966..4b9bcf26a 100644
--- a/grafana.json
+++ b/grafana.json
@@ -235,7 +235,7 @@
       "targets": [
         {
           "exemplar": true,
-          "expr": "ingress_controller_configuration_push_count{success=\"false\"}",
+          "expr": "sum by (failure_reason) (ingress_controller_configuration_push_count{success=\"false\"})",
           "instant": false,
           "interval": "",
           "legendFormat": "",
@@ -375,6 +375,5 @@
   "timezone": "",
   "title": "Kong ingress controller",
   "uid": "SaGwMOtnz",
-  "version": 1,
-  "weekStart": ""
-}
\ No newline at end of file
+  "version": 2
+}
diff --git a/internal/dataplane/sendconfig/sendconfig.go b/internal/dataplane/sendconfig/sendconfig.go
index 6b6203789..b353368ac 100644
--- a/internal/dataplane/sendconfig/sendconfig.go
+++ b/internal/dataplane/sendconfig/sendconfig.go
@@ -5,7 +5,9 @@ import (
 	"context"
 	"encoding/hex"
 	"encoding/json"
+	"errors"
 	"fmt"
+	"net"
 	"net/http"
 	"reflect"
 	"sync"
@@ -16,6 +18,7 @@ import (
 	"github.com/kong/deck/file"
 	"github.com/kong/deck/state"
 	deckutils "github.com/kong/deck/utils"
+	"github.com/kong/go-kong/kong"
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/sirupsen/logrus"
 
@@ -85,8 +88,9 @@ func PerformUpdate(ctx context.Context,
 
 	if err != nil {
 		promMetrics.ConfigPushCount.With(prometheus.Labels{
-			metrics.SuccessKey:  metrics.SuccessFalse,
-			metrics.ProtocolKey: metricsProtocol,
+			metrics.SuccessKey:       metrics.SuccessFalse,
+			metrics.ProtocolKey:      metricsProtocol,
+			metrics.FailureReasonKey: pushFailureReason(err),
 		}).Inc()
 		promMetrics.ConfigPushDuration.With(prometheus.Labels{
 			metrics.SuccessKey:  metrics.SuccessFalse,
@@ -96,8 +100,9 @@ func PerformUpdate(ctx context.Context,
 	}
 
 	promMetrics.ConfigPushCount.With(prometheus.Labels{
-		metrics.SuccessKey:  metrics.SuccessTrue,
-		metrics.ProtocolKey: metricsProtocol,
+		metrics.SuccessKey:       metrics.SuccessTrue,
+		metrics.ProtocolKey:      metricsProtocol,
+		metrics.FailureReasonKey: "",
 	}).Inc()
 	promMetrics.ConfigPushDuration.With(prometheus.Labels{
 		metrics.SuccessKey:  metrics.SuccessTrue,
@@ -204,38 +209,27 @@ func onUpdateDBMode(ctx context.Context,
 	skipCACertificates bool,
 ) error {
 	dumpConfig := dump.Config{SelectorTags: selectorTags, SkipCACerts: skipCACertificates}
-	// read the current state
-	rawState, err := dump.Get(ctx, kongConfig.Client, dumpConfig)
-	if err != nil {
-		return fmt.Errorf("loading configuration from kong: %w", err)
-	}
-	currentState, err := state.Get(rawState)
-	if err != nil {
-		return err
-	}
 
-	// read the target state
-	rawState, err = file.Get(ctx, targetContent, file.RenderConfig{
-		CurrentState: currentState,
-		KongVersion:  kongConfig.Version,
-	}, dumpConfig, kongConfig.Client)
+	cs, err := currentState(ctx, kongConfig, dumpConfig)
 	if err != nil {
 		return err
 	}
-	targetState, err := state.Get(rawState)
+
+	ts, err := targetState(ctx, targetContent, cs, kongConfig, dumpConfig)
 	if err != nil {
-		return err
+		return deckConfigConflictError{err}
 	}
 
 	syncer, err := diff.NewSyncer(diff.SyncerOpts{
-		CurrentState:    currentState,
-		TargetState:     targetState,
+		CurrentState:    cs,
+		TargetState:     ts,
 		KongClient:      kongConfig.Client,
 		SilenceWarnings: true,
 	})
 	if err != nil {
 		return fmt.Errorf("creating a new syncer: %w", err)
 	}
+
 	_, errs := syncer.Solve(ctx, kongConfig.Concurrency, false)
 	if errs != nil {
 		return deckutils.ErrArray{Errors: errs}
@@ -243,6 +237,27 @@ func onUpdateDBMode(ctx context.Context,
 	return nil
 }
 
+func currentState(ctx context.Context, kongConfig *Kong, dumpConfig dump.Config) (*state.KongState, error) {
+	rawState, err := dump.Get(ctx, kongConfig.Client, dumpConfig)
+	if err != nil {
+		return nil, fmt.Errorf("loading configuration from kong: %w", err)
+	}
+
+	return state.Get(rawState)
+}
+
+func targetState(ctx context.Context, targetContent *file.Content, currentState *state.KongState, kongConfig *Kong, dumpConfig dump.Config) (*state.KongState, error) {
+	rawState, err := file.Get(ctx, targetContent, file.RenderConfig{
+		CurrentState: currentState,
+		KongVersion:  kongConfig.Version,
+	}, dumpConfig, kongConfig.Client)
+	if err != nil {
+		return nil, err
+	}
+
+	return state.Get(rawState)
+}
+
 func equalSHA(a, b []byte) bool {
 	return reflect.DeepEqual(a, b)
 }
@@ -273,3 +288,55 @@ func hasSHAUpdateAlreadyBeenReported(latestUpdateSHA []byte) bool {
 	latestReportedSHA = latestUpdateSHA
 	return false
 }
+
+// deckConfigConflictError is an error used to wrap deck config conflict errors returned from deck functions
+// transforming KongRawState to KongState (e.g. state.Get, dump.Get).
+type deckConfigConflictError struct {
+	err error
+}
+
+func (e deckConfigConflictError) Error() string {
+	return e.err.Error()
+}
+
+func (e deckConfigConflictError) Is(target error) bool {
+	_, ok := target.(deckConfigConflictError)
+	return ok
+}
+
+func (e deckConfigConflictError) Unwrap() error {
+	return e.err
+}
+
+// pushFailureReason extracts config push failure reason from an error returned from onUpdateInMemoryMode or onUpdateDBMode.
+func pushFailureReason(err error) string {
+	var netErr net.Error
+	if errors.As(err, &netErr) {
+		return metrics.FailureReasonNetwork
+	}
+
+	if isConflictErr(err) {
+		return metrics.FailureReasonConflict
+	}
+
+	return metrics.FailureReasonOther
+}
+
+func isConflictErr(err error) bool {
+	var apiErr *kong.APIError
+	if errors.As(err, &apiErr) && apiErr.Code() == http.StatusConflict ||
+		errors.Is(err, deckConfigConflictError{}) {
+		return true
+	}
+
+	var deckErrArray deckutils.ErrArray
+	if errors.As(err, &deckErrArray) {
+		for _, err := range deckErrArray.Errors {
+			if isConflictErr(err) {
+				return true
+			}
+		}
+	}
+
+	return false
+}
diff --git a/internal/metrics/prometheus.go b/internal/metrics/prometheus.go
index 432359599..dca2f8198 100644
--- a/internal/metrics/prometheus.go
+++ b/internal/metrics/prometheus.go
@@ -1,6 +1,8 @@
 package metrics
 
 import (
+	"fmt"
+
 	"github.com/prometheus/client_golang/prometheus"
 	"sigs.k8s.io/controller-runtime/pkg/metrics"
 )
@@ -19,7 +21,7 @@ type CtrlFuncMetrics struct {
 const (
 	// SuccessTrue indicates that the operation was successful.
 	SuccessTrue string = "true"
-	// SuccessTrue indicates that the operation was not successful.
+	// SuccessFalse indicates that the operation was not successful.
 	SuccessFalse string = "false"
 
 	// SuccessKey defines the key of the metric label indicating success/failure of an operation.
@@ -36,6 +38,20 @@ const (
 	ProtocolKey string = "protocol"
 )
 
+const (
+	// FailureReasonConflict indicates that the config push failed due to configuration conflicts.
+	FailureReasonConflict string = "conflict"
+
+	// FailureReasonNetwork indicates that the config push failed due to network issues.
+	FailureReasonNetwork string = "network"
+
+	// FailureReasonOther indicates that the config push failed due to other reasons.
+	FailureReasonOther string = "other"
+
+	// FailureReasonKey defines the key of the metric label indicating failure reason.
+	FailureReasonKey string = "failure_reason"
+)
+
 const (
 	MetricNameConfigPushCount    = "ingress_controller_configuration_push_count"
 	MetricNameTranslationCount   = "ingress_controller_translation_count"
@@ -48,21 +64,27 @@ func NewCtrlFuncMetrics() *CtrlFuncMetrics {
 	controllerMetrics.ConfigPushCount = prometheus.NewCounterVec(
 		prometheus.CounterOpts{
 			Name: MetricNameConfigPushCount,
-			Help: "Count of successful/failed configuration pushes to Kong. `" +
-				ProtocolKey + "` describes the configuration protocol (" + ProtocolDBLess + " or " +
-				ProtocolDeck + ") in use. `" +
-				SuccessKey + "` describes whether there were unrecoverable errors (`" +
-				SuccessFalse + "`) or not (`" + SuccessTrue + "`).",
+			Help: fmt.Sprintf(
+				"Count of successful/failed configuration pushes to Kong. "+
+					"`%s` describes the configuration protocol (`%s` or `%s`) in use. "+
+					"`%s` describes whether there were unrecoverable errors (`%s`) or not (`%s`). "+
+					"`%s` is populated in case of `%s=\"%s\"` and describes the reason of failure "+
+					"(one of `%s`, `%s`, `%s`).",
+				ProtocolKey, ProtocolDBLess, ProtocolDeck, SuccessKey, SuccessFalse, SuccessTrue, FailureReasonKey, SuccessKey,
+				SuccessFalse, FailureReasonConflict, FailureReasonNetwork, FailureReasonOther,
+			),
 		},
-		[]string{SuccessKey, ProtocolKey},
+		[]string{SuccessKey, ProtocolKey, FailureReasonKey},
 	)
 
 	controllerMetrics.TranslationCount = prometheus.NewCounterVec(
 		prometheus.CounterOpts{
 			Name: MetricNameTranslationCount,
-			Help: "Count of translations from Kubernetes state to Kong state. `" +
-				SuccessKey + "` describes whether there were unrecoverable errors (`" +
-				SuccessFalse + "`) or not (`" + SuccessTrue + "`).",
+			Help: fmt.Sprintf(
+				"Count of translations from Kubernetes state to Kong state. "+
+					"`%s` describes whether there were unrecoverable errors (`%s`) or not (`%s`).",
+				SuccessKey, SuccessFalse, SuccessTrue,
+			),
 		},
 		[]string{SuccessKey},
 	)
@@ -70,11 +92,12 @@ func NewCtrlFuncMetrics() *CtrlFuncMetrics {
 	controllerMetrics.ConfigPushDuration = prometheus.NewHistogramVec(
 		prometheus.HistogramOpts{
 			Name: MetricNameConfigPushDuration,
-			Help: "How long it took to push the configuration to Kong, in milliseconds. `" +
-				ProtocolKey + "` describes the configuration protocol (" + ProtocolDBLess + " or " +
-				ProtocolDeck + ") in use. `" +
-				SuccessKey + "` describes whether there were unrecoverable errors (`" +
-				SuccessFalse + "`) or not (`" + SuccessTrue + "`).",
+			Help: fmt.Sprintf(
+				"How long it took to push the configuration to Kong, in milliseconds. "+
+					"`%s` describes the configuration protocol (`%s` or `%s`) in use. "+
+					"`%s` describes whether there were unrecoverable errors (`%s`) or not (`%s`).",
+				ProtocolKey, ProtocolDBLess, ProtocolDeck, SuccessKey, SuccessFalse, SuccessTrue,
+			),
 			Buckets: prometheus.ExponentialBuckets(100, 1.33, 30),
 		},
 		[]string{SuccessKey, ProtocolKey},


Test patch:
diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml
index 79eb94e45..c30caff1f 100644
--- a/.github/workflows/e2e.yaml
+++ b/.github/workflows/e2e.yaml
@@ -10,17 +10,13 @@ jobs:
     environment: "Configure ci"
     runs-on: ubuntu-latest
     strategy:
+      fail-fast: false
       matrix:
         kubernetes-version:
           - 'v1.21.12'
           - 'v1.22.9'
           - 'v1.23.6'
           - 'v1.24.2'
-        istio-version:
-          - 'v1.14.1'
-          - 'v1.13.2'
-          - 'v1.12.5'
-          - 'v1.11.8'
     steps:
     - name: setup golang
       uses: actions/setup-go@v3
@@ -48,6 +44,62 @@ jobs:
 
     - name: run e2e tests
       run: make test.e2e
+      env:
+        TEST_KONG_CONTROLLER_IMAGE_OVERRIDE: "kong/nightly-ingress-controller:nightly"
+        KONG_LICENSE_DATA: ${{ steps.license.outputs.license }}
+        KONG_CLUSTER_VERSION: ${{ matrix.kubernetes-version }}
+        NCPU: 1 # it was found that github actions (specifically) did not seem to perform well when spawning
+                # multiple kind clusters within a single job, so only 1 is allowed at a time.
+
+    - name: upload diagnostics
+      if: ${{ always() }}
+      uses: actions/upload-artifact@v3
+      with:
+        name: diagnostics-e2e-tests
+        path: /tmp/ktf-diag*
+        if-no-files-found: ignore
+
+  istio-tests:
+    environment: "Configure ci"
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        kubernetes-version:
+          - 'v1.24.2'
+        istio-version:
+          - 'v1.15.1'
+          - 'v1.14.4'
+          - 'v1.13.8'
+          - 'v1.12.9'
+          - 'v1.11.8'
+    steps:
+    - name: setup golang
+      uses: actions/setup-go@v3
+      with:
+        go-version: '^1.19'
+
+    - name: cache go modules
+      uses: actions/cache@v3
+      with:
+        path: ~/go/pkg/mod
+        key: ${{ runner.os }}-build-codegen-${{ hashFiles('**/go.sum') }}
+        restore-keys: |
+          ${{ runner.os }}-build-codegen-
+
+    - name: checkout repository
+      uses: actions/checkout@v3
+      with:
+        fetch-depth: 0
+
+    - uses: Kong/kong-license@master
+      id: license
+      with:
+        # PULP_PASSWORD secret is set in "Configure ci" environment
+        password: ${{ secrets.PULP_PASSWORD }}
+
+    - name: run Istio tests
+      run: make test.istio
       env:
         TEST_KONG_CONTROLLER_IMAGE_OVERRIDE: "kong/nightly-ingress-controller:nightly"
         KONG_LICENSE_DATA: ${{ steps.license.outputs.license }}
diff --git a/.github/workflows/e2e_targeted.yaml b/.github/workflows/e2e_targeted.yaml
index bfd399f3e..145951f46 100644
--- a/.github/workflows/e2e_targeted.yaml
+++ b/.github/workflows/e2e_targeted.yaml
@@ -10,7 +10,7 @@ on:
       istio-version:
         description: 'Istio version to test with'
         required: true
-        default: 'v1.12.2'
+        default: 'v1.15.1'
       controller-image:
         description: 'KIC Docker image to test with. The default "kong/kubernetes-ingress-controller:ci" builds an image from the dispatch branch'
         required: true
diff --git a/hack/e2e/run-tests.sh b/hack/e2e/run-tests.sh
deleted file mode 100755
index 0fb83e12d..000000000
--- a/hack/e2e/run-tests.sh
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/bin/bash
-
-# TODO: for now our e2e tests are effectively just our integration tests run
-#       against a conformant, production grade cluster. In the future we will
-#       add a dedicated e2e test suite.
-#
-#       See: https://github.com/Kong/kubernetes-ingress-controller/issues/1605
-
-set -euo pipefail
-
-WORKDIR="$(dirname "${BASH_SOURCE}")/../.."
-cd "${WORKDIR}"
-
-CLUSTER_NAME="e2e-$(uuidgen)"
-KUBERNETES_CLUSTER_NAME="${CLUSTER_NAME}" go run hack/e2e/cluster/deploy/main.go
-
-function cleanup() {
-    go run hack/e2e/cluster/cleanup/main.go ${CLUSTER_NAME}
-}
-trap cleanup EXIT SIGINT SIGQUIT
-
-NCPU="$(getconf _NPROCESSORS_ONLN)"
-GOFLAGS="-tags=integration_tests" KONG_TEST_CLUSTER="gke:${CLUSTER_NAME}" go test -parallel "${NCPU}" -timeout 60m -v ./test/integration/...
diff --git a/internal/controllers/gateway/gateway_controller_test.go b/internal/controllers/gateway/gateway_controller_test.go
index b65721924..5b5c5c8fa 100644
--- a/internal/controllers/gateway/gateway_controller_test.go
+++ b/internal/controllers/gateway/gateway_controller_test.go
@@ -15,7 +15,7 @@ import (
 	"github.com/kong/kubernetes-ingress-controller/v2/internal/util"
 )
 
-func Test_readyConditionExistsForObservedGeneration(t *testing.T) {
+func TestReadyConditionExistsForObservedGeneration(t *testing.T) {
 	t.Log("checking ready condition for currently ready gateway")
 	currentlyReadyGateway := &gatewayv1beta1.Gateway{
 		ObjectMeta: metav1.ObjectMeta{
@@ -60,7 +60,7 @@ func Test_readyConditionExistsForObservedGeneration(t *testing.T) {
 	assert.False(t, isGatewayReady(neverBeenReadyGateway))
 }
 
-func Test_setGatewayCondtion(t *testing.T) {
+func TestSetGatewayCondtion(t *testing.T) {
 	testCases := []struct {
 		name            string
 		gw              *gatewayv1beta1.Gateway
@@ -164,7 +164,7 @@ func Test_setGatewayCondtion(t *testing.T) {
 	}
 }
 
-func Test_isGatewayMarkedAsScheduled(t *testing.T) {
+func TestIsGatewayMarkedAsScheduled(t *testing.T) {
 	t.Log("verifying scheduled check for gateway object which has been scheduled")
 	scheduledGateway := &gatewayv1beta1.Gateway{
 		ObjectMeta: metav1.ObjectMeta{Generation: 1},
@@ -185,7 +185,7 @@ func Test_isGatewayMarkedAsScheduled(t *testing.T) {
 	assert.False(t, isGatewayScheduled(unscheduledGateway))
 }
 
-func Test_getRefFromPublishService(t *testing.T) {
+func TestGetRefFromPublishService(t *testing.T) {
 	t.Log("verifying refs for valid publish services")
 	valid := "california/sanfrancisco"
 	nsn, err := getRefFromPublishService(valid)
@@ -202,7 +202,7 @@ func Test_getRefFromPublishService(t *testing.T) {
 	assert.Error(t, err)
 }
 
-func Test_pruneStatusConditions(t *testing.T) {
+func TestPruneStatusConditions(t *testing.T) {
 	t.Log("verifying that a gateway with minimal status conditions is not pruned")
 	gateway := &gatewayv1beta1.Gateway{}
 	for i := 0; i < 4; i++ {
@@ -230,7 +230,7 @@ func Test_pruneStatusConditions(t *testing.T) {
 	assert.Equal(t, int64(11), gateway.Status.Conditions[7].ObservedGeneration)
 }
 
-func Test_reconcileGatewaysIfClassMatches(t *testing.T) {
+func TestReconcileGatewaysIfClassMatches(t *testing.T) {
 	t.Log("generating a gatewayclass to test reconciliation filters")
 	gatewayClass := &gatewayv1beta1.GatewayClass{
 		ObjectMeta: metav1.ObjectMeta{
@@ -325,7 +325,7 @@ func Test_reconcileGatewaysIfClassMatches(t *testing.T) {
 	assert.Equal(t, expected, reconcileGatewaysIfClassMatches(gatewayClass, matching))
 }
 
-func Test_isGatewayControlledAndUnmanagedMode(t *testing.T) {
+func TestIsGatewayControlledAndUnmanagedMode(t *testing.T) {
 	var testControllerName gatewayv1beta1.GatewayController = "acme.io/gateway-controller"
 
 	testCases := []struct {
@@ -398,7 +398,7 @@ func Test_isGatewayControlledAndUnmanagedMode(t *testing.T) {
 	}
 }
 
-func Test_areAllowedRoutesConsistentByProtocol(t *testing.T) {
+func TestAreAllowedRoutesConsistentByProtocol(t *testing.T) {
 	same := gatewayv1alpha2.NamespacesFromSame
 	all := gatewayv1alpha2.NamespacesFromAll
 	selector := gatewayv1alpha2.NamespacesFromSelector
@@ -549,7 +549,7 @@ func Test_areAllowedRoutesConsistentByProtocol(t *testing.T) {
 	}
 }
 
-func Test_getReferenceGrantConditionReason(t *testing.T) {
+func TestGetReferenceGrantConditionReason(t *testing.T) {
 	testCases := []struct {
 		name             string
 		gatewayNamespace string
diff --git a/internal/controllers/gateway/gatewayclass_controller_test.go b/internal/controllers/gateway/gatewayclass_controller_test.go
index d64b5ddf1..ced114414 100644
--- a/internal/controllers/gateway/gatewayclass_controller_test.go
+++ b/internal/controllers/gateway/gatewayclass_controller_test.go
@@ -8,7 +8,7 @@ import (
 	gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
 )
 
-func Test_setGatewayClassCondtion(t *testing.T) {
+func TestSetGatewayClassCondtion(t *testing.T) {
 	testCases := []struct {
 		name            string
 		gwc             *gatewayv1beta1.GatewayClass
diff --git a/internal/controllers/gateway/route_utils_test.go b/internal/controllers/gateway/route_utils_test.go
index c572fe784..313c6886e 100644
--- a/internal/controllers/gateway/route_utils_test.go
+++ b/internal/controllers/gateway/route_utils_test.go
@@ -9,7 +9,7 @@ import (
 	"github.com/kong/kubernetes-ingress-controller/v2/internal/util"
 )
 
-func Test_filterHostnames(t *testing.T) {
+func TestFilterHostnames(t *testing.T) {
 	commonGateway := &gatewayv1beta1.Gateway{
 		Spec: gatewayv1beta1.GatewaySpec{
 			Listeners: []Listener{
diff --git a/internal/dataplane/kongstate/kongstate_test.go b/internal/dataplane/kongstate/kongstate_test.go
index 15b1289e1..de4851934 100644
--- a/internal/dataplane/kongstate/kongstate_test.go
+++ b/internal/dataplane/kongstate/kongstate_test.go
@@ -54,7 +54,7 @@ func TestKongState_SanitizedCopy(t *testing.T) {
 	}
 }
 
-func Test_getPluginRelations(t *testing.T) {
+func TestGetPluginRelations(t *testing.T) {
 	type args struct {
 		state KongState
 	}
@@ -307,7 +307,7 @@ func Test_getPluginRelations(t *testing.T) {
 	}
 }
 
-func Test_FillConsumersAndCredentials(t *testing.T) {
+func TestFillConsumersAndCredentials(t *testing.T) {
 	secrets := []*corev1.Secret{
 		{
 			ObjectMeta: metav1.ObjectMeta{
diff --git a/internal/dataplane/kongstate/route_test.go b/internal/dataplane/kongstate/route_test.go
index f883a2bd1..5d6a3d43e 100644
--- a/internal/dataplane/kongstate/route_test.go
+++ b/internal/dataplane/kongstate/route_test.go
@@ -427,7 +427,7 @@ func TestUseSSLProtocol(t *testing.T) {
 	}
 }
 
-func Test_overrideRouteStripPath(t *testing.T) {
+func TestOverrideRouteStripPath(t *testing.T) {
 	type args struct {
 		route Route
 		anns  map[string]string
@@ -516,7 +516,7 @@ func Test_overrideRouteStripPath(t *testing.T) {
 	}
 }
 
-func Test_overrideRouteHTTPSRedirectCode(t *testing.T) {
+func TestOverrideRouteHTTPSRedirectCode(t *testing.T) {
 	type args struct {
 		route Route
 		anns  map[string]string
@@ -624,7 +624,7 @@ func Test_overrideRouteHTTPSRedirectCode(t *testing.T) {
 	}
 }
 
-func Test_overrideRoutePreserveHost(t *testing.T) {
+func TestOverrideRoutePreserveHost(t *testing.T) {
 	type args struct {
 		route Route
 		anns  map[string]string
@@ -688,7 +688,7 @@ func Test_overrideRoutePreserveHost(t *testing.T) {
 	}
 }
 
-func Test_overrideRouteRegexPriority(t *testing.T) {
+func TestOverrideRouteRegexPriority(t *testing.T) {
 	type args struct {
 		route Route
 		anns  map[string]string
@@ -752,7 +752,7 @@ func Test_overrideRouteRegexPriority(t *testing.T) {
 	}
 }
 
-func Test_overrideRouteMethods(t *testing.T) {
+func TestOverrideRouteMethods(t *testing.T) {
 	type args struct {
 		route Route
 		anns  map[string]string
@@ -795,7 +795,7 @@ func Test_overrideRouteMethods(t *testing.T) {
 	}
 }
 
-func Test_overrideRouteSNIs(t *testing.T) {
+func TestOverrideRouteSNIs(t *testing.T) {
 	type args struct {
 		route Route
 		anns  map[string]string
@@ -846,7 +846,7 @@ func Test_overrideRouteSNIs(t *testing.T) {
 	}
 }
 
-func Test_overrideRequestBuffering(t *testing.T) {
+func TestOverrideRequestBuffering(t *testing.T) {
 	type args struct {
 		route Route
 		anns  map[string]string
@@ -919,7 +919,7 @@ func Test_overrideRequestBuffering(t *testing.T) {
 	}
 }
 
-func Test_overrideResponseBuffering(t *testing.T) {
+func TestOverrideResponseBuffering(t *testing.T) {
 	type args struct {
 		route Route
 		anns  map[string]string
@@ -992,7 +992,7 @@ func Test_overrideResponseBuffering(t *testing.T) {
 	}
 }
 
-func Test_overrideHosts(t *testing.T) {
+func TestOverrideHosts(t *testing.T) {
 	type args struct {
 		route Route
 		anns  map[string]string
diff --git a/internal/dataplane/kongstate/service_test.go b/internal/dataplane/kongstate/service_test.go
index fc10ba703..72b9a13c5 100644
--- a/internal/dataplane/kongstate/service_test.go
+++ b/internal/dataplane/kongstate/service_test.go
@@ -382,7 +382,7 @@ func TestOverrideService(t *testing.T) {
 	})
 }
 
-func Test_overrideServicePath(t *testing.T) {
+func TestOverrideServicePath(t *testing.T) {
 	type args struct {
 		service Service
 		anns    map[string]string
diff --git a/internal/dataplane/kongstate/util_test.go b/internal/dataplane/kongstate/util_test.go
index a00055fc4..886fca373 100644
--- a/internal/dataplane/kongstate/util_test.go
+++ b/internal/dataplane/kongstate/util_test.go
@@ -293,7 +293,7 @@ func TestKongPluginFromK8SPlugin(t *testing.T) {
 	}
 }
 
-func Test_getKongIngressForServices(t *testing.T) {
+func TestGetKongIngressForServices(t *testing.T) {
 	for _, tt := range []struct {
 		name                string
 		services            map[string]*corev1.Service
@@ -577,7 +577,7 @@ func TestGetKongIngressFromObjectMeta(t *testing.T) {
 	}
 }
 
-func Test_prettyPrintServiceList(t *testing.T) {
+func TestPrettyPrintServiceList(t *testing.T) {
 	for _, tt := range []struct {
 		name     string
 		services map[string]*corev1.Service
diff --git a/internal/dataplane/parser/ingressclassparemeters_test.go b/internal/dataplane/parser/ingressclassparemeters_test.go
index 9323f419f..3b997af6a 100644
--- a/internal/dataplane/parser/ingressclassparemeters_test.go
+++ b/internal/dataplane/parser/ingressclassparemeters_test.go
@@ -15,7 +15,7 @@ import (
 	configurationv1alpha1 "github.com/kong/kubernetes-ingress-controller/v2/pkg/apis/configuration/v1alpha1"
 )
 
-func Test_GetIngressClassParameters(t *testing.T) {
+func TestGetIngressClassParameters(t *testing.T) {
 	var (
 		scopeNamespace    = "Namespace"
 		testNamespaceName = "test-ns"
diff --git a/internal/dataplane/parser/ingressrules_test.go b/internal/dataplane/parser/ingressrules_test.go
index dc784a393..8d8c7295d 100644
--- a/internal/dataplane/parser/ingressrules_test.go
+++ b/internal/dataplane/parser/ingressrules_test.go
@@ -108,7 +108,7 @@ func TestMergeIngressRules(t *testing.T) {
 	}
 }
 
-func Test_addFromIngressV1beta1TLS(t *testing.T) {
+func TestAddFromIngressV1beta1TLS(t *testing.T) {
 	type args struct {
 		tlsSections []netv1beta1.IngressTLS
 		namespace   string
@@ -178,7 +178,7 @@ func Test_addFromIngressV1beta1TLS(t *testing.T) {
 	}
 }
 
-func Test_getK8sServicesForBackends(t *testing.T) {
+func TestGetK8sServicesForBackends(t *testing.T) {
 	for _, tt := range []struct {
 		name                string
 		namespace           string
@@ -290,7 +290,7 @@ func Test_getK8sServicesForBackends(t *testing.T) {
 	}
 }
 
-func Test_doK8sServicesMatchAnnotations(t *testing.T) {
+func TestDoK8sServicesMatchAnnotations(t *testing.T) {
 	for _, tt := range []struct {
 		name               string
 		services           []*corev1.Service
diff --git a/internal/dataplane/parser/parser_test.go b/internal/dataplane/parser/parser_test.go
index 8fb9671b0..3e389d581 100644
--- a/internal/dataplane/parser/parser_test.go
+++ b/internal/dataplane/parser/parser_test.go
@@ -4212,7 +4212,7 @@ func TestGetEndpoints(t *testing.T) {
 	}
 }
 
-func Test_knativeSelectSplit(t *testing.T) {
+func TestKnativeSelectSplit(t *testing.T) {
 	type args struct {
 		splits []knative.IngressBackendSplit
 	}
diff --git a/internal/dataplane/parser/translate_httproute_test.go b/internal/dataplane/parser/translate_httproute_test.go
index 84750a781..b1e640247 100644
--- a/internal/dataplane/parser/translate_httproute_test.go
+++ b/internal/dataplane/parser/translate_httproute_test.go
@@ -35,7 +35,7 @@ var (
 	queryMatchExact = gatewayv1beta1.QueryParamMatchExact
 )
 
-func Test_ingressRulesFromHTTPRoutes(t *testing.T) {
+func TestIngressRulesFromHTTPRoutes(t *testing.T) {
 	fakestore, err := store.NewFakeStore(store.FakeObjects{})
 	require.NoError(t, err)
 	p := NewParser(logrus.New(), fakestore)
@@ -664,7 +664,7 @@ func Test_ingressRulesFromHTTPRoutes(t *testing.T) {
 	}
 }
 
-func Test_getHTTPRouteHostnamesAsSliceOfStringPointers(t *testing.T) {
+func TestGetHTTPRouteHostnamesAsSliceOfStringPointers(t *testing.T) {
 	for _, tt := range []struct {
 		msg      string
 		input    *gatewayv1beta1.HTTPRoute
@@ -712,7 +712,7 @@ func Test_getHTTPRouteHostnamesAsSliceOfStringPointers(t *testing.T) {
 	}
 }
 
-func Test_ingressRulesFromHTTPRoutes_RegexPrefix(t *testing.T) {
+func TestIngressRulesFromHTTPRoutes_RegexPrefix(t *testing.T) {
 	fakestore, err := store.NewFakeStore(store.FakeObjects{})
 	require.NoError(t, err)
 	p := NewParser(logrus.New(), fakestore)
diff --git a/internal/dataplane/parser/translate_utils_test.go b/internal/dataplane/parser/translate_utils_test.go
index 621a5eeb7..43289bf98 100644
--- a/internal/dataplane/parser/translate_utils_test.go
+++ b/internal/dataplane/parser/translate_utils_test.go
@@ -19,7 +19,7 @@ import (
 	"github.com/kong/kubernetes-ingress-controller/v2/internal/versions"
 )
 
-func Test_convertGatewayMatchHeadersToKongRouteMatchHeadersVersionBehavior(t *testing.T) {
+func TestConvertGatewayMatchHeadersToKongRouteMatchHeadersVersionBehavior(t *testing.T) {
 	regexType := gatewayv1beta1.HeaderMatchRegularExpression
 
 	type Case struct {
@@ -86,7 +86,7 @@ func Test_convertGatewayMatchHeadersToKongRouteMatchHeadersVersionBehavior(t *te
 	}
 }
 
-func Test_convertGatewayMatchHeadersToKongRouteMatchHeaders(t *testing.T) {
+func TestConvertGatewayMatchHeadersToKongRouteMatchHeaders(t *testing.T) {
 	regexType := gatewayv1beta1.HeaderMatchRegularExpression
 	exactType := gatewayv1beta1.HeaderMatchExact
 	versions.SetKongVersion(semver.MustParse("2.8.0"))
@@ -179,7 +179,7 @@ func Test_convertGatewayMatchHeadersToKongRouteMatchHeaders(t *testing.T) {
 	}
 }
 
-func Test_isRefAllowedByGrant(t *testing.T) {
+func TestIsRefAllowedByGrant(t *testing.T) {
 	fitrat := gatewayv1beta1.Namespace("fitrat")
 	cholpon := gatewayv1beta1.Namespace("cholpon")
 	behbudiy := gatewayv1beta1.Namespace("behbudiy")
@@ -293,7 +293,7 @@ func Test_isRefAllowedByGrant(t *testing.T) {
 	}
 }
 
-func Test_getPermittedForReferenceGrantFrom(t *testing.T) {
+func TestGetPermittedForReferenceGrantFrom(t *testing.T) {
 	grants := []*gatewayv1alpha2.ReferenceGrant{
 		{
 			ObjectMeta: metav1.ObjectMeta{
@@ -443,7 +443,7 @@ func Test_getPermittedForReferenceGrantFrom(t *testing.T) {
 	}
 }
 
-func Test_generateKongServiceFromBackendRef(t *testing.T) {
+func TestGenerateKongServiceFromBackendRef(t *testing.T) {
 	grants := []*gatewayv1alpha2.ReferenceGrant{
 		{
 			ObjectMeta: metav1.ObjectMeta{
diff --git a/internal/dataplane/parser/translators/ingress_test.go b/internal/dataplane/parser/translators/ingress_test.go
index 1f37e752d..a012ad9aa 100644
--- a/internal/dataplane/parser/translators/ingress_test.go
+++ b/internal/dataplane/parser/translators/ingress_test.go
@@ -868,7 +868,7 @@ func TestTranslateIngress(t *testing.T) {
 	}
 }
 
-func Test_PathsFromIngressPaths(t *testing.T) {
+func TestPathsFromIngressPaths(t *testing.T) {
 	for _, tt := range []struct {
 		name string
 		in   netv1.HTTPIngressPath
@@ -944,7 +944,7 @@ func Test_PathsFromIngressPaths(t *testing.T) {
 	}
 }
 
-func Test_flattenMultipleSlashes(t *testing.T) {
+func TestFlattenMultipleSlashes(t *testing.T) {
 	for _, tt := range []struct {
 		name string
 		in   string
@@ -1087,7 +1087,7 @@ func TestTranslateIngressRegexPrefix(t *testing.T) {
 	}
 }
 
-func Test_PathsFromIngressPathsRegexPrefix(t *testing.T) {
+func TestPathsFromIngressPathsRegexPrefix(t *testing.T) {
 	for _, tt := range []struct {
 		name string
 		in   netv1.HTTPIngressPath
diff --git a/internal/dataplane/sendconfig/sendconfig_test.go b/internal/dataplane/sendconfig/sendconfig_test.go
index 5c7b766c2..8527982ba 100644
--- a/internal/dataplane/sendconfig/sendconfig_test.go
+++ b/internal/dataplane/sendconfig/sendconfig_test.go
@@ -1,16 +1,24 @@
 package sendconfig
 
 import (
+	"errors"
+	"fmt"
+	"net"
+	"net/http"
 	"reflect"
 	"testing"
 
 	"github.com/kong/deck/file"
+	deckutils "github.com/kong/deck/utils"
 	"github.com/kong/go-kong/kong"
 	"github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/metrics"
 )
 
-func Test_renderConfigWithCustomEntities(t *testing.T) {
+func TestRenderConfigWithCustomEntities(t *testing.T) {
 	type args struct {
 		state                   *file.Content
 		customEntitiesJSONBytes []byte
@@ -118,7 +126,7 @@ func Test_renderConfigWithCustomEntities(t *testing.T) {
 	}
 }
 
-func Test_updateReportingUtilities(t *testing.T) {
+func TestUpdateReportingUtilities(t *testing.T) {
 	assert.False(t, hasSHAUpdateAlreadyBeenReported([]byte("fake-sha")))
 	assert.True(t, hasSHAUpdateAlreadyBeenReported([]byte("fake-sha")))
 	assert.False(t, hasSHAUpdateAlreadyBeenReported([]byte("another-fake-sha")))
@@ -128,3 +136,81 @@ func Test_updateReportingUtilities(t *testing.T) {
 	assert.True(t, hasSHAUpdateAlreadyBeenReported([]byte("yet-another-fake-sha")))
 	assert.True(t, hasSHAUpdateAlreadyBeenReported([]byte("yet-another-fake-sha")))
 }
+
+func TestPushFailureReason(t *testing.T) {
+	apiConflictErr := kong.NewAPIError(http.StatusConflict, "conflict api error")
+	networkErr := net.UnknownNetworkError("network error")
+	genericError := errors.New("generic error")
+
+	testCases := []struct {
+		name           string
+		err            error
+		expectedReason string
+	}{
+		{
+			name:           "generic_error",
+			err:            genericError,
+			expectedReason: metrics.FailureReasonOther,
+		},
+		{
+			name:           "api_conflict_error",
+			err:            apiConflictErr,
+			expectedReason: metrics.FailureReasonConflict,
+		},
+		{
+			name:           "api_conflict_error_wrapped",
+			err:            fmt.Errorf("wrapped conflict api err: %w", apiConflictErr),
+			expectedReason: metrics.FailureReasonConflict,
+		},
+		{
+			name:           "deck_config_conflict_error_empty",
+			err:            deckConfigConflictError{},
+			expectedReason: metrics.FailureReasonConflict,
+		},
+		{
+			name:           "deck_config_conflict_error_with_generic_error",
+			err:            deckConfigConflictError{genericError},
+			expectedReason: metrics.FailureReasonConflict,
+		},
+		{
+			name:           "deck_err_array_with_api_conflict_error",
+			err:            deckutils.ErrArray{Errors: []error{apiConflictErr}},
+			expectedReason: metrics.FailureReasonConflict,
+		},
+		{
+			name:           "wrapped_deck_err_array_with_api_conflict_error",
+			err:            fmt.Errorf("wrapped: %w", deckutils.ErrArray{Errors: []error{apiConflictErr}}),
+			expectedReason: metrics.FailureReasonConflict,
+		},
+		{
+			name:           "deck_err_array_with_generic_error",
+			err:            deckutils.ErrArray{Errors: []error{genericError}},
+			expectedReason: metrics.FailureReasonOther,
+		},
+		{
+			name:           "deck_err_array_empty",
+			err:            deckutils.ErrArray{Errors: []error{genericError}},
+			expectedReason: metrics.FailureReasonOther,
+		},
+		{
+			name:           "network_error",
+			err:            networkErr,
+			expectedReason: metrics.FailureReasonNetwork,
+		},
+		{
+			name:           "network_error_wrapped_in_deck_config_conflict_error",
+			err:            deckConfigConflictError{networkErr},
+			expectedReason: metrics.FailureReasonNetwork,
+		},
+	}
+
+	for _, tc := range testCases {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+
+			reason := pushFailureReason(tc.err)
+			require.Equal(t, tc.expectedReason, reason)
+		})
+	}
+}
diff --git a/internal/store/fake_store_test.go b/internal/store/fake_store_test.go
index 11d381488..3d6831d82 100644
--- a/internal/store/fake_store_test.go
+++ b/internal/store/fake_store_test.go
@@ -20,7 +20,7 @@ import (
 	configurationv1beta1 "github.com/kong/kubernetes-ingress-controller/v2/pkg/apis/configuration/v1beta1"
 )
 
-func Test_keyFunc(t *testing.T) {
+func TestKeyFunc(t *testing.T) {
 	type args struct {
 		obj interface{}
 	}
diff --git a/internal/store/store_test.go b/internal/store/store_test.go
index 9767f0708..799b26129 100644
--- a/internal/store/store_test.go
+++ b/internal/store/store_test.go
@@ -19,7 +19,7 @@ import (
 	"github.com/kong/kubernetes-ingress-controller/v2/internal/annotations"
 )
 
-func Test_networkingIngressV1Beta1(t *testing.T) {
+func TestNetworkingIngressV1Beta1(t *testing.T) {
 	type args struct {
 		obj interface{}
 	}
@@ -204,7 +204,7 @@ spec:
 	assert.True(t, exists)
 }
 
-func Test_getIngressClassHandling(t *testing.T) {
+func TestGetIngressClassHandling(t *testing.T) {
 	tests := []struct {
 		name string
 		objs FakeObjects
diff --git a/internal/util/relations_test.go b/internal/util/relations_test.go
index a273ec4b5..cdb635312 100644
--- a/internal/util/relations_test.go
+++ b/internal/util/relations_test.go
@@ -5,7 +5,7 @@ import (
 	"testing"
 )
 
-func Test_GetCombinations(t *testing.T) {
+func TestGetCombinations(t *testing.T) {
 	type args struct {
 		relations ForeignRelations
 	}
diff --git a/test/e2e/helpers_test.go b/test/e2e/helpers_test.go
index 004dcd29c..89caee0f1 100644
--- a/test/e2e/helpers_test.go
+++ b/test/e2e/helpers_test.go
@@ -153,9 +153,10 @@ func verifyIngress(ctx context.Context, t *testing.T, env environments.Environme
 		defer resp.Body.Close()
 		if resp.StatusCode == http.StatusOK {
 			b := new(bytes.Buffer)
-			n, err := b.ReadFrom(resp.Body)
-			require.NoError(t, err)
-			require.True(t, n > 0)
+			_, err := b.ReadFrom(resp.Body)
+			if err != nil {
+				return false
+			}
 			if !strings.Contains(b.String(), "<title>httpbin.org</title>") {
 				return false
 			}
diff --git a/test/e2e/istio_test.go b/test/e2e/istio_test.go
index bfcca7409..3638b6f1a 100644
--- a/test/e2e/istio_test.go
+++ b/test/e2e/istio_test.go
@@ -39,6 +39,9 @@ var (
 	// If not provided, the latest version of Istio will be tested.
 	istioVersionStr = os.Getenv("ISTIO_VERSION")
 
+	// enableIstioTest skips Istio tests if not set.
+	enableIstioTest = os.Getenv("ISTIO_TEST_ENABLED")
+
 	// kialiAPIPort is the port number that the Kiali API will use.
 	kialiAPIPort = 20001
 
@@ -55,11 +58,15 @@ var (
 // See: https://docs.konghq.com/kubernetes-ingress-controller/latest/references/version-compatibility/#istio
 func TestIstioWithKongIngressGateway(t *testing.T) {
 	t.Parallel()
+
 	// Istio's test is unique in that it operates like the integration tests, and runs the controller manager from the
 	// test, whereas most E2E tests deploy it to the cluster normally. The upshot of this is that the Istio test
 	// pollutes E2E logs with a bunch of controller log nonsense and a boatload of goroutines that litter the panic
-	// logs. Temporarily skip it because it's not failing and it's making it harder to read the failure results.
-	// Ultimately we should probably move it elsewhere.
+	// logs. It uses a different make target that enables this and only runs tests beginning with "TestIstio" as such.
+	if len(enableIstioTest) == 0 {
+		t.Log("Istio test disabled, skipping...")
+		t.Skip()
+	}
 
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
diff --git a/test/integration/ingress_test.go b/test/integration/ingress_test.go
index f0adb6a6d..4d6bf448d 100644
--- a/test/integration/ingress_test.go
+++ b/test/integration/ingress_test.go
@@ -632,6 +632,14 @@ func TestIngressClassRegexToggle(t *testing.T) {
 	if !versions.GetKongVersion().MajorOnly().GTE(versions.ExplicitRegexPathVersionCutoff) {
 		t.Skip("legacy regex detection is only relevant for Kong 3.0+")
 	}
+
+	// skip the test if the cluster does not support namespaced ingress class parameter (<=1.21).
+	// since 1.21 is End of Life now.
+	namespacedIngressClassParameterMinKubernetesVersion := semver.MustParse("1.22.0")
+	if clusterVersion.LT(namespacedIngressClassParameterMinKubernetesVersion) {
+		t.Skipf("kubernetes cluster version %s does not support namespaced ingress class parameters", clusterVersion.String())
+	}
+
 	t.Log("locking IngressClass management")
 	ingressClassMutex.Lock()
 	defer func() {
diff --git a/test/integration/suite_test.go b/test/integration/suite_test.go
index c4a1b2da6..6632ffc54 100644
--- a/test/integration/suite_test.go
+++ b/test/integration/suite_test.go
@@ -29,6 +29,23 @@ import (
 	testutils "github.com/kong/kubernetes-ingress-controller/v2/internal/util/test"
 )
 
+// For some reason we've ended up using kind v0.15.0 (which by default deploys k8s v1.25)
+// even though that
+//   - our CI runners use ubuntu-latest (which at the time of writing this comment was ubuntu20.04)
+//     which uses kind v0.14 https://github.com/actions/runner-images/blob/main/images/linux/Ubuntu2004-Readme.md
+//   - when used with ktf (which at the time of writing this comment was set to v0.19.0) we
+//     should use kind v0.14 since that what ktf has set as dependency
+//
+// With all that said, we still managed to get kind v0.15 on our CI
+// https://github.com/Kong/kubernetes-ingress-controller/runs/8211490522?check_suite_focus=true#step:5:6
+// which causes issues down the line (metallb manifests using PSP which is not available
+// in k8s v1.25+).
+var defaultKindClusterVesion = semver.Version{
+	Major: 1,
+	Minor: 24,
+	Patch: 4,
+}
+
 var k8sClient *kubernetes.Clientset
 
 // -----------------------------------------------------------------------------
@@ -117,29 +134,16 @@ func TestMain(m *testing.M) {
 		fmt.Println("INFO: no existing cluster found, deploying using Kubernetes In Docker (KIND)")
 
 		builder.WithAddons(metallb.New())
-		// For some reason we've ended up using kind v0.15.0 (which by default deploys k8s v1.25)
-		// even though that
-		// * our CI runners use ubuntu-latest (which at the time of writing this comment was ubuntu20.04)
-		//   which uses kind v0.14 https://github.com/actions/runner-images/blob/main/images/linux/Ubuntu2004-Readme.md
-		// * when used with ktf (which at the time of writing this comment was set to v0.19.0) we
-		//   should use kind v0.14 since that what ktf has set as dependency
-		//
-		// With all that said, we still managed to get kind v0.15 on our CI
-		// https://github.com/Kong/kubernetes-ingress-controller/runs/8211490522?check_suite_focus=true#step:5:6
-		// which causes issues down the line (metallb manifests using PSP which is not available
-		// in k8s v1.25+).
-		builder.WithKubernetesVersion(semver.Version{
-			Major: 1,
-			Minor: 24,
-			Patch: 4,
-		})
-	}
-	if clusterVersionStr != "" {
-		clusterVersion, err := semver.Parse(strings.TrimPrefix(clusterVersionStr, "v"))
-		exitOnErr(err)
-		cluster, err := kind.NewBuilder().WithClusterVersion(clusterVersion).Build(ctx)
-		exitOnErr(err)
-		builder.WithExistingCluster(cluster)
+
+		clusterVersion = defaultKindClusterVesion
+		if clusterVersionStr != "" {
+			var err error
+			clusterVersion, err = semver.Parse(strings.TrimPrefix(clusterVersionStr, "v"))
+			exitOnErr(err)
+
+		}
+		fmt.Printf("INFO: build a new KIND cluster with version %s\n", clusterVersion.String())
+		builder.WithKubernetesVersion(clusterVersion)
 	}
 
 	fmt.Println("INFO: building test environment")