Repository: Kong/kubernetes-ingress-controller
Pull request #2296 (2022-02-28 20:34:25)

chore: refactor dataplane components (client, synchronizer, and parser)
**What this PR does / why we need it**:

This significantly refactors the `internal/dataplane` code the original motivation was to make it easier to later add options to parser for https://github.com/Kong/kubernetes-ingress-controller/issues/1492.

Most notably this PR splits what was previous called the `Proxy` interface, which had both the aspects of a high level data-plane API client _and_ a synchronization server for the data-plane. It's responsibilities are now split into `dataplane.KongClient` and `dataplane.Synchronizer` and while the diff is large, the code has mainly just been moved around and the resulting objects are much easier to read and understand.

High level refactoring notes:

- the `Proxy` interface no longer exists, it's concerns have been separated into a `dataplane.Client` and a `dataplane.Synchronizer`
- the `dataplane.KongClient` is now responsible for updates to the dataplane: `internal/dataplane/sendconfig/common_workflows.go` has been consumed
- `internal/dataplane/parser` is now an object which can be configured to allow attributes to affect runtime logic instead of having to continually add more function parameters and feed them down the call chain, this will make it easier to add options that are specific to some resource types, or otherwise influence parsing with lighter weight PRs
- translate code (and tests) now have their own files to help make it easier to locate the specific object translator you're looking for
- the data-plane has in general become more abstract: higher level components are more able to treat the data-plane as opaque than before, and not have to think about Kong specific semantics. We don't have current plans to support any data-plane APIs other than the Kong Admin API, but this change will inherently make it easier to support alternative APIs in the future.

**Which issue this PR fixes**

Resolves https://github.com/Kong/kubernetes-ingress-controller/issues/2295

**Special notes for your reviewer**:

No user facing changes, and despite the fact that the diff looks large functionality was just moved not added.

shaneutt: > Can you clarify the "make it easier to later add options to parser" refactor/indicate what we should look at that isn't just reorganization? What did we do before (I think this is "add new parameters to `NewCacheBasedProxyWithStagger()` and friends) and how do we handle that after this change?

Before options were passed as function parameters and would need to be passed through several layers of function calls (options like the logger for instance). Now the parser is a struct with the logger (again, for instance) as an attribute. Future iterations will now be able to feed options from the `dataplane.KongClient` to the parser as object attributes that can be accessed in methods.


Patch:
diff --git a/go.mod b/go.mod
index 78f3a275f..09765c592 100644
--- a/go.mod
+++ b/go.mod
@@ -26,6 +26,7 @@ require (
 	github.com/spf13/pflag v1.0.5
 	github.com/stretchr/testify v1.7.0
 	github.com/tidwall/gjson v1.14.0
+	golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
 	google.golang.org/api v0.70.0
 	google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf
 	k8s.io/api v0.23.4
@@ -126,7 +127,6 @@ require (
 	go.uber.org/zap v1.19.1 // indirect
 	golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce // indirect
 	golang.org/x/mod v0.5.1 // indirect
-	golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
 	golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
 	golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
diff --git a/hack/generators/controllers/networking/main.go b/hack/generators/controllers/networking/main.go
index 9d083a04a..2ec3e6551 100644
--- a/hack/generators/controllers/networking/main.go
+++ b/hack/generators/controllers/networking/main.go
@@ -380,7 +380,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	ctrlutils "github.com/kong/kubernetes-ingress-controller/v2/internal/controllers/utils"
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/proxy"
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane"
 	"github.com/kong/kubernetes-ingress-controller/v2/internal/util"
 	kongv1 "github.com/kong/kubernetes-ingress-controller/v2/pkg/apis/configuration/v1"
 	kongv1beta1 "github.com/kong/kubernetes-ingress-controller/v2/pkg/apis/configuration/v1beta1"
@@ -404,9 +404,10 @@ var controllerTemplate = `
 type {{.PackageAlias}}{{.Type}}Reconciler struct {
 	client.Client
 
+	DataplaneClient *dataplane.KongClient
+
 	Log    logr.Logger
 	Scheme *runtime.Scheme
-	Proxy  proxy.Proxy
 {{- if or .AcceptsIngressClassNameSpec .AcceptsIngressClassNameAnnotation}}
 
 	IngressClassName string
@@ -438,7 +439,7 @@ func (r *{{.PackageAlias}}{{.Type}}Reconciler) Reconcile(ctx context.Context, re
 		if errors.IsNotFound(err) {
 			obj.Namespace = req.Namespace
 			obj.Name = req.Name
-			return ctrlutils.EnsureProxyDeleteObject(r.Proxy, obj)
+			return ctrlutils.EnsureProxyDeleteObject(r.DataplaneClient, obj)
 		}
 		return ctrl.Result{}, err
 	}
@@ -447,12 +448,12 @@ func (r *{{.PackageAlias}}{{.Type}}Reconciler) Reconcile(ctx context.Context, re
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
 		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "{{.Type}}", "namespace", req.Namespace, "name", req.Name)
-		objectExistsInCache, err := r.Proxy.ObjectExists(obj)
+		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
 		if err != nil {
 			return ctrl.Result{}, err
 		}
 		if objectExistsInCache {
-			if err := r.Proxy.DeleteObject(obj); err != nil {
+			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
 				return ctrl.Result{}, err
 			}
 			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
@@ -463,14 +464,14 @@ func (r *{{.PackageAlias}}{{.Type}}Reconciler) Reconcile(ctx context.Context, re
 	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
 	if !ctrlutils.MatchesIngressClassName(obj, r.IngressClassName) {
 		log.V(util.DebugLevel).Info("object missing ingress class, ensuring it's removed from configuration", "namespace", req.Namespace, "name", req.Name)
-		if err := r.Proxy.DeleteObject(obj); err != nil {
+		if err := r.DataplaneClient.DeleteObject(obj); err != nil {
 			return ctrl.Result{}, err
 		}
 		return ctrl.Result{}, nil
 	}
 {{end}}
 	// update the kong Admin API with the changes
-	if err := r.Proxy.UpdateObject(obj); err != nil {
+	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
 		return ctrl.Result{}, err
 	}
 
diff --git a/internal/controllers/configuration/zz_generated_controllers.go b/internal/controllers/configuration/zz_generated_controllers.go
index ebf31c976..bd55e142b 100644
--- a/internal/controllers/configuration/zz_generated_controllers.go
+++ b/internal/controllers/configuration/zz_generated_controllers.go
@@ -35,7 +35,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	ctrlutils "github.com/kong/kubernetes-ingress-controller/v2/internal/controllers/utils"
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/proxy"
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane"
 	"github.com/kong/kubernetes-ingress-controller/v2/internal/util"
 	kongv1 "github.com/kong/kubernetes-ingress-controller/v2/pkg/apis/configuration/v1"
 	kongv1beta1 "github.com/kong/kubernetes-ingress-controller/v2/pkg/apis/configuration/v1beta1"
@@ -49,9 +49,10 @@ import (
 type CoreV1ServiceReconciler struct {
 	client.Client
 
+	DataplaneClient *dataplane.KongClient
+
 	Log    logr.Logger
 	Scheme *runtime.Scheme
-	Proxy  proxy.Proxy
 }
 
 // SetupWithManager sets up the controller with the Manager.
@@ -72,7 +73,7 @@ func (r *CoreV1ServiceReconciler) Reconcile(ctx context.Context, req ctrl.Reques
 		if errors.IsNotFound(err) {
 			obj.Namespace = req.Namespace
 			obj.Name = req.Name
-			return ctrlutils.EnsureProxyDeleteObject(r.Proxy, obj)
+			return ctrlutils.EnsureProxyDeleteObject(r.DataplaneClient, obj)
 		}
 		return ctrl.Result{}, err
 	}
@@ -81,12 +82,12 @@ func (r *CoreV1ServiceReconciler) Reconcile(ctx context.Context, req ctrl.Reques
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
 		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "Service", "namespace", req.Namespace, "name", req.Name)
-		objectExistsInCache, err := r.Proxy.ObjectExists(obj)
+		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
 		if err != nil {
 			return ctrl.Result{}, err
 		}
 		if objectExistsInCache {
-			if err := r.Proxy.DeleteObject(obj); err != nil {
+			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
 				return ctrl.Result{}, err
 			}
 			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
@@ -95,7 +96,7 @@ func (r *CoreV1ServiceReconciler) Reconcile(ctx context.Context, req ctrl.Reques
 	}
 
 	// update the kong Admin API with the changes
-	if err := r.Proxy.UpdateObject(obj); err != nil {
+	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
 		return ctrl.Result{}, err
 	}
 
@@ -110,9 +111,10 @@ func (r *CoreV1ServiceReconciler) Reconcile(ctx context.Context, req ctrl.Reques
 type CoreV1EndpointsReconciler struct {
 	client.Client
 
+	DataplaneClient *dataplane.KongClient
+
 	Log    logr.Logger
 	Scheme *runtime.Scheme
-	Proxy  proxy.Proxy
 }
 
 // SetupWithManager sets up the controller with the Manager.
@@ -133,7 +135,7 @@ func (r *CoreV1EndpointsReconciler) Reconcile(ctx context.Context, req ctrl.Requ
 		if errors.IsNotFound(err) {
 			obj.Namespace = req.Namespace
 			obj.Name = req.Name
-			return ctrlutils.EnsureProxyDeleteObject(r.Proxy, obj)
+			return ctrlutils.EnsureProxyDeleteObject(r.DataplaneClient, obj)
 		}
 		return ctrl.Result{}, err
 	}
@@ -142,12 +144,12 @@ func (r *CoreV1EndpointsReconciler) Reconcile(ctx context.Context, req ctrl.Requ
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
 		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "Endpoints", "namespace", req.Namespace, "name", req.Name)
-		objectExistsInCache, err := r.Proxy.ObjectExists(obj)
+		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
 		if err != nil {
 			return ctrl.Result{}, err
 		}
 		if objectExistsInCache {
-			if err := r.Proxy.DeleteObject(obj); err != nil {
+			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
 				return ctrl.Result{}, err
 			}
 			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
@@ -156,7 +158,7 @@ func (r *CoreV1EndpointsReconciler) Reconcile(ctx context.Context, req ctrl.Requ
 	}
 
 	// update the kong Admin API with the changes
-	if err := r.Proxy.UpdateObject(obj); err != nil {
+	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
 		return ctrl.Result{}, err
 	}
 
@@ -171,9 +173,10 @@ func (r *CoreV1EndpointsReconciler) Reconcile(ctx context.Context, req ctrl.Requ
 type CoreV1SecretReconciler struct {
 	client.Client
 
+	DataplaneClient *dataplane.KongClient
+
 	Log    logr.Logger
 	Scheme *runtime.Scheme
-	Proxy  proxy.Proxy
 }
 
 // SetupWithManager sets up the controller with the Manager.
@@ -194,7 +197,7 @@ func (r *CoreV1SecretReconciler) Reconcile(ctx context.Context, req ctrl.Request
 		if errors.IsNotFound(err) {
 			obj.Namespace = req.Namespace
 			obj.Name = req.Name
-			return ctrlutils.EnsureProxyDeleteObject(r.Proxy, obj)
+			return ctrlutils.EnsureProxyDeleteObject(r.DataplaneClient, obj)
 		}
 		return ctrl.Result{}, err
 	}
@@ -203,12 +206,12 @@ func (r *CoreV1SecretReconciler) Reconcile(ctx context.Context, req ctrl.Request
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
 		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "Secret", "namespace", req.Namespace, "name", req.Name)
-		objectExistsInCache, err := r.Proxy.ObjectExists(obj)
+		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
 		if err != nil {
 			return ctrl.Result{}, err
 		}
 		if objectExistsInCache {
-			if err := r.Proxy.DeleteObject(obj); err != nil {
+			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
 				return ctrl.Result{}, err
 			}
 			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
@@ -217,7 +220,7 @@ func (r *CoreV1SecretReconciler) Reconcile(ctx context.Context, req ctrl.Request
 	}
 
 	// update the kong Admin API with the changes
-	if err := r.Proxy.UpdateObject(obj); err != nil {
+	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
 		return ctrl.Result{}, err
 	}
 
@@ -232,9 +235,10 @@ func (r *CoreV1SecretReconciler) Reconcile(ctx context.Context, req ctrl.Request
 type NetV1IngressReconciler struct {
 	client.Client
 
+	DataplaneClient *dataplane.KongClient
+
 	Log    logr.Logger
 	Scheme *runtime.Scheme
-	Proxy  proxy.Proxy
 
 	IngressClassName string
 }
@@ -258,7 +262,7 @@ func (r *NetV1IngressReconciler) Reconcile(ctx context.Context, req ctrl.Request
 		if errors.IsNotFound(err) {
 			obj.Namespace = req.Namespace
 			obj.Name = req.Name
-			return ctrlutils.EnsureProxyDeleteObject(r.Proxy, obj)
+			return ctrlutils.EnsureProxyDeleteObject(r.DataplaneClient, obj)
 		}
 		return ctrl.Result{}, err
 	}
@@ -267,12 +271,12 @@ func (r *NetV1IngressReconciler) Reconcile(ctx context.Context, req ctrl.Request
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
 		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "Ingress", "namespace", req.Namespace, "name", req.Name)
-		objectExistsInCache, err := r.Proxy.ObjectExists(obj)
+		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
 		if err != nil {
 			return ctrl.Result{}, err
 		}
 		if objectExistsInCache {
-			if err := r.Proxy.DeleteObject(obj); err != nil {
+			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
 				return ctrl.Result{}, err
 			}
 			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
@@ -283,14 +287,14 @@ func (r *NetV1IngressReconciler) Reconcile(ctx context.Context, req ctrl.Request
 	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
 	if !ctrlutils.MatchesIngressClassName(obj, r.IngressClassName) {
 		log.V(util.DebugLevel).Info("object missing ingress class, ensuring it's removed from configuration", "namespace", req.Namespace, "name", req.Name)
-		if err := r.Proxy.DeleteObject(obj); err != nil {
+		if err := r.DataplaneClient.DeleteObject(obj); err != nil {
 			return ctrl.Result{}, err
 		}
 		return ctrl.Result{}, nil
 	}
 
 	// update the kong Admin API with the changes
-	if err := r.Proxy.UpdateObject(obj); err != nil {
+	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
 		return ctrl.Result{}, err
 	}
 
@@ -305,9 +309,10 @@ func (r *NetV1IngressReconciler) Reconcile(ctx context.Context, req ctrl.Request
 type NetV1IngressClassReconciler struct {
 	client.Client
 
+	DataplaneClient *dataplane.KongClient
+
 	Log    logr.Logger
 	Scheme *runtime.Scheme
-	Proxy  proxy.Proxy
 }
 
 // SetupWithManager sets up the controller with the Manager.
@@ -327,7 +332,7 @@ func (r *NetV1IngressClassReconciler) Reconcile(ctx context.Context, req ctrl.Re
 		if errors.IsNotFound(err) {
 			obj.Namespace = req.Namespace
 			obj.Name = req.Name
-			return ctrlutils.EnsureProxyDeleteObject(r.Proxy, obj)
+			return ctrlutils.EnsureProxyDeleteObject(r.DataplaneClient, obj)
 		}
 		return ctrl.Result{}, err
 	}
@@ -336,12 +341,12 @@ func (r *NetV1IngressClassReconciler) Reconcile(ctx context.Context, req ctrl.Re
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
 		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "IngressClass", "namespace", req.Namespace, "name", req.Name)
-		objectExistsInCache, err := r.Proxy.ObjectExists(obj)
+		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
 		if err != nil {
 			return ctrl.Result{}, err
 		}
 		if objectExistsInCache {
-			if err := r.Proxy.DeleteObject(obj); err != nil {
+			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
 				return ctrl.Result{}, err
 			}
 			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
@@ -350,7 +355,7 @@ func (r *NetV1IngressClassReconciler) Reconcile(ctx context.Context, req ctrl.Re
 	}
 
 	// update the kong Admin API with the changes
-	if err := r.Proxy.UpdateObject(obj); err != nil {
+	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
 		return ctrl.Result{}, err
 	}
 
@@ -365,9 +370,10 @@ func (r *NetV1IngressClassReconciler) Reconcile(ctx context.Context, req ctrl.Re
 type NetV1Beta1IngressReconciler struct {
 	client.Client
 
+	DataplaneClient *dataplane.KongClient
+
 	Log    logr.Logger
 	Scheme *runtime.Scheme
-	Proxy  proxy.Proxy
 
 	IngressClassName string
 }
@@ -391,7 +397,7 @@ func (r *NetV1Beta1IngressReconciler) Reconcile(ctx context.Context, req ctrl.Re
 		if errors.IsNotFound(err) {
 			obj.Namespace = req.Namespace
 			obj.Name = req.Name
-			return ctrlutils.EnsureProxyDeleteObject(r.Proxy, obj)
+			return ctrlutils.EnsureProxyDeleteObject(r.DataplaneClient, obj)
 		}
 		return ctrl.Result{}, err
 	}
@@ -400,12 +406,12 @@ func (r *NetV1Beta1IngressReconciler) Reconcile(ctx context.Context, req ctrl.Re
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
 		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "Ingress", "namespace", req.Namespace, "name", req.Name)
-		objectExistsInCache, err := r.Proxy.ObjectExists(obj)
+		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
 		if err != nil {
 			return ctrl.Result{}, err
 		}
 		if objectExistsInCache {
-			if err := r.Proxy.DeleteObject(obj); err != nil {
+			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
 				return ctrl.Result{}, err
 			}
 			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
@@ -416,14 +422,14 @@ func (r *NetV1Beta1IngressReconciler) Reconcile(ctx context.Context, req ctrl.Re
 	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
 	if !ctrlutils.MatchesIngressClassName(obj, r.IngressClassName) {
 		log.V(util.DebugLevel).Info("object missing ingress class, ensuring it's removed from configuration", "namespace", req.Namespace, "name", req.Name)
-		if err := r.Proxy.DeleteObject(obj); err != nil {
+		if err := r.DataplaneClient.DeleteObject(obj); err != nil {
 			return ctrl.Result{}, err
 		}
 		return ctrl.Result{}, nil
 	}
 
 	// update the kong Admin API with the changes
-	if err := r.Proxy.UpdateObject(obj); err != nil {
+	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
 		return ctrl.Result{}, err
 	}
 
@@ -438,9 +444,10 @@ func (r *NetV1Beta1IngressReconciler) Reconcile(ctx context.Context, req ctrl.Re
 type ExtV1Beta1IngressReconciler struct {
 	client.Client
 
+	DataplaneClient *dataplane.KongClient
+
 	Log    logr.Logger
 	Scheme *runtime.Scheme
-	Proxy  proxy.Proxy
 
 	IngressClassName string
 }
@@ -464,7 +471,7 @@ func (r *ExtV1Beta1IngressReconciler) Reconcile(ctx context.Context, req ctrl.Re
 		if errors.IsNotFound(err) {
 			obj.Namespace = req.Namespace
 			obj.Name = req.Name
-			return ctrlutils.EnsureProxyDeleteObject(r.Proxy, obj)
+			return ctrlutils.EnsureProxyDeleteObject(r.DataplaneClient, obj)
 		}
 		return ctrl.Result{}, err
 	}
@@ -473,12 +480,12 @@ func (r *ExtV1Beta1IngressReconciler) Reconcile(ctx context.Context, req ctrl.Re
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
 		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "Ingress", "namespace", req.Namespace, "name", req.Name)
-		objectExistsInCache, err := r.Proxy.ObjectExists(obj)
+		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
 		if err != nil {
 			return ctrl.Result{}, err
 		}
 		if objectExistsInCache {
-			if err := r.Proxy.DeleteObject(obj); err != nil {
+			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
 				return ctrl.Result{}, err
 			}
 			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
@@ -489,14 +496,14 @@ func (r *ExtV1Beta1IngressReconciler) Reconcile(ctx context.Context, req ctrl.Re
 	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
 	if !ctrlutils.MatchesIngressClassName(obj, r.IngressClassName) {
 		log.V(util.DebugLevel).Info("object missing ingress class, ensuring it's removed from configuration", "namespace", req.Namespace, "name", req.Name)
-		if err := r.Proxy.DeleteObject(obj); err != nil {
+		if err := r.DataplaneClient.DeleteObject(obj); err != nil {
 			return ctrl.Result{}, err
 		}
 		return ctrl.Result{}, nil
 	}
 
 	// update the kong Admin API with the changes
-	if err := r.Proxy.UpdateObject(obj); err != nil {
+	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
 		return ctrl.Result{}, err
 	}
 
@@ -511,9 +518,10 @@ func (r *ExtV1Beta1IngressReconciler) Reconcile(ctx context.Context, req ctrl.Re
 type KongV1KongIngressReconciler struct {
 	client.Client
 
+	DataplaneClient *dataplane.KongClient
+
 	Log    logr.Logger
 	Scheme *runtime.Scheme
-	Proxy  proxy.Proxy
 }
 
 // SetupWithManager sets up the controller with the Manager.
@@ -534,7 +542,7 @@ func (r *KongV1KongIngressReconciler) Reconcile(ctx context.Context, req ctrl.Re
 		if errors.IsNotFound(err) {
 			obj.Namespace = req.Namespace
 			obj.Name = req.Name
-			return ctrlutils.EnsureProxyDeleteObject(r.Proxy, obj)
+			return ctrlutils.EnsureProxyDeleteObject(r.DataplaneClient, obj)
 		}
 		return ctrl.Result{}, err
 	}
@@ -543,12 +551,12 @@ func (r *KongV1KongIngressReconciler) Reconcile(ctx context.Context, req ctrl.Re
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
 		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "KongIngress", "namespace", req.Namespace, "name", req.Name)
-		objectExistsInCache, err := r.Proxy.ObjectExists(obj)
+		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
 		if err != nil {
 			return ctrl.Result{}, err
 		}
 		if objectExistsInCache {
-			if err := r.Proxy.DeleteObject(obj); err != nil {
+			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
 				return ctrl.Result{}, err
 			}
 			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
@@ -557,7 +565,7 @@ func (r *KongV1KongIngressReconciler) Reconcile(ctx context.Context, req ctrl.Re
 	}
 
 	// update the kong Admin API with the changes
-	if err := r.Proxy.UpdateObject(obj); err != nil {
+	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
 		return ctrl.Result{}, err
 	}
 
@@ -572,9 +580,10 @@ func (r *KongV1KongIngressReconciler) Reconcile(ctx context.Context, req ctrl.Re
 type KongV1KongPluginReconciler struct {
 	client.Client
 
+	DataplaneClient *dataplane.KongClient
+
 	Log    logr.Logger
 	Scheme *runtime.Scheme
-	Proxy  proxy.Proxy
 }
 
 // SetupWithManager sets up the controller with the Manager.
@@ -595,7 +604,7 @@ func (r *KongV1KongPluginReconciler) Reconcile(ctx context.Context, req ctrl.Req
 		if errors.IsNotFound(err) {
 			obj.Namespace = req.Namespace
 			obj.Name = req.Name
-			return ctrlutils.EnsureProxyDeleteObject(r.Proxy, obj)
+			return ctrlutils.EnsureProxyDeleteObject(r.DataplaneClient, obj)
 		}
 		return ctrl.Result{}, err
 	}
@@ -604,12 +613,12 @@ func (r *KongV1KongPluginReconciler) Reconcile(ctx context.Context, req ctrl.Req
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
 		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "KongPlugin", "namespace", req.Namespace, "name", req.Name)
-		objectExistsInCache, err := r.Proxy.ObjectExists(obj)
+		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
 		if err != nil {
 			return ctrl.Result{}, err
 		}
 		if objectExistsInCache {
-			if err := r.Proxy.DeleteObject(obj); err != nil {
+			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
 				return ctrl.Result{}, err
 			}
 			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
@@ -618,7 +627,7 @@ func (r *KongV1KongPluginReconciler) Reconcile(ctx context.Context, req ctrl.Req
 	}
 
 	// update the kong Admin API with the changes
-	if err := r.Proxy.UpdateObject(obj); err != nil {
+	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
 		return ctrl.Result{}, err
 	}
 
@@ -633,9 +642,10 @@ func (r *KongV1KongPluginReconciler) Reconcile(ctx context.Context, req ctrl.Req
 type KongV1KongClusterPluginReconciler struct {
 	client.Client
 
+	DataplaneClient *dataplane.KongClient
+
 	Log    logr.Logger
 	Scheme *runtime.Scheme
-	Proxy  proxy.Proxy
 
 	IngressClassName string
 }
@@ -659,7 +669,7 @@ func (r *KongV1KongClusterPluginReconciler) Reconcile(ctx context.Context, req c
 		if errors.IsNotFound(err) {
 			obj.Namespace = req.Namespace
 			obj.Name = req.Name
-			return ctrlutils.EnsureProxyDeleteObject(r.Proxy, obj)
+			return ctrlutils.EnsureProxyDeleteObject(r.DataplaneClient, obj)
 		}
 		return ctrl.Result{}, err
 	}
@@ -668,12 +678,12 @@ func (r *KongV1KongClusterPluginReconciler) Reconcile(ctx context.Context, req c
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
 		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "KongClusterPlugin", "namespace", req.Namespace, "name", req.Name)
-		objectExistsInCache, err := r.Proxy.ObjectExists(obj)
+		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
 		if err != nil {
 			return ctrl.Result{}, err
 		}
 		if objectExistsInCache {
-			if err := r.Proxy.DeleteObject(obj); err != nil {
+			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
 				return ctrl.Result{}, err
 			}
 			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
@@ -684,14 +694,14 @@ func (r *KongV1KongClusterPluginReconciler) Reconcile(ctx context.Context, req c
 	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
 	if !ctrlutils.MatchesIngressClassName(obj, r.IngressClassName) {
 		log.V(util.DebugLevel).Info("object missing ingress class, ensuring it's removed from configuration", "namespace", req.Namespace, "name", req.Name)
-		if err := r.Proxy.DeleteObject(obj); err != nil {
+		if err := r.DataplaneClient.DeleteObject(obj); err != nil {
 			return ctrl.Result{}, err
 		}
 		return ctrl.Result{}, nil
 	}
 
 	// update the kong Admin API with the changes
-	if err := r.Proxy.UpdateObject(obj); err != nil {
+	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
 		return ctrl.Result{}, err
 	}
 
@@ -706,9 +716,10 @@ func (r *KongV1KongClusterPluginReconciler) Reconcile(ctx context.Context, req c
 type KongV1KongConsumerReconciler struct {
 	client.Client
 
+	DataplaneClient *dataplane.KongClient
+
 	Log    logr.Logger
 	Scheme *runtime.Scheme
-	Proxy  proxy.Proxy
 
 	IngressClassName string
 }
@@ -732,7 +743,7 @@ func (r *KongV1KongConsumerReconciler) Reconcile(ctx context.Context, req ctrl.R
 		if errors.IsNotFound(err) {
 			obj.Namespace = req.Namespace
 			obj.Name = req.Name
-			return ctrlutils.EnsureProxyDeleteObject(r.Proxy, obj)
+			return ctrlutils.EnsureProxyDeleteObject(r.DataplaneClient, obj)
 		}
 		return ctrl.Result{}, err
 	}
@@ -741,12 +752,12 @@ func (r *KongV1KongConsumerReconciler) Reconcile(ctx context.Context, req ctrl.R
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
 		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "KongConsumer", "namespace", req.Namespace, "name", req.Name)
-		objectExistsInCache, err := r.Proxy.ObjectExists(obj)
+		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
 		if err != nil {
 			return ctrl.Result{}, err
 		}
 		if objectExistsInCache {
-			if err := r.Proxy.DeleteObject(obj); err != nil {
+			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
 				return ctrl.Result{}, err
 			}
 			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
@@ -757,14 +768,14 @@ func (r *KongV1KongConsumerReconciler) Reconcile(ctx context.Context, req ctrl.R
 	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
 	if !ctrlutils.MatchesIngressClassName(obj, r.IngressClassName) {
 		log.V(util.DebugLevel).Info("object missing ingress class, ensuring it's removed from configuration", "namespace", req.Namespace, "name", req.Name)
-		if err := r.Proxy.DeleteObject(obj); err != nil {
+		if err := r.DataplaneClient.DeleteObject(obj); err != nil {
 			return ctrl.Result{}, err
 		}
 		return ctrl.Result{}, nil
 	}
 
 	// update the kong Admin API with the changes
-	if err := r.Proxy.UpdateObject(obj); err != nil {
+	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
 		return ctrl.Result{}, err
 	}
 
@@ -779,9 +790,10 @@ func (r *KongV1KongConsumerReconciler) Reconcile(ctx context.Context, req ctrl.R
 type KongV1Beta1TCPIngressReconciler struct {
 	client.Client
 
+	DataplaneClient *dataplane.KongClient
+
 	Log    logr.Logger
 	Scheme *runtime.Scheme
-	Proxy  proxy.Proxy
 
 	IngressClassName string
 }
@@ -805,7 +817,7 @@ func (r *KongV1Beta1TCPIngressReconciler) Reconcile(ctx context.Context, req ctr
 		if errors.IsNotFound(err) {
 			obj.Namespace = req.Namespace
 			obj.Name = req.Name
-			return ctrlutils.EnsureProxyDeleteObject(r.Proxy, obj)
+			return ctrlutils.EnsureProxyDeleteObject(r.DataplaneClient, obj)
 		}
 		return ctrl.Result{}, err
 	}
@@ -814,12 +826,12 @@ func (r *KongV1Beta1TCPIngressReconciler) Reconcile(ctx context.Context, req ctr
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
 		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "TCPIngress", "namespace", req.Namespace, "name", req.Name)
-		objectExistsInCache, err := r.Proxy.ObjectExists(obj)
+		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
 		if err != nil {
 			return ctrl.Result{}, err
 		}
 		if objectExistsInCache {
-			if err := r.Proxy.DeleteObject(obj); err != nil {
+			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
 				return ctrl.Result{}, err
 			}
 			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
@@ -830,14 +842,14 @@ func (r *KongV1Beta1TCPIngressReconciler) Reconcile(ctx context.Context, req ctr
 	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
 	if !ctrlutils.MatchesIngressClassName(obj, r.IngressClassName) {
 		log.V(util.DebugLevel).Info("object missing ingress class, ensuring it's removed from configuration", "namespace", req.Namespace, "name", req.Name)
-		if err := r.Proxy.DeleteObject(obj); err != nil {
+		if err := r.DataplaneClient.DeleteObject(obj); err != nil {
 			return ctrl.Result{}, err
 		}
 		return ctrl.Result{}, nil
 	}
 
 	// update the kong Admin API with the changes
-	if err := r.Proxy.UpdateObject(obj); err != nil {
+	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
 		return ctrl.Result{}, err
 	}
 
@@ -852,9 +864,10 @@ func (r *KongV1Beta1TCPIngressReconciler) Reconcile(ctx context.Context, req ctr
 type KongV1Beta1UDPIngressReconciler struct {
 	client.Client
 
+	DataplaneClient *dataplane.KongClient
+
 	Log    logr.Logger
 	Scheme *runtime.Scheme
-	Proxy  proxy.Proxy
 
 	IngressClassName string
 }
@@ -878,7 +891,7 @@ func (r *KongV1Beta1UDPIngressReconciler) Reconcile(ctx context.Context, req ctr
 		if errors.IsNotFound(err) {
 			obj.Namespace = req.Namespace
 			obj.Name = req.Name
-			return ctrlutils.EnsureProxyDeleteObject(r.Proxy, obj)
+			return ctrlutils.EnsureProxyDeleteObject(r.DataplaneClient, obj)
 		}
 		return ctrl.Result{}, err
 	}
@@ -887,12 +900,12 @@ func (r *KongV1Beta1UDPIngressReconciler) Reconcile(ctx context.Context, req ctr
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
 		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "UDPIngress", "namespace", req.Namespace, "name", req.Name)
-		objectExistsInCache, err := r.Proxy.ObjectExists(obj)
+		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
 		if err != nil {
 			return ctrl.Result{}, err
 		}
 		if objectExistsInCache {
-			if err := r.Proxy.DeleteObject(obj); err != nil {
+			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
 				return ctrl.Result{}, err
 			}
 			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
@@ -903,14 +916,14 @@ func (r *KongV1Beta1UDPIngressReconciler) Reconcile(ctx context.Context, req ctr
 	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
 	if !ctrlutils.MatchesIngressClassName(obj, r.IngressClassName) {
 		log.V(util.DebugLevel).Info("object missing ingress class, ensuring it's removed from configuration", "namespace", req.Namespace, "name", req.Name)
-		if err := r.Proxy.DeleteObject(obj); err != nil {
+		if err := r.DataplaneClient.DeleteObject(obj); err != nil {
 			return ctrl.Result{}, err
 		}
 		return ctrl.Result{}, nil
 	}
 
 	// update the kong Admin API with the changes
-	if err := r.Proxy.UpdateObject(obj); err != nil {
+	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
 		return ctrl.Result{}, err
 	}
 
@@ -925,9 +938,10 @@ func (r *KongV1Beta1UDPIngressReconciler) Reconcile(ctx context.Context, req ctr
 type Knativev1alpha1IngressReconciler struct {
 	client.Client
 
+	DataplaneClient *dataplane.KongClient
+
 	Log    logr.Logger
 	Scheme *runtime.Scheme
-	Proxy  proxy.Proxy
 
 	IngressClassName string
 }
@@ -951,7 +965,7 @@ func (r *Knativev1alpha1IngressReconciler) Reconcile(ctx context.Context, req ct
 		if errors.IsNotFound(err) {
 			obj.Namespace = req.Namespace
 			obj.Name = req.Name
-			return ctrlutils.EnsureProxyDeleteObject(r.Proxy, obj)
+			return ctrlutils.EnsureProxyDeleteObject(r.DataplaneClient, obj)
 		}
 		return ctrl.Result{}, err
 	}
@@ -960,12 +974,12 @@ func (r *Knativev1alpha1IngressReconciler) Reconcile(ctx context.Context, req ct
 	// clean the object up if it's being deleted
 	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
 		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "Ingress", "namespace", req.Namespace, "name", req.Name)
-		objectExistsInCache, err := r.Proxy.ObjectExists(obj)
+		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
 		if err != nil {
 			return ctrl.Result{}, err
 		}
 		if objectExistsInCache {
-			if err := r.Proxy.DeleteObject(obj); err != nil {
+			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
 				return ctrl.Result{}, err
 			}
 			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
@@ -976,14 +990,14 @@ func (r *Knativev1alpha1IngressReconciler) Reconcile(ctx context.Context, req ct
 	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
 	if !ctrlutils.MatchesIngressClassName(obj, r.IngressClassName) {
 		log.V(util.DebugLevel).Info("object missing ingress class, ensuring it's removed from configuration", "namespace", req.Namespace, "name", req.Name)
-		if err := r.Proxy.DeleteObject(obj); err != nil {
+		if err := r.DataplaneClient.DeleteObject(obj); err != nil {
 			return ctrl.Result{}, err
 		}
 		return ctrl.Result{}, nil
 	}
 
 	// update the kong Admin API with the changes
-	if err := r.Proxy.UpdateObject(obj); err != nil {
+	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
 		return ctrl.Result{}, err
 	}
 
diff --git a/internal/controllers/gateway/gateway_controller.go b/internal/controllers/gateway/gateway_controller.go
index 17e55a32d..742dbae2f 100644
--- a/internal/controllers/gateway/gateway_controller.go
+++ b/internal/controllers/gateway/gateway_controller.go
@@ -22,7 +22,7 @@ import (
 	gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
 
 	"github.com/kong/kubernetes-ingress-controller/v2/internal/annotations"
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/proxy"
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane"
 )
 
 // -----------------------------------------------------------------------------
@@ -50,9 +50,9 @@ var (
 type GatewayReconciler struct { //nolint:revive
 	client.Client
 
-	Log    logr.Logger
-	Scheme *runtime.Scheme
-	Proxy  proxy.Proxy
+	Log             logr.Logger
+	Scheme          *runtime.Scheme
+	DataplaneClient *dataplane.KongClient
 
 	PublishService  string
 	WatchNamespaces []string
@@ -495,7 +495,7 @@ func (r *GatewayReconciler) determineListenersFromDataPlane(ctx context.Context,
 	// gather the proxy and stream listeners from the data-plane and map them
 	// to their respective ports (which will be the targetPorts of the proxy
 	// Service in Kubernetes).
-	proxyListeners, streamListeners, err := r.Proxy.Listeners(ctx)
+	proxyListeners, streamListeners, err := r.DataplaneClient.Listeners(ctx)
 	if err != nil {
 		return nil, fmt.Errorf("unable to retrieve listeners from the data-plane: %w", err)
 	}
diff --git a/internal/controllers/gateway/httproute_controller.go b/internal/controllers/gateway/httproute_controller.go
index 33e000435..035ff2bc0 100644
--- a/internal/controllers/gateway/httproute_controller.go
+++ b/internal/controllers/gateway/httproute_controller.go
@@ -21,7 +21,7 @@ import (
 	gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
 
 	ctrlutils "github.com/kong/kubernetes-ingress-controller/v2/internal/controllers/utils"
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/proxy"
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane"
 )
 
 // -----------------------------------------------------------------------------
@@ -32,9 +32,9 @@ import (
 type HTTPRouteReconciler struct {
 	client.Client
 
-	Log    logr.Logger
-	Scheme *runtime.Scheme
-	Proxy  proxy.Proxy
+	Log             logr.Logger
+	Scheme          *runtime.Scheme
+	DataplaneClient *dataplane.KongClient
 }
 
 // SetupWithManager sets up the controller with the Manager.
@@ -237,7 +237,7 @@ func (r *HTTPRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
 			debug(log, httproute, "object does not exist, ensuring it is not present in the proxy cache")
 			httproute.Namespace = req.Namespace
 			httproute.Name = req.Name
-			return ctrlutils.EnsureProxyDeleteObject(r.Proxy, httproute)
+			return ctrlutils.EnsureProxyDeleteObject(r.DataplaneClient, httproute)
 		}
 
 		// for any error other than 404, requeue
@@ -252,12 +252,12 @@ func (r *HTTPRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
 	debug(log, httproute, "checking deletion timestamp")
 	if httproute.DeletionTimestamp != nil {
 		debug(log, httproute, "httproute is being deleted, re-configuring data-plane")
-		if err := r.Proxy.DeleteObject(httproute); err != nil {
+		if err := r.DataplaneClient.DeleteObject(httproute); err != nil {
 			debug(log, httproute, "failed to delete object from data-plane, requeuing")
 			return ctrl.Result{}, err
 		}
 		debug(log, httproute, "ensured object was removed from the data-plane (if ever present)")
-		return ctrl.Result{}, nil
+		return ctrlutils.EnsureProxyDeleteObject(r.DataplaneClient, httproute)
 	}
 
 	// we need to pull the Gateway parent objects for the HTTPRoute to verify
@@ -266,6 +266,7 @@ func (r *HTTPRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
 	gateways, err := getSupportedGatewayForRoute(ctx, r.Client, httproute)
 	if err != nil {
 		if err.Error() == unsupportedGW {
+			debug(log, httproute, "unsupported route found, processing to verify whether it was ever supported")
 			// if there's no supported Gateway then this route could have been previously
 			// supported by this controller. As such we ensure that no supported Gateway
 			// references exist in the object status any longer.
@@ -277,6 +278,7 @@ func (r *HTTPRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
 			if statusUpdated {
 				// the status did in fact needed to be updated, so no need to requeue
 				// as the status update will trigger a requeue.
+				debug(log, httproute, "unsupported route was previously supported, status was updated")
 				return ctrl.Result{}, nil
 			}
 
@@ -284,7 +286,8 @@ func (r *HTTPRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
 			// it it's possible it became orphaned after becoming queued. In either case
 			// ensure that it's removed from the proxy cache to avoid orphaned data-plane
 			// configurations.
-			return ctrlutils.EnsureProxyDeleteObject(r.Proxy, httproute)
+			debug(log, httproute, "ensuring that dataplane is updated to remove unsupported route (if applicable)")
+			return ctrlutils.EnsureProxyDeleteObject(r.DataplaneClient, httproute)
 		}
 		return ctrl.Result{}, err
 	}
@@ -318,7 +321,7 @@ func (r *HTTPRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
 	// finally if all matching has succeeded and the object is not being deleted,
 	// we can configure it in the data-plane.
 	debug(log, httproute, "sending httproute information to the data-plane for configuration")
-	if err := r.Proxy.UpdateObject(httproute); err != nil {
+	if err := r.DataplaneClient.UpdateObject(httproute); err != nil {
 		debug(log, httproute, "failed to update object in data-plane, requeueing")
 		return ctrl.Result{}, err
 	}
diff --git a/internal/controllers/utils/utils.go b/internal/controllers/utils/utils.go
index 1cb6e74a3..8d73abf16 100644
--- a/internal/controllers/utils/utils.go
+++ b/internal/controllers/utils/utils.go
@@ -13,7 +13,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/predicate"
 
 	"github.com/kong/kubernetes-ingress-controller/v2/internal/annotations"
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/proxy"
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane"
 )
 
 // HasAnnotation is a helper function to determine whether an object has a given annotation, and whether it's
@@ -105,9 +105,9 @@ func CRDExists(client client.Client, gvr schema.GroupVersionResource) bool {
 
 // EnsureProxyDeleteObject is a reconciliation helper to ensure that an object is removed from
 // the backend proxy cache so that it gets removed from data-plane configurations.
-func EnsureProxyDeleteObject(proxy proxy.Proxy, obj client.Object) (ctrl.Result, error) {
+func EnsureProxyDeleteObject(dataplaneClient *dataplane.KongClient, obj client.Object) (ctrl.Result, error) {
 	// check whether the object is at all present in the proxy cache.
-	objectExistsInCache, err := proxy.ObjectExists(obj)
+	objectExistsInCache, err := dataplaneClient.ObjectExists(obj)
 	if err != nil {
 		return ctrl.Result{}, err
 	}
@@ -115,7 +115,7 @@ func EnsureProxyDeleteObject(proxy proxy.Proxy, obj client.Object) (ctrl.Result,
 	// if the object is still present in the proxy cache, we need to keep trying to
 	// remove it until its gone so that it gets removed from backend data-plane.
 	if objectExistsInCache {
-		if err := proxy.DeleteObject(obj); err != nil {
+		if err := dataplaneClient.DeleteObject(obj); err != nil {
 			return ctrl.Result{}, err
 		}
 
diff --git a/internal/dataplane/client.go b/internal/dataplane/client.go
new file mode 100644
index 000000000..562b514e7
--- /dev/null
+++ b/internal/dataplane/client.go
@@ -0,0 +1,32 @@
+package dataplane
+
+import (
+	"context"
+)
+
+// -----------------------------------------------------------------------------
+// Dataplane Client - Public Vars & Consts
+// -----------------------------------------------------------------------------
+
+const (
+	// DefaultTimeoutSeconds indicates the time.Duration allowed for responses to
+	// come back from the backend data-plane API.
+	//
+	// NOTE: the current default is based on observed latency in a CI environment using
+	// the GKE cloud provider with the Kong Admin API.
+	DefaultTimeoutSeconds float32 = 10.0
+)
+
+// -----------------------------------------------------------------------------
+// Dataplane Client - Public Interface
+// -----------------------------------------------------------------------------
+
+type Client interface {
+	// DBMode informs the caller which DB mode the data-plane has employed
+	// (e.g. "off" (dbless) or "postgres").
+	DBMode() string
+
+	// Update the data-plane by parsing the current configuring and applying
+	// it to the backend API.
+	Update(ctx context.Context) error
+}
diff --git a/internal/dataplane/doc.go b/internal/dataplane/doc.go
index e9130b0a9..7bb3e654a 100644
--- a/internal/dataplane/doc.go
+++ b/internal/dataplane/doc.go
@@ -1,3 +1,3 @@
 // Package dataplane provides functionality for communicating with and
-// providing configuration to Kong Gateways.
+// providing configuration to backend dataplane APIs.
 package dataplane
diff --git a/internal/dataplane/kong_client.go b/internal/dataplane/kong_client.go
new file mode 100644
index 000000000..4dd9155b5
--- /dev/null
+++ b/internal/dataplane/kong_client.go
@@ -0,0 +1,273 @@
+package dataplane
+
+import (
+	"context"
+	"fmt"
+	"sync"
+	"time"
+
+	"github.com/kong/deck/file"
+	"github.com/kong/go-kong/kong"
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/sirupsen/logrus"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/deckgen"
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/parser"
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/sendconfig"
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/metrics"
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/store"
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/util"
+)
+
+// -----------------------------------------------------------------------------
+// Dataplane Client - Kong - Public Types
+// -----------------------------------------------------------------------------
+
+// KongClient is a threadsafe high level API client for the Kong data-plane
+// which parses Kubernetes object caches into Kong Admin configurations and
+// sends them as updates to the data-plane (Kong Admin API).
+type KongClient struct {
+	logger logrus.FieldLogger
+
+	// ingressClass indicates the Kubernetes ingress class that should be
+	// used to qualify support for any given Kubernetes object to be parsed
+	// into data-plane configuration.
+	ingressClass string
+
+	// enableReverseSync indicates that reverse sync should be enabled for
+	// updates to the data-plane.
+	enableReverseSync bool
+
+	// requestTimeout is the maximum amount of time that should be waited for
+	// requests to the data-plane to receive a response.
+	requestTimeout time.Duration
+
+	// cache is the Kubernetes object cache which is used to list Kubernetes
+	// objects for parsing into Kong objects.
+	cache *store.CacheStores
+
+	// kongConfig is the client configuration for the Kong Admin API
+	kongConfig sendconfig.Kong
+
+	// dbmode indicates the current database mode of the backend Kong Admin API
+	dbmode string
+
+	// lastConfigSHA is a checksum of the last successful update to the data-plane
+	lastConfigSHA []byte
+
+	// lock is used to ensure threadsafety of the KongClient object
+	lock sync.RWMutex
+
+	// diagnostic is the client and configuration for reporting diagnostic
+	// information during data-plane update runtime.
+	diagnostic util.ConfigDumpDiagnostic
+
+	// prometheusMetrics is the client for shipping metrics information
+	// updates to the prometheus exporter.
+	prometheusMetrics *metrics.CtrlFuncMetrics
+}
+
+// NewKongClient provides a new KongClient object after connecting to the
+// data-plane API and verifying integrity.
+func NewKongClient(
+	logger logrus.FieldLogger,
+	timeout time.Duration,
+	ingressClass string,
+	enableReverseSync bool,
+	diagnostic util.ConfigDumpDiagnostic,
+	kongConfig sendconfig.Kong,
+) (*KongClient, error) {
+	// build the client object
+	cache := store.NewCacheStores()
+	c := &KongClient{
+		logger:            logger,
+		ingressClass:      ingressClass,
+		enableReverseSync: enableReverseSync,
+		requestTimeout:    timeout,
+		diagnostic:        diagnostic,
+		prometheusMetrics: metrics.NewCtrlFuncMetrics(),
+		cache:             &cache,
+		kongConfig:        kongConfig,
+	}
+
+	// download the kong root configuration (and validate connectivity to the proxy API)
+	root, err := c.RootWithTimeout()
+	if err != nil {
+		return nil, err
+	}
+
+	// pull the proxy configuration out of the root config and validate it
+	proxyConfig, ok := root["configuration"].(map[string]interface{})
+	if !ok {
+		return nil, fmt.Errorf("invalid root configuration, expected a map[string]interface{} got %T", proxyConfig["configuration"])
+	}
+
+	// validate the database configuration for the proxy and check for supported database configurations
+	dbmode, ok := proxyConfig["database"].(string)
+	if !ok {
+		return nil, fmt.Errorf("invalid database configuration, expected a string got %t", proxyConfig["database"])
+	}
+	switch dbmode {
+	case "off", "":
+		c.kongConfig.InMemory = true
+	case "postgres":
+		c.kongConfig.InMemory = false
+	case "cassandra":
+		return nil, fmt.Errorf("Cassandra-backed deployments of Kong managed by the ingress controller are no longer supported; you must migrate to a Postgres-backed or DB-less deployment")
+	default:
+		return nil, fmt.Errorf("%s is not a supported database backend", dbmode)
+	}
+
+	// validate the proxy version
+	proxySemver, err := kong.ParseSemanticVersion(kong.VersionFromInfo(root))
+	if err != nil {
+		return nil, err
+	}
+
+	// store the gathered configuration options
+	c.kongConfig.Version = proxySemver
+	c.dbmode = dbmode
+
+	return c, nil
+}
+
+// -----------------------------------------------------------------------------
+// Dataplane Client - Kong - Public Methods
+// -----------------------------------------------------------------------------
+
+// UpdateObject accepts a Kubernetes controller-runtime client.Object and adds/updates that to the configuration cache.
+// It will be asynchronously converted into the upstream Kong DSL and applied to the Kong Admin API.
+// A status will later be added to the object whether the configuration update succeeds or fails.
+func (c *KongClient) UpdateObject(obj client.Object) error {
+	return c.cache.Add(obj)
+}
+
+// DeleteObject accepts a Kubernetes controller-runtime client.Object and removes it from the configuration cache.
+// The delete action will asynchronously be converted to Kong DSL and applied to the Kong Admin API.
+// A status will later be added to the object whether the configuration update succeeds or fails.
+func (c *KongClient) DeleteObject(obj client.Object) error {
+	return c.cache.Delete(obj)
+}
+
+// ObjectExists indicates whether or not any version of the provided object is already present in the proxy.
+func (c *KongClient) ObjectExists(obj client.Object) (bool, error) {
+	_, exists, err := c.cache.Get(obj)
+	return exists, err
+}
+
+// Listeners retrieves the currently configured listeners from the
+// underlying proxy so that callers can gather this metadata to
+// know which ports and protocols are in use by the proxy.
+func (c *KongClient) Listeners(ctx context.Context) ([]kong.ProxyListener, []kong.StreamListener, error) {
+	return c.kongConfig.Client.Listeners(ctx)
+}
+
+// RootWithTimeout provides the root configuration from Kong, but uses a configurable timeout to avoid long waits if the Admin API
+// is not yet ready to respond. If a timeout error occurs, the caller is responsible for providing a retry mechanism.
+func (c *KongClient) RootWithTimeout() (map[string]interface{}, error) {
+	ctx, cancel := context.WithTimeout(context.Background(), c.requestTimeout)
+	defer cancel()
+	return c.kongConfig.Client.Root(ctx)
+}
+
+// -----------------------------------------------------------------------------
+// Dataplane Client - Kong - Interface Implementation
+// -----------------------------------------------------------------------------
+
+// DBMode indicates which database the Kong Gateway is using
+func (c *KongClient) DBMode() string {
+	c.lock.RLock()
+	defer c.lock.RUnlock()
+	return c.dbmode
+}
+
+// Update parses the Cache present in the client and converts current
+// Kubernetes state into Kong objects and state, and then ships the
+// resulting configuration to the data-plane (Kong Admin API).
+func (c *KongClient) Update(ctx context.Context) error {
+	c.lock.Lock()
+	defer c.lock.Unlock()
+
+	// build the kongstate object from the Kubernetes objects in the storer
+	storer := store.New(*c.cache, c.ingressClass, false, false, false, c.logger)
+
+	// initialize a parser and convert the Kubernetes objects to Kong objects
+	p := parser.NewParser(c.logger, storer)
+	kongstate, err := p.Build()
+	if err != nil {
+		c.prometheusMetrics.TranslationCount.With(prometheus.Labels{
+			metrics.SuccessKey: metrics.SuccessFalse,
+		}).Inc()
+		return err
+	}
+	c.prometheusMetrics.TranslationCount.With(prometheus.Labels{
+		metrics.SuccessKey: metrics.SuccessTrue,
+	}).Inc()
+
+	// generate the deck configuration to be applied to the admin API
+	targetConfig := deckgen.ToDeckContent(ctx,
+		c.logger, kongstate,
+		c.kongConfig.PluginSchemaStore,
+		c.kongConfig.FilterTags,
+	)
+
+	// generate diagnostic configuration if enabled
+	// "diagnostic" will be empty if --dump-config is not set
+	var diagnosticConfig *file.Content
+	if c.diagnostic != (util.ConfigDumpDiagnostic{}) {
+		if !c.diagnostic.DumpsIncludeSensitive {
+			redactedConfig := deckgen.ToDeckContent(ctx,
+				c.logger,
+				kongstate.SanitizedCopy(),
+				c.kongConfig.PluginSchemaStore,
+				c.kongConfig.FilterTags,
+			)
+			diagnosticConfig = redactedConfig
+		} else {
+			diagnosticConfig = targetConfig
+		}
+	}
+
+	// apply the configuration update in Kong
+	timedCtx, cancel := context.WithTimeout(ctx, c.requestTimeout)
+	defer cancel()
+	newConfigSHA, err := sendconfig.PerformUpdate(timedCtx,
+		c.logger,
+		&c.kongConfig,
+		c.kongConfig.InMemory,
+		c.enableReverseSync,
+		targetConfig,
+		c.kongConfig.FilterTags,
+		nil,
+		c.lastConfigSHA,
+		false,
+		c.prometheusMetrics,
+	)
+	if err != nil {
+		// ship diagnostics if enabled
+		if c.diagnostic != (util.ConfigDumpDiagnostic{}) {
+			select {
+			case c.diagnostic.Configs <- util.ConfigDump{Failed: true, Config: *diagnosticConfig}:
+				c.logger.Debug("shipping config to diagnostic server")
+			default:
+				c.logger.Error("config diagnostic buffer full, dropping diagnostic config")
+			}
+		}
+		return err
+	}
+
+	// ship diagnostics if enabled
+	if c.diagnostic != (util.ConfigDumpDiagnostic{}) {
+		select {
+		case c.diagnostic.Configs <- util.ConfigDump{Failed: false, Config: *diagnosticConfig}:
+			c.logger.Debug("shipping config to diagnostic server")
+		default:
+			c.logger.Error("config diagnostic buffer full, dropping diagnostic config")
+		}
+	}
+
+	// update the lastConfigSHA with the new updated checksum
+	c.lastConfigSHA = newConfigSHA
+	return nil
+}
diff --git a/internal/dataplane/parser/parser.go b/internal/dataplane/parser/parser.go
index f62054b0c..2d940af11 100644
--- a/internal/dataplane/parser/parser.go
+++ b/internal/dataplane/parser/parser.go
@@ -25,86 +25,84 @@ import (
 	configurationv1beta1 "github.com/kong/kubernetes-ingress-controller/v2/pkg/apis/configuration/v1beta1"
 )
 
-func parseAll(log logrus.FieldLogger, s store.Storer) ingressRules {
-	parsedIngressV1beta1 := fromIngressV1beta1(log, s.ListIngressesV1beta1())
-	parsedIngressV1 := fromIngressV1(log, s.ListIngressesV1())
-
-	tcpIngresses, err := s.ListTCPIngresses()
-	if err != nil {
-		log.Errorf("failed to list TCPIngresses: %v", err)
-	}
-	parsedTCPIngress := fromTCPIngressV1beta1(log, tcpIngresses)
-
-	udpIngresses, err := s.ListUDPIngresses()
-	if err != nil {
-		log.Errorf("failed to list UDPIngresses: %v", err)
-	}
-	parsedUDPIngresses := fromUDPIngressV1beta1(log, udpIngresses)
-
-	httproutes, err := s.ListHTTPRoutes()
-	if err != nil {
-		log.Errorf("failed to list HTTPRoutes: %w", err)
-	}
-	parsedHTTPRoutes, errs := ingressRulesFromHTTPRoutes(httproutes)
-	if len(errs) > 0 {
-		for _, err := range errs {
-			// we log the error here instead of returning it for historical reasons
-			// and because this allows other HTTPRoute objects to be resolved. This
-			// loop structure was common at the time of writing but needs significant
-			// refactor.
-			// See: https://github.com/Kong/kubernetes-ingress-controller/issues/2130
-			log.Errorf(err.Error())
-		}
-	}
+// -----------------------------------------------------------------------------
+// Parser - Public Types
+// -----------------------------------------------------------------------------
+
+// Parser parses Kubernetes objects and configurations into their
+// equivalent Kong objects and configurations, producing a complete
+// state configuration for the Kong Admin API.
+type Parser struct {
+	logger logrus.FieldLogger
+	storer store.Storer
+}
 
-	knativeIngresses, err := s.ListKnativeIngresses()
-	if err != nil {
-		log.Errorf("failed to list Knative Ingresses: %v", err)
+// NewParser produces a new Parser object provided a logging mechanism
+// and a Kubernetes object store.
+func NewParser(logger logrus.FieldLogger, storer store.Storer) *Parser {
+	return &Parser{
+		logger: logger,
+		storer: storer,
 	}
-	parsedKnative := fromKnativeIngress(log, knativeIngresses)
-
-	return mergeIngressRules(parsedIngressV1beta1, parsedIngressV1, parsedTCPIngress, parsedUDPIngresses, parsedKnative, parsedHTTPRoutes)
 }
 
+// -----------------------------------------------------------------------------
+// Parser - Public Methods
+// -----------------------------------------------------------------------------
+
 // Build creates a Kong configuration from Ingress and Custom resources
 // defined in Kuberentes.
 // It throws an error if there is an error returned from client-go.
-func Build(log logrus.FieldLogger, s store.Storer) (*kongstate.KongState, error) {
-	parsedAll := parseAll(log, s)
-	parsedAll.populateServices(log, s)
+func (p *Parser) Build() (*kongstate.KongState, error) {
+	// parse and merge all rules together from all Kubernetes API sources
+	ingressRules := mergeIngressRules(
+		p.ingressRulesFromIngressV1beta1(),
+		p.ingressRulesFromIngressV1(),
+		p.ingressRulesFromTCPIngressV1beta1(),
+		p.ingressRulesFromUDPIngressV1beta1(),
+		p.ingressRulesFromKnativeIngress(),
+		p.ingressRulesFromHTTPRoutes(),
+	)
+
+	// populate any Kubernetes Service objects relevant objects
+	ingressRules.populateServices(p.logger, p.storer)
 
-	var result kongstate.KongState
 	// add the routes and services to the state
-	for _, service := range parsedAll.ServiceNameToServices {
+	var result kongstate.KongState
+	for _, service := range ingressRules.ServiceNameToServices {
 		result.Services = append(result.Services, service)
 	}
 
 	// generate Upstreams and Targets from service defs
-	result.Upstreams = getUpstreams(log, s, parsedAll.ServiceNameToServices)
+	result.Upstreams = getUpstreams(p.logger, p.storer, ingressRules.ServiceNameToServices)
 
 	// merge KongIngress with Routes, Services and Upstream
-	result.FillOverrides(log, s)
+	result.FillOverrides(p.logger, p.storer)
 
 	// generate consumers and credentials
-	result.FillConsumersAndCredentials(log, s)
+	result.FillConsumersAndCredentials(p.logger, p.storer)
 
 	// process annotation plugins
-	result.FillPlugins(log, s)
+	result.FillPlugins(p.logger, p.storer)
 
 	// generate Certificates and SNIs
-	result.Certificates = getCerts(log, s, parsedAll.SecretNameToSNIs)
+	result.Certificates = getCerts(p.logger, p.storer, ingressRules.SecretNameToSNIs)
 
 	// populate CA certificates in Kong
 	var err error
-	caCertSecrets, err := s.ListCACerts()
+	caCertSecrets, err := p.storer.ListCACerts()
 	if err != nil {
 		return nil, err
 	}
-	result.CACertificates = toCACerts(log, caCertSecrets)
+	result.CACertificates = toCACerts(p.logger, caCertSecrets)
 
 	return &result, nil
 }
 
+// -----------------------------------------------------------------------------
+// Parser - Private Methods
+// -----------------------------------------------------------------------------
+
 func toCACerts(log logrus.FieldLogger, caCertSecrets []*corev1.Secret) []kong.CACertificate {
 	var caCerts []kong.CACertificate
 	for _, certSecret := range caCertSecrets {
diff --git a/internal/dataplane/parser/translate.go b/internal/dataplane/parser/translate.go
index 25d37786d..1e4f6bd9e 100644
--- a/internal/dataplane/parser/translate.go
+++ b/internal/dataplane/parser/translate.go
@@ -2,20 +2,13 @@ package parser
 
 import (
 	"fmt"
-	"sort"
-	"strconv"
 	"strings"
 
 	"github.com/kong/go-kong/kong"
-	"github.com/sirupsen/logrus"
 	networkingv1 "k8s.io/api/networking/v1"
-	networkingv1beta1 "k8s.io/api/networking/v1beta1"
 	"k8s.io/apimachinery/pkg/util/intstr"
-	knative "knative.dev/networking/pkg/apis/networking/v1alpha1"
 
 	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/kongstate"
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/util"
-	configurationv1beta1 "github.com/kong/kubernetes-ingress-controller/v2/pkg/apis/configuration/v1beta1"
 )
 
 func serviceBackendPortToStr(port networkingv1.ServiceBackendPort) string {
@@ -25,555 +18,6 @@ func serviceBackendPortToStr(port networkingv1.ServiceBackendPort) string {
 	return fmt.Sprintf("pnum-%d", port.Number)
 }
 
-func fromIngressV1beta1(log logrus.FieldLogger, ingressList []*networkingv1beta1.Ingress) ingressRules {
-	result := newIngressRules()
-
-	var allDefaultBackends []networkingv1beta1.Ingress
-	sort.SliceStable(ingressList, func(i, j int) bool {
-		return ingressList[i].CreationTimestamp.Before(
-			&ingressList[j].CreationTimestamp)
-	})
-
-	for _, ingress := range ingressList {
-		ingressSpec := ingress.Spec
-		log = log.WithFields(logrus.Fields{
-			"ingress_namespace": ingress.Namespace,
-			"ingress_name":      ingress.Name,
-		})
-
-		if ingressSpec.Backend != nil {
-			allDefaultBackends = append(allDefaultBackends, *ingress)
-		}
-
-		result.SecretNameToSNIs.addFromIngressV1beta1TLS(ingressSpec.TLS, ingress.Namespace)
-
-		for i, rule := range ingressSpec.Rules {
-			host := rule.Host
-			if rule.HTTP == nil {
-				continue
-			}
-			for j, rule := range rule.HTTP.Paths {
-				path := rule.Path
-
-				if strings.Contains(path, "//") {
-					log.Errorf("rule skipped: invalid path: '%v'", path)
-					continue
-				}
-				if path == "" {
-					path = "/"
-				}
-				r := kongstate.Route{
-					Ingress: util.FromK8sObject(ingress),
-					Route: kong.Route{
-						Name:              kong.String(fmt.Sprintf("%s.%s.%d%d", ingress.Namespace, ingress.Name, i, j)),
-						Paths:             kong.StringSlice(path),
-						StripPath:         kong.Bool(false),
-						PreserveHost:      kong.Bool(true),
-						Protocols:         kong.StringSlice("http", "https"),
-						RegexPriority:     kong.Int(0),
-						RequestBuffering:  kong.Bool(true),
-						ResponseBuffering: kong.Bool(true),
-					},
-				}
-				if host != "" {
-					hosts := kong.StringSlice(host)
-					r.Hosts = hosts
-				}
-
-				serviceName := ingress.Namespace + "." +
-					rule.Backend.ServiceName + "." +
-					rule.Backend.ServicePort.String()
-				service, ok := result.ServiceNameToServices[serviceName]
-				if !ok {
-					service = kongstate.Service{
-						Service: kong.Service{
-							Name: kong.String(serviceName),
-							Host: kong.String(rule.Backend.ServiceName +
-								"." + ingress.Namespace + "." +
-								rule.Backend.ServicePort.String() + ".svc"),
-							Port:           kong.Int(DefaultHTTPPort),
-							Protocol:       kong.String("http"),
-							Path:           kong.String("/"),
-							ConnectTimeout: kong.Int(DefaultServiceTimeout),
-							ReadTimeout:    kong.Int(DefaultServiceTimeout),
-							WriteTimeout:   kong.Int(DefaultServiceTimeout),
-							Retries:        kong.Int(DefaultRetries),
-						},
-						Namespace: ingress.Namespace,
-						Backend: kongstate.ServiceBackend{
-							Name: rule.Backend.ServiceName,
-							Port: PortDefFromIntStr(rule.Backend.ServicePort),
-						},
-					}
-				}
-				service.Routes = append(service.Routes, r)
-				result.ServiceNameToServices[serviceName] = service
-			}
-		}
-	}
-
-	sort.SliceStable(allDefaultBackends, func(i, j int) bool {
-		return allDefaultBackends[i].CreationTimestamp.Before(&allDefaultBackends[j].CreationTimestamp)
-	})
-
-	// Process the default backend
-	if len(allDefaultBackends) > 0 {
-		ingress := allDefaultBackends[0]
-		defaultBackend := allDefaultBackends[0].Spec.Backend
-		serviceName := allDefaultBackends[0].Namespace + "." +
-			defaultBackend.ServiceName + "." +
-			defaultBackend.ServicePort.String()
-		service, ok := result.ServiceNameToServices[serviceName]
-		if !ok {
-			service = kongstate.Service{
-				Service: kong.Service{
-					Name: kong.String(serviceName),
-					Host: kong.String(defaultBackend.ServiceName + "." +
-						ingress.Namespace + "." +
-						defaultBackend.ServicePort.String() + ".svc"),
-					Port:           kong.Int(DefaultHTTPPort),
-					Protocol:       kong.String("http"),
-					ConnectTimeout: kong.Int(DefaultServiceTimeout),
-					ReadTimeout:    kong.Int(DefaultServiceTimeout),
-					WriteTimeout:   kong.Int(DefaultServiceTimeout),
-					Retries:        kong.Int(DefaultRetries),
-				},
-				Namespace: ingress.Namespace,
-				Backend: kongstate.ServiceBackend{
-					Name: defaultBackend.ServiceName,
-					Port: PortDefFromIntStr(defaultBackend.ServicePort),
-				},
-			}
-		}
-		r := kongstate.Route{
-			Ingress: util.FromK8sObject(&ingress),
-			Route: kong.Route{
-				Name:              kong.String(ingress.Namespace + "." + ingress.Name),
-				Paths:             kong.StringSlice("/"),
-				StripPath:         kong.Bool(false),
-				PreserveHost:      kong.Bool(true),
-				Protocols:         kong.StringSlice("http", "https"),
-				RegexPriority:     kong.Int(0),
-				RequestBuffering:  kong.Bool(true),
-				ResponseBuffering: kong.Bool(true),
-			},
-		}
-		service.Routes = append(service.Routes, r)
-		result.ServiceNameToServices[serviceName] = service
-	}
-
-	return result
-}
-
-func fromIngressV1(log logrus.FieldLogger, ingressList []*networkingv1.Ingress) ingressRules {
-	result := newIngressRules()
-
-	var allDefaultBackends []networkingv1.Ingress
-	sort.SliceStable(ingressList, func(i, j int) bool {
-		return ingressList[i].CreationTimestamp.Before(
-			&ingressList[j].CreationTimestamp)
-	})
-
-	for _, ingress := range ingressList {
-		ingressSpec := ingress.Spec
-		log = log.WithFields(logrus.Fields{
-			"ingress_namespace": ingress.Namespace,
-			"ingress_name":      ingress.Name,
-		})
-
-		if ingressSpec.DefaultBackend != nil {
-			allDefaultBackends = append(allDefaultBackends, *ingress)
-		}
-
-		result.SecretNameToSNIs.addFromIngressV1TLS(ingressSpec.TLS, ingress.Namespace)
-
-		for i, rule := range ingressSpec.Rules {
-			if rule.HTTP == nil {
-				continue
-			}
-			for j, rulePath := range rule.HTTP.Paths {
-				if strings.Contains(rulePath.Path, "//") {
-					log.Errorf("rule skipped: invalid path: '%v'", rulePath.Path)
-					continue
-				}
-
-				pathType := networkingv1.PathTypeImplementationSpecific
-				if rulePath.PathType != nil {
-					pathType = *rulePath.PathType
-				}
-
-				paths, err := pathsFromK8s(rulePath.Path, pathType)
-				if err != nil {
-					log.Errorf("rule skipped: pathsFromK8s: %v", err)
-					continue
-				}
-
-				r := kongstate.Route{
-					Ingress: util.FromK8sObject(ingress),
-					Route: kong.Route{
-						Name:              kong.String(fmt.Sprintf("%s.%s.%d%d", ingress.Namespace, ingress.Name, i, j)),
-						Paths:             paths,
-						StripPath:         kong.Bool(false),
-						PreserveHost:      kong.Bool(true),
-						Protocols:         kong.StringSlice("http", "https"),
-						RegexPriority:     kong.Int(priorityForPath[pathType]),
-						RequestBuffering:  kong.Bool(true),
-						ResponseBuffering: kong.Bool(true),
-					},
-				}
-				if rule.Host != "" {
-					r.Hosts = kong.StringSlice(rule.Host)
-				}
-
-				port := PortDefFromServiceBackendPort(&rulePath.Backend.Service.Port)
-				serviceName := fmt.Sprintf("%s.%s.%s", ingress.Namespace, rulePath.Backend.Service.Name,
-					serviceBackendPortToStr(rulePath.Backend.Service.Port))
-				service, ok := result.ServiceNameToServices[serviceName]
-				if !ok {
-					service = kongstate.Service{
-						Service: kong.Service{
-							Name: kong.String(serviceName),
-							Host: kong.String(fmt.Sprintf("%s.%s.%s.svc", rulePath.Backend.Service.Name, ingress.Namespace,
-								port.CanonicalString())),
-							Port:           kong.Int(DefaultHTTPPort),
-							Protocol:       kong.String("http"),
-							Path:           kong.String("/"),
-							ConnectTimeout: kong.Int(DefaultServiceTimeout),
-							ReadTimeout:    kong.Int(DefaultServiceTimeout),
-							WriteTimeout:   kong.Int(DefaultServiceTimeout),
-							Retries:        kong.Int(DefaultRetries),
-						},
-						Namespace: ingress.Namespace,
-						Backend: kongstate.ServiceBackend{
-							Name: rulePath.Backend.Service.Name,
-							Port: port,
-						},
-					}
-				}
-				service.Routes = append(service.Routes, r)
-				result.ServiceNameToServices[serviceName] = service
-			}
-		}
-	}
-
-	sort.SliceStable(allDefaultBackends, func(i, j int) bool {
-		return allDefaultBackends[i].CreationTimestamp.Before(&allDefaultBackends[j].CreationTimestamp)
-	})
-
-	// Process the default backend
-	if len(allDefaultBackends) > 0 {
-		ingress := allDefaultBackends[0]
-		defaultBackend := allDefaultBackends[0].Spec.DefaultBackend
-		port := PortDefFromServiceBackendPort(&defaultBackend.Service.Port)
-		serviceName := fmt.Sprintf("%s.%s.%s", allDefaultBackends[0].Namespace, defaultBackend.Service.Name,
-			port.CanonicalString())
-		service, ok := result.ServiceNameToServices[serviceName]
-		if !ok {
-			service = kongstate.Service{
-				Service: kong.Service{
-					Name: kong.String(serviceName),
-					Host: kong.String(fmt.Sprintf("%s.%s.%d.svc", defaultBackend.Service.Name, ingress.Namespace,
-						defaultBackend.Service.Port.Number)),
-					Port:           kong.Int(DefaultHTTPPort),
-					Protocol:       kong.String("http"),
-					ConnectTimeout: kong.Int(DefaultServiceTimeout),
-					ReadTimeout:    kong.Int(DefaultServiceTimeout),
-					WriteTimeout:   kong.Int(DefaultServiceTimeout),
-					Retries:        kong.Int(DefaultRetries),
-				},
-				Namespace: ingress.Namespace,
-				Backend: kongstate.ServiceBackend{
-					Name: defaultBackend.Service.Name,
-					Port: PortDefFromServiceBackendPort(&defaultBackend.Service.Port),
-				},
-			}
-		}
-		r := kongstate.Route{
-			Ingress: util.FromK8sObject(&ingress),
-			Route: kong.Route{
-				Name:              kong.String(ingress.Namespace + "." + ingress.Name),
-				Paths:             kong.StringSlice("/"),
-				StripPath:         kong.Bool(false),
-				PreserveHost:      kong.Bool(true),
-				Protocols:         kong.StringSlice("http", "https"),
-				RegexPriority:     kong.Int(0),
-				RequestBuffering:  kong.Bool(true),
-				ResponseBuffering: kong.Bool(true),
-			},
-		}
-		service.Routes = append(service.Routes, r)
-		result.ServiceNameToServices[serviceName] = service
-	}
-
-	return result
-}
-
-func fromTCPIngressV1beta1(log logrus.FieldLogger, tcpIngressList []*configurationv1beta1.TCPIngress) ingressRules {
-	result := newIngressRules()
-
-	sort.SliceStable(tcpIngressList, func(i, j int) bool {
-		return tcpIngressList[i].CreationTimestamp.Before(
-			&tcpIngressList[j].CreationTimestamp)
-	})
-
-	for _, ingress := range tcpIngressList {
-		ingressSpec := ingress.Spec
-
-		log = log.WithFields(logrus.Fields{
-			"tcpingress_namespace": ingress.Namespace,
-			"tcpingress_name":      ingress.Name,
-		})
-
-		result.SecretNameToSNIs.addFromIngressV1beta1TLS(tcpIngressToNetworkingTLS(ingressSpec.TLS), ingress.Namespace)
-
-		for i, rule := range ingressSpec.Rules {
-			if !util.IsValidPort(rule.Port) {
-				log.Errorf("invalid TCPIngress: invalid port: %v", rule.Port)
-				continue
-			}
-			r := kongstate.Route{
-				Ingress: util.FromK8sObject(ingress),
-				Route: kong.Route{
-					Name:      kong.String(ingress.Namespace + "." + ingress.Name + "." + strconv.Itoa(i)),
-					Protocols: kong.StringSlice("tcp", "tls"),
-					Destinations: []*kong.CIDRPort{
-						{
-							Port: kong.Int(rule.Port),
-						},
-					},
-				},
-			}
-			host := rule.Host
-			if host != "" {
-				r.SNIs = kong.StringSlice(host)
-			}
-			if rule.Backend.ServiceName == "" {
-				log.Errorf("invalid TCPIngress: empty serviceName")
-				continue
-			}
-			if !util.IsValidPort(rule.Backend.ServicePort) {
-				log.Errorf("invalid TCPIngress: invalid servicePort: %v", rule.Backend.ServicePort)
-				continue
-			}
-
-			serviceName := fmt.Sprintf("%s.%s.%d", ingress.Namespace, rule.Backend.ServiceName, rule.Backend.ServicePort)
-			service, ok := result.ServiceNameToServices[serviceName]
-			if !ok {
-				service = kongstate.Service{
-					Service: kong.Service{
-						Name: kong.String(serviceName),
-						Host: kong.String(fmt.Sprintf("%s.%s.%d.svc", rule.Backend.ServiceName, ingress.Namespace,
-							rule.Backend.ServicePort)),
-						Port:           kong.Int(DefaultHTTPPort),
-						Protocol:       kong.String("tcp"),
-						ConnectTimeout: kong.Int(DefaultServiceTimeout),
-						ReadTimeout:    kong.Int(DefaultServiceTimeout),
-						WriteTimeout:   kong.Int(DefaultServiceTimeout),
-						Retries:        kong.Int(DefaultRetries),
-					},
-					Namespace: ingress.Namespace,
-					Backend: kongstate.ServiceBackend{
-						Name: rule.Backend.ServiceName,
-						Port: kongstate.PortDef{Mode: kongstate.PortModeByNumber, Number: int32(rule.Backend.ServicePort)},
-					},
-				}
-			}
-			service.Routes = append(service.Routes, r)
-			result.ServiceNameToServices[serviceName] = service
-		}
-	}
-
-	return result
-}
-
-func fromUDPIngressV1beta1(log logrus.FieldLogger, ingressList []*configurationv1beta1.UDPIngress) ingressRules {
-	result := newIngressRules()
-
-	sort.SliceStable(ingressList, func(i, j int) bool {
-		return ingressList[i].CreationTimestamp.Before(&ingressList[j].CreationTimestamp)
-	})
-
-	for _, ingress := range ingressList {
-		ingressSpec := ingress.Spec
-
-		log = log.WithFields(logrus.Fields{
-			"udpingress_namespace": ingress.Namespace,
-			"udpingress_name":      ingress.Name,
-		})
-
-		for i, rule := range ingressSpec.Rules {
-			// validate the ports and servicenames for the rule
-			if !util.IsValidPort(rule.Port) {
-				log.Errorf("invalid UDPIngress: invalid port: %d", rule.Port)
-				continue
-			}
-			if rule.Backend.ServiceName == "" {
-				log.Errorf("invalid UDPIngress: empty serviceName")
-				continue
-			}
-			if !util.IsValidPort(rule.Backend.ServicePort) {
-				log.Errorf("invalid UDPIngress: invalid servicePort: %d", rule.Backend.ServicePort)
-				continue
-			}
-
-			// generate the kong Route based on the listen port
-			route := kongstate.Route{
-				Ingress: util.FromK8sObject(ingress),
-				Route: kong.Route{
-					Name:         kong.String(ingress.Namespace + "." + ingress.Name + "." + strconv.Itoa(i) + ".udp"),
-					Protocols:    kong.StringSlice("udp"),
-					Destinations: []*kong.CIDRPort{{Port: kong.Int(rule.Port)}},
-				},
-			}
-
-			// generate the kong Service backend for the UDPIngress rules
-			host := fmt.Sprintf("%s.%s.%d.svc", rule.Backend.ServiceName, ingress.Namespace, rule.Backend.ServicePort)
-			serviceName := fmt.Sprintf("%s.%s.%d.udp", ingress.Namespace, rule.Backend.ServiceName, rule.Backend.ServicePort)
-			service, ok := result.ServiceNameToServices[serviceName]
-			if !ok {
-				service = kongstate.Service{
-					Namespace: ingress.Namespace,
-					Service: kong.Service{
-						Name:     kong.String(serviceName),
-						Protocol: kong.String("udp"),
-						Host:     kong.String(host),
-						Port:     kong.Int(rule.Backend.ServicePort),
-					},
-					Backend: kongstate.ServiceBackend{
-						Name: rule.Backend.ServiceName,
-						Port: kongstate.PortDef{Mode: kongstate.PortModeByNumber, Number: int32(rule.Backend.ServicePort)},
-					},
-				}
-			}
-			service.Routes = append(service.Routes, route)
-			result.ServiceNameToServices[serviceName] = service
-		}
-	}
-
-	return result
-}
-
-func fromKnativeIngress(log logrus.FieldLogger, ingressList []*knative.Ingress) ingressRules {
-
-	sort.SliceStable(ingressList, func(i, j int) bool {
-		return ingressList[i].CreationTimestamp.Before(
-			&ingressList[j].CreationTimestamp)
-	})
-
-	services := map[string]kongstate.Service{}
-	secretToSNIs := newSecretNameToSNIs()
-
-	for _, ingress := range ingressList {
-		log = log.WithFields(logrus.Fields{
-			"knativeingress_namespace": ingress.Namespace,
-			"knativeingress_name":      ingress.Name,
-		})
-
-		ingressSpec := ingress.Spec
-
-		secretToSNIs.addFromIngressV1beta1TLS(knativeIngressToNetworkingTLS(ingress.Spec.TLS), ingress.Namespace)
-
-		for i, rule := range ingressSpec.Rules {
-			hosts := rule.Hosts
-			if rule.HTTP == nil {
-				continue
-			}
-			for j, rule := range rule.HTTP.Paths {
-				path := rule.Path
-
-				if path == "" {
-					path = "/"
-				}
-				r := kongstate.Route{
-					Ingress: util.FromK8sObject(ingress),
-					Route: kong.Route{
-						Name:              kong.String(fmt.Sprintf("%s.%s.%d%d", ingress.Namespace, ingress.Name, i, j)),
-						Paths:             kong.StringSlice(path),
-						StripPath:         kong.Bool(false),
-						PreserveHost:      kong.Bool(true),
-						Protocols:         kong.StringSlice("http", "https"),
-						RegexPriority:     kong.Int(0),
-						RequestBuffering:  kong.Bool(true),
-						ResponseBuffering: kong.Bool(true),
-					},
-				}
-				r.Hosts = kong.StringSlice(hosts...)
-
-				knativeBackend := knativeSelectSplit(rule.Splits)
-				serviceName := fmt.Sprintf("%s.%s.%s", knativeBackend.ServiceNamespace, knativeBackend.ServiceName,
-					knativeBackend.ServicePort.String())
-				serviceHost := fmt.Sprintf("%s.%s.%s.svc", knativeBackend.ServiceName, knativeBackend.ServiceNamespace,
-					knativeBackend.ServicePort.String())
-				service, ok := services[serviceName]
-				if !ok {
-
-					var headers []string
-					for key, value := range knativeBackend.AppendHeaders {
-						headers = append(headers, key+":"+value)
-					}
-					for key, value := range rule.AppendHeaders {
-						headers = append(headers, key+":"+value)
-					}
-
-					service = kongstate.Service{
-						Service: kong.Service{
-							Name:           kong.String(serviceName),
-							Host:           kong.String(serviceHost),
-							Port:           kong.Int(DefaultHTTPPort),
-							Protocol:       kong.String("http"),
-							Path:           kong.String("/"),
-							ConnectTimeout: kong.Int(DefaultServiceTimeout),
-							ReadTimeout:    kong.Int(DefaultServiceTimeout),
-							WriteTimeout:   kong.Int(DefaultServiceTimeout),
-							Retries:        kong.Int(DefaultRetries),
-						},
-						Namespace: ingress.Namespace,
-						Backend: kongstate.ServiceBackend{
-							Name: knativeBackend.ServiceName,
-							Port: PortDefFromIntStr(knativeBackend.ServicePort),
-						},
-					}
-					if len(headers) > 0 {
-						service.Plugins = append(service.Plugins, kong.Plugin{
-							Name: kong.String("request-transformer"),
-							Config: kong.Configuration{
-								"add": map[string]interface{}{
-									"headers": headers,
-								},
-							},
-						})
-					}
-				}
-				service.Routes = append(service.Routes, r)
-				services[serviceName] = service
-			}
-		}
-	}
-
-	return ingressRules{
-		ServiceNameToServices: services,
-		SecretNameToSNIs:      secretToSNIs,
-	}
-}
-
-func knativeSelectSplit(splits []knative.IngressBackendSplit) knative.IngressBackendSplit {
-	if len(splits) == 0 {
-		return knative.IngressBackendSplit{}
-	}
-	res := splits[0]
-	maxPercentage := splits[0].Percent
-	if len(splits) == 1 {
-		return res
-	}
-	for i := 1; i < len(splits); i++ {
-		if splits[i].Percent > maxPercentage {
-			res = splits[i]
-			maxPercentage = res.Percent
-		}
-	}
-	return res
-}
-
 func pathsFromK8s(path string, pathType networkingv1.PathType) ([]*string, error) {
 	switch pathType {
 	case networkingv1.PathTypePrefix:
diff --git a/internal/dataplane/parser/translate_httproute.go b/internal/dataplane/parser/translate_httproute.go
index 5395eb3a4..e86898421 100644
--- a/internal/dataplane/parser/translate_httproute.go
+++ b/internal/dataplane/parser/translate_httproute.go
@@ -16,9 +16,15 @@ import (
 
 // ingressRulesFromHTTPRoutes processes a list of HTTPRoute objects and translates
 // then into Kong configuration objects.
-func ingressRulesFromHTTPRoutes(httpRouteList []*gatewayv1alpha2.HTTPRoute) (ingressRules, []error) {
+func (p *Parser) ingressRulesFromHTTPRoutes() ingressRules {
 	result := newIngressRules()
 
+	httpRouteList, err := p.storer.ListHTTPRoutes()
+	if err != nil {
+		p.logger.Errorf("failed to list HTTPRoutes: %w", err)
+		return result
+	}
+
 	var errs []error
 	for _, httproute := range httpRouteList {
 		if err := ingressRulesFromHTTPRoute(&result, httproute); err != nil {
@@ -27,7 +33,13 @@ func ingressRulesFromHTTPRoutes(httpRouteList []*gatewayv1alpha2.HTTPRoute) (ing
 		}
 	}
 
-	return result, errs
+	if len(errs) > 0 {
+		for _, err := range errs {
+			p.logger.Errorf(err.Error())
+		}
+	}
+
+	return result
 }
 
 func ingressRulesFromHTTPRoute(result *ingressRules, httproute *gatewayv1alpha2.HTTPRoute) error {
diff --git a/internal/dataplane/parser/translate_ingress.go b/internal/dataplane/parser/translate_ingress.go
new file mode 100644
index 000000000..ccab6d740
--- /dev/null
+++ b/internal/dataplane/parser/translate_ingress.go
@@ -0,0 +1,302 @@
+package parser
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+
+	"github.com/kong/go-kong/kong"
+	"github.com/sirupsen/logrus"
+	networkingv1 "k8s.io/api/networking/v1"
+	networkingv1beta1 "k8s.io/api/networking/v1beta1"
+
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/kongstate"
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/util"
+)
+
+func (p *Parser) ingressRulesFromIngressV1beta1() ingressRules {
+	result := newIngressRules()
+
+	ingressList := p.storer.ListIngressesV1beta1()
+
+	var allDefaultBackends []networkingv1beta1.Ingress
+	sort.SliceStable(ingressList, func(i, j int) bool {
+		return ingressList[i].CreationTimestamp.Before(
+			&ingressList[j].CreationTimestamp)
+	})
+
+	for _, ingress := range ingressList {
+		ingressSpec := ingress.Spec
+		log := p.logger.WithFields(logrus.Fields{
+			"ingress_namespace": ingress.Namespace,
+			"ingress_name":      ingress.Name,
+		})
+
+		if ingressSpec.Backend != nil {
+			allDefaultBackends = append(allDefaultBackends, *ingress)
+		}
+
+		result.SecretNameToSNIs.addFromIngressV1beta1TLS(ingressSpec.TLS, ingress.Namespace)
+
+		for i, rule := range ingressSpec.Rules {
+			host := rule.Host
+			if rule.HTTP == nil {
+				continue
+			}
+			for j, rule := range rule.HTTP.Paths {
+				path := rule.Path
+
+				if strings.Contains(path, "//") {
+					log.Errorf("rule skipped: invalid path: '%v'", path)
+					continue
+				}
+				if path == "" {
+					path = "/"
+				}
+				r := kongstate.Route{
+					Ingress: util.FromK8sObject(ingress),
+					Route: kong.Route{
+						Name:              kong.String(fmt.Sprintf("%s.%s.%d%d", ingress.Namespace, ingress.Name, i, j)),
+						Paths:             kong.StringSlice(path),
+						StripPath:         kong.Bool(false),
+						PreserveHost:      kong.Bool(true),
+						Protocols:         kong.StringSlice("http", "https"),
+						RegexPriority:     kong.Int(0),
+						RequestBuffering:  kong.Bool(true),
+						ResponseBuffering: kong.Bool(true),
+					},
+				}
+				if host != "" {
+					hosts := kong.StringSlice(host)
+					r.Hosts = hosts
+				}
+
+				serviceName := ingress.Namespace + "." +
+					rule.Backend.ServiceName + "." +
+					rule.Backend.ServicePort.String()
+				service, ok := result.ServiceNameToServices[serviceName]
+				if !ok {
+					service = kongstate.Service{
+						Service: kong.Service{
+							Name: kong.String(serviceName),
+							Host: kong.String(rule.Backend.ServiceName +
+								"." + ingress.Namespace + "." +
+								rule.Backend.ServicePort.String() + ".svc"),
+							Port:           kong.Int(DefaultHTTPPort),
+							Protocol:       kong.String("http"),
+							Path:           kong.String("/"),
+							ConnectTimeout: kong.Int(DefaultServiceTimeout),
+							ReadTimeout:    kong.Int(DefaultServiceTimeout),
+							WriteTimeout:   kong.Int(DefaultServiceTimeout),
+							Retries:        kong.Int(DefaultRetries),
+						},
+						Namespace: ingress.Namespace,
+						Backend: kongstate.ServiceBackend{
+							Name: rule.Backend.ServiceName,
+							Port: PortDefFromIntStr(rule.Backend.ServicePort),
+						},
+					}
+				}
+				service.Routes = append(service.Routes, r)
+				result.ServiceNameToServices[serviceName] = service
+			}
+		}
+	}
+
+	sort.SliceStable(allDefaultBackends, func(i, j int) bool {
+		return allDefaultBackends[i].CreationTimestamp.Before(&allDefaultBackends[j].CreationTimestamp)
+	})
+
+	// Process the default backend
+	if len(allDefaultBackends) > 0 {
+		ingress := allDefaultBackends[0]
+		defaultBackend := allDefaultBackends[0].Spec.Backend
+		serviceName := allDefaultBackends[0].Namespace + "." +
+			defaultBackend.ServiceName + "." +
+			defaultBackend.ServicePort.String()
+		service, ok := result.ServiceNameToServices[serviceName]
+		if !ok {
+			service = kongstate.Service{
+				Service: kong.Service{
+					Name: kong.String(serviceName),
+					Host: kong.String(defaultBackend.ServiceName + "." +
+						ingress.Namespace + "." +
+						defaultBackend.ServicePort.String() + ".svc"),
+					Port:           kong.Int(DefaultHTTPPort),
+					Protocol:       kong.String("http"),
+					ConnectTimeout: kong.Int(DefaultServiceTimeout),
+					ReadTimeout:    kong.Int(DefaultServiceTimeout),
+					WriteTimeout:   kong.Int(DefaultServiceTimeout),
+					Retries:        kong.Int(DefaultRetries),
+				},
+				Namespace: ingress.Namespace,
+				Backend: kongstate.ServiceBackend{
+					Name: defaultBackend.ServiceName,
+					Port: PortDefFromIntStr(defaultBackend.ServicePort),
+				},
+			}
+		}
+		r := kongstate.Route{
+			Ingress: util.FromK8sObject(&ingress),
+			Route: kong.Route{
+				Name:              kong.String(ingress.Namespace + "." + ingress.Name),
+				Paths:             kong.StringSlice("/"),
+				StripPath:         kong.Bool(false),
+				PreserveHost:      kong.Bool(true),
+				Protocols:         kong.StringSlice("http", "https"),
+				RegexPriority:     kong.Int(0),
+				RequestBuffering:  kong.Bool(true),
+				ResponseBuffering: kong.Bool(true),
+			},
+		}
+		service.Routes = append(service.Routes, r)
+		result.ServiceNameToServices[serviceName] = service
+	}
+
+	return result
+}
+
+func (p *Parser) ingressRulesFromIngressV1() ingressRules {
+	result := newIngressRules()
+
+	ingressList := p.storer.ListIngressesV1()
+
+	var allDefaultBackends []networkingv1.Ingress
+	sort.SliceStable(ingressList, func(i, j int) bool {
+		return ingressList[i].CreationTimestamp.Before(
+			&ingressList[j].CreationTimestamp)
+	})
+
+	for _, ingress := range ingressList {
+		ingressSpec := ingress.Spec
+		log := p.logger.WithFields(logrus.Fields{
+			"ingress_namespace": ingress.Namespace,
+			"ingress_name":      ingress.Name,
+		})
+
+		if ingressSpec.DefaultBackend != nil {
+			allDefaultBackends = append(allDefaultBackends, *ingress)
+		}
+
+		result.SecretNameToSNIs.addFromIngressV1TLS(ingressSpec.TLS, ingress.Namespace)
+
+		for i, rule := range ingressSpec.Rules {
+			if rule.HTTP == nil {
+				continue
+			}
+			for j, rulePath := range rule.HTTP.Paths {
+				if strings.Contains(rulePath.Path, "//") {
+					log.Errorf("rule skipped: invalid path: '%v'", rulePath.Path)
+					continue
+				}
+
+				pathType := networkingv1.PathTypeImplementationSpecific
+				if rulePath.PathType != nil {
+					pathType = *rulePath.PathType
+				}
+
+				paths, err := pathsFromK8s(rulePath.Path, pathType)
+				if err != nil {
+					log.Errorf("rule skipped: pathsFromK8s: %v", err)
+					continue
+				}
+
+				r := kongstate.Route{
+					Ingress: util.FromK8sObject(ingress),
+					Route: kong.Route{
+						Name:              kong.String(fmt.Sprintf("%s.%s.%d%d", ingress.Namespace, ingress.Name, i, j)),
+						Paths:             paths,
+						StripPath:         kong.Bool(false),
+						PreserveHost:      kong.Bool(true),
+						Protocols:         kong.StringSlice("http", "https"),
+						RegexPriority:     kong.Int(priorityForPath[pathType]),
+						RequestBuffering:  kong.Bool(true),
+						ResponseBuffering: kong.Bool(true),
+					},
+				}
+				if rule.Host != "" {
+					r.Hosts = kong.StringSlice(rule.Host)
+				}
+
+				port := PortDefFromServiceBackendPort(&rulePath.Backend.Service.Port)
+				serviceName := fmt.Sprintf("%s.%s.%s", ingress.Namespace, rulePath.Backend.Service.Name,
+					serviceBackendPortToStr(rulePath.Backend.Service.Port))
+				service, ok := result.ServiceNameToServices[serviceName]
+				if !ok {
+					service = kongstate.Service{
+						Service: kong.Service{
+							Name: kong.String(serviceName),
+							Host: kong.String(fmt.Sprintf("%s.%s.%s.svc", rulePath.Backend.Service.Name, ingress.Namespace,
+								port.CanonicalString())),
+							Port:           kong.Int(DefaultHTTPPort),
+							Protocol:       kong.String("http"),
+							Path:           kong.String("/"),
+							ConnectTimeout: kong.Int(DefaultServiceTimeout),
+							ReadTimeout:    kong.Int(DefaultServiceTimeout),
+							WriteTimeout:   kong.Int(DefaultServiceTimeout),
+							Retries:        kong.Int(DefaultRetries),
+						},
+						Namespace: ingress.Namespace,
+						Backend: kongstate.ServiceBackend{
+							Name: rulePath.Backend.Service.Name,
+							Port: port,
+						},
+					}
+				}
+				service.Routes = append(service.Routes, r)
+				result.ServiceNameToServices[serviceName] = service
+			}
+		}
+	}
+
+	sort.SliceStable(allDefaultBackends, func(i, j int) bool {
+		return allDefaultBackends[i].CreationTimestamp.Before(&allDefaultBackends[j].CreationTimestamp)
+	})
+
+	// Process the default backend
+	if len(allDefaultBackends) > 0 {
+		ingress := allDefaultBackends[0]
+		defaultBackend := allDefaultBackends[0].Spec.DefaultBackend
+		port := PortDefFromServiceBackendPort(&defaultBackend.Service.Port)
+		serviceName := fmt.Sprintf("%s.%s.%s", allDefaultBackends[0].Namespace, defaultBackend.Service.Name,
+			port.CanonicalString())
+		service, ok := result.ServiceNameToServices[serviceName]
+		if !ok {
+			service = kongstate.Service{
+				Service: kong.Service{
+					Name: kong.String(serviceName),
+					Host: kong.String(fmt.Sprintf("%s.%s.%d.svc", defaultBackend.Service.Name, ingress.Namespace,
+						defaultBackend.Service.Port.Number)),
+					Port:           kong.Int(DefaultHTTPPort),
+					Protocol:       kong.String("http"),
+					ConnectTimeout: kong.Int(DefaultServiceTimeout),
+					ReadTimeout:    kong.Int(DefaultServiceTimeout),
+					WriteTimeout:   kong.Int(DefaultServiceTimeout),
+					Retries:        kong.Int(DefaultRetries),
+				},
+				Namespace: ingress.Namespace,
+				Backend: kongstate.ServiceBackend{
+					Name: defaultBackend.Service.Name,
+					Port: PortDefFromServiceBackendPort(&defaultBackend.Service.Port),
+				},
+			}
+		}
+		r := kongstate.Route{
+			Ingress: util.FromK8sObject(&ingress),
+			Route: kong.Route{
+				Name:              kong.String(ingress.Namespace + "." + ingress.Name),
+				Paths:             kong.StringSlice("/"),
+				StripPath:         kong.Bool(false),
+				PreserveHost:      kong.Bool(true),
+				Protocols:         kong.StringSlice("http", "https"),
+				RegexPriority:     kong.Int(0),
+				RequestBuffering:  kong.Bool(true),
+				ResponseBuffering: kong.Bool(true),
+			},
+		}
+		service.Routes = append(service.Routes, r)
+		result.ServiceNameToServices[serviceName] = service
+	}
+
+	return result
+}
diff --git a/internal/dataplane/parser/translate_knative.go b/internal/dataplane/parser/translate_knative.go
new file mode 100644
index 000000000..b8c87f0a4
--- /dev/null
+++ b/internal/dataplane/parser/translate_knative.go
@@ -0,0 +1,134 @@
+package parser
+
+import (
+	"fmt"
+	"sort"
+
+	"github.com/kong/go-kong/kong"
+	knative "knative.dev/networking/pkg/apis/networking/v1alpha1"
+
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/kongstate"
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/util"
+)
+
+func (p *Parser) ingressRulesFromKnativeIngress() ingressRules {
+	result := newIngressRules()
+
+	ingressList, err := p.storer.ListKnativeIngresses()
+	if err != nil {
+		p.logger.Errorf("failed to list Knative Ingresses: %v", err)
+		return result
+	}
+
+	sort.SliceStable(ingressList, func(i, j int) bool {
+		return ingressList[i].CreationTimestamp.Before(
+			&ingressList[j].CreationTimestamp)
+	})
+
+	services := map[string]kongstate.Service{}
+	secretToSNIs := newSecretNameToSNIs()
+
+	for _, ingress := range ingressList {
+		ingressSpec := ingress.Spec
+
+		secretToSNIs.addFromIngressV1beta1TLS(knativeIngressToNetworkingTLS(ingress.Spec.TLS), ingress.Namespace)
+
+		for i, rule := range ingressSpec.Rules {
+			hosts := rule.Hosts
+			if rule.HTTP == nil {
+				continue
+			}
+			for j, rule := range rule.HTTP.Paths {
+				path := rule.Path
+
+				if path == "" {
+					path = "/"
+				}
+				r := kongstate.Route{
+					Ingress: util.FromK8sObject(ingress),
+					Route: kong.Route{
+						Name:              kong.String(fmt.Sprintf("%s.%s.%d%d", ingress.Namespace, ingress.Name, i, j)),
+						Paths:             kong.StringSlice(path),
+						StripPath:         kong.Bool(false),
+						PreserveHost:      kong.Bool(true),
+						Protocols:         kong.StringSlice("http", "https"),
+						RegexPriority:     kong.Int(0),
+						RequestBuffering:  kong.Bool(true),
+						ResponseBuffering: kong.Bool(true),
+					},
+				}
+				r.Hosts = kong.StringSlice(hosts...)
+
+				knativeBackend := knativeSelectSplit(rule.Splits)
+				serviceName := fmt.Sprintf("%s.%s.%s", knativeBackend.ServiceNamespace, knativeBackend.ServiceName,
+					knativeBackend.ServicePort.String())
+				serviceHost := fmt.Sprintf("%s.%s.%s.svc", knativeBackend.ServiceName, knativeBackend.ServiceNamespace,
+					knativeBackend.ServicePort.String())
+				service, ok := services[serviceName]
+				if !ok {
+
+					var headers []string
+					for key, value := range knativeBackend.AppendHeaders {
+						headers = append(headers, key+":"+value)
+					}
+					for key, value := range rule.AppendHeaders {
+						headers = append(headers, key+":"+value)
+					}
+
+					service = kongstate.Service{
+						Service: kong.Service{
+							Name:           kong.String(serviceName),
+							Host:           kong.String(serviceHost),
+							Port:           kong.Int(DefaultHTTPPort),
+							Protocol:       kong.String("http"),
+							Path:           kong.String("/"),
+							ConnectTimeout: kong.Int(DefaultServiceTimeout),
+							ReadTimeout:    kong.Int(DefaultServiceTimeout),
+							WriteTimeout:   kong.Int(DefaultServiceTimeout),
+							Retries:        kong.Int(DefaultRetries),
+						},
+						Namespace: ingress.Namespace,
+						Backend: kongstate.ServiceBackend{
+							Name: knativeBackend.ServiceName,
+							Port: PortDefFromIntStr(knativeBackend.ServicePort),
+						},
+					}
+					if len(headers) > 0 {
+						service.Plugins = append(service.Plugins, kong.Plugin{
+							Name: kong.String("request-transformer"),
+							Config: kong.Configuration{
+								"add": map[string]interface{}{
+									"headers": headers,
+								},
+							},
+						})
+					}
+				}
+				service.Routes = append(service.Routes, r)
+				services[serviceName] = service
+			}
+		}
+	}
+
+	result.ServiceNameToServices = services
+	result.SecretNameToSNIs = secretToSNIs
+	return result
+}
+
+func knativeSelectSplit(splits []knative.IngressBackendSplit) knative.IngressBackendSplit {
+	if len(splits) == 0 {
+		return knative.IngressBackendSplit{}
+	}
+	res := splits[0]
+	maxPercentage := splits[0].Percent
+	if len(splits) == 1 {
+		return res
+	}
+	for i := 1; i < len(splits); i++ {
+		if splits[i].Percent > maxPercentage {
+			res = splits[i]
+			maxPercentage = res.Percent
+		}
+	}
+	return res
+}
diff --git a/internal/dataplane/parser/translate_kong_l4.go b/internal/dataplane/parser/translate_kong_l4.go
new file mode 100644
index 000000000..745f05b58
--- /dev/null
+++ b/internal/dataplane/parser/translate_kong_l4.go
@@ -0,0 +1,170 @@
+package parser
+
+import (
+	"fmt"
+	"sort"
+	"strconv"
+
+	"github.com/kong/go-kong/kong"
+	"github.com/sirupsen/logrus"
+
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/kongstate"
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/util"
+)
+
+func (p *Parser) ingressRulesFromTCPIngressV1beta1() ingressRules {
+	result := newIngressRules()
+
+	ingressList, err := p.storer.ListTCPIngresses()
+	if err != nil {
+		p.logger.Errorf("failed to list TCPIngresses: %v", err)
+		return result
+	}
+
+	sort.SliceStable(ingressList, func(i, j int) bool {
+		return ingressList[i].CreationTimestamp.Before(
+			&ingressList[j].CreationTimestamp)
+	})
+
+	for _, ingress := range ingressList {
+		ingressSpec := ingress.Spec
+
+		log := p.logger.WithFields(logrus.Fields{
+			"tcpingress_namespace": ingress.Namespace,
+			"tcpingress_name":      ingress.Name,
+		})
+
+		result.SecretNameToSNIs.addFromIngressV1beta1TLS(tcpIngressToNetworkingTLS(ingressSpec.TLS), ingress.Namespace)
+
+		for i, rule := range ingressSpec.Rules {
+			if !util.IsValidPort(rule.Port) {
+				log.Errorf("invalid TCPIngress: invalid port: %v", rule.Port)
+				continue
+			}
+			r := kongstate.Route{
+				Ingress: util.FromK8sObject(ingress),
+				Route: kong.Route{
+					Name:      kong.String(ingress.Namespace + "." + ingress.Name + "." + strconv.Itoa(i)),
+					Protocols: kong.StringSlice("tcp", "tls"),
+					Destinations: []*kong.CIDRPort{
+						{
+							Port: kong.Int(rule.Port),
+						},
+					},
+				},
+			}
+			host := rule.Host
+			if host != "" {
+				r.SNIs = kong.StringSlice(host)
+			}
+			if rule.Backend.ServiceName == "" {
+				log.Errorf("invalid TCPIngress: empty serviceName")
+				continue
+			}
+			if !util.IsValidPort(rule.Backend.ServicePort) {
+				log.Errorf("invalid TCPIngress: invalid servicePort: %v", rule.Backend.ServicePort)
+				continue
+			}
+
+			serviceName := fmt.Sprintf("%s.%s.%d", ingress.Namespace, rule.Backend.ServiceName, rule.Backend.ServicePort)
+			service, ok := result.ServiceNameToServices[serviceName]
+			if !ok {
+				service = kongstate.Service{
+					Service: kong.Service{
+						Name: kong.String(serviceName),
+						Host: kong.String(fmt.Sprintf("%s.%s.%d.svc", rule.Backend.ServiceName, ingress.Namespace,
+							rule.Backend.ServicePort)),
+						Port:           kong.Int(DefaultHTTPPort),
+						Protocol:       kong.String("tcp"),
+						ConnectTimeout: kong.Int(DefaultServiceTimeout),
+						ReadTimeout:    kong.Int(DefaultServiceTimeout),
+						WriteTimeout:   kong.Int(DefaultServiceTimeout),
+						Retries:        kong.Int(DefaultRetries),
+					},
+					Namespace: ingress.Namespace,
+					Backend: kongstate.ServiceBackend{
+						Name: rule.Backend.ServiceName,
+						Port: kongstate.PortDef{Mode: kongstate.PortModeByNumber, Number: int32(rule.Backend.ServicePort)},
+					},
+				}
+			}
+			service.Routes = append(service.Routes, r)
+			result.ServiceNameToServices[serviceName] = service
+		}
+	}
+
+	return result
+}
+
+func (p *Parser) ingressRulesFromUDPIngressV1beta1() ingressRules {
+	result := newIngressRules()
+
+	ingressList, err := p.storer.ListUDPIngresses()
+	if err != nil {
+		p.logger.Errorf("failed to list UDPIngresses: %v", err)
+		return result
+	}
+
+	sort.SliceStable(ingressList, func(i, j int) bool {
+		return ingressList[i].CreationTimestamp.Before(&ingressList[j].CreationTimestamp)
+	})
+
+	for _, ingress := range ingressList {
+		ingressSpec := ingress.Spec
+
+		log := p.logger.WithFields(logrus.Fields{
+			"udpingress_namespace": ingress.Namespace,
+			"udpingress_name":      ingress.Name,
+		})
+
+		for i, rule := range ingressSpec.Rules {
+			// validate the ports and servicenames for the rule
+			if !util.IsValidPort(rule.Port) {
+				log.Errorf("invalid UDPIngress: invalid port: %d", rule.Port)
+				continue
+			}
+			if rule.Backend.ServiceName == "" {
+				log.Errorf("invalid UDPIngress: empty serviceName")
+				continue
+			}
+			if !util.IsValidPort(rule.Backend.ServicePort) {
+				log.Errorf("invalid UDPIngress: invalid servicePort: %d", rule.Backend.ServicePort)
+				continue
+			}
+
+			// generate the kong Route based on the listen port
+			route := kongstate.Route{
+				Ingress: util.FromK8sObject(ingress),
+				Route: kong.Route{
+					Name:         kong.String(ingress.Namespace + "." + ingress.Name + "." + strconv.Itoa(i) + ".udp"),
+					Protocols:    kong.StringSlice("udp"),
+					Destinations: []*kong.CIDRPort{{Port: kong.Int(rule.Port)}},
+				},
+			}
+
+			// generate the kong Service backend for the UDPIngress rules
+			host := fmt.Sprintf("%s.%s.%d.svc", rule.Backend.ServiceName, ingress.Namespace, rule.Backend.ServicePort)
+			serviceName := fmt.Sprintf("%s.%s.%d.udp", ingress.Namespace, rule.Backend.ServiceName, rule.Backend.ServicePort)
+			service, ok := result.ServiceNameToServices[serviceName]
+			if !ok {
+				service = kongstate.Service{
+					Namespace: ingress.Namespace,
+					Service: kong.Service{
+						Name:     kong.String(serviceName),
+						Protocol: kong.String("udp"),
+						Host:     kong.String(host),
+						Port:     kong.Int(rule.Backend.ServicePort),
+					},
+					Backend: kongstate.ServiceBackend{
+						Name: rule.Backend.ServiceName,
+						Port: kongstate.PortDef{Mode: kongstate.PortModeByNumber, Number: int32(rule.Backend.ServicePort)},
+					},
+				}
+			}
+			service.Routes = append(service.Routes, route)
+			result.ServiceNameToServices[serviceName] = service
+		}
+	}
+
+	return result
+}
diff --git a/internal/dataplane/proxy/clientgo_cached_proxy_resolver.go b/internal/dataplane/proxy/clientgo_cached_proxy_resolver.go
deleted file mode 100644
index 169127735..000000000
--- a/internal/dataplane/proxy/clientgo_cached_proxy_resolver.go
+++ /dev/null
@@ -1,305 +0,0 @@
-package proxy
-
-import (
-	"context"
-	"fmt"
-	"sync"
-	"time"
-
-	"github.com/blang/semver/v4"
-	"github.com/bombsimon/logrusr/v2"
-	"github.com/go-logr/logr"
-	"github.com/kong/go-kong/kong"
-	"github.com/sirupsen/logrus"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/sendconfig"
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/metrics"
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/store"
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/util"
-)
-
-// -----------------------------------------------------------------------------
-// Client Go Cached Proxy Resolver - Public Functions
-// -----------------------------------------------------------------------------
-
-// NewCacheBasedProxy will provide a new Proxy object. Note that this starts some background goroutines and the
-// caller is resonsible for marking the provided context.Context as "Done()" to shut down the background routines.
-func NewCacheBasedProxy(logger logrus.FieldLogger,
-	k8s client.Client,
-	kongConfig sendconfig.Kong,
-	ingressClassName string,
-	enableReverseSync bool,
-	kongUpdater KongUpdater,
-	diagnostic util.ConfigDumpDiagnostic,
-	proxyRequestTimeout time.Duration,
-) (Proxy, error) {
-	stagger, err := time.ParseDuration(fmt.Sprintf("%gs", DefaultSyncSeconds))
-	if err != nil {
-		return nil, err
-	}
-	return NewCacheBasedProxyWithStagger(logger, k8s, kongConfig, ingressClassName, enableReverseSync, stagger, proxyRequestTimeout, diagnostic, kongUpdater)
-}
-
-// NewCacheBasedProxy will provide a new Proxy object. Note that this starts some background goroutines and the caller
-// is resonsible for marking the provided context.Context as "Done()" to shut down the background routines. A "stagger"
-// time duration is provided to indicate how often the background routines will sync configuration to the Kong Admin API.
-func NewCacheBasedProxyWithStagger(logger logrus.FieldLogger,
-	k8s client.Client,
-	kongConfig sendconfig.Kong,
-	ingressClassName string,
-	enableReverseSync bool,
-	stagger time.Duration,
-	proxyRequestTimeout time.Duration,
-	diagnostic util.ConfigDumpDiagnostic,
-	kongUpdater KongUpdater,
-) (Proxy, error) {
-	// configure the cachestores and the proxy instance
-	cache := store.NewCacheStores()
-	proxy := &clientgoCachedProxyResolver{
-		cache: &cache,
-
-		kongConfig:        kongConfig,
-		kongUpdater:       kongUpdater,
-		diagnostic:        diagnostic,
-		enableReverseSync: enableReverseSync,
-
-		deprecatedLogger: logger,
-		logger:           logrusr.New(logger),
-
-		ingressClassName: ingressClassName,
-		stopCh:           make(chan struct{}),
-
-		stagger:             stagger,
-		proxyRequestTimeout: proxyRequestTimeout,
-		syncTicker:          time.NewTicker(stagger),
-
-		configApplied: false,
-	}
-
-	// initialize the proxy which validates connectivity with the Admin API and
-	// checks several proxy server attributes such as version and dbmode.
-	if err := proxy.initialize(); err != nil {
-		return nil, err
-	}
-
-	return proxy, nil
-}
-
-// -----------------------------------------------------------------------------
-// Client Go Cached Proxy Resolver - Private Types
-// -----------------------------------------------------------------------------
-
-// clientgoCachedProxyResolver represents the cached objects and Kong DSL configuration.
-//
-// This implements the Proxy interface to provide asynchronous, non-blocking updates to
-// the Kong Admin API for controller-runtime based controller managers.
-//
-// This object's attributes are immutable (private), and it is threadsafe.
-type clientgoCachedProxyResolver struct {
-	// kubernetes configuration
-	cache *store.CacheStores
-
-	// lastConfigSHA indicates the last SHA sum for the last configuration
-	// updated in the Kong Proxy and is used to avoid making unnecessary updates.
-	lastConfigSHA []byte
-
-	// configApplied is true if config has been applied at least once
-	configApplied      bool
-	configAppliedMutex sync.RWMutex
-
-	// kong configuration
-	kongConfig        sendconfig.Kong
-	enableReverseSync bool
-	dbmode            string
-	version           semver.Version
-
-	// KongCustomEntitiesSecret is a "namespace/name" Secret locator for a Secret
-	// that contains raw YAML custom entities, for use with DB-less mode
-	KongCustomEntitiesSecret string
-
-	// kongUpdater is the function that will be used by the cache server to ultimately make the API
-	// call to resolve the current cache to the Kong Admin API configuration endpoint.
-	// It may ship diagnostic information through diagnostic
-	kongUpdater KongUpdater
-	diagnostic  util.ConfigDumpDiagnostic
-	promMetrics *metrics.CtrlFuncMetrics
-
-	// server configuration, flow control, channels and utility attributes
-	ingressClassName    string
-	stagger             time.Duration
-	proxyRequestTimeout time.Duration
-	syncTicker          *time.Ticker
-	stopCh              chan struct{}
-
-	// New code should log using "logger". "deprecatedLogger" is here for compatibility with legacy code that relies
-	// on the logrus API.
-	deprecatedLogger logrus.FieldLogger
-	logger           logr.Logger
-}
-
-// -----------------------------------------------------------------------------
-// Client Go Cached Proxy Resolver - Public Methods - Interface Implementation
-// -----------------------------------------------------------------------------
-
-func (p *clientgoCachedProxyResolver) UpdateObject(obj client.Object) error {
-	return p.cache.Add(obj)
-}
-
-func (p *clientgoCachedProxyResolver) DeleteObject(obj client.Object) error {
-	return p.cache.Delete(obj)
-}
-
-func (p *clientgoCachedProxyResolver) ObjectExists(obj client.Object) (bool, error) {
-	_, exists, err := p.cache.Get(obj)
-	return exists, err
-}
-
-func (p *clientgoCachedProxyResolver) NeedLeaderElection() bool {
-	return true
-}
-
-func (p *clientgoCachedProxyResolver) Start(ctx context.Context) error {
-	// TODO https://github.com/Kong/kubernetes-ingress-controller/issues/2249
-	// This is a temporary mitigation to allow some time for controllers to populate the proxy cache
-	time.Sleep(time.Second * 5)
-	go p.startProxyUpdateServer(ctx)
-	return nil
-}
-
-func (p *clientgoCachedProxyResolver) IsReady() bool {
-	// If the proxy is has no database, it is only ready after a successful sync
-	// Otherwise, it has no configuration loaded
-	if p.dbmode == "off" {
-		p.configAppliedMutex.RLock()
-		defer p.configAppliedMutex.RUnlock()
-		return p.configApplied
-	}
-	// If the proxy has a database, it is ready immediately
-	// It will load existing configuration from the database
-	return true
-}
-
-func (p *clientgoCachedProxyResolver) Listeners(ctx context.Context) ([]kong.ProxyListener, []kong.StreamListener, error) {
-	return p.kongConfig.Client.Listeners(ctx)
-}
-
-// -----------------------------------------------------------------------------
-// Client Go Cached Proxy Resolver - Private Methods - Servers
-// -----------------------------------------------------------------------------
-
-// startProxyUpdateServer runs a server in a background goroutine that is responsible for
-// updating the kong proxy backend at regular intervals.
-func (p *clientgoCachedProxyResolver) startProxyUpdateServer(ctx context.Context) {
-	var initialConfig sync.Once
-	for {
-		select {
-		case <-ctx.Done():
-			p.logger.Info("context done: shutting down the proxy update server")
-			if err := ctx.Err(); err != nil {
-				p.logger.Error(err, "context completed with error")
-			}
-			p.syncTicker.Stop()
-			return
-		case <-p.syncTicker.C:
-			updateConfigSHA, err := p.kongUpdater(ctx, p.lastConfigSHA, p.cache,
-				p.ingressClassName, p.deprecatedLogger, p.kongConfig, p.enableReverseSync, p.diagnostic, p.proxyRequestTimeout, p.promMetrics)
-			if err != nil {
-				p.logger.Error(err, "could not update kong admin")
-				break
-			}
-			p.lastConfigSHA = updateConfigSHA
-			initialConfig.Do(p.markConfigApplied)
-		}
-	}
-}
-
-// -----------------------------------------------------------------------------
-// Client Go Cached Proxy Resolver - Private Methods - Server Utils
-// -----------------------------------------------------------------------------
-
-// initialize validates connectivity with the Kong proxy and some of the configuration options thereof
-// and populates several local attributes given retrieved configuration data from the proxy root config.
-//
-// Note: this must be run (and must succeed) in order to successfully start the cache server.
-func (p *clientgoCachedProxyResolver) initialize() error {
-	// download the kong root configuration (and validate connectivity to the proxy API)
-	root, err := p.kongRootWithTimeout()
-	if err != nil {
-		return err
-	}
-
-	// pull the proxy configuration out of the root config and validate it
-	proxyConfig, ok := root["configuration"].(map[string]interface{})
-	if !ok {
-		return fmt.Errorf("invalid root configuration, expected a map[string]interface{} got %T", proxyConfig["configuration"])
-	}
-
-	// validate the database configuration for the proxy and check for supported database configurations
-	dbmode, ok := proxyConfig["database"].(string)
-	if !ok {
-		return fmt.Errorf("invalid database configuration, expected a string got %t", proxyConfig["database"])
-	}
-	switch dbmode {
-	case "off", "":
-		p.kongConfig.InMemory = true
-	case "postgres":
-		p.kongConfig.InMemory = false
-	case "cassandra":
-		return fmt.Errorf("Cassandra-backed deployments of Kong managed by the ingress controller are no longer supported; you must migrate to a Postgres-backed or DB-less deployment")
-	default:
-		return fmt.Errorf("%s is not a supported database backend", dbmode)
-	}
-
-	// validate the proxy version
-	proxySemver, err := kong.ParseSemanticVersion(kong.VersionFromInfo(root))
-	if err != nil {
-		return err
-	}
-
-	// store the gathered configuration options
-	p.kongConfig.Version = proxySemver
-	p.dbmode = dbmode
-	p.version = proxySemver
-	p.promMetrics = metrics.NewCtrlFuncMetrics()
-
-	return nil
-}
-
-// kongRootWithTimeout provides the root configuration from Kong, but uses a configurable timeout to avoid long waits if the Admin API
-// is not yet ready to respond. If a timeout error occurs, the caller is responsible for providing a retry mechanism.
-func (p *clientgoCachedProxyResolver) kongRootWithTimeout() (map[string]interface{}, error) {
-	ctx, cancel := context.WithTimeout(context.Background(), p.proxyRequestTimeout)
-	defer cancel()
-	return p.kongConfig.Client.Root(ctx)
-}
-
-// markConfigApplied marks that config has been applied
-func (p *clientgoCachedProxyResolver) markConfigApplied() {
-	p.configAppliedMutex.Lock()
-	defer p.configAppliedMutex.Unlock()
-	p.configApplied = true
-}
-
-// -----------------------------------------------------------------------------
-// Private Helper Functions
-// -----------------------------------------------------------------------------
-
-// fetchCustomEntities returns the value of the "config" key from a Secret (identified by a "namespace/secretName"
-// string in the store.
-func fetchCustomEntities(secretName string, store store.Storer) ([]byte, error) {
-	ns, name, err := util.ParseNameNS(secretName)
-	if err != nil {
-		return nil, fmt.Errorf("parsing kong custom entities secret: %w", err)
-	}
-	secret, err := store.GetSecret(ns, name)
-	if err != nil {
-		return nil, fmt.Errorf("fetching secret: %w", err)
-	}
-	config, ok := secret.Data["config"]
-	if !ok {
-		return nil, fmt.Errorf("'config' key not found in "+
-			"custom entities secret '%v'", secretName)
-	}
-	return config, nil
-}
diff --git a/internal/dataplane/proxy/proxy.go b/internal/dataplane/proxy/proxy.go
deleted file mode 100644
index 403148092..000000000
--- a/internal/dataplane/proxy/proxy.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package proxy
-
-import (
-	"context"
-	"time"
-
-	"github.com/kong/go-kong/kong"
-	"github.com/sirupsen/logrus"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/controller-runtime/pkg/manager"
-
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/sendconfig"
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/metrics"
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/store"
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/util"
-)
-
-// -----------------------------------------------------------------------------
-// Proxy - Public Vars
-// -----------------------------------------------------------------------------
-
-const (
-	// DefaultProxyTimeoutSeconds indicates the time.Duration allowed for responses to
-	// come back from the backend proxy API.
-	//
-	// NOTE: the current default is based on observed latency in a CI environment using
-	// the GKE cloud provider.
-	DefaultProxyTimeoutSeconds float32 = 10.0
-
-	// DefaultSyncSeconds indicates the time.Duration (minimum) that will occur between
-	// updates to the Kong Proxy Admin API when using the NewProxy() constructor.
-	// this 1s default was based on local testing wherein it appeared sub-second updates
-	// to the Admin API could be problematic (or at least operate differently) based on
-	// which storage backend was in use (i.e. "dbless", "postgres"). This is a workaround
-	// for improvements we still need to investigate upstream.
-	//
-	// See Also: https://github.com/Kong/kubernetes-ingress-controller/issues/1398
-	DefaultSyncSeconds float32 = 3.0
-)
-
-// -----------------------------------------------------------------------------
-// Proxy - Public Types
-// -----------------------------------------------------------------------------
-
-// Proxy represents the Kong Proxy from the perspective of Kubernetes allowing
-// callers to update and remove Kubernetes objects in the backend proxy without
-// having to understand or be aware of Kong DSLs or how types are converted between
-// Kubernetes and the Kong Admin API.
-//
-// NOTE: implementations of this interface are: threadsafe, non-blocking
-type Proxy interface {
-	// UpdateObject accepts a Kubernetes controller-runtime client.Object and adds/updates that to the configuration cache.
-	// It will be asynchronously converted into the upstream Kong DSL and applied to the Kong Admin API.
-	// A status will later be added to the object whether the configuration update succeeds or fails.
-	UpdateObject(obj client.Object) error
-
-	// DeleteObject accepts a Kubernetes controller-runtime client.Object and removes it from the configuration cache.
-	// The delete action will asynchronously be converted to Kong DSL and applied to the Kong Admin API.
-	// A status will later be added to the object whether the configuration update succeeds or fails.
-	DeleteObject(obj client.Object) error
-
-	// ObjectExists indicates whether or not any version of the provided object is already present in the proxy.
-	ObjectExists(obj client.Object) (bool, error)
-
-	// IsReady returns true if the proxy is considered ready.
-	// A ready proxy has configuration available and can handle traffic.
-	IsReady() bool
-
-	// Listeners retrieves the currently configured listeners from the
-	// underlying proxy so that callers can gather this metadata to
-	// know which ports and protocols are in use by the proxy.
-	Listeners(ctx context.Context) ([]kong.ProxyListener, []kong.StreamListener, error)
-
-	manager.Runnable
-	manager.LeaderElectionRunnable
-}
-
-// KongUpdater is a type of function that describes how to provide updates to the Kong Admin API
-// and implementations will report the configuration SHA that results from any update performed.
-type KongUpdater func(ctx context.Context,
-	lastConfigSHA []byte,
-	cache *store.CacheStores,
-	ingressClassName string,
-	deprecatedLogger logrus.FieldLogger,
-	kongConfig sendconfig.Kong,
-	enableReverseSync bool,
-	diagnostic util.ConfigDumpDiagnostic,
-	proxyRequestTimeout time.Duration,
-	promMetrics *metrics.CtrlFuncMetrics) ([]byte, error)
diff --git a/internal/dataplane/sendconfig/common_workflows.go b/internal/dataplane/sendconfig/common_workflows.go
deleted file mode 100644
index ecc662ca7..000000000
--- a/internal/dataplane/sendconfig/common_workflows.go
+++ /dev/null
@@ -1,108 +0,0 @@
-package sendconfig
-
-import (
-	"context"
-	"time"
-
-	"github.com/kong/deck/file"
-	"github.com/prometheus/client_golang/prometheus"
-	"github.com/sirupsen/logrus"
-
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/deckgen"
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/parser"
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/metrics"
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/store"
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/util"
-)
-
-// -----------------------------------------------------------------------------
-// Sendconfig - Workflow Functions
-// -----------------------------------------------------------------------------
-
-// UpdateKongAdminSimple is a helper function for the most common usage of PerformUpdate() with only minimal
-// upfront configuration required. This function is specialized and highly opinionated.
-//
-// If you're implementation needs to expand on the configuration and usage of the following inner components:
-//
-//   - store.Storer
-//   - kongstate.Kong
-//   - deckgen.ToDeckContent()
-//   - sendconfig.PerformUpdate()
-//
-// Or any other encapsulated components this function makes all of that opaque to the caller.
-// Treat this function as a very specific "workflow" to update the Kong Admin API,
-// and use it as a reference to implement the workflow you need.
-func UpdateKongAdminSimple(ctx context.Context,
-	lastConfigSHA []byte,
-	cache *store.CacheStores,
-	ingressClassName string,
-	deprecatedLogger logrus.FieldLogger,
-	kongConfig Kong,
-	enableReverseSync bool,
-	diagnostic util.ConfigDumpDiagnostic,
-	proxyRequestTimeout time.Duration,
-	promMetrics *metrics.CtrlFuncMetrics,
-) ([]byte, error) {
-	// build the kongstate object from the Kubernetes objects in the storer
-	storer := store.New(*cache, ingressClassName, false, false, false, deprecatedLogger)
-	kongstate, err := parser.Build(deprecatedLogger, storer)
-	if err != nil {
-		promMetrics.TranslationCount.With(prometheus.Labels{
-			metrics.SuccessKey: metrics.SuccessFalse,
-		}).Inc()
-		return nil, err
-	}
-	promMetrics.TranslationCount.With(prometheus.Labels{
-		metrics.SuccessKey: metrics.SuccessTrue,
-	}).Inc()
-	var diagnosticConfig *file.Content
-
-	// generate the deck configuration to be applied to the admin API
-	targetConfig := deckgen.ToDeckContent(ctx,
-		deprecatedLogger, kongstate,
-		kongConfig.PluginSchemaStore, kongConfig.FilterTags)
-
-	// generate diagnostic configuration if enabled
-	// "diagnostic" will be empty if --dump-config is not set
-	if diagnostic != (util.ConfigDumpDiagnostic{}) {
-		if !diagnostic.DumpsIncludeSensitive {
-			redactedConfig := deckgen.ToDeckContent(ctx,
-				deprecatedLogger, kongstate.SanitizedCopy(),
-				kongConfig.PluginSchemaStore, kongConfig.FilterTags)
-			diagnosticConfig = redactedConfig
-		} else {
-			diagnosticConfig = targetConfig
-		}
-	}
-
-	// apply the configuration update in Kong
-	timedCtx, cancel := context.WithTimeout(ctx, proxyRequestTimeout)
-	defer cancel()
-
-	configSHA, err := PerformUpdate(timedCtx,
-		deprecatedLogger, &kongConfig,
-		kongConfig.InMemory, enableReverseSync,
-		targetConfig, kongConfig.FilterTags, nil, lastConfigSHA, false, promMetrics,
-	)
-	if err != nil {
-		if diagnostic != (util.ConfigDumpDiagnostic{}) {
-			select {
-			case diagnostic.Configs <- util.ConfigDump{Failed: true, Config: *diagnosticConfig}:
-				deprecatedLogger.Debug("shipping config to diagnostic server")
-			default:
-				deprecatedLogger.Error("config diagnostic buffer full, dropping diagnostic config")
-			}
-		}
-		return nil, err
-	}
-	if diagnostic != (util.ConfigDumpDiagnostic{}) {
-		select {
-		case diagnostic.Configs <- util.ConfigDump{Failed: false, Config: *diagnosticConfig}:
-			deprecatedLogger.Debug("shipping config to diagnostic server")
-		default:
-			deprecatedLogger.Error("config diagnostic buffer full, dropping diagnostic config")
-		}
-	}
-
-	return configSHA, nil
-}
diff --git a/internal/dataplane/sendconfig/kong.go b/internal/dataplane/sendconfig/kong.go
index ff3b35b96..a5234ddfe 100644
--- a/internal/dataplane/sendconfig/kong.go
+++ b/internal/dataplane/sendconfig/kong.go
@@ -10,6 +10,10 @@ import (
 	"github.com/kong/kubernetes-ingress-controller/v2/internal/util"
 )
 
+// -----------------------------------------------------------------------------
+// Sendconfig - Public Types
+// -----------------------------------------------------------------------------
+
 // KongConfigUpdate is a Kong configuration and the time it was generated
 type KongConfigUpdate struct {
 	Timestamp time.Time
diff --git a/internal/dataplane/sendconfig/sendconfig.go b/internal/dataplane/sendconfig/sendconfig.go
index 6633675a7..93d160f42 100644
--- a/internal/dataplane/sendconfig/sendconfig.go
+++ b/internal/dataplane/sendconfig/sendconfig.go
@@ -23,9 +23,9 @@ import (
 	"github.com/kong/kubernetes-ingress-controller/v2/internal/metrics"
 )
 
-func equalSHA(a, b []byte) bool {
-	return reflect.DeepEqual(a, b)
-}
+// -----------------------------------------------------------------------------
+// Sendconfig - Public Functions
+// -----------------------------------------------------------------------------
 
 // PerformUpdate writes `targetContent` and `customEntities` to Kong Admin API specified by `kongConfig`.
 func PerformUpdate(ctx context.Context,
@@ -98,6 +98,10 @@ func PerformUpdate(ctx context.Context,
 	return newSHA, nil
 }
 
+// -----------------------------------------------------------------------------
+// Sendconfig - Private Functions
+// -----------------------------------------------------------------------------
+
 func renderConfigWithCustomEntities(log logrus.FieldLogger, state *file.Content,
 	customEntitiesJSONBytes []byte) ([]byte, error) {
 
@@ -229,9 +233,9 @@ func onUpdateDBMode(ctx context.Context,
 	return nil
 }
 
-// -----------------------------------------------------------------------------
-// Sendconfig - Logging & Reporting Helper Functions
-// -----------------------------------------------------------------------------
+func equalSHA(a, b []byte) bool {
+	return reflect.DeepEqual(a, b)
+}
 
 var (
 	latestReportedSHA []byte
diff --git a/internal/dataplane/synchronizer.go b/internal/dataplane/synchronizer.go
new file mode 100644
index 000000000..8cefd1791
--- /dev/null
+++ b/internal/dataplane/synchronizer.go
@@ -0,0 +1,180 @@
+package dataplane
+
+import (
+	"context"
+	"fmt"
+	"sync"
+	"time"
+
+	"github.com/bombsimon/logrusr/v2"
+	"github.com/go-logr/logr"
+	"github.com/sirupsen/logrus"
+)
+
+// -----------------------------------------------------------------------------
+// Dataplane Synchronizer - Public Vars
+// -----------------------------------------------------------------------------
+
+const (
+	// DefaultSyncSeconds indicates the time.Duration (minimum) that will occur between
+	// updates to the DataplaneClient.
+	//
+	// This 1s default was based on local testing wherein it appeared sub-second updates
+	// to the Admin API could be problematic (or at least operate differently) based on
+	// which storage backend was in use (i.e. "dbless", "postgres"). This is a workaround
+	// for improvements we still need to investigate upstream.
+	//
+	// See Also: https://github.com/Kong/kubernetes-ingress-controller/issues/1398
+	DefaultSyncSeconds float32 = 3.0
+)
+
+// -----------------------------------------------------------------------------
+// Synchronizer - Public Types
+// -----------------------------------------------------------------------------
+
+// Synchronizer is a threadsafe object which starts a goroutine to updates
+// the data-plane at regular intervals.
+type Synchronizer struct {
+	logger logr.Logger
+
+	// dataplane client to send updates to the Kong Admin API
+	dataplaneClient Client
+
+	// server configuration, flow control, channels and utility attributes
+	stagger         time.Duration
+	syncTicker      *time.Ticker
+	configApplied   bool
+	isServerRunning bool
+
+	lock sync.RWMutex
+}
+
+// NewSynchronizer will provide a new Synchronizer object. Note that this
+// starts some background goroutines and the caller is resonsible for marking
+// the provided context.Context as "Done()" to shut down the background routines
+func NewSynchronizer(logger logrus.FieldLogger, dataplaneClient Client) (*Synchronizer, error) {
+	stagger, err := time.ParseDuration(fmt.Sprintf("%gs", DefaultSyncSeconds))
+	if err != nil {
+		return nil, err
+	}
+	return NewSynchronizerWithStagger(logger, dataplaneClient, stagger)
+}
+
+// NewSynchronizer will provide a new Synchronizer object with a specified
+// stagger time for data-plane updates to occur. Note that this starts some
+// background goroutines and the caller is resonsible for marking the provided
+// context.Context as "Done()" to shut down the background routines
+func NewSynchronizerWithStagger(logger logrus.FieldLogger, dataplaneClient Client, stagger time.Duration) (*Synchronizer, error) {
+	synchronizer := &Synchronizer{
+		logger:          logrusr.New(logger),
+		dataplaneClient: dataplaneClient,
+		stagger:         stagger,
+		configApplied:   false,
+	}
+
+	return synchronizer, nil
+}
+
+// -----------------------------------------------------------------------------
+// Synchronizer - Public Methods
+// -----------------------------------------------------------------------------
+
+// Start starts the goroutine synchronization server that will perform an
+// Update() on the provided dataplane.Client according to the provided stagger
+// time, or using the DefaultSyncSeconds if not otherwise provided.
+//
+// To stop the server, the provided context must be Done().
+func (p *Synchronizer) Start(ctx context.Context) error {
+	// TODO https://github.com/Kong/kubernetes-ingress-controller/issues/2249
+	// This is a temporary mitigation to allow some time for controllers to
+	// populate their dataplaneClient cache.
+	time.Sleep(time.Second * 5)
+	p.lock.Lock()
+	defer p.lock.Unlock()
+
+	if p.isServerRunning {
+		return fmt.Errorf("server is already running")
+	}
+
+	p.syncTicker = time.NewTicker(p.stagger)
+	go p.startUpdateServer(ctx)
+	p.isServerRunning = true
+
+	return nil
+}
+
+// IsRunning informs the caller whether the synchronization server is running.
+func (p *Synchronizer) IsRunning() bool {
+	p.lock.RLock()
+	defer p.lock.RUnlock()
+	return p.isServerRunning
+}
+
+// IsReady indicates whether the synchronizer is actively able to synchronize
+// configuration to the dataplane. It's similar to IsRunning() but reports
+// on whether configuration can actually be successful and is also used as part
+// of a controller-runtime Runnable interface to wait for readiness before
+// starting controllers.
+func (p *Synchronizer) IsReady() bool {
+	// If the proxy is has no database, it is only ready after a successful sync
+	// Otherwise, it has no configuration loaded
+	if p.dataplaneClient.DBMode() == "off" {
+		p.lock.RLock()
+		defer p.lock.RUnlock()
+		return p.configApplied
+	}
+	// If the proxy has a database, it is ready immediately
+	// It will load existing configuration from the database
+	return true
+}
+
+// NeedLeaderElection implements the controller-runtime Runnable interface to
+// inform the controller manager whether leadership election is needed, which
+// is always true in our case.
+func (p *Synchronizer) NeedLeaderElection() bool {
+	return true
+}
+
+// -----------------------------------------------------------------------------
+// Synchronizer - Private Methods - Server Utilities
+// -----------------------------------------------------------------------------
+
+// startUpdateServer runs a server in a background goroutine that is responsible for
+// updating the kong proxy backend at regular intervals.
+func (p *Synchronizer) startUpdateServer(ctx context.Context) {
+	var initialConfig sync.Once
+	for {
+		select {
+		case <-ctx.Done():
+			p.logger.Info("context done: shutting down the proxy update server")
+			if err := ctx.Err(); err != nil {
+				p.logger.Error(err, "context completed with error")
+			}
+			p.syncTicker.Stop()
+
+			p.lock.Lock()
+			defer p.lock.Unlock()
+			p.isServerRunning = false
+			p.configApplied = false
+
+			return
+		case <-p.syncTicker.C:
+			if err := p.dataplaneClient.Update(ctx); err != nil {
+				p.logger.Error(err, "could not update kong admin")
+				break
+			}
+			initialConfig.Do(p.markConfigApplied)
+		}
+	}
+}
+
+// -----------------------------------------------------------------------------
+// Synchronizer - Private Methods - Helper
+// -----------------------------------------------------------------------------
+
+// markConfigApplied marks that config has been applied
+func (p *Synchronizer) markConfigApplied() {
+	p.lock.Lock()
+	defer p.lock.Unlock()
+	p.configApplied = true
+}
diff --git a/internal/manager/config.go b/internal/manager/config.go
index b75495d23..7b2ba437b 100644
--- a/internal/manager/config.go
+++ b/internal/manager/config.go
@@ -15,7 +15,7 @@ import (
 	"github.com/kong/kubernetes-ingress-controller/v2/internal/adminapi"
 	"github.com/kong/kubernetes-ingress-controller/v2/internal/admission"
 	"github.com/kong/kubernetes-ingress-controller/v2/internal/annotations"
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/proxy"
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane"
 )
 
 // -----------------------------------------------------------------------------
@@ -125,10 +125,10 @@ func (c *Config) FlagSet() *pflag.FlagSet {
 	flagSet.StringVar(&c.MetricsAddr, "metrics-bind-address", fmt.Sprintf(":%v", MetricsPort), "The address the metric endpoint binds to.")
 	flagSet.StringVar(&c.ProbeAddr, "health-probe-bind-address", fmt.Sprintf(":%v", HealthzPort), "The address the probe endpoint binds to.")
 	flagSet.StringVar(&c.KongAdminURL, "kong-admin-url", "http://localhost:8001", `The Kong Admin URL to connect to in the format "protocol://address:port".`)
-	flagSet.Float32Var(&c.ProxySyncSeconds, "proxy-sync-seconds", proxy.DefaultSyncSeconds,
+	flagSet.Float32Var(&c.ProxySyncSeconds, "proxy-sync-seconds", dataplane.DefaultSyncSeconds,
 		"Define the rate (in seconds) in which configuration updates will be applied to the Kong Admin API.",
 	)
-	flagSet.Float32Var(&c.ProxyTimeoutSeconds, "proxy-timeout-seconds", proxy.DefaultProxyTimeoutSeconds,
+	flagSet.Float32Var(&c.ProxyTimeoutSeconds, "proxy-timeout-seconds", dataplane.DefaultTimeoutSeconds,
 		"Define the rate (in seconds) in which the timeout configuration will be applied to the Kong client.",
 	)
 	flagSet.StringVar(&c.KongCustomEntitiesSecret, "kong-custom-entities-secret", "", `A Secret containing custom entities for DB-less mode, in "namespace/name" format`)
@@ -192,7 +192,7 @@ func (c *Config) FlagSet() *pflag.FlagSet {
 		fmt.Sprintf("See the Feature Gates documentation for information and available options: %s", featureGatesDocsURL))
 
 	// Deprecated (to be removed in future releases)
-	flagSet.Float32Var(&c.ProxySyncSeconds, "sync-rate-limit", proxy.DefaultSyncSeconds,
+	flagSet.Float32Var(&c.ProxySyncSeconds, "sync-rate-limit", dataplane.DefaultSyncSeconds,
 		"Define the rate (in seconds) in which configuration updates will be applied to the Kong Admin API (DEPRECATED, use --proxy-sync-seconds instead)",
 	)
 	flagSet.Int("stderrthreshold", 0, "DEPRECATED: has no effect and will be removed in future releases (see github issue #1297)")
diff --git a/internal/manager/controllerdef.go b/internal/manager/controllerdef.go
index 1eb2c63f6..0914ead84 100644
--- a/internal/manager/controllerdef.go
+++ b/internal/manager/controllerdef.go
@@ -14,7 +14,7 @@ import (
 	"github.com/kong/kubernetes-ingress-controller/v2/internal/controllers/configuration"
 	"github.com/kong/kubernetes-ingress-controller/v2/internal/controllers/gateway"
 	ctrlutils "github.com/kong/kubernetes-ingress-controller/v2/internal/controllers/utils"
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/proxy"
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane"
 	konghqcomv1 "github.com/kong/kubernetes-ingress-controller/v2/pkg/apis/configuration/v1"
 )
 
@@ -61,7 +61,7 @@ func (c *ControllerDef) MaybeSetupWithManager(mgr ctrl.Manager) error {
 // Controller Manager - Controller Setup Functions
 // -----------------------------------------------------------------------------
 
-func setupControllers(mgr manager.Manager, proxy proxy.Proxy, c *Config, featureGates map[string]bool) ([]ControllerDef, error) {
+func setupControllers(mgr manager.Manager, dataplaneClient *dataplane.KongClient, c *Config, featureGates map[string]bool) ([]ControllerDef, error) {
 	// Choose the best API version of Ingress to inform which ingress controller to enable.
 	var ingressPicker ingressControllerStrategy
 	if err := ingressPicker.Initialize(c, mgr.GetClient()); err != nil {
@@ -79,7 +79,7 @@ func setupControllers(mgr manager.Manager, proxy proxy.Proxy, c *Config, feature
 				Client:           mgr.GetClient(),
 				Log:              ctrl.Log.WithName("controllers").WithName("Ingress").WithName("netv1"),
 				Scheme:           mgr.GetScheme(),
-				Proxy:            proxy,
+				DataplaneClient:  dataplaneClient,
 				IngressClassName: c.IngressClassName,
 			},
 		},
@@ -90,7 +90,6 @@ func setupControllers(mgr manager.Manager, proxy proxy.Proxy, c *Config, feature
 				Client: mgr.GetClient(),
 				Log:    ctrl.Log.WithName("controllers").WithName("IngressClass").WithName("netv1"),
 				Scheme: mgr.GetScheme(),
-				Proxy:  proxy,
 			},
 		},
 		{
@@ -100,7 +99,7 @@ func setupControllers(mgr manager.Manager, proxy proxy.Proxy, c *Config, feature
 				Client:           mgr.GetClient(),
 				Log:              ctrl.Log.WithName("controllers").WithName("Ingress").WithName("netv1beta1"),
 				Scheme:           mgr.GetScheme(),
-				Proxy:            proxy,
+				DataplaneClient:  dataplaneClient,
 				IngressClassName: c.IngressClassName,
 			},
 		},
@@ -111,35 +110,35 @@ func setupControllers(mgr manager.Manager, proxy proxy.Proxy, c *Config, feature
 				Client:           mgr.GetClient(),
 				Log:              ctrl.Log.WithName("controllers").WithName("Ingress").WithName("extv1beta1"),
 				Scheme:           mgr.GetScheme(),
-				Proxy:            proxy,
+				DataplaneClient:  dataplaneClient,
 				IngressClassName: c.IngressClassName,
 			},
 		},
 		{
 			Enabled: c.ServiceEnabled,
 			Controller: &configuration.CoreV1ServiceReconciler{
-				Client: mgr.GetClient(),
-				Log:    ctrl.Log.WithName("controllers").WithName("Service"),
-				Scheme: mgr.GetScheme(),
-				Proxy:  proxy,
+				Client:          mgr.GetClient(),
+				Log:             ctrl.Log.WithName("controllers").WithName("Service"),
+				Scheme:          mgr.GetScheme(),
+				DataplaneClient: dataplaneClient,
 			},
 		},
 		{
 			Enabled: c.ServiceEnabled,
 			Controller: &configuration.CoreV1EndpointsReconciler{
-				Client: mgr.GetClient(),
-				Log:    ctrl.Log.WithName("controllers").WithName("Endpoints"),
-				Scheme: mgr.GetScheme(),
-				Proxy:  proxy,
+				Client:          mgr.GetClient(),
+				Log:             ctrl.Log.WithName("controllers").WithName("Endpoints"),
+				Scheme:          mgr.GetScheme(),
+				DataplaneClient: dataplaneClient,
 			},
 		},
 		{
 			Enabled: true,
 			Controller: &configuration.CoreV1SecretReconciler{
-				Client: mgr.GetClient(),
-				Log:    ctrl.Log.WithName("controllers").WithName("Secrets"),
-				Scheme: mgr.GetScheme(),
-				Proxy:  proxy,
+				Client:          mgr.GetClient(),
+				Log:             ctrl.Log.WithName("controllers").WithName("Secrets"),
+				Scheme:          mgr.GetScheme(),
+				DataplaneClient: dataplaneClient,
 			},
 		},
 		// ---------------------------------------------------------------------------
@@ -151,7 +150,7 @@ func setupControllers(mgr manager.Manager, proxy proxy.Proxy, c *Config, feature
 				Client:           mgr.GetClient(),
 				Log:              ctrl.Log.WithName("controllers").WithName("UDPIngress"),
 				Scheme:           mgr.GetScheme(),
-				Proxy:            proxy,
+				DataplaneClient:  dataplaneClient,
 				IngressClassName: c.IngressClassName,
 			},
 		},
@@ -161,26 +160,26 @@ func setupControllers(mgr manager.Manager, proxy proxy.Proxy, c *Config, feature
 				Client:           mgr.GetClient(),
 				Log:              ctrl.Log.WithName("controllers").WithName("TCPIngress"),
 				Scheme:           mgr.GetScheme(),
-				Proxy:            proxy,
+				DataplaneClient:  dataplaneClient,
 				IngressClassName: c.IngressClassName,
 			},
 		},
 		{
 			Enabled: c.KongIngressEnabled,
 			Controller: &configuration.KongV1KongIngressReconciler{
-				Client: mgr.GetClient(),
-				Log:    ctrl.Log.WithName("controllers").WithName("KongIngress"),
-				Scheme: mgr.GetScheme(),
-				Proxy:  proxy,
+				Client:          mgr.GetClient(),
+				Log:             ctrl.Log.WithName("controllers").WithName("KongIngress"),
+				Scheme:          mgr.GetScheme(),
+				DataplaneClient: dataplaneClient,
 			},
 		},
 		{
 			Enabled: c.KongPluginEnabled,
 			Controller: &configuration.KongV1KongPluginReconciler{
-				Client: mgr.GetClient(),
-				Log:    ctrl.Log.WithName("controllers").WithName("KongPlugin"),
-				Scheme: mgr.GetScheme(),
-				Proxy:  proxy,
+				Client:          mgr.GetClient(),
+				Log:             ctrl.Log.WithName("controllers").WithName("KongPlugin"),
+				Scheme:          mgr.GetScheme(),
+				DataplaneClient: dataplaneClient,
 			},
 		},
 		{
@@ -189,7 +188,7 @@ func setupControllers(mgr manager.Manager, proxy proxy.Proxy, c *Config, feature
 				Client:           mgr.GetClient(),
 				Log:              ctrl.Log.WithName("controllers").WithName("KongConsumer"),
 				Scheme:           mgr.GetScheme(),
-				Proxy:            proxy,
+				DataplaneClient:  dataplaneClient,
 				IngressClassName: c.IngressClassName,
 			},
 		},
@@ -204,7 +203,7 @@ func setupControllers(mgr manager.Manager, proxy proxy.Proxy, c *Config, feature
 				Client:           mgr.GetClient(),
 				Log:              ctrl.Log.WithName("controllers").WithName("KongClusterPlugin"),
 				Scheme:           mgr.GetScheme(),
-				Proxy:            proxy,
+				DataplaneClient:  dataplaneClient,
 				IngressClassName: c.IngressClassName,
 			},
 		},
@@ -225,7 +224,7 @@ func setupControllers(mgr manager.Manager, proxy proxy.Proxy, c *Config, feature
 				Client:           mgr.GetClient(),
 				Log:              ctrl.Log.WithName("controllers").WithName("Ingress").WithName("KnativeV1Alpha1"),
 				Scheme:           mgr.GetScheme(),
-				Proxy:            proxy,
+				DataplaneClient:  dataplaneClient,
 				IngressClassName: c.IngressClassName,
 			},
 		},
@@ -244,7 +243,7 @@ func setupControllers(mgr manager.Manager, proxy proxy.Proxy, c *Config, feature
 				Client:          mgr.GetClient(),
 				Log:             ctrl.Log.WithName("controllers").WithName(gatewayFeature),
 				Scheme:          mgr.GetScheme(),
-				Proxy:           proxy,
+				DataplaneClient: dataplaneClient,
 				PublishService:  c.PublishService,
 				WatchNamespaces: c.WatchNamespaces,
 			},
@@ -258,10 +257,10 @@ func setupControllers(mgr manager.Manager, proxy proxy.Proxy, c *Config, feature
 					Resource: "httproutes",
 				}}.CRDExists,
 			Controller: &gateway.HTTPRouteReconciler{
-				Client: mgr.GetClient(),
-				Log:    ctrl.Log.WithName("controllers").WithName("HTTPRoute"),
-				Scheme: mgr.GetScheme(),
-				Proxy:  proxy,
+				Client:          mgr.GetClient(),
+				Log:             ctrl.Log.WithName("controllers").WithName("HTTPRoute"),
+				Scheme:          mgr.GetScheme(),
+				DataplaneClient: dataplaneClient,
 			},
 		},
 	}
diff --git a/internal/manager/run.go b/internal/manager/run.go
index 897da1f91..63a6e9b9e 100644
--- a/internal/manager/run.go
+++ b/internal/manager/run.go
@@ -6,6 +6,7 @@ import (
 	"errors"
 	"fmt"
 	"net/http"
+	"time"
 
 	"k8s.io/apimachinery/pkg/runtime"
 	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
@@ -15,6 +16,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/healthz"
 	gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
 
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane"
 	"github.com/kong/kubernetes-ingress-controller/v2/internal/manager/metadata"
 	mgrutils "github.com/kong/kubernetes-ingress-controller/v2/internal/manager/utils"
 	"github.com/kong/kubernetes-ingress-controller/v2/internal/util"
@@ -94,14 +96,24 @@ func Run(ctx context.Context, c *Config, diagnostic util.ConfigDumpDiagnostic) e
 		return err
 	}
 
-	setupLog.Info("Initializing Proxy Cache Server")
-	proxy, err := setupProxyServer(setupLog, deprecatedLogger, mgr, kongConfig, diagnostic, c)
+	setupLog.Info("Initializing Dataplane Client")
+	timeoutDuration, err := time.ParseDuration(fmt.Sprintf("%gs", c.ProxyTimeoutSeconds))
 	if err != nil {
-		return fmt.Errorf("unable to initialize proxy cache server: %w", err)
+		return fmt.Errorf("%f is not a valid number of seconds to the timeout config for the kong client: %w", c.ProxyTimeoutSeconds, err)
+	}
+	dataplaneClient, err := dataplane.NewKongClient(deprecatedLogger, timeoutDuration, c.IngressClassName, c.EnableReverseSync, diagnostic, kongConfig)
+	if err != nil {
+		return fmt.Errorf("failed to initialize kong data-plane client: %w", err)
+	}
+
+	setupLog.Info("Initializing Dataplane Synchronizer")
+	synchronizer, err := setupDataplaneSynchronizer(setupLog, deprecatedLogger, mgr, dataplaneClient, c)
+	if err != nil {
+		return fmt.Errorf("unable to initialize dataplane synchronizer: %w", err)
 	}
 
 	setupLog.Info("Starting Enabled Controllers")
-	controllers, err := setupControllers(mgr, proxy, c, featureGates)
+	controllers, err := setupControllers(mgr, dataplaneClient, c, featureGates)
 	if err != nil {
 		return fmt.Errorf("unable to setup controller as expected %w", err)
 	}
@@ -120,8 +132,8 @@ func Run(ctx context.Context, c *Config, diagnostic util.ConfigDumpDiagnostic) e
 		return fmt.Errorf("unable to setup healthz: %w", err)
 	}
 	if err := mgr.AddReadyzCheck("check", func(_ *http.Request) error {
-		if !proxy.IsReady() {
-			return errors.New("proxy not yet configured")
+		if !synchronizer.IsReady() {
+			return errors.New("synchronizer not yet configured")
 		}
 		return nil
 	}); err != nil {
diff --git a/internal/manager/setup.go b/internal/manager/setup.go
index d7bfe3423..55763ade6 100644
--- a/internal/manager/setup.go
+++ b/internal/manager/setup.go
@@ -20,7 +20,7 @@ import (
 
 	"github.com/kong/kubernetes-ingress-controller/v2/internal/admission"
 	ctrlutils "github.com/kong/kubernetes-ingress-controller/v2/internal/controllers/utils"
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/proxy"
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane"
 	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/sendconfig"
 	"github.com/kong/kubernetes-ingress-controller/v2/internal/util"
 )
@@ -132,14 +132,17 @@ func setupKongConfig(ctx context.Context, logger logr.Logger, c *Config) (sendco
 	return cfg, nil
 }
 
-func setupProxyServer(logger logr.Logger, fieldLogger logrus.FieldLogger,
-	mgr manager.Manager, kongConfig sendconfig.Kong,
-	diagnostic util.ConfigDumpDiagnostic, c *Config,
-) (proxy.Proxy, error) {
-	if c.ProxySyncSeconds < proxy.DefaultSyncSeconds {
+func setupDataplaneSynchronizer(
+	logger logr.Logger,
+	fieldLogger logrus.FieldLogger,
+	mgr manager.Manager,
+	dataplaneClient dataplane.Client,
+	c *Config,
+) (*dataplane.Synchronizer, error) {
+	if c.ProxySyncSeconds < dataplane.DefaultSyncSeconds {
 		logger.Info(fmt.Sprintf("WARNING: --proxy-sync-seconds is configured for %fs, in DBLESS mode this may result in"+
 			" problems of inconsistency in the proxy state. For DBLESS mode %fs+ is recommended (3s is the default).",
-			c.ProxySyncSeconds, proxy.DefaultSyncSeconds,
+			c.ProxySyncSeconds, dataplane.DefaultSyncSeconds,
 		))
 	}
 
@@ -149,31 +152,21 @@ func setupProxyServer(logger logr.Logger, fieldLogger logrus.FieldLogger,
 		return nil, err
 	}
 
-	timeoutDuration, err := time.ParseDuration(fmt.Sprintf("%gs", c.ProxyTimeoutSeconds))
-	if err != nil {
-		logger.Error(err, "%s is not a valid number of seconds to the timeout config for the kong client")
-		return nil, err
-	}
-
-	proxyServer, err := proxy.NewCacheBasedProxyWithStagger(fieldLogger.WithField("subsystem", "proxy-cache-resolver"),
-		mgr.GetClient(),
-		kongConfig,
-		c.IngressClassName,
-		c.EnableReverseSync,
+	dataplaneSynchronizer, err := dataplane.NewSynchronizerWithStagger(
+		fieldLogger.WithField("subsystem", "dataplane-synchronizer"),
+		dataplaneClient,
 		syncTickDuration,
-		timeoutDuration,
-		diagnostic,
-		sendconfig.UpdateKongAdminSimple)
+	)
 	if err != nil {
 		return nil, err
 	}
 
-	err = mgr.Add(proxyServer)
+	err = mgr.Add(dataplaneSynchronizer)
 	if err != nil {
 		return nil, err
 	}
 
-	return proxyServer, nil
+	return dataplaneSynchronizer, nil
 }
 
 func setupAdmissionServer(ctx context.Context, managerConfig *Config, managerClient client.Client) error {


Test patch:
diff --git a/internal/dataplane/parser/parser_test.go b/internal/dataplane/parser/parser_test.go
index 39aa5c397..af68bcab4 100644
--- a/internal/dataplane/parser/parser_test.go
+++ b/internal/dataplane/parser/parser_test.go
@@ -263,7 +263,8 @@ func TestGlobalPlugin(t *testing.T) {
 			},
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 		assert.Equal(1, len(state.Plugins),
@@ -443,7 +444,8 @@ func TestSecretConfigurationPlugin(t *testing.T) {
 			}
 			store, err := store.NewFakeStore(objects)
 			assert.Nil(err)
-			state, err := Build(logrus.New(), store)
+			p := NewParser(logrus.New(), store)
+			state, err := p.Build()
 			assert.Nil(err)
 			assert.NotNil(state)
 			assert.Equal(3, len(state.Plugins),
@@ -547,7 +549,8 @@ func TestSecretConfigurationPlugin(t *testing.T) {
 			}
 			store, err := store.NewFakeStore(objects)
 			assert.Nil(err)
-			state, err := Build(logrus.New(), store)
+			p := NewParser(logrus.New(), store)
+			state, err := p.Build()
 			assert.Nil(err)
 			assert.NotNil(state)
 			assert.Equal(0, len(state.Plugins),
@@ -648,7 +651,8 @@ func TestSecretConfigurationPlugin(t *testing.T) {
 			}
 			store, err := store.NewFakeStore(objects)
 			assert.Nil(err)
-			state, err := Build(logrus.New(), store)
+			p := NewParser(logrus.New(), store)
+			state, err := p.Build()
 			assert.Nil(err)
 			assert.NotNil(state)
 			assert.Equal(0, len(state.Plugins),
@@ -699,7 +703,8 @@ func TestSecretConfigurationPlugin(t *testing.T) {
 		}
 		store, err := store.NewFakeStore(objects)
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 		for _, testcase := range references {
@@ -797,7 +802,8 @@ func TestSecretConfigurationPlugin(t *testing.T) {
 			}
 			store, err := store.NewFakeStore(objects)
 			assert.Nil(err)
-			state, err := Build(logrus.New(), store)
+			p := NewParser(logrus.New(), store)
+			state, err := p.Build()
 			assert.Nil(err)
 			assert.NotNil(state)
 			assert.Equal(0, len(state.Plugins),
@@ -831,7 +837,8 @@ func TestCACertificate(t *testing.T) {
 			Secrets: secrets,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 
@@ -881,7 +888,8 @@ func TestCACertificate(t *testing.T) {
 			Secrets: secrets,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 
@@ -943,7 +951,8 @@ func TestCACertificate(t *testing.T) {
 			Secrets: secrets,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 
@@ -1026,7 +1035,8 @@ func TestServiceClientCertificate(t *testing.T) {
 			Services:         services,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 		assert.Equal(1, len(state.Certificates),
@@ -1093,7 +1103,8 @@ func TestServiceClientCertificate(t *testing.T) {
 			Services:         services,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 		assert.Equal(0, len(state.Certificates),
@@ -1153,7 +1164,8 @@ func TestKongRouteAnnotations(t *testing.T) {
 			Services:         services,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 
@@ -1232,7 +1244,8 @@ func TestKongRouteAnnotations(t *testing.T) {
 			Services:         services,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 
@@ -1312,7 +1325,8 @@ func TestKongRouteAnnotations(t *testing.T) {
 				Services:         services,
 			})
 			assert.Nil(err)
-			state, err := Build(logrus.New(), store)
+			p := NewParser(logrus.New(), store)
+			state, err := p.Build()
 			assert.Nil(err)
 			assert.NotNil(state)
 
@@ -1393,7 +1407,8 @@ func TestKongRouteAnnotations(t *testing.T) {
 				Services:         services,
 			})
 			assert.Nil(err)
-			state, err := Build(logrus.New(), store)
+			p := NewParser(logrus.New(), store)
+			state, err := p.Build()
 			assert.Nil(err)
 			assert.NotNil(state)
 
@@ -1473,7 +1488,8 @@ func TestKongRouteAnnotations(t *testing.T) {
 				Services:         services,
 			})
 			assert.Nil(err)
-			state, err := Build(logrus.New(), store)
+			p := NewParser(logrus.New(), store)
+			state, err := p.Build()
 			assert.Nil(err)
 			assert.NotNil(state)
 
@@ -1553,7 +1569,8 @@ func TestKongRouteAnnotations(t *testing.T) {
 				Services:         services,
 			})
 			assert.Nil(err)
-			state, err := Build(logrus.New(), store)
+			p := NewParser(logrus.New(), store)
+			state, err := p.Build()
 			assert.Nil(err)
 			assert.NotNil(state)
 
@@ -1633,7 +1650,8 @@ func TestKongRouteAnnotations(t *testing.T) {
 				Services:         services,
 			})
 			assert.Nil(err)
-			state, err := Build(logrus.New(), store)
+			p := NewParser(logrus.New(), store)
+			state, err := p.Build()
 			assert.Nil(err)
 			assert.NotNil(state)
 
@@ -1713,7 +1731,8 @@ func TestKongRouteAnnotations(t *testing.T) {
 				Services:         services,
 			})
 			assert.Nil(err)
-			state, err := Build(logrus.New(), store)
+			p := NewParser(logrus.New(), store)
+			state, err := p.Build()
 			assert.Nil(err)
 			assert.NotNil(state)
 
@@ -1793,7 +1812,8 @@ func TestKongRouteAnnotations(t *testing.T) {
 			Services:         services,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 
@@ -1871,7 +1891,8 @@ func TestKongRouteAnnotations(t *testing.T) {
 			Services:         services,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 
@@ -1949,7 +1970,8 @@ func TestKongRouteAnnotations(t *testing.T) {
 			Services:         services,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 
@@ -2009,7 +2031,8 @@ func TestKongProcessClasslessIngress(t *testing.T) {
 			Services:         services,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 
@@ -2059,7 +2082,8 @@ func TestKongProcessClasslessIngress(t *testing.T) {
 			Services:         services,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 
@@ -2136,7 +2160,8 @@ func TestKnativeIngressAndPlugins(t *testing.T) {
 			Services:         services,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 
@@ -2214,7 +2239,8 @@ func TestKnativeIngressAndPlugins(t *testing.T) {
 			Services:         services,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 
@@ -2283,7 +2309,8 @@ func TestKnativeIngressAndPlugins(t *testing.T) {
 			Services:         services,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 
@@ -2368,7 +2395,8 @@ func TestKnativeIngressAndPlugins(t *testing.T) {
 			KongPlugins:      plugins,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 
@@ -2478,7 +2506,8 @@ func TestKongServiceAnnotations(t *testing.T) {
 			Services:         services,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 
@@ -2560,7 +2589,8 @@ func TestKongServiceAnnotations(t *testing.T) {
 			Services:         services,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 
@@ -2648,7 +2678,8 @@ func TestKongServiceAnnotations(t *testing.T) {
 				Services:         services,
 			})
 			assert.Nil(err)
-			state, err := Build(logrus.New(), store)
+			p := NewParser(logrus.New(), store)
+			state, err := p.Build()
 			assert.Nil(err)
 			assert.NotNil(state)
 
@@ -2717,7 +2748,8 @@ func TestDefaultBackend(t *testing.T) {
 			Services:         services,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 		assert.Equal(1, len(state.Services),
@@ -2785,7 +2817,8 @@ func TestDefaultBackend(t *testing.T) {
 			Services:         services,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 		assert.Equal(0, len(state.Certificates),
@@ -2850,7 +2883,8 @@ func TestParserSecret(t *testing.T) {
 			Secrets:          secrets,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 		assert.Equal(0, len(state.Certificates),
@@ -2931,7 +2965,8 @@ func TestParserSecret(t *testing.T) {
 			Secrets:          secrets,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 		assert.Equal(1, len(state.Certificates),
@@ -3012,7 +3047,8 @@ func TestParserSecret(t *testing.T) {
 			Secrets:          secrets,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 		assert.Equal(1, len(state.Certificates),
@@ -3094,7 +3130,8 @@ func TestParserSNI(t *testing.T) {
 			Secrets:          secrets,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 		assert.Equal(kong.Route{
@@ -3158,7 +3195,8 @@ func TestParserSNI(t *testing.T) {
 			IngressesV1beta1: ingresses,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 		assert.Equal(kong.Route{
@@ -3217,7 +3255,8 @@ func TestParserHostAliases(t *testing.T) {
 			IngressesV1beta1: ingresses,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 		assert.Equal(kong.Route{
@@ -3269,7 +3308,8 @@ func TestParserHostAliases(t *testing.T) {
 			IngressesV1beta1: ingresses,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 		assert.Equal(kong.Route{
@@ -3322,7 +3362,8 @@ func TestParserHostAliases(t *testing.T) {
 			IngressesV1beta1: ingresses,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 		assert.Equal(kong.Route{
@@ -3411,14 +3452,15 @@ func TestPluginAnnotations(t *testing.T) {
 			KongPlugins:      plugins,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 		assert.Equal(1, len(state.Plugins),
 			"expected no plugins to be rendered with missing plugin")
-		p := state.Plugins[0].Plugin
-		p.Route = nil
-		assert.Equal(p, kong.Plugin{
+		pl := state.Plugins[0].Plugin
+		pl.Route = nil
+		assert.Equal(pl, kong.Plugin{
 			Name:      kong.String("key-auth"),
 			Protocols: kong.StringSlice("grpc"),
 			Config: kong.Configuration{
@@ -3508,7 +3550,8 @@ func TestPluginAnnotations(t *testing.T) {
 			KongClusterPlugins: clusterPlugins,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 		assert.Equal(1, len(state.Plugins),
@@ -3578,7 +3621,8 @@ func TestPluginAnnotations(t *testing.T) {
 			KongClusterPlugins: clusterPlugins,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 		assert.Equal(1, len(state.Plugins),
@@ -3624,7 +3668,8 @@ func TestPluginAnnotations(t *testing.T) {
 			IngressesV1beta1: ingresses,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 		assert.Equal(0, len(state.Plugins),
@@ -4392,7 +4437,8 @@ func TestPickPort(t *testing.T) {
 			store, err := store.NewFakeStore(tt.objs)
 			assert.NoError(err)
 
-			state, err := Build(logrus.New(), store)
+			p := NewParser(logrus.New(), store)
+			state, err := p.Build()
 			assert.NoError(err)
 
 			assert.Equal(tt.wantTarget, *state.Upstreams[0].Targets[0].Target.Target)
@@ -4505,7 +4551,8 @@ func TestCertificate(t *testing.T) {
 			Secrets:          secrets,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 		assert.Equal(3, len(state.Certificates))
@@ -4597,7 +4644,8 @@ func TestCertificate(t *testing.T) {
 			Secrets:          secrets,
 		})
 		assert.Nil(err)
-		state, err := Build(logrus.New(), store)
+		p := NewParser(logrus.New(), store)
+		state, err := p.Build()
 		assert.Nil(err)
 		assert.NotNil(state)
 		assert.Equal(1, len(state.Certificates))
diff --git a/internal/dataplane/parser/translate_httproute_test.go b/internal/dataplane/parser/translate_httproute_test.go
index 5405a56a5..84bb30d42 100644
--- a/internal/dataplane/parser/translate_httproute_test.go
+++ b/internal/dataplane/parser/translate_httproute_test.go
@@ -142,7 +142,7 @@ func Test_ingressRulesFromHTTPRoutes(t *testing.T) {
 				ServiceNameToServices: make(map[string]kongstate.Service),
 			},
 			errs: []error{
-				fmt.Errorf("HTTPRoute default/basic-httproute can't be routed: %w", fmt.Errorf("no match rules or hostnames specified")),
+				fmt.Errorf("no match rules or hostnames specified"),
 			},
 		},
 		{
@@ -242,7 +242,7 @@ func Test_ingressRulesFromHTTPRoutes(t *testing.T) {
 				ServiceNameToServices: make(map[string]kongstate.Service),
 			},
 			errs: []error{
-				fmt.Errorf("HTTPRoute default/basic-httproute can't be routed: %w", fmt.Errorf("no rules provided")),
+				fmt.Errorf("no rules provided"),
 			},
 		},
 		{
@@ -282,7 +282,7 @@ func Test_ingressRulesFromHTTPRoutes(t *testing.T) {
 				ServiceNameToServices: make(map[string]kongstate.Service),
 			},
 			errs: []error{
-				fmt.Errorf("HTTPRoute default/basic-httproute can't be routed: %w", fmt.Errorf("query param matches are not yet supported")),
+				fmt.Errorf("query param matches are not yet supported"),
 			},
 		},
 		{
@@ -441,8 +441,16 @@ func Test_ingressRulesFromHTTPRoutes(t *testing.T) {
 		},
 	} {
 		t.Run(tt.msg, func(t *testing.T) {
-			// generate the ingress rules
-			ingressRules, errs := ingressRulesFromHTTPRoutes(tt.routes)
+			ingressRules := newIngressRules()
+
+			var errs []error
+			for _, httproute := range tt.routes {
+				// generate the ingress rules
+				err := ingressRulesFromHTTPRoute(&ingressRules, httproute)
+				if err != nil {
+					errs = append(errs, err)
+				}
+			}
 
 			// verify that we receive the expected values
 			assert.Equal(t, tt.expected, ingressRules)
diff --git a/internal/dataplane/parser/translate_ingress_test.go b/internal/dataplane/parser/translate_ingress_test.go
new file mode 100644
index 000000000..8bb19ed01
--- /dev/null
+++ b/internal/dataplane/parser/translate_ingress_test.go
@@ -0,0 +1,863 @@
+package parser
+
+import (
+	"testing"
+
+	"github.com/sirupsen/logrus"
+	"github.com/stretchr/testify/assert"
+	networkingv1 "k8s.io/api/networking/v1"
+	networkingv1beta1 "k8s.io/api/networking/v1beta1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/annotations"
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/kongstate"
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/store"
+)
+
+func TestFromIngressV1beta1(t *testing.T) {
+	assert := assert.New(t)
+	ingressList := []*networkingv1beta1.Ingress{
+		// 0
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "foo",
+				Namespace: "foo-namespace",
+				Annotations: map[string]string{
+					annotations.IngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: networkingv1beta1.IngressSpec{
+				Rules: []networkingv1beta1.IngressRule{
+					{
+						Host: "example.com",
+						IngressRuleValue: networkingv1beta1.IngressRuleValue{
+							HTTP: &networkingv1beta1.HTTPIngressRuleValue{
+								Paths: []networkingv1beta1.HTTPIngressPath{
+									{
+										Path: "/",
+										Backend: networkingv1beta1.IngressBackend{
+											ServiceName: "foo-svc",
+											ServicePort: intstr.FromInt(80),
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		// 1
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "ing-with-tls",
+				Namespace: "bar-namespace",
+				Annotations: map[string]string{
+					annotations.IngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: networkingv1beta1.IngressSpec{
+				TLS: []networkingv1beta1.IngressTLS{
+					{
+						Hosts: []string{
+							"1.example.com",
+							"2.example.com",
+						},
+						SecretName: "sooper-secret",
+					},
+					{
+						Hosts: []string{
+							"3.example.com",
+							"4.example.com",
+						},
+						SecretName: "sooper-secret2",
+					},
+				},
+				Rules: []networkingv1beta1.IngressRule{
+					{
+						Host: "example.com",
+						IngressRuleValue: networkingv1beta1.IngressRuleValue{
+							HTTP: &networkingv1beta1.HTTPIngressRuleValue{
+								Paths: []networkingv1beta1.HTTPIngressPath{
+									{
+										Path: "/",
+										Backend: networkingv1beta1.IngressBackend{
+											ServiceName: "foo-svc",
+											ServicePort: intstr.FromInt(80),
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		// 2
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "ing-with-default-backend",
+				Namespace: "bar-namespace",
+				Annotations: map[string]string{
+					annotations.IngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: networkingv1beta1.IngressSpec{
+				Backend: &networkingv1beta1.IngressBackend{
+					ServiceName: "default-svc",
+					ServicePort: intstr.FromInt(80),
+				},
+			},
+		},
+		// 3
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "foo",
+				Namespace: "foo-namespace",
+				Annotations: map[string]string{
+					annotations.IngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: networkingv1beta1.IngressSpec{
+				Rules: []networkingv1beta1.IngressRule{
+					{
+						Host: "example.com",
+						IngressRuleValue: networkingv1beta1.IngressRuleValue{
+							HTTP: &networkingv1beta1.HTTPIngressRuleValue{
+								Paths: []networkingv1beta1.HTTPIngressPath{
+									{
+										Path: "/.well-known/acme-challenge/yolo",
+										Backend: networkingv1beta1.IngressBackend{
+											ServiceName: "cert-manager-solver-pod",
+											ServicePort: intstr.FromInt(80),
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		// 4
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "foo",
+				Namespace: "foo-namespace",
+				Annotations: map[string]string{
+					annotations.IngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: networkingv1beta1.IngressSpec{
+				Rules: []networkingv1beta1.IngressRule{
+					{
+						Host: "example.com",
+						IngressRuleValue: networkingv1beta1.IngressRuleValue{
+							HTTP: &networkingv1beta1.HTTPIngressRuleValue{
+								Paths: []networkingv1beta1.HTTPIngressPath{
+									{
+										Backend: networkingv1beta1.IngressBackend{
+											ServiceName: "foo-svc",
+											ServicePort: intstr.FromInt(80),
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		// 5
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "baz",
+				Namespace: "foo-namespace",
+				Annotations: map[string]string{
+					annotations.IngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: networkingv1beta1.IngressSpec{
+				Rules: []networkingv1beta1.IngressRule{
+					{
+						Host:             "example.com",
+						IngressRuleValue: networkingv1beta1.IngressRuleValue{},
+					},
+				},
+			},
+		},
+		// 6
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "foo",
+				Namespace: "foo-namespace",
+				Annotations: map[string]string{
+					annotations.IngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: networkingv1beta1.IngressSpec{
+				Rules: []networkingv1beta1.IngressRule{
+					{
+						Host: "example.com",
+						IngressRuleValue: networkingv1beta1.IngressRuleValue{
+							HTTP: &networkingv1beta1.HTTPIngressRuleValue{
+								Paths: []networkingv1beta1.HTTPIngressPath{
+									{
+										Backend: networkingv1beta1.IngressBackend{
+											ServiceName: "foo-svc",
+											ServicePort: intstr.FromInt(80),
+										},
+									},
+								},
+							},
+						},
+					},
+					{
+						Host: "example.net",
+						IngressRuleValue: networkingv1beta1.IngressRuleValue{
+							HTTP: &networkingv1beta1.HTTPIngressRuleValue{
+								Paths: []networkingv1beta1.HTTPIngressPath{
+									{
+										Backend: networkingv1beta1.IngressBackend{
+											ServiceName: "foo-svc",
+											ServicePort: intstr.FromInt(8000),
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		// 7
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "invalid-path",
+				Namespace: "foo-namespace",
+				Annotations: map[string]string{
+					annotations.IngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: networkingv1beta1.IngressSpec{
+				Rules: []networkingv1beta1.IngressRule{
+					{
+						Host: "example.com",
+						IngressRuleValue: networkingv1beta1.IngressRuleValue{
+							HTTP: &networkingv1beta1.HTTPIngressRuleValue{
+								Paths: []networkingv1beta1.HTTPIngressPath{
+									{
+										Path: "/foo//bar",
+										Backend: networkingv1beta1.IngressBackend{
+											ServiceName: "foo-svc",
+											ServicePort: intstr.FromInt(80),
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	t.Run("no ingress returns empty info", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			IngressesV1beta1: []*networkingv1beta1.Ingress{},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromIngressV1beta1()
+		assert.Equal(ingressRules{
+			ServiceNameToServices: make(map[string]kongstate.Service),
+			SecretNameToSNIs:      make(map[string][]string),
+		}, parsedInfo)
+	})
+	t.Run("simple ingress rule is parsed", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			IngressesV1beta1: []*networkingv1beta1.Ingress{
+				ingressList[0],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromIngressV1beta1()
+		assert.Equal(1, len(parsedInfo.ServiceNameToServices))
+		assert.Equal("foo-svc.foo-namespace.80.svc", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.80"].Host)
+		assert.Equal(80, *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.80"].Port)
+
+		assert.Equal("/", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.80"].Routes[0].Paths[0])
+		assert.Equal("example.com", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.80"].Routes[0].Hosts[0])
+	})
+	t.Run("ingress rule with default backend", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			IngressesV1beta1: []*networkingv1beta1.Ingress{ingressList[0], ingressList[2]},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromIngressV1beta1()
+		assert.Equal(2, len(parsedInfo.ServiceNameToServices))
+		assert.Equal("foo-svc.foo-namespace.80.svc", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.80"].Host)
+		assert.Equal(80, *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.80"].Port)
+
+		assert.Equal("/", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.80"].Routes[0].Paths[0])
+		assert.Equal("example.com", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.80"].Routes[0].Hosts[0])
+
+		assert.Equal(1, len(parsedInfo.ServiceNameToServices["bar-namespace.default-svc.80"].Routes))
+		assert.Equal("/", *parsedInfo.ServiceNameToServices["bar-namespace.default-svc.80"].Routes[0].Paths[0])
+		assert.Equal(0, len(parsedInfo.ServiceNameToServices["bar-namespace.default-svc.80"].Routes[0].Hosts))
+	})
+	t.Run("ingress rule with TLS", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			IngressesV1beta1: []*networkingv1beta1.Ingress{
+				ingressList[1],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromIngressV1beta1()
+		assert.Equal(2, len(parsedInfo.SecretNameToSNIs))
+		assert.Equal(2, len(parsedInfo.SecretNameToSNIs["bar-namespace/sooper-secret"]))
+		assert.Equal(2, len(parsedInfo.SecretNameToSNIs["bar-namespace/sooper-secret2"]))
+	})
+	t.Run("ingress rule with ACME like path has strip_path set to false", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			IngressesV1beta1: []*networkingv1beta1.Ingress{
+				ingressList[3],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromIngressV1beta1()
+		assert.Equal(1, len(parsedInfo.ServiceNameToServices))
+		assert.Equal("cert-manager-solver-pod.foo-namespace.80.svc",
+			*parsedInfo.ServiceNameToServices["foo-namespace.cert-manager-solver-pod.80"].Host)
+		assert.Equal(80, *parsedInfo.ServiceNameToServices["foo-namespace.cert-manager-solver-pod.80"].Port)
+
+		assert.Equal("/.well-known/acme-challenge/yolo",
+			*parsedInfo.ServiceNameToServices["foo-namespace.cert-manager-solver-pod.80"].Routes[0].Paths[0])
+		assert.Equal("example.com",
+			*parsedInfo.ServiceNameToServices["foo-namespace.cert-manager-solver-pod.80"].Routes[0].Hosts[0])
+		assert.False(*parsedInfo.ServiceNameToServices["foo-namespace.cert-manager-solver-pod.80"].Routes[0].StripPath)
+	})
+	t.Run("ingress with empty path is correctly parsed", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			IngressesV1beta1: []*networkingv1beta1.Ingress{
+				ingressList[4],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromIngressV1beta1()
+		assert.Equal("/", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.80"].Routes[0].Paths[0])
+		assert.Equal("example.com", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.80"].Routes[0].Hosts[0])
+	})
+	t.Run("empty Ingress rule doesn't cause a panic", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			IngressesV1beta1: []*networkingv1beta1.Ingress{
+				ingressList[5],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		assert.NotPanics(func() {
+			p.ingressRulesFromIngressV1beta1()
+		})
+	})
+	t.Run("Ingress rules with multiple ports for one Service use separate hostnames for each port", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			IngressesV1beta1: []*networkingv1beta1.Ingress{
+				ingressList[6],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromIngressV1beta1()
+		assert.Equal("foo-svc.foo-namespace.80.svc", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.80"].Host)
+		assert.Equal("foo-svc.foo-namespace.8000.svc", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.8000"].Host)
+	})
+	t.Run("Ingress rule with path containing multiple slashes ('//') is skipped", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			IngressesV1beta1: []*networkingv1beta1.Ingress{
+				ingressList[7],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromIngressV1beta1()
+		assert.Empty(parsedInfo.ServiceNameToServices)
+	})
+}
+
+func TestFromIngressV1(t *testing.T) {
+	assert := assert.New(t)
+	ingressList := []*networkingv1.Ingress{
+		// 0
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "foo",
+				Namespace: "foo-namespace",
+				Annotations: map[string]string{
+					annotations.IngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: networkingv1.IngressSpec{
+				Rules: []networkingv1.IngressRule{
+					{
+						Host: "example.com",
+						IngressRuleValue: networkingv1.IngressRuleValue{
+							HTTP: &networkingv1.HTTPIngressRuleValue{
+								Paths: []networkingv1.HTTPIngressPath{
+									{
+										Path: "/",
+										Backend: networkingv1.IngressBackend{
+											Service: &networkingv1.IngressServiceBackend{
+												Name: "foo-svc",
+												Port: networkingv1.ServiceBackendPort{Number: 80},
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		// 1
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "ing-with-tls",
+				Namespace: "bar-namespace",
+				Annotations: map[string]string{
+					annotations.IngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: networkingv1.IngressSpec{
+				TLS: []networkingv1.IngressTLS{
+					{
+						Hosts: []string{
+							"1.example.com",
+							"2.example.com",
+						},
+						SecretName: "sooper-secret",
+					},
+					{
+						Hosts: []string{
+							"3.example.com",
+							"4.example.com",
+						},
+						SecretName: "sooper-secret2",
+					},
+				},
+				Rules: []networkingv1.IngressRule{
+					{
+						Host: "example.com",
+						IngressRuleValue: networkingv1.IngressRuleValue{
+							HTTP: &networkingv1.HTTPIngressRuleValue{
+								Paths: []networkingv1.HTTPIngressPath{
+									{
+										Path: "/",
+										Backend: networkingv1.IngressBackend{
+											Service: &networkingv1.IngressServiceBackend{
+												Name: "foo-svc",
+												Port: networkingv1.ServiceBackendPort{Number: 80},
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		// 2
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "ing-with-default-backend",
+				Namespace: "bar-namespace",
+				Annotations: map[string]string{
+					annotations.IngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: networkingv1.IngressSpec{
+				DefaultBackend: &networkingv1.IngressBackend{
+					Service: &networkingv1.IngressServiceBackend{
+						Name: "default-svc",
+						Port: networkingv1.ServiceBackendPort{Number: 80},
+					},
+				},
+			},
+		},
+		// 3
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "foo",
+				Namespace: "foo-namespace",
+				Annotations: map[string]string{
+					annotations.IngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: networkingv1.IngressSpec{
+				Rules: []networkingv1.IngressRule{
+					{
+						Host: "example.com",
+						IngressRuleValue: networkingv1.IngressRuleValue{
+							HTTP: &networkingv1.HTTPIngressRuleValue{
+								Paths: []networkingv1.HTTPIngressPath{
+									{
+										Path: "/.well-known/acme-challenge/yolo",
+										Backend: networkingv1.IngressBackend{
+											Service: &networkingv1.IngressServiceBackend{
+												Name: "cert-manager-solver-pod",
+												Port: networkingv1.ServiceBackendPort{Number: 80},
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		// 4
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "foo",
+				Namespace: "foo-namespace",
+				Annotations: map[string]string{
+					annotations.IngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: networkingv1.IngressSpec{
+				Rules: []networkingv1.IngressRule{
+					{
+						Host: "example.com",
+						IngressRuleValue: networkingv1.IngressRuleValue{
+							HTTP: &networkingv1.HTTPIngressRuleValue{
+								Paths: []networkingv1.HTTPIngressPath{
+									{
+										Backend: networkingv1.IngressBackend{
+											Service: &networkingv1.IngressServiceBackend{
+												Name: "foo-svc",
+												Port: networkingv1.ServiceBackendPort{Number: 80},
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		// 5
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "baz",
+				Namespace: "foo-namespace",
+				Annotations: map[string]string{
+					annotations.IngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: networkingv1.IngressSpec{
+				Rules: []networkingv1.IngressRule{
+					{
+						Host:             "example.com",
+						IngressRuleValue: networkingv1.IngressRuleValue{},
+					},
+				},
+			},
+		},
+		// 6
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "foo",
+				Namespace: "foo-namespace",
+				Annotations: map[string]string{
+					annotations.IngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: networkingv1.IngressSpec{
+				Rules: []networkingv1.IngressRule{
+					{
+						Host: "example.com",
+						IngressRuleValue: networkingv1.IngressRuleValue{
+							HTTP: &networkingv1.HTTPIngressRuleValue{
+								Paths: []networkingv1.HTTPIngressPath{
+									{
+										Backend: networkingv1.IngressBackend{
+											Service: &networkingv1.IngressServiceBackend{
+												Name: "foo-svc",
+												Port: networkingv1.ServiceBackendPort{Number: 80},
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+					{
+						Host: "example.net",
+						IngressRuleValue: networkingv1.IngressRuleValue{
+							HTTP: &networkingv1.HTTPIngressRuleValue{
+								Paths: []networkingv1.HTTPIngressPath{
+									{
+										Backend: networkingv1.IngressBackend{
+											Service: &networkingv1.IngressServiceBackend{
+												Name: "foo-svc",
+												Port: networkingv1.ServiceBackendPort{Number: 8000},
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		// 7
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "invalid-path",
+				Namespace: "foo-namespace",
+				Annotations: map[string]string{
+					annotations.IngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: networkingv1.IngressSpec{
+				Rules: []networkingv1.IngressRule{
+					{
+						Host: "example.com",
+						IngressRuleValue: networkingv1.IngressRuleValue{
+							HTTP: &networkingv1.HTTPIngressRuleValue{
+								Paths: []networkingv1.HTTPIngressPath{
+									{
+										Path: "/foo//bar",
+										Backend: networkingv1.IngressBackend{
+											Service: &networkingv1.IngressServiceBackend{
+												Name: "foo-svc",
+												Port: networkingv1.ServiceBackendPort{Number: 80},
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		// 8
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "foo",
+				Namespace: "foo-namespace",
+				Annotations: map[string]string{
+					annotations.IngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: networkingv1.IngressSpec{
+				Rules: []networkingv1.IngressRule{
+					{
+						Host: "example.com",
+						IngressRuleValue: networkingv1.IngressRuleValue{
+							HTTP: &networkingv1.HTTPIngressRuleValue{
+								Paths: []networkingv1.HTTPIngressPath{
+									{
+										Path: "/",
+										Backend: networkingv1.IngressBackend{
+											Service: &networkingv1.IngressServiceBackend{
+												Name: "foo-svc",
+												Port: networkingv1.ServiceBackendPort{Name: "http"},
+											},
+										},
+									},
+									{
+										Path: "/ws",
+										Backend: networkingv1.IngressBackend{
+											Service: &networkingv1.IngressServiceBackend{
+												Name: "foo-svc",
+												Port: networkingv1.ServiceBackendPort{Name: "ws"},
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	t.Run("no ingress returns empty info", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			IngressesV1: []*networkingv1.Ingress{},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromIngressV1()
+		assert.Equal(ingressRules{
+			ServiceNameToServices: make(map[string]kongstate.Service),
+			SecretNameToSNIs:      make(map[string][]string),
+		}, parsedInfo)
+	})
+	t.Run("simple ingress rule is parsed", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			IngressesV1: []*networkingv1.Ingress{
+				ingressList[0],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromIngressV1()
+		assert.Equal(1, len(parsedInfo.ServiceNameToServices))
+		assert.Equal("foo-svc.foo-namespace.80.svc", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pnum-80"].Host)
+		assert.Equal(80, *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pnum-80"].Port)
+
+		assert.Equal("/", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pnum-80"].Routes[0].Paths[0])
+		assert.Equal("example.com", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pnum-80"].Routes[0].Hosts[0])
+	})
+	t.Run("ingress rule with default backend", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			IngressesV1: []*networkingv1.Ingress{
+				ingressList[0],
+				ingressList[2],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromIngressV1()
+		assert.Equal(2, len(parsedInfo.ServiceNameToServices))
+		assert.Equal("foo-svc.foo-namespace.80.svc", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pnum-80"].Host)
+		assert.Equal(80, *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pnum-80"].Port)
+
+		assert.Equal("/", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pnum-80"].Routes[0].Paths[0])
+		assert.Equal("example.com", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pnum-80"].Routes[0].Hosts[0])
+
+		assert.Equal(1, len(parsedInfo.ServiceNameToServices["bar-namespace.default-svc.80"].Routes))
+		assert.Equal("/", *parsedInfo.ServiceNameToServices["bar-namespace.default-svc.80"].Routes[0].Paths[0])
+		assert.Equal(0, len(parsedInfo.ServiceNameToServices["bar-namespace.default-svc.80"].Routes[0].Hosts))
+	})
+	t.Run("ingress rule with TLS", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			IngressesV1: []*networkingv1.Ingress{
+				ingressList[1],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromIngressV1()
+		assert.Equal(2, len(parsedInfo.SecretNameToSNIs))
+		assert.Equal(2, len(parsedInfo.SecretNameToSNIs["bar-namespace/sooper-secret"]))
+		assert.Equal(2, len(parsedInfo.SecretNameToSNIs["bar-namespace/sooper-secret2"]))
+	})
+	t.Run("ingress rule with ACME like path has strip_path set to false", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			IngressesV1: []*networkingv1.Ingress{
+				ingressList[3],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromIngressV1()
+		assert.Equal(1, len(parsedInfo.ServiceNameToServices))
+		assert.Equal("cert-manager-solver-pod.foo-namespace.80.svc",
+			*parsedInfo.ServiceNameToServices["foo-namespace.cert-manager-solver-pod.pnum-80"].Host)
+		assert.Equal(80, *parsedInfo.ServiceNameToServices["foo-namespace.cert-manager-solver-pod.pnum-80"].Port)
+
+		assert.Equal("/.well-known/acme-challenge/yolo",
+			*parsedInfo.ServiceNameToServices["foo-namespace.cert-manager-solver-pod.pnum-80"].Routes[0].Paths[0])
+		assert.Equal("example.com",
+			*parsedInfo.ServiceNameToServices["foo-namespace.cert-manager-solver-pod.pnum-80"].Routes[0].Hosts[0])
+		assert.False(*parsedInfo.ServiceNameToServices["foo-namespace.cert-manager-solver-pod.pnum-80"].Routes[0].StripPath)
+	})
+	t.Run("ingress with empty path is correctly parsed", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			IngressesV1: []*networkingv1.Ingress{
+				ingressList[4],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromIngressV1()
+		assert.Equal("/", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pnum-80"].Routes[0].Paths[0])
+		assert.Equal("example.com", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pnum-80"].Routes[0].Hosts[0])
+	})
+	t.Run("empty Ingress rule doesn't cause a panic", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			IngressesV1: []*networkingv1.Ingress{
+				ingressList[5],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		assert.NotPanics(func() {
+			p.ingressRulesFromIngressV1()
+		})
+	})
+	t.Run("Ingress rules with multiple ports for one Service use separate hostnames for each port", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			IngressesV1: []*networkingv1.Ingress{
+				ingressList[6],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromIngressV1()
+		assert.Equal("foo-svc.foo-namespace.80.svc",
+			*parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pnum-80"].Host)
+		assert.Equal("foo-svc.foo-namespace.8000.svc",
+			*parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pnum-8000"].Host)
+	})
+	t.Run("Ingress rule with path containing multiple slashes ('//') is skipped", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			IngressesV1: []*networkingv1.Ingress{
+				ingressList[7],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromIngressV1()
+		assert.Empty(parsedInfo.ServiceNameToServices)
+	})
+	t.Run("Ingress rule with ports defined by name", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			IngressesV1: []*networkingv1.Ingress{
+				ingressList[8],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromIngressV1()
+		_, ok := parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pname-http"]
+		assert.True(ok)
+		_, ok = parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pname-ws"]
+		assert.True(ok)
+	})
+}
diff --git a/internal/dataplane/parser/translate_knative_test.go b/internal/dataplane/parser/translate_knative_test.go
new file mode 100644
index 000000000..9ff6a7f5a
--- /dev/null
+++ b/internal/dataplane/parser/translate_knative_test.go
@@ -0,0 +1,309 @@
+package parser
+
+import (
+	"testing"
+
+	"github.com/kong/go-kong/kong"
+	"github.com/sirupsen/logrus"
+	"github.com/stretchr/testify/assert"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+	knative "knative.dev/networking/pkg/apis/networking/v1alpha1"
+
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/annotations"
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/kongstate"
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/store"
+)
+
+func TestFromKnativeIngress(t *testing.T) {
+	assert := assert.New(t)
+	ingressList := []*knative.Ingress{
+		// 0
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "foo",
+				Namespace: "foo-namespace",
+				Annotations: map[string]string{
+					annotations.KnativeIngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: knative.IngressSpec{
+				Rules: []knative.IngressRule{
+					{},
+				},
+			},
+		},
+		// 1
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "foo",
+				Namespace: "foo-namespace",
+				Annotations: map[string]string{
+					annotations.KnativeIngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: knative.IngressSpec{
+				Rules: []knative.IngressRule{
+					{
+						Hosts: []string{"my-func.example.com"},
+						HTTP: &knative.HTTPIngressRuleValue{
+							Paths: []knative.HTTPIngressPath{
+								{
+									Path: "/",
+									AppendHeaders: map[string]string{
+										"foo": "bar",
+									},
+									Splits: []knative.IngressBackendSplit{
+										{
+											IngressBackend: knative.IngressBackend{
+												ServiceNamespace: "foo-ns",
+												ServiceName:      "foo-svc",
+												ServicePort:      intstr.FromInt(42),
+											},
+											Percent: 100,
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		// 2
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "foo",
+				Namespace: "foo-namespace",
+				Annotations: map[string]string{
+					annotations.KnativeIngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: knative.IngressSpec{
+				Rules: []knative.IngressRule{
+					{
+						Hosts: []string{"my-func.example.com"},
+						HTTP: &knative.HTTPIngressRuleValue{
+							Paths: []knative.HTTPIngressPath{
+								{
+									Path: "/",
+									AppendHeaders: map[string]string{
+										"foo": "bar",
+									},
+									Splits: []knative.IngressBackendSplit{
+										{
+											IngressBackend: knative.IngressBackend{
+												ServiceNamespace: "bar-ns",
+												ServiceName:      "bar-svc",
+												ServicePort:      intstr.FromInt(42),
+											},
+											Percent: 20,
+										},
+										{
+											IngressBackend: knative.IngressBackend{
+												ServiceNamespace: "foo-ns",
+												ServiceName:      "foo-svc",
+												ServicePort:      intstr.FromInt(42),
+											},
+											Percent: 100,
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		// 3
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "foo",
+				Namespace: "foo-namespace",
+				Annotations: map[string]string{
+					annotations.KnativeIngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: knative.IngressSpec{
+				Rules: []knative.IngressRule{
+					{
+						Hosts: []string{"my-func.example.com"},
+						HTTP: &knative.HTTPIngressRuleValue{
+							Paths: []knative.HTTPIngressPath{
+								{
+									Path: "/",
+									AppendHeaders: map[string]string{
+										"foo": "bar",
+									},
+									Splits: []knative.IngressBackendSplit{
+										{
+											IngressBackend: knative.IngressBackend{
+												ServiceNamespace: "bar-ns",
+												ServiceName:      "bar-svc",
+												ServicePort:      intstr.FromInt(42),
+											},
+											Percent: 20,
+										},
+										{
+											IngressBackend: knative.IngressBackend{
+												ServiceNamespace: "foo-ns",
+												ServiceName:      "foo-svc",
+												ServicePort:      intstr.FromInt(42),
+											},
+											Percent: 100,
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+				TLS: []knative.IngressTLS{
+					{
+						Hosts: []string{
+							"foo.example.com",
+							"foo1.example.com",
+						},
+						SecretName: "foo-secret",
+					},
+					{
+						Hosts: []string{
+							"bar.example.com",
+							"bar1.example.com",
+						},
+						SecretName: "bar-secret",
+					},
+				},
+			},
+		},
+	}
+	t.Run("no ingress returns empty info", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			KnativeIngresses: []*knative.Ingress{},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromKnativeIngress()
+		assert.Equal(map[string]kongstate.Service{}, parsedInfo.ServiceNameToServices)
+		assert.Equal(newSecretNameToSNIs(), parsedInfo.SecretNameToSNIs)
+	})
+	t.Run("empty ingress returns empty info", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			KnativeIngresses: []*knative.Ingress{
+				ingressList[0],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromKnativeIngress()
+		assert.Equal(map[string]kongstate.Service{}, parsedInfo.ServiceNameToServices)
+		assert.Equal(newSecretNameToSNIs(), parsedInfo.SecretNameToSNIs)
+	})
+	t.Run("basic knative Ingress resource is parsed", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			KnativeIngresses: []*knative.Ingress{
+				ingressList[1],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromKnativeIngress()
+		assert.Equal(1, len(parsedInfo.ServiceNameToServices))
+		svc := parsedInfo.ServiceNameToServices["foo-ns.foo-svc.42"]
+		assert.Equal(kong.Service{
+			Name:           kong.String("foo-ns.foo-svc.42"),
+			Port:           kong.Int(80),
+			Host:           kong.String("foo-svc.foo-ns.42.svc"),
+			Path:           kong.String("/"),
+			Protocol:       kong.String("http"),
+			WriteTimeout:   kong.Int(60000),
+			ReadTimeout:    kong.Int(60000),
+			ConnectTimeout: kong.Int(60000),
+			Retries:        kong.Int(5),
+		}, svc.Service)
+		assert.Equal(kong.Route{
+			Name:              kong.String("foo-namespace.foo.00"),
+			RegexPriority:     kong.Int(0),
+			StripPath:         kong.Bool(false),
+			Paths:             kong.StringSlice("/"),
+			PreserveHost:      kong.Bool(true),
+			Protocols:         kong.StringSlice("http", "https"),
+			Hosts:             kong.StringSlice("my-func.example.com"),
+			ResponseBuffering: kong.Bool(true),
+			RequestBuffering:  kong.Bool(true),
+		}, svc.Routes[0].Route)
+		assert.Equal(kong.Plugin{
+			Name: kong.String("request-transformer"),
+			Config: kong.Configuration{
+				"add": map[string]interface{}{
+					"headers": []string{"foo:bar"},
+				},
+			},
+		}, svc.Plugins[0])
+
+		assert.Equal(newSecretNameToSNIs(), parsedInfo.SecretNameToSNIs)
+	})
+	t.Run("knative TLS section is correctly parsed", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			KnativeIngresses: []*knative.Ingress{
+				ingressList[3],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromKnativeIngress()
+		assert.Equal(SecretNameToSNIs(map[string][]string{
+			"foo-namespace/bar-secret": {"bar.example.com", "bar1.example.com"},
+			"foo-namespace/foo-secret": {"foo.example.com", "foo1.example.com"},
+		}), parsedInfo.SecretNameToSNIs)
+	})
+	t.Run("split knative Ingress resource chooses the highest split", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			KnativeIngresses: []*knative.Ingress{
+				ingressList[2],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromKnativeIngress()
+		assert.Equal(1, len(parsedInfo.ServiceNameToServices))
+		svc := parsedInfo.ServiceNameToServices["foo-ns.foo-svc.42"]
+		assert.Equal(kong.Service{
+			Name:           kong.String("foo-ns.foo-svc.42"),
+			Port:           kong.Int(80),
+			Host:           kong.String("foo-svc.foo-ns.42.svc"),
+			Path:           kong.String("/"),
+			Protocol:       kong.String("http"),
+			WriteTimeout:   kong.Int(60000),
+			ReadTimeout:    kong.Int(60000),
+			ConnectTimeout: kong.Int(60000),
+			Retries:        kong.Int(5),
+		}, svc.Service)
+		assert.Equal(kong.Route{
+			Name:              kong.String("foo-namespace.foo.00"),
+			RegexPriority:     kong.Int(0),
+			StripPath:         kong.Bool(false),
+			Paths:             kong.StringSlice("/"),
+			PreserveHost:      kong.Bool(true),
+			Protocols:         kong.StringSlice("http", "https"),
+			Hosts:             kong.StringSlice("my-func.example.com"),
+			ResponseBuffering: kong.Bool(true),
+			RequestBuffering:  kong.Bool(true),
+		}, svc.Routes[0].Route)
+		assert.Equal(kong.Plugin{
+			Name: kong.String("request-transformer"),
+			Config: kong.Configuration{
+				"add": map[string]interface{}{
+					"headers": []string{"foo:bar"},
+				},
+			},
+		}, svc.Plugins[0])
+
+		assert.Equal(newSecretNameToSNIs(), parsedInfo.SecretNameToSNIs)
+	})
+}
diff --git a/internal/dataplane/parser/translate_kong_l4_test.go b/internal/dataplane/parser/translate_kong_l4_test.go
new file mode 100644
index 000000000..2b270454a
--- /dev/null
+++ b/internal/dataplane/parser/translate_kong_l4_test.go
@@ -0,0 +1,309 @@
+package parser
+
+import (
+	"testing"
+
+	"github.com/kong/go-kong/kong"
+	"github.com/sirupsen/logrus"
+	"github.com/stretchr/testify/assert"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/annotations"
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/kongstate"
+	"github.com/kong/kubernetes-ingress-controller/v2/internal/store"
+	configurationv1beta1 "github.com/kong/kubernetes-ingress-controller/v2/pkg/apis/configuration/v1beta1"
+)
+
+func TestFromTCPIngressV1beta1(t *testing.T) {
+	assert := assert.New(t)
+	tcpIngressList := []*configurationv1beta1.TCPIngress{
+		// 0
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "foo",
+				Namespace: "default",
+				Annotations: map[string]string{
+					annotations.IngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+		},
+		// 1
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "foo",
+				Namespace: "default",
+				Annotations: map[string]string{
+					annotations.IngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: configurationv1beta1.TCPIngressSpec{
+				Rules: []configurationv1beta1.IngressRule{
+					{
+						Port: 9000,
+						Backend: configurationv1beta1.IngressBackend{
+							ServiceName: "foo-svc",
+							ServicePort: 80,
+						},
+					},
+				},
+			},
+		},
+		// 2
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "foo",
+				Namespace: "default",
+				Annotations: map[string]string{
+					annotations.IngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: configurationv1beta1.TCPIngressSpec{
+				Rules: []configurationv1beta1.IngressRule{
+					{
+						Host: "example.com",
+						Port: 9000,
+						Backend: configurationv1beta1.IngressBackend{
+							ServiceName: "foo-svc",
+							ServicePort: 80,
+						},
+					},
+				},
+			},
+		},
+		// 3
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "foo",
+				Namespace: "default",
+				Annotations: map[string]string{
+					annotations.IngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: configurationv1beta1.TCPIngressSpec{
+				TLS: []configurationv1beta1.IngressTLS{
+					{
+						Hosts: []string{
+							"1.example.com",
+							"2.example.com",
+						},
+						SecretName: "sooper-secret",
+					},
+					{
+						Hosts: []string{
+							"3.example.com",
+							"4.example.com",
+						},
+						SecretName: "sooper-secret2",
+					},
+				},
+			},
+		},
+		// 4
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "foo",
+				Namespace: "default",
+				Annotations: map[string]string{
+					annotations.IngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: configurationv1beta1.TCPIngressSpec{
+				Rules: []configurationv1beta1.IngressRule{
+					{
+						Port: 9000,
+						Backend: configurationv1beta1.IngressBackend{
+							ServiceName: "",
+							ServicePort: 80,
+						},
+					},
+				},
+			},
+		},
+		// 5
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "foo",
+				Namespace: "default",
+				Annotations: map[string]string{
+					annotations.IngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: configurationv1beta1.TCPIngressSpec{
+				Rules: []configurationv1beta1.IngressRule{
+					{
+						Port: 0,
+						Backend: configurationv1beta1.IngressBackend{
+							ServiceName: "foo-svc",
+							ServicePort: 80,
+						},
+					},
+				},
+			},
+		},
+		// 6
+		{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "foo",
+				Namespace: "default",
+				Annotations: map[string]string{
+					annotations.IngressClassKey: annotations.DefaultIngressClass,
+				},
+			},
+			Spec: configurationv1beta1.TCPIngressSpec{
+				Rules: []configurationv1beta1.IngressRule{
+					{
+						Port: 9000,
+						Backend: configurationv1beta1.IngressBackend{
+							ServiceName: "foo-svc",
+							ServicePort: 0,
+						},
+					},
+				},
+			},
+		},
+	}
+	t.Run("no TCPIngress returns empty info", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			TCPIngresses: []*configurationv1beta1.TCPIngress{},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromTCPIngressV1beta1()
+		assert.Equal(ingressRules{
+			ServiceNameToServices: make(map[string]kongstate.Service),
+			SecretNameToSNIs:      make(map[string][]string),
+		}, parsedInfo)
+	})
+	t.Run("empty TCPIngress return empty info", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			TCPIngresses: []*configurationv1beta1.TCPIngress{
+				tcpIngressList[0],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromTCPIngressV1beta1()
+		assert.Equal(ingressRules{
+			ServiceNameToServices: make(map[string]kongstate.Service),
+			SecretNameToSNIs:      make(map[string][]string),
+		}, parsedInfo)
+	})
+	t.Run("simple TCPIngress rule is parsed", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			TCPIngresses: []*configurationv1beta1.TCPIngress{
+				tcpIngressList[1],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromTCPIngressV1beta1()
+		assert.Equal(1, len(parsedInfo.ServiceNameToServices))
+		svc := parsedInfo.ServiceNameToServices["default.foo-svc.80"]
+		assert.Equal("foo-svc.default.80.svc", *svc.Host)
+		assert.Equal(80, *svc.Port)
+		assert.Equal("tcp", *svc.Protocol)
+
+		assert.Equal(1, len(svc.Routes))
+		route := svc.Routes[0]
+		assert.Equal(kong.Route{
+			Name:      kong.String("default.foo.0"),
+			Protocols: kong.StringSlice("tcp", "tls"),
+			Destinations: []*kong.CIDRPort{
+				{
+					Port: kong.Int(9000),
+				},
+			},
+		}, route.Route)
+	})
+	t.Run("TCPIngress rule with host is parsed", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			TCPIngresses: []*configurationv1beta1.TCPIngress{
+				tcpIngressList[2],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromTCPIngressV1beta1()
+		assert.Equal(1, len(parsedInfo.ServiceNameToServices))
+		svc := parsedInfo.ServiceNameToServices["default.foo-svc.80"]
+		assert.Equal("foo-svc.default.80.svc", *svc.Host)
+		assert.Equal(80, *svc.Port)
+		assert.Equal("tcp", *svc.Protocol)
+
+		assert.Equal(1, len(svc.Routes))
+		route := svc.Routes[0]
+		assert.Equal(kong.Route{
+			Name:      kong.String("default.foo.0"),
+			Protocols: kong.StringSlice("tcp", "tls"),
+			SNIs:      kong.StringSlice("example.com"),
+			Destinations: []*kong.CIDRPort{
+				{
+					Port: kong.Int(9000),
+				},
+			},
+		}, route.Route)
+	})
+	t.Run("TCPIngress with TLS", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			TCPIngresses: []*configurationv1beta1.TCPIngress{
+				tcpIngressList[3],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromTCPIngressV1beta1()
+		assert.Equal(2, len(parsedInfo.SecretNameToSNIs))
+		assert.Equal(2, len(parsedInfo.SecretNameToSNIs["default/sooper-secret"]))
+		assert.Equal(2, len(parsedInfo.SecretNameToSNIs["default/sooper-secret2"]))
+	})
+	t.Run("TCPIngress without service name returns empty info", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			TCPIngresses: []*configurationv1beta1.TCPIngress{
+				tcpIngressList[4],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromTCPIngressV1beta1()
+		assert.Equal(ingressRules{
+			ServiceNameToServices: make(map[string]kongstate.Service),
+			SecretNameToSNIs:      make(map[string][]string),
+		}, parsedInfo)
+	})
+	t.Run("TCPIngress with invalid port returns empty info", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			TCPIngresses: []*configurationv1beta1.TCPIngress{
+				tcpIngressList[5],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromTCPIngressV1beta1()
+		assert.Equal(ingressRules{
+			ServiceNameToServices: make(map[string]kongstate.Service),
+			SecretNameToSNIs:      make(map[string][]string),
+		}, parsedInfo)
+	})
+	t.Run("empty TCPIngress with invalid service port returns empty info", func(t *testing.T) {
+		store, err := store.NewFakeStore(store.FakeObjects{
+			TCPIngresses: []*configurationv1beta1.TCPIngress{
+				tcpIngressList[6],
+			},
+		})
+		assert.NoError(err)
+		p := NewParser(logrus.New(), store)
+
+		parsedInfo := p.ingressRulesFromTCPIngressV1beta1()
+		assert.Equal(ingressRules{
+			ServiceNameToServices: make(map[string]kongstate.Service),
+			SecretNameToSNIs:      make(map[string][]string),
+		}, parsedInfo)
+	})
+}
diff --git a/internal/dataplane/parser/translate_test.go b/internal/dataplane/parser/translate_test.go
index 8da0d5ee5..2124ae0af 100644
--- a/internal/dataplane/parser/translate_test.go
+++ b/internal/dataplane/parser/translate_test.go
@@ -4,1167 +4,10 @@ import (
 	"testing"
 
 	"github.com/kong/go-kong/kong"
-	"github.com/sirupsen/logrus"
-	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	networkingv1 "k8s.io/api/networking/v1"
-	networkingv1beta1 "k8s.io/api/networking/v1beta1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/util/intstr"
-	knative "knative.dev/networking/pkg/apis/networking/v1alpha1"
-
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/annotations"
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/kongstate"
-	configurationv1beta1 "github.com/kong/kubernetes-ingress-controller/v2/pkg/apis/configuration/v1beta1"
 )
 
-func TestFromIngressV1beta1(t *testing.T) {
-	assert := assert.New(t)
-	ingressList := []*networkingv1beta1.Ingress{
-		// 0
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "foo",
-				Namespace: "foo-namespace",
-				Annotations: map[string]string{
-					annotations.IngressClassKey: annotations.DefaultIngressClass,
-				},
-			},
-			Spec: networkingv1beta1.IngressSpec{
-				Rules: []networkingv1beta1.IngressRule{
-					{
-						Host: "example.com",
-						IngressRuleValue: networkingv1beta1.IngressRuleValue{
-							HTTP: &networkingv1beta1.HTTPIngressRuleValue{
-								Paths: []networkingv1beta1.HTTPIngressPath{
-									{
-										Path: "/",
-										Backend: networkingv1beta1.IngressBackend{
-											ServiceName: "foo-svc",
-											ServicePort: intstr.FromInt(80),
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-		// 1
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "ing-with-tls",
-				Namespace: "bar-namespace",
-			},
-			Spec: networkingv1beta1.IngressSpec{
-				TLS: []networkingv1beta1.IngressTLS{
-					{
-						Hosts: []string{
-							"1.example.com",
-							"2.example.com",
-						},
-						SecretName: "sooper-secret",
-					},
-					{
-						Hosts: []string{
-							"3.example.com",
-							"4.example.com",
-						},
-						SecretName: "sooper-secret2",
-					},
-				},
-				Rules: []networkingv1beta1.IngressRule{
-					{
-						Host: "example.com",
-						IngressRuleValue: networkingv1beta1.IngressRuleValue{
-							HTTP: &networkingv1beta1.HTTPIngressRuleValue{
-								Paths: []networkingv1beta1.HTTPIngressPath{
-									{
-										Path: "/",
-										Backend: networkingv1beta1.IngressBackend{
-											ServiceName: "foo-svc",
-											ServicePort: intstr.FromInt(80),
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-		// 2
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "ing-with-default-backend",
-				Namespace: "bar-namespace",
-			},
-			Spec: networkingv1beta1.IngressSpec{
-				Backend: &networkingv1beta1.IngressBackend{
-					ServiceName: "default-svc",
-					ServicePort: intstr.FromInt(80),
-				},
-			},
-		},
-		// 3
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "foo",
-				Namespace: "foo-namespace",
-			},
-			Spec: networkingv1beta1.IngressSpec{
-				Rules: []networkingv1beta1.IngressRule{
-					{
-						Host: "example.com",
-						IngressRuleValue: networkingv1beta1.IngressRuleValue{
-							HTTP: &networkingv1beta1.HTTPIngressRuleValue{
-								Paths: []networkingv1beta1.HTTPIngressPath{
-									{
-										Path: "/.well-known/acme-challenge/yolo",
-										Backend: networkingv1beta1.IngressBackend{
-											ServiceName: "cert-manager-solver-pod",
-											ServicePort: intstr.FromInt(80),
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-		// 4
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "foo",
-				Namespace: "foo-namespace",
-			},
-			Spec: networkingv1beta1.IngressSpec{
-				Rules: []networkingv1beta1.IngressRule{
-					{
-						Host: "example.com",
-						IngressRuleValue: networkingv1beta1.IngressRuleValue{
-							HTTP: &networkingv1beta1.HTTPIngressRuleValue{
-								Paths: []networkingv1beta1.HTTPIngressPath{
-									{
-										Backend: networkingv1beta1.IngressBackend{
-											ServiceName: "foo-svc",
-											ServicePort: intstr.FromInt(80),
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-		// 5
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "baz",
-				Namespace: "foo-namespace",
-			},
-			Spec: networkingv1beta1.IngressSpec{
-				Rules: []networkingv1beta1.IngressRule{
-					{
-						Host:             "example.com",
-						IngressRuleValue: networkingv1beta1.IngressRuleValue{},
-					},
-				},
-			},
-		},
-		// 6
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "foo",
-				Namespace: "foo-namespace",
-			},
-			Spec: networkingv1beta1.IngressSpec{
-				Rules: []networkingv1beta1.IngressRule{
-					{
-						Host: "example.com",
-						IngressRuleValue: networkingv1beta1.IngressRuleValue{
-							HTTP: &networkingv1beta1.HTTPIngressRuleValue{
-								Paths: []networkingv1beta1.HTTPIngressPath{
-									{
-										Backend: networkingv1beta1.IngressBackend{
-											ServiceName: "foo-svc",
-											ServicePort: intstr.FromInt(80),
-										},
-									},
-								},
-							},
-						},
-					},
-					{
-						Host: "example.net",
-						IngressRuleValue: networkingv1beta1.IngressRuleValue{
-							HTTP: &networkingv1beta1.HTTPIngressRuleValue{
-								Paths: []networkingv1beta1.HTTPIngressPath{
-									{
-										Backend: networkingv1beta1.IngressBackend{
-											ServiceName: "foo-svc",
-											ServicePort: intstr.FromInt(8000),
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-		// 7
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "invalid-path",
-				Namespace: "foo-namespace",
-			},
-			Spec: networkingv1beta1.IngressSpec{
-				Rules: []networkingv1beta1.IngressRule{
-					{
-						Host: "example.com",
-						IngressRuleValue: networkingv1beta1.IngressRuleValue{
-							HTTP: &networkingv1beta1.HTTPIngressRuleValue{
-								Paths: []networkingv1beta1.HTTPIngressPath{
-									{
-										Path: "/foo//bar",
-										Backend: networkingv1beta1.IngressBackend{
-											ServiceName: "foo-svc",
-											ServicePort: intstr.FromInt(80),
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-	}
-
-	t.Run("no ingress returns empty info", func(t *testing.T) {
-		parsedInfo := fromIngressV1beta1(logrus.New(), []*networkingv1beta1.Ingress{})
-		assert.Equal(ingressRules{
-			ServiceNameToServices: make(map[string]kongstate.Service),
-			SecretNameToSNIs:      make(map[string][]string),
-		}, parsedInfo)
-	})
-	t.Run("simple ingress rule is parsed", func(t *testing.T) {
-		parsedInfo := fromIngressV1beta1(logrus.New(), []*networkingv1beta1.Ingress{
-			ingressList[0],
-		})
-		assert.Equal(1, len(parsedInfo.ServiceNameToServices))
-		assert.Equal("foo-svc.foo-namespace.80.svc", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.80"].Host)
-		assert.Equal(80, *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.80"].Port)
-
-		assert.Equal("/", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.80"].Routes[0].Paths[0])
-		assert.Equal("example.com", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.80"].Routes[0].Hosts[0])
-	})
-	t.Run("ingress rule with default backend", func(t *testing.T) {
-		parsedInfo := fromIngressV1beta1(logrus.New(),
-			[]*networkingv1beta1.Ingress{ingressList[0], ingressList[2]},
-		)
-		assert.Equal(2, len(parsedInfo.ServiceNameToServices))
-		assert.Equal("foo-svc.foo-namespace.80.svc", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.80"].Host)
-		assert.Equal(80, *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.80"].Port)
-
-		assert.Equal("/", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.80"].Routes[0].Paths[0])
-		assert.Equal("example.com", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.80"].Routes[0].Hosts[0])
-
-		assert.Equal(1, len(parsedInfo.ServiceNameToServices["bar-namespace.default-svc.80"].Routes))
-		assert.Equal("/", *parsedInfo.ServiceNameToServices["bar-namespace.default-svc.80"].Routes[0].Paths[0])
-		assert.Equal(0, len(parsedInfo.ServiceNameToServices["bar-namespace.default-svc.80"].Routes[0].Hosts))
-	})
-	t.Run("ingress rule with TLS", func(t *testing.T) {
-		parsedInfo := fromIngressV1beta1(logrus.New(), []*networkingv1beta1.Ingress{
-			ingressList[1],
-		})
-		assert.Equal(2, len(parsedInfo.SecretNameToSNIs))
-		assert.Equal(2, len(parsedInfo.SecretNameToSNIs["bar-namespace/sooper-secret"]))
-		assert.Equal(2, len(parsedInfo.SecretNameToSNIs["bar-namespace/sooper-secret2"]))
-	})
-	t.Run("ingress rule with ACME like path has strip_path set to false", func(t *testing.T) {
-		parsedInfo := fromIngressV1beta1(logrus.New(), []*networkingv1beta1.Ingress{
-			ingressList[3],
-		})
-		assert.Equal(1, len(parsedInfo.ServiceNameToServices))
-		assert.Equal("cert-manager-solver-pod.foo-namespace.80.svc",
-			*parsedInfo.ServiceNameToServices["foo-namespace.cert-manager-solver-pod.80"].Host)
-		assert.Equal(80, *parsedInfo.ServiceNameToServices["foo-namespace.cert-manager-solver-pod.80"].Port)
-
-		assert.Equal("/.well-known/acme-challenge/yolo",
-			*parsedInfo.ServiceNameToServices["foo-namespace.cert-manager-solver-pod.80"].Routes[0].Paths[0])
-		assert.Equal("example.com",
-			*parsedInfo.ServiceNameToServices["foo-namespace.cert-manager-solver-pod.80"].Routes[0].Hosts[0])
-		assert.False(*parsedInfo.ServiceNameToServices["foo-namespace.cert-manager-solver-pod.80"].Routes[0].StripPath)
-	})
-	t.Run("ingress with empty path is correctly parsed", func(t *testing.T) {
-		parsedInfo := fromIngressV1beta1(logrus.New(), []*networkingv1beta1.Ingress{
-			ingressList[4],
-		})
-		assert.Equal("/", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.80"].Routes[0].Paths[0])
-		assert.Equal("example.com", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.80"].Routes[0].Hosts[0])
-	})
-	t.Run("empty Ingress rule doesn't cause a panic", func(t *testing.T) {
-		assert.NotPanics(func() {
-			fromIngressV1beta1(logrus.New(), []*networkingv1beta1.Ingress{
-				ingressList[5],
-			})
-		})
-	})
-	t.Run("Ingress rules with multiple ports for one Service use separate hostnames for each port", func(t *testing.T) {
-		parsedInfo := fromIngressV1beta1(logrus.New(), []*networkingv1beta1.Ingress{
-			ingressList[6],
-		})
-		assert.Equal("foo-svc.foo-namespace.80.svc", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.80"].Host)
-		assert.Equal("foo-svc.foo-namespace.8000.svc", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.8000"].Host)
-	})
-	t.Run("Ingress rule with path containing multiple slashes ('//') is skipped", func(t *testing.T) {
-		parsedInfo := fromIngressV1beta1(logrus.New(), []*networkingv1beta1.Ingress{
-			ingressList[7],
-		})
-		assert.Empty(parsedInfo.ServiceNameToServices)
-	})
-}
-
-func TestFromIngressV1(t *testing.T) {
-	assert := assert.New(t)
-	ingressList := []*networkingv1.Ingress{
-		// 0
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "foo",
-				Namespace: "foo-namespace",
-				Annotations: map[string]string{
-					annotations.IngressClassKey: annotations.DefaultIngressClass,
-				},
-			},
-			Spec: networkingv1.IngressSpec{
-				Rules: []networkingv1.IngressRule{
-					{
-						Host: "example.com",
-						IngressRuleValue: networkingv1.IngressRuleValue{
-							HTTP: &networkingv1.HTTPIngressRuleValue{
-								Paths: []networkingv1.HTTPIngressPath{
-									{
-										Path: "/",
-										Backend: networkingv1.IngressBackend{
-											Service: &networkingv1.IngressServiceBackend{
-												Name: "foo-svc",
-												Port: networkingv1.ServiceBackendPort{Number: 80},
-											},
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-		// 1
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "ing-with-tls",
-				Namespace: "bar-namespace",
-			},
-			Spec: networkingv1.IngressSpec{
-				TLS: []networkingv1.IngressTLS{
-					{
-						Hosts: []string{
-							"1.example.com",
-							"2.example.com",
-						},
-						SecretName: "sooper-secret",
-					},
-					{
-						Hosts: []string{
-							"3.example.com",
-							"4.example.com",
-						},
-						SecretName: "sooper-secret2",
-					},
-				},
-				Rules: []networkingv1.IngressRule{
-					{
-						Host: "example.com",
-						IngressRuleValue: networkingv1.IngressRuleValue{
-							HTTP: &networkingv1.HTTPIngressRuleValue{
-								Paths: []networkingv1.HTTPIngressPath{
-									{
-										Path: "/",
-										Backend: networkingv1.IngressBackend{
-											Service: &networkingv1.IngressServiceBackend{
-												Name: "foo-svc",
-												Port: networkingv1.ServiceBackendPort{Number: 80},
-											},
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-		// 2
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "ing-with-default-backend",
-				Namespace: "bar-namespace",
-			},
-			Spec: networkingv1.IngressSpec{
-				DefaultBackend: &networkingv1.IngressBackend{
-					Service: &networkingv1.IngressServiceBackend{
-						Name: "default-svc",
-						Port: networkingv1.ServiceBackendPort{Number: 80},
-					},
-				},
-			},
-		},
-		// 3
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "foo",
-				Namespace: "foo-namespace",
-			},
-			Spec: networkingv1.IngressSpec{
-				Rules: []networkingv1.IngressRule{
-					{
-						Host: "example.com",
-						IngressRuleValue: networkingv1.IngressRuleValue{
-							HTTP: &networkingv1.HTTPIngressRuleValue{
-								Paths: []networkingv1.HTTPIngressPath{
-									{
-										Path: "/.well-known/acme-challenge/yolo",
-										Backend: networkingv1.IngressBackend{
-											Service: &networkingv1.IngressServiceBackend{
-												Name: "cert-manager-solver-pod",
-												Port: networkingv1.ServiceBackendPort{Number: 80},
-											},
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-		// 4
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "foo",
-				Namespace: "foo-namespace",
-			},
-			Spec: networkingv1.IngressSpec{
-				Rules: []networkingv1.IngressRule{
-					{
-						Host: "example.com",
-						IngressRuleValue: networkingv1.IngressRuleValue{
-							HTTP: &networkingv1.HTTPIngressRuleValue{
-								Paths: []networkingv1.HTTPIngressPath{
-									{
-										Backend: networkingv1.IngressBackend{
-											Service: &networkingv1.IngressServiceBackend{
-												Name: "foo-svc",
-												Port: networkingv1.ServiceBackendPort{Number: 80},
-											},
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-		// 5
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "baz",
-				Namespace: "foo-namespace",
-			},
-			Spec: networkingv1.IngressSpec{
-				Rules: []networkingv1.IngressRule{
-					{
-						Host:             "example.com",
-						IngressRuleValue: networkingv1.IngressRuleValue{},
-					},
-				},
-			},
-		},
-		// 6
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "foo",
-				Namespace: "foo-namespace",
-			},
-			Spec: networkingv1.IngressSpec{
-				Rules: []networkingv1.IngressRule{
-					{
-						Host: "example.com",
-						IngressRuleValue: networkingv1.IngressRuleValue{
-							HTTP: &networkingv1.HTTPIngressRuleValue{
-								Paths: []networkingv1.HTTPIngressPath{
-									{
-										Backend: networkingv1.IngressBackend{
-											Service: &networkingv1.IngressServiceBackend{
-												Name: "foo-svc",
-												Port: networkingv1.ServiceBackendPort{Number: 80},
-											},
-										},
-									},
-								},
-							},
-						},
-					},
-					{
-						Host: "example.net",
-						IngressRuleValue: networkingv1.IngressRuleValue{
-							HTTP: &networkingv1.HTTPIngressRuleValue{
-								Paths: []networkingv1.HTTPIngressPath{
-									{
-										Backend: networkingv1.IngressBackend{
-											Service: &networkingv1.IngressServiceBackend{
-												Name: "foo-svc",
-												Port: networkingv1.ServiceBackendPort{Number: 8000},
-											},
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-		// 7
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "invalid-path",
-				Namespace: "foo-namespace",
-			},
-			Spec: networkingv1.IngressSpec{
-				Rules: []networkingv1.IngressRule{
-					{
-						Host: "example.com",
-						IngressRuleValue: networkingv1.IngressRuleValue{
-							HTTP: &networkingv1.HTTPIngressRuleValue{
-								Paths: []networkingv1.HTTPIngressPath{
-									{
-										Path: "/foo//bar",
-										Backend: networkingv1.IngressBackend{
-											Service: &networkingv1.IngressServiceBackend{
-												Name: "foo-svc",
-												Port: networkingv1.ServiceBackendPort{Number: 80},
-											},
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-		// 8
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "foo",
-				Namespace: "foo-namespace",
-				Annotations: map[string]string{
-					annotations.IngressClassKey: annotations.DefaultIngressClass,
-				},
-			},
-			Spec: networkingv1.IngressSpec{
-				Rules: []networkingv1.IngressRule{
-					{
-						Host: "example.com",
-						IngressRuleValue: networkingv1.IngressRuleValue{
-							HTTP: &networkingv1.HTTPIngressRuleValue{
-								Paths: []networkingv1.HTTPIngressPath{
-									{
-										Path: "/",
-										Backend: networkingv1.IngressBackend{
-											Service: &networkingv1.IngressServiceBackend{
-												Name: "foo-svc",
-												Port: networkingv1.ServiceBackendPort{Name: "http"},
-											},
-										},
-									},
-									{
-										Path: "/ws",
-										Backend: networkingv1.IngressBackend{
-											Service: &networkingv1.IngressServiceBackend{
-												Name: "foo-svc",
-												Port: networkingv1.ServiceBackendPort{Name: "ws"},
-											},
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-	}
-
-	t.Run("no ingress returns empty info", func(t *testing.T) {
-		parsedInfo := fromIngressV1(logrus.New(), []*networkingv1.Ingress{})
-		assert.Equal(ingressRules{
-			ServiceNameToServices: make(map[string]kongstate.Service),
-			SecretNameToSNIs:      make(map[string][]string),
-		}, parsedInfo)
-	})
-	t.Run("simple ingress rule is parsed", func(t *testing.T) {
-		parsedInfo := fromIngressV1(logrus.New(), []*networkingv1.Ingress{
-			ingressList[0],
-		})
-		assert.Equal(1, len(parsedInfo.ServiceNameToServices))
-		assert.Equal("foo-svc.foo-namespace.80.svc", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pnum-80"].Host)
-		assert.Equal(80, *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pnum-80"].Port)
-
-		assert.Equal("/", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pnum-80"].Routes[0].Paths[0])
-		assert.Equal("example.com", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pnum-80"].Routes[0].Hosts[0])
-	})
-	t.Run("ingress rule with default backend", func(t *testing.T) {
-		parsedInfo := fromIngressV1(logrus.New(),
-			[]*networkingv1.Ingress{ingressList[0], ingressList[2]},
-		)
-		assert.Equal(2, len(parsedInfo.ServiceNameToServices))
-		assert.Equal("foo-svc.foo-namespace.80.svc", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pnum-80"].Host)
-		assert.Equal(80, *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pnum-80"].Port)
-
-		assert.Equal("/", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pnum-80"].Routes[0].Paths[0])
-		assert.Equal("example.com", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pnum-80"].Routes[0].Hosts[0])
-
-		assert.Equal(1, len(parsedInfo.ServiceNameToServices["bar-namespace.default-svc.80"].Routes))
-		assert.Equal("/", *parsedInfo.ServiceNameToServices["bar-namespace.default-svc.80"].Routes[0].Paths[0])
-		assert.Equal(0, len(parsedInfo.ServiceNameToServices["bar-namespace.default-svc.80"].Routes[0].Hosts))
-	})
-	t.Run("ingress rule with TLS", func(t *testing.T) {
-		parsedInfo := fromIngressV1(logrus.New(), []*networkingv1.Ingress{
-			ingressList[1],
-		})
-		assert.Equal(2, len(parsedInfo.SecretNameToSNIs))
-		assert.Equal(2, len(parsedInfo.SecretNameToSNIs["bar-namespace/sooper-secret"]))
-		assert.Equal(2, len(parsedInfo.SecretNameToSNIs["bar-namespace/sooper-secret2"]))
-	})
-	t.Run("ingress rule with ACME like path has strip_path set to false", func(t *testing.T) {
-		parsedInfo := fromIngressV1(logrus.New(), []*networkingv1.Ingress{
-			ingressList[3],
-		})
-		assert.Equal(1, len(parsedInfo.ServiceNameToServices))
-		assert.Equal("cert-manager-solver-pod.foo-namespace.80.svc",
-			*parsedInfo.ServiceNameToServices["foo-namespace.cert-manager-solver-pod.pnum-80"].Host)
-		assert.Equal(80, *parsedInfo.ServiceNameToServices["foo-namespace.cert-manager-solver-pod.pnum-80"].Port)
-
-		assert.Equal("/.well-known/acme-challenge/yolo",
-			*parsedInfo.ServiceNameToServices["foo-namespace.cert-manager-solver-pod.pnum-80"].Routes[0].Paths[0])
-		assert.Equal("example.com",
-			*parsedInfo.ServiceNameToServices["foo-namespace.cert-manager-solver-pod.pnum-80"].Routes[0].Hosts[0])
-		assert.False(*parsedInfo.ServiceNameToServices["foo-namespace.cert-manager-solver-pod.pnum-80"].Routes[0].StripPath)
-	})
-	t.Run("ingress with empty path is correctly parsed", func(t *testing.T) {
-		parsedInfo := fromIngressV1(logrus.New(), []*networkingv1.Ingress{
-			ingressList[4],
-		})
-		assert.Equal("/", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pnum-80"].Routes[0].Paths[0])
-		assert.Equal("example.com", *parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pnum-80"].Routes[0].Hosts[0])
-	})
-	t.Run("empty Ingress rule doesn't cause a panic", func(t *testing.T) {
-		assert.NotPanics(func() {
-			fromIngressV1(logrus.New(), []*networkingv1.Ingress{
-				ingressList[5],
-			})
-		})
-	})
-	t.Run("Ingress rules with multiple ports for one Service use separate hostnames for each port", func(t *testing.T) {
-		parsedInfo := fromIngressV1(logrus.New(), []*networkingv1.Ingress{
-			ingressList[6],
-		})
-		assert.Equal("foo-svc.foo-namespace.80.svc",
-			*parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pnum-80"].Host)
-		assert.Equal("foo-svc.foo-namespace.8000.svc",
-			*parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pnum-8000"].Host)
-	})
-	t.Run("Ingress rule with path containing multiple slashes ('//') is skipped", func(t *testing.T) {
-		parsedInfo := fromIngressV1(logrus.New(), []*networkingv1.Ingress{
-			ingressList[7],
-		})
-		assert.Empty(parsedInfo.ServiceNameToServices)
-	})
-	t.Run("Ingress rule with ports defined by name", func(t *testing.T) {
-		parsedInfo := fromIngressV1(logrus.New(), []*networkingv1.Ingress{
-			ingressList[8],
-		})
-		_, ok := parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pname-http"]
-		assert.True(ok)
-		_, ok = parsedInfo.ServiceNameToServices["foo-namespace.foo-svc.pname-ws"]
-		assert.True(ok)
-	})
-}
-
-func TestFromTCPIngressV1beta1(t *testing.T) {
-	assert := assert.New(t)
-	tcpIngressList := []*configurationv1beta1.TCPIngress{
-		// 0
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "foo",
-				Namespace: "default",
-			},
-		},
-		// 1
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "foo",
-				Namespace: "default",
-			},
-			Spec: configurationv1beta1.TCPIngressSpec{
-				Rules: []configurationv1beta1.IngressRule{
-					{
-						Port: 9000,
-						Backend: configurationv1beta1.IngressBackend{
-							ServiceName: "foo-svc",
-							ServicePort: 80,
-						},
-					},
-				},
-			},
-		},
-		// 2
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "foo",
-				Namespace: "default",
-			},
-			Spec: configurationv1beta1.TCPIngressSpec{
-				Rules: []configurationv1beta1.IngressRule{
-					{
-						Host: "example.com",
-						Port: 9000,
-						Backend: configurationv1beta1.IngressBackend{
-							ServiceName: "foo-svc",
-							ServicePort: 80,
-						},
-					},
-				},
-			},
-		},
-		// 3
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "foo",
-				Namespace: "default",
-			},
-			Spec: configurationv1beta1.TCPIngressSpec{
-				TLS: []configurationv1beta1.IngressTLS{
-					{
-						Hosts: []string{
-							"1.example.com",
-							"2.example.com",
-						},
-						SecretName: "sooper-secret",
-					},
-					{
-						Hosts: []string{
-							"3.example.com",
-							"4.example.com",
-						},
-						SecretName: "sooper-secret2",
-					},
-				},
-			},
-		},
-		// 4
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "foo",
-				Namespace: "default",
-			},
-			Spec: configurationv1beta1.TCPIngressSpec{
-				Rules: []configurationv1beta1.IngressRule{
-					{
-						Port: 9000,
-						Backend: configurationv1beta1.IngressBackend{
-							ServiceName: "",
-							ServicePort: 80,
-						},
-					},
-				},
-			},
-		},
-		// 5
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "foo",
-				Namespace: "default",
-			},
-			Spec: configurationv1beta1.TCPIngressSpec{
-				Rules: []configurationv1beta1.IngressRule{
-					{
-						Port: 0,
-						Backend: configurationv1beta1.IngressBackend{
-							ServiceName: "foo-svc",
-							ServicePort: 80,
-						},
-					},
-				},
-			},
-		},
-		// 6
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "foo",
-				Namespace: "default",
-			},
-			Spec: configurationv1beta1.TCPIngressSpec{
-				Rules: []configurationv1beta1.IngressRule{
-					{
-						Port: 9000,
-						Backend: configurationv1beta1.IngressBackend{
-							ServiceName: "foo-svc",
-							ServicePort: 0,
-						},
-					},
-				},
-			},
-		},
-	}
-	t.Run("no TCPIngress returns empty info", func(t *testing.T) {
-		parsedInfo := fromTCPIngressV1beta1(logrus.New(), []*configurationv1beta1.TCPIngress{})
-		assert.Equal(ingressRules{
-			ServiceNameToServices: make(map[string]kongstate.Service),
-			SecretNameToSNIs:      make(map[string][]string),
-		}, parsedInfo)
-	})
-	t.Run("empty TCPIngress return empty info", func(t *testing.T) {
-		parsedInfo := fromTCPIngressV1beta1(logrus.New(), []*configurationv1beta1.TCPIngress{tcpIngressList[0]})
-		assert.Equal(ingressRules{
-			ServiceNameToServices: make(map[string]kongstate.Service),
-			SecretNameToSNIs:      make(map[string][]string),
-		}, parsedInfo)
-	})
-	t.Run("simple TCPIngress rule is parsed", func(t *testing.T) {
-		parsedInfo := fromTCPIngressV1beta1(logrus.New(), []*configurationv1beta1.TCPIngress{tcpIngressList[1]})
-		assert.Equal(1, len(parsedInfo.ServiceNameToServices))
-		svc := parsedInfo.ServiceNameToServices["default.foo-svc.80"]
-		assert.Equal("foo-svc.default.80.svc", *svc.Host)
-		assert.Equal(80, *svc.Port)
-		assert.Equal("tcp", *svc.Protocol)
-
-		assert.Equal(1, len(svc.Routes))
-		route := svc.Routes[0]
-		assert.Equal(kong.Route{
-			Name:      kong.String("default.foo.0"),
-			Protocols: kong.StringSlice("tcp", "tls"),
-			Destinations: []*kong.CIDRPort{
-				{
-					Port: kong.Int(9000),
-				},
-			},
-		}, route.Route)
-	})
-	t.Run("TCPIngress rule with host is parsed", func(t *testing.T) {
-		parsedInfo := fromTCPIngressV1beta1(logrus.New(), []*configurationv1beta1.TCPIngress{tcpIngressList[2]})
-		assert.Equal(1, len(parsedInfo.ServiceNameToServices))
-		svc := parsedInfo.ServiceNameToServices["default.foo-svc.80"]
-		assert.Equal("foo-svc.default.80.svc", *svc.Host)
-		assert.Equal(80, *svc.Port)
-		assert.Equal("tcp", *svc.Protocol)
-
-		assert.Equal(1, len(svc.Routes))
-		route := svc.Routes[0]
-		assert.Equal(kong.Route{
-			Name:      kong.String("default.foo.0"),
-			Protocols: kong.StringSlice("tcp", "tls"),
-			SNIs:      kong.StringSlice("example.com"),
-			Destinations: []*kong.CIDRPort{
-				{
-					Port: kong.Int(9000),
-				},
-			},
-		}, route.Route)
-	})
-	t.Run("TCPIngress with TLS", func(t *testing.T) {
-		parsedInfo := fromTCPIngressV1beta1(logrus.New(), []*configurationv1beta1.TCPIngress{tcpIngressList[3]})
-		assert.Equal(2, len(parsedInfo.SecretNameToSNIs))
-		assert.Equal(2, len(parsedInfo.SecretNameToSNIs["default/sooper-secret"]))
-		assert.Equal(2, len(parsedInfo.SecretNameToSNIs["default/sooper-secret2"]))
-	})
-	t.Run("TCPIngress without service name returns empty info", func(t *testing.T) {
-		parsedInfo := fromTCPIngressV1beta1(logrus.New(), []*configurationv1beta1.TCPIngress{tcpIngressList[4]})
-		assert.Equal(ingressRules{
-			ServiceNameToServices: make(map[string]kongstate.Service),
-			SecretNameToSNIs:      make(map[string][]string),
-		}, parsedInfo)
-	})
-	t.Run("TCPIngress with invalid port returns empty info", func(t *testing.T) {
-		parsedInfo := fromTCPIngressV1beta1(logrus.New(), []*configurationv1beta1.TCPIngress{tcpIngressList[5]})
-		assert.Equal(ingressRules{
-			ServiceNameToServices: make(map[string]kongstate.Service),
-			SecretNameToSNIs:      make(map[string][]string),
-		}, parsedInfo)
-	})
-	t.Run("empty TCPIngress with invalid service port returns empty info", func(t *testing.T) {
-		parsedInfo := fromTCPIngressV1beta1(logrus.New(), []*configurationv1beta1.TCPIngress{tcpIngressList[6]})
-		assert.Equal(ingressRules{
-			ServiceNameToServices: make(map[string]kongstate.Service),
-			SecretNameToSNIs:      make(map[string][]string),
-		}, parsedInfo)
-	})
-}
-
-func TestFromKnativeIngress(t *testing.T) {
-	assert := assert.New(t)
-	ingressList := []*knative.Ingress{
-		// 0
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "foo",
-				Namespace: "foo-namespace",
-			},
-			Spec: knative.IngressSpec{
-				Rules: []knative.IngressRule{
-					{},
-				},
-			},
-		},
-		// 1
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "foo",
-				Namespace: "foo-namespace",
-			},
-			Spec: knative.IngressSpec{
-				Rules: []knative.IngressRule{
-					{
-						Hosts: []string{"my-func.example.com"},
-						HTTP: &knative.HTTPIngressRuleValue{
-							Paths: []knative.HTTPIngressPath{
-								{
-									Path: "/",
-									AppendHeaders: map[string]string{
-										"foo": "bar",
-									},
-									Splits: []knative.IngressBackendSplit{
-										{
-											IngressBackend: knative.IngressBackend{
-												ServiceNamespace: "foo-ns",
-												ServiceName:      "foo-svc",
-												ServicePort:      intstr.FromInt(42),
-											},
-											Percent: 100,
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-		// 2
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "foo",
-				Namespace: "foo-namespace",
-			},
-			Spec: knative.IngressSpec{
-				Rules: []knative.IngressRule{
-					{
-						Hosts: []string{"my-func.example.com"},
-						HTTP: &knative.HTTPIngressRuleValue{
-							Paths: []knative.HTTPIngressPath{
-								{
-									Path: "/",
-									AppendHeaders: map[string]string{
-										"foo": "bar",
-									},
-									Splits: []knative.IngressBackendSplit{
-										{
-											IngressBackend: knative.IngressBackend{
-												ServiceNamespace: "bar-ns",
-												ServiceName:      "bar-svc",
-												ServicePort:      intstr.FromInt(42),
-											},
-											Percent: 20,
-										},
-										{
-											IngressBackend: knative.IngressBackend{
-												ServiceNamespace: "foo-ns",
-												ServiceName:      "foo-svc",
-												ServicePort:      intstr.FromInt(42),
-											},
-											Percent: 100,
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-		// 3
-		{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "foo",
-				Namespace: "foo-namespace",
-			},
-			Spec: knative.IngressSpec{
-				Rules: []knative.IngressRule{
-					{
-						Hosts: []string{"my-func.example.com"},
-						HTTP: &knative.HTTPIngressRuleValue{
-							Paths: []knative.HTTPIngressPath{
-								{
-									Path: "/",
-									AppendHeaders: map[string]string{
-										"foo": "bar",
-									},
-									Splits: []knative.IngressBackendSplit{
-										{
-											IngressBackend: knative.IngressBackend{
-												ServiceNamespace: "bar-ns",
-												ServiceName:      "bar-svc",
-												ServicePort:      intstr.FromInt(42),
-											},
-											Percent: 20,
-										},
-										{
-											IngressBackend: knative.IngressBackend{
-												ServiceNamespace: "foo-ns",
-												ServiceName:      "foo-svc",
-												ServicePort:      intstr.FromInt(42),
-											},
-											Percent: 100,
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-				TLS: []knative.IngressTLS{
-					{
-						Hosts: []string{
-							"foo.example.com",
-							"foo1.example.com",
-						},
-						SecretName: "foo-secret",
-					},
-					{
-						Hosts: []string{
-							"bar.example.com",
-							"bar1.example.com",
-						},
-						SecretName: "bar-secret",
-					},
-				},
-			},
-		},
-	}
-	t.Run("no ingress returns empty info", func(t *testing.T) {
-		parsedInfo := fromKnativeIngress(logrus.New(), []*knative.Ingress{})
-		assert.Equal(map[string]kongstate.Service{}, parsedInfo.ServiceNameToServices)
-		assert.Equal(newSecretNameToSNIs(), parsedInfo.SecretNameToSNIs)
-	})
-	t.Run("empty ingress returns empty info", func(t *testing.T) {
-		parsedInfo := fromKnativeIngress(logrus.New(), []*knative.Ingress{ingressList[0]})
-		assert.Equal(map[string]kongstate.Service{}, parsedInfo.ServiceNameToServices)
-		assert.Equal(newSecretNameToSNIs(), parsedInfo.SecretNameToSNIs)
-	})
-	t.Run("basic knative Ingress resource is parsed", func(t *testing.T) {
-		parsedInfo := fromKnativeIngress(logrus.New(), []*knative.Ingress{ingressList[1]})
-		assert.Equal(1, len(parsedInfo.ServiceNameToServices))
-		svc := parsedInfo.ServiceNameToServices["foo-ns.foo-svc.42"]
-		assert.Equal(kong.Service{
-			Name:           kong.String("foo-ns.foo-svc.42"),
-			Port:           kong.Int(80),
-			Host:           kong.String("foo-svc.foo-ns.42.svc"),
-			Path:           kong.String("/"),
-			Protocol:       kong.String("http"),
-			WriteTimeout:   kong.Int(60000),
-			ReadTimeout:    kong.Int(60000),
-			ConnectTimeout: kong.Int(60000),
-			Retries:        kong.Int(5),
-		}, svc.Service)
-		assert.Equal(kong.Route{
-			Name:              kong.String("foo-namespace.foo.00"),
-			RegexPriority:     kong.Int(0),
-			StripPath:         kong.Bool(false),
-			Paths:             kong.StringSlice("/"),
-			PreserveHost:      kong.Bool(true),
-			Protocols:         kong.StringSlice("http", "https"),
-			Hosts:             kong.StringSlice("my-func.example.com"),
-			ResponseBuffering: kong.Bool(true),
-			RequestBuffering:  kong.Bool(true),
-		}, svc.Routes[0].Route)
-		assert.Equal(kong.Plugin{
-			Name: kong.String("request-transformer"),
-			Config: kong.Configuration{
-				"add": map[string]interface{}{
-					"headers": []string{"foo:bar"},
-				},
-			},
-		}, svc.Plugins[0])
-
-		assert.Equal(newSecretNameToSNIs(), parsedInfo.SecretNameToSNIs)
-	})
-	t.Run("knative TLS section is correctly parsed", func(t *testing.T) {
-		parsedInfo := fromKnativeIngress(logrus.New(), []*knative.Ingress{ingressList[3]})
-
-		assert.Equal(SecretNameToSNIs(map[string][]string{
-			"foo-namespace/bar-secret": {"bar.example.com", "bar1.example.com"},
-			"foo-namespace/foo-secret": {"foo.example.com", "foo1.example.com"},
-		}), parsedInfo.SecretNameToSNIs)
-	})
-	t.Run("split knative Ingress resource chooses the highest split", func(t *testing.T) {
-		parsedInfo := fromKnativeIngress(logrus.New(), []*knative.Ingress{ingressList[2]})
-		assert.Equal(1, len(parsedInfo.ServiceNameToServices))
-		svc := parsedInfo.ServiceNameToServices["foo-ns.foo-svc.42"]
-		assert.Equal(kong.Service{
-			Name:           kong.String("foo-ns.foo-svc.42"),
-			Port:           kong.Int(80),
-			Host:           kong.String("foo-svc.foo-ns.42.svc"),
-			Path:           kong.String("/"),
-			Protocol:       kong.String("http"),
-			WriteTimeout:   kong.Int(60000),
-			ReadTimeout:    kong.Int(60000),
-			ConnectTimeout: kong.Int(60000),
-			Retries:        kong.Int(5),
-		}, svc.Service)
-		assert.Equal(kong.Route{
-			Name:              kong.String("foo-namespace.foo.00"),
-			RegexPriority:     kong.Int(0),
-			StripPath:         kong.Bool(false),
-			Paths:             kong.StringSlice("/"),
-			PreserveHost:      kong.Bool(true),
-			Protocols:         kong.StringSlice("http", "https"),
-			Hosts:             kong.StringSlice("my-func.example.com"),
-			ResponseBuffering: kong.Bool(true),
-			RequestBuffering:  kong.Bool(true),
-		}, svc.Routes[0].Route)
-		assert.Equal(kong.Plugin{
-			Name: kong.String("request-transformer"),
-			Config: kong.Configuration{
-				"add": map[string]interface{}{
-					"headers": []string{"foo:bar"},
-				},
-			},
-		}, svc.Plugins[0])
-
-		assert.Equal(newSecretNameToSNIs(), parsedInfo.SecretNameToSNIs)
-	})
-}
-
 func TestPathsFromK8s(t *testing.T) {
 	for _, tt := range []struct {
 		name         string
diff --git a/internal/dataplane/proxy/clientgo_cached_proxy_resolver_test.go b/internal/dataplane/proxy/clientgo_cached_proxy_resolver_test.go
deleted file mode 100644
index b366f716e..000000000
--- a/internal/dataplane/proxy/clientgo_cached_proxy_resolver_test.go
+++ /dev/null
@@ -1,190 +0,0 @@
-package proxy
-
-import (
-	"net/http"
-	"testing"
-	"time"
-
-	"github.com/google/uuid"
-	"github.com/kong/kubernetes-testing-framework/pkg/utils/kong"
-	"github.com/kong/kubernetes-testing-framework/pkg/utils/kubernetes/generators"
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
-	corev1 "k8s.io/api/core/v1"
-	netv1 "k8s.io/api/networking/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/sendconfig"
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/store"
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/util"
-)
-
-func Test_FetchCustomEntities(t *testing.T) {
-	assert := assert.New(t)
-	type args struct {
-		secret string
-	}
-	tests := []struct {
-		name    string
-		args    args
-		want    []byte
-		wantErr bool
-	}{
-		{
-			name: "valid secret",
-			args: args{
-				secret: "default/validCustomEntities",
-			},
-			want:    []byte("carp"),
-			wantErr: true,
-		},
-		{
-			name: "incorrect name format",
-			args: args{
-				secret: "!",
-			},
-			want:    nil,
-			wantErr: true,
-		},
-		{
-			name: "non-existent secret",
-			args: args{
-				secret: "default/nope",
-			},
-			want:    nil,
-			wantErr: true,
-		},
-		{
-			name: "secret lacks config key",
-			args: args{
-				secret: "default/invalidCustomEntities",
-			},
-			want:    nil,
-			wantErr: true,
-		},
-	}
-	store, err := store.NewFakeStore(store.FakeObjects{
-		Secrets: []*corev1.Secret{
-			{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "validCustomEntities",
-					Namespace: "default",
-				},
-				Data: map[string][]byte{
-					"config": []byte("carp"),
-				},
-			},
-			{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "invalidCustomEntities",
-					Namespace: "default",
-				},
-				Data: map[string][]byte{
-					"ohno": []byte("carp"),
-				},
-			},
-		},
-	})
-	assert.Nil(err)
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			got, err := fetchCustomEntities(tt.args.secret, store)
-			if err != nil && !tt.wantErr {
-				t.Errorf("kongPluginFromK8SClusterPlugin error = %v, wantErr %v", err, tt.wantErr)
-			}
-			assert.Equal(tt.want, got)
-		})
-	}
-}
-
-func TestIsReady(t *testing.T) {
-	fakePostgresKong := sendconfig.Kong{InMemory: false}
-	fakeDblessKong := sendconfig.Kong{InMemory: true}
-	postgresProxy := clientgoCachedProxyResolver{
-		kongConfig: fakePostgresKong,
-		dbmode:     "postgres",
-	}
-	dblessProxy := clientgoCachedProxyResolver{
-		kongConfig: fakeDblessKong,
-		dbmode:     "off",
-	}
-
-	t.Log("checking initial readiness state")
-	assert.True(t, postgresProxy.IsReady())
-	assert.False(t, dblessProxy.IsReady())
-
-	t.Log("marking config applied and checking readiness after")
-	postgresProxy.markConfigApplied()
-	dblessProxy.markConfigApplied()
-	assert.True(t, postgresProxy.IsReady())
-	assert.True(t, dblessProxy.IsReady())
-}
-
-func TestCaching(t *testing.T) {
-	t.Log("configuring and starting a new proxy server")
-	proxyInterface, err := NewCacheBasedProxy(logger, fakeK8sClient, fakeKongConfig,
-		"kongtests", false, mockKongAdmin, util.ConfigDumpDiagnostic{}, time.Millisecond*300)
-	assert.NoError(t, err)
-
-	t.Log("ensuring the integrity of the proxy server")
-	proxy, ok := proxyInterface.(*clientgoCachedProxyResolver)
-	assert.True(t, ok)
-	assert.NotNil(t, proxy.cache)
-
-	t.Log("intentionally freezing async updates to inspect cache state during tests")
-	proxy.syncTicker.Reset(time.Minute * 3)
-
-	t.Log("generating 10 new objects to the proxy cache server")
-	testObjects := make([]client.Object, 10)
-	for i := 0; i < 10; i++ {
-		name := uuid.NewString()
-		deployment := generators.NewDeploymentForContainer(generators.NewContainer(name, name, 8080))
-		service := generators.NewServiceForDeployment(deployment, corev1.ServiceTypeClusterIP)
-		ingress := generators.NewIngressForService("/testing", nil, service)
-		testObjects[i] = ingress
-	}
-
-	t.Logf("adding %d new objects to the proxy cache server", len(testObjects))
-	assert.Len(t, proxy.cache.IngressV1.List(), 0)
-	for _, testObject := range testObjects {
-		require.NoError(t, proxy.UpdateObject(testObject))
-	}
-
-	t.Log("ensuring the consistency of the underlying object cache (that objects were added properly)")
-	assert.Eventually(t, func() bool {
-		return len(proxy.cache.IngressV1.List()) == len(testObjects)
-	}, time.Second*10, time.Millisecond*200)
-
-	t.Log("verifying the integrity of the object cache items")
-	matches := 0
-	for _, testObj := range testObjects {
-		for _, obj := range proxy.cache.IngressV1.List() {
-			ing, ok := obj.(*netv1.Ingress)
-			require.True(t, ok)
-			if ing.Namespace == testObj.GetNamespace() && ing.Name == testObj.GetName() {
-				matches++
-			}
-		}
-	}
-	require.Equal(t, len(testObjects), matches)
-}
-
-func TestProxyTimeout(t *testing.T) {
-	t.Log("configuring and starting a new proxy server")
-
-	// mock the next Admin API response (which will be / to get the root config) to ensure
-	// that it takes longer than the timeout we will set, in order to trigger the timeout.
-	fakeKongAdminAPI.MockNextResponse(kong.AdminAPIResponse{
-		Status:   http.StatusGatewayTimeout,
-		Body:     []byte{},
-		Callback: func() { time.Sleep(time.Millisecond * 30) },
-	})
-
-	// the timeout is shorter than the wait time for the http response, we should expect
-	// to see the the context deadline for the http response triggered.
-	timeout := time.Millisecond * 10
-
-	_, err := NewCacheBasedProxy(logger, fakeK8sClient, fakeKongConfig, "kongtests", false, mockKongAdmin, util.ConfigDumpDiagnostic{}, timeout)
-	assert.Contains(t, err.Error(), "context deadline exceeded")
-}
diff --git a/internal/dataplane/proxy/suite_test.go b/internal/dataplane/proxy/suite_test.go
deleted file mode 100644
index 37b88f813..000000000
--- a/internal/dataplane/proxy/suite_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package proxy
-
-import (
-	"context"
-	"fmt"
-	"os"
-	"sync"
-	"testing"
-	"time"
-
-	kongt "github.com/kong/kubernetes-testing-framework/pkg/utils/kong"
-	"github.com/sirupsen/logrus"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/controller-runtime/pkg/client/fake"
-
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/sendconfig"
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/metrics"
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/store"
-	"github.com/kong/kubernetes-ingress-controller/v2/internal/util"
-)
-
-// -----------------------------------------------------------------------------
-// Test Vars
-// -----------------------------------------------------------------------------
-
-var (
-	logger logrus.FieldLogger
-
-	fakeKongConfig   sendconfig.Kong
-	fakeKongAdminAPI *kongt.FakeAdminAPIServer
-
-	fakeK8sClient client.Client
-)
-
-// -----------------------------------------------------------------------------
-// Test Main
-// -----------------------------------------------------------------------------
-
-func TestMain(m *testing.M) {
-	setupVars()
-	code := m.Run()
-	os.Exit(code)
-}
-
-// -----------------------------------------------------------------------------
-// Test Main - Helper Functions
-// -----------------------------------------------------------------------------
-
-func setupVars() {
-	var err error
-
-	// setup logging and other general configurations
-	logger, err = util.MakeLogger("debug", "json")
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "could not create test logger: %v\n", err)
-		os.Exit(10)
-	}
-
-	// setup kubernetes related configurations
-	fakeK8sClient = fake.NewClientBuilder().Build()
-
-	// setup kong proxy related configurations
-	fakeKongAdminAPI, err = kongt.NewFakeAdminAPIServer()
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "could not setup Kong Proxy testing environment: %v\n", err)
-		os.Exit(10)
-	}
-	fakeKongConfig.Client = fakeKongAdminAPI.KongClient
-}
-
-// this mock will stop us from needing a functioning Kong proxy and simply
-// assumes that all updates to the Kong Admin API succeed. Test which want
-// to test failure conditions will need to add their own mock, and then put
-// this one back when they're done.
-//
-// NOTE: as these tests grow, we can use the kongt.FakeAdminAPIServer implementation to properly
-//       mock out the requests (this is used above to ensure that tests can properly initialize a Proxy instance)
-//       instead of the always-succeed functionality we use currently.
-var mockKongAdmin KongUpdater = func(ctx context.Context,
-	lastConfigSHA []byte,
-	cache *store.CacheStores,
-	ingressClassName string,
-	deprecatedLogger logrus.FieldLogger,
-	kongConfig sendconfig.Kong,
-	enableReverseSync bool,
-	diagnostic util.ConfigDumpDiagnostic,
-	proxyRequestTimeout time.Duration,
-	promMetrics *metrics.CtrlFuncMetrics,
-) ([]byte, error) {
-	fakeKongAdminUpdateCount(1)
-	return lastConfigSHA, nil
-}
-
-// these globs are for threadsafety and tracking of the fakeKongAdminUpdateCount() function,
-// use that function directly, don't use these vars.
-var (
-	countLock   = &sync.RWMutex{}
-	updateCount int
-)
-
-// fakeKongAdminUpdateCount keeps track of the number of times the Kong Admin API has
-// received updates from the proxy cache server during the runtime of the tests. This
-// can be useful for simple checks to ensure updates ran, or to validate counts of the
-// number of updates run over time when cache server resolution configuration options
-// are tweaked to change throughput and test performance.
-//
-// If newcounts are provided the function appends the count with those provided counts first.
-func fakeKongAdminUpdateCount(newcounts ...int) int {
-	countLock.Lock()
-	defer countLock.Unlock()
-	if len(newcounts) < 1 {
-		return updateCount
-	}
-	if len(newcounts) == 1 && newcounts[0] == 0 {
-		updateCount = 0
-		return 0
-	}
-	for _, count := range newcounts {
-		updateCount = updateCount + count
-	}
-	return updateCount
-}
diff --git a/internal/dataplane/synchronizer_test.go b/internal/dataplane/synchronizer_test.go
new file mode 100644
index 000000000..7595ff30a
--- /dev/null
+++ b/internal/dataplane/synchronizer_test.go
@@ -0,0 +1,103 @@
+package dataplane
+
+import (
+	"sync"
+	"testing"
+	"time"
+
+	"github.com/sirupsen/logrus"
+	"github.com/stretchr/testify/assert"
+	"golang.org/x/net/context"
+)
+
+func TestSynchronizer(t *testing.T) {
+	t.Log("setting up a fake dataplane client to test the synchronizer")
+	c := &fakeDataplaneClient{dbmode: "postgres"}
+
+	t.Log("configuring the dataplane synchronizer")
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	t.Log("initializing the dataplane synchronizer")
+	stagger := time.Millisecond * 200
+	sync, err := NewSynchronizerWithStagger(logrus.New(), c, stagger)
+	assert.NoError(t, err)
+	assert.NotNil(t, sync)
+
+	t.Log("verifying that a non-started dataplane synchronizer reports as not running")
+	assert.False(t, sync.IsRunning())
+
+	t.Log("verifying that postgres dp makes the synchronizer immediately ready")
+	c.dbmode = "postgres"
+	assert.True(t, sync.IsReady())
+
+	t.Log("verifying dbless mode means the synchronizer wont be ready until a config has been applied")
+	c.dbmode = "off"
+	assert.False(t, sync.IsReady())
+
+	t.Log("starting the dataplane synchronizer server")
+	assert.NoError(t, sync.Start(ctx))
+	assert.Eventually(t, func() bool { return sync.IsRunning() }, time.Second, time.Millisecond*200)
+	assert.True(t, sync.NeedLeaderElection())
+
+	t.Log("verifying that trying to start the dataplane synchronizer while it's already started fails")
+	err = sync.Start(ctx)
+	assert.Error(t, err)
+	assert.Equal(t, err.Error(), "server is already running")
+
+	t.Log("verifying that eventually the synchronizer reports as ready for a dbless dataplane")
+	assert.Eventually(t, func() bool { return sync.IsReady() }, stagger*2, time.Millisecond*200)
+
+	t.Log("verifying that the dataplane eventually receieves several successful updates from the synchronizer")
+	assert.Eventually(t, func() bool {
+		return c.totalUpdates() >= 5
+	}, stagger*2, time.Millisecond*200)
+
+	t.Log("verifying that the server shuts down when the context is cancelled")
+	cancel()
+	assert.Eventually(t, func() bool { return !sync.IsRunning() }, time.Second, time.Millisecond*200)
+	assert.Eventually(t, func() bool { return !sync.IsReady() }, time.Second, time.Millisecond*200)
+	totalUpdatesSeenSoFar := c.totalUpdates()
+
+	t.Log("verifying that the server can be started back up with a new context")
+	ctx, cancel = context.WithCancel(context.Background())
+	defer cancel()
+	assert.NoError(t, sync.Start(ctx))
+	assert.Eventually(t, func() bool { return sync.IsRunning() }, time.Second, time.Millisecond*200)
+	assert.Eventually(t, func() bool { return sync.IsReady() }, time.Second, time.Millisecond*200)
+
+	t.Log("verifying that a server that was restarted continues to send successful updates to the dataplane")
+	assert.Eventually(t, func() bool { return c.totalUpdates() >= totalUpdatesSeenSoFar+3 }, time.Second, time.Millisecond*200)
+
+	t.Log("verifying that the server can be shut down a second time")
+	cancel()
+	assert.Eventually(t, func() bool { return !sync.IsRunning() }, time.Second, time.Millisecond*200)
+	assert.Eventually(t, func() bool { return !sync.IsReady() }, time.Second, time.Millisecond*200)
+}
+
+// fakeDataplaneClient fakes the dataplane.Client interface so that we can
+// unit test the dataplane.Synchronizer.
+type fakeDataplaneClient struct {
+	dbmode      string
+	updateCount int
+	lock        sync.RWMutex
+}
+
+func (c *fakeDataplaneClient) DBMode() string {
+	c.lock.RLock()
+	defer c.lock.RUnlock()
+	return c.dbmode
+}
+
+func (c *fakeDataplaneClient) Update(ctx context.Context) error {
+	c.lock.Lock()
+	defer c.lock.Unlock()
+	c.updateCount++
+	return nil
+}
+
+func (c *fakeDataplaneClient) totalUpdates() int {
+	c.lock.RLock()
+	defer c.lock.RUnlock()
+	return c.updateCount
+}
diff --git a/test/e2e/features_test.go b/test/e2e/features_test.go
index c414f92dc..8c8595f8d 100644
--- a/test/e2e/features_test.go
+++ b/test/e2e/features_test.go
@@ -222,7 +222,7 @@ func TestWebhookUpdate(t *testing.T) {
 		}
 	}
 
-	deployment, err = env.Cluster().Client().AppsV1().Deployments(deployment.Namespace).Update(ctx,
+	_, err = env.Cluster().Client().AppsV1().Deployments(deployment.Namespace).Update(ctx,
 		deployment, metav1.UpdateOptions{})
 	require.NoError(t, err)
 
diff --git a/test/e2e/helpers_test.go b/test/e2e/helpers_test.go
index 9580edd55..129c64885 100644
--- a/test/e2e/helpers_test.go
+++ b/test/e2e/helpers_test.go
@@ -27,7 +27,6 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	kerrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-
 	gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
 	gatewayclient "sigs.k8s.io/gateway-api/pkg/client/clientset/gateway/versioned"
 
diff --git a/test/e2e/utils_test.go b/test/e2e/utils_test.go
index e987e200d..c025aacc0 100644
--- a/test/e2e/utils_test.go
+++ b/test/e2e/utils_test.go
@@ -284,8 +284,7 @@ func startPortForwarder(ctx context.Context, t *testing.T, env environments.Envi
 	written, err := kubeconfigFile.Write(kubeconfig)
 	require.NoError(t, err)
 	require.Equal(t, len(kubeconfig), written)
-	cmd := exec.CommandContext(ctx, "kubectl", "--kubeconfig", kubeconfigFile.Name(), "port-forward", "-n", namespace,
-		name, fmt.Sprintf("%s:%s", localPort, targetPort)) //nolint:gosec
+	cmd := exec.CommandContext(ctx, "kubectl", "--kubeconfig", kubeconfigFile.Name(), "port-forward", "-n", namespace, name, fmt.Sprintf("%s:%s", localPort, targetPort)) //nolint:gosec
 	t.Logf("forwarding port %s to %s/%s:%s", localPort, namespace, name, targetPort)
 	if startErr := cmd.Start(); startErr != nil {
 		startOutput, outputErr := cmd.Output()
@@ -301,24 +300,3 @@ func startPortForwarder(ctx context.Context, t *testing.T, env environments.Envi
 		return false
 	}, kongComponentWait, time.Second)
 }
-
-func getKubernetesLogs(t *testing.T, env environments.Environment, namespace, name string) (string, error) {
-	kubeconfig, err := generators.NewKubeConfigForRestConfig(env.Name(), env.Cluster().Config())
-	require.NoError(t, err)
-	kubeconfigFile, err := os.CreateTemp(os.TempDir(), "deploy-logs-tests-kubeconfig-")
-	require.NoError(t, err)
-	defer os.Remove(kubeconfigFile.Name())
-	defer kubeconfigFile.Close()
-	written, err := kubeconfigFile.Write(kubeconfig)
-	require.NoError(t, err)
-	require.Equal(t, len(kubeconfig), written)
-	stderr := new(bytes.Buffer)
-	cmd := exec.Command("kubectl", "--kubeconfig", kubeconfigFile.Name(), "logs", "-n", namespace, name,
-		"--all-containers")
-	cmd.Stderr = stderr
-	out, err := cmd.Output()
-	if err != nil {
-		return "", fmt.Errorf("%s", stderr.String())
-	}
-	return string(out), nil
-}
diff --git a/test/integration/suite_test.go b/test/integration/suite_test.go
index 5c3827361..63b8c9201 100644
--- a/test/integration/suite_test.go
+++ b/test/integration/suite_test.go
@@ -106,7 +106,7 @@ func TestMain(m *testing.M) {
 	clusterVersion, err = env.Cluster().Version()
 	exitOnErr(err)
 	if clusterVersion.GE(knativeMinKubernetesVersion) {
-		fmt.Printf("INFO: deploying knative addon")
+		fmt.Println("INFO: deploying knative addon")
 		knativeBuilder := knative.NewBuilder()
 		knativeAddon := knativeBuilder.Build()
 		exitOnErr(env.Cluster().DeployAddon(ctx, knativeAddon))
diff --git a/test/integration/utils_test.go b/test/integration/utils_test.go
index f7c2cd211..3801e35ab 100644
--- a/test/integration/utils_test.go
+++ b/test/integration/utils_test.go
@@ -289,7 +289,7 @@ func identifyTestCasesForFile(filePath string) ([]string, error) {
 // not pay attention to hostname or other routing rules. This uses a "require"
 // for the desired conditions so if this request doesn't eventually succeed the
 // calling test will fail and stop.
-func eventuallyGETPath(t *testing.T, path string, statusCode int, bodyContents string) { //nolint:unparam
+func eventuallyGETPath(t *testing.T, path string, statusCode int, bodyContents string) {
 	require.Eventually(t, func() bool {
 		resp, err := httpc.Get(fmt.Sprintf("%s/%s", proxyURL, path))
 		if err != nil {