Repository: kubernetes-sigs/external-dns

Issue:
Gateway source will create DNS record on old `spec.parentRefs`
<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks!

-->

**What happened**:
I have a `kind: HTTPRoute` with `spec` like this:
```yaml
spec:
  hostnames: ["host.my.domain"]
  parentRefs:
    - name: external
      namespace: networking-system
      port: 443
```
And then I update the resource to this:
```yaml
spec:
  hostnames: ["host.my.domain"]
  parentRefs:
    - name: internal
      namespace: networking-system
      port: 443
```

Now, `external-dns` will create 2 DNS records for this domain with `external` and `internal` gateway address (even if I deleted all the records created to let `external-dns` recreate new records). I think this is because `external-dns` is looking at the `status` field instead of the `spec` field of `HTTPRoute`? Because here's how `HTTPRoute.status` looks like after the resource edit:
```yaml
Status:
  Parents:
    Conditions:
      Last Transition Time:  2025-04-28T06:55:13Z
      Message:               Route is accepted
      Observed Generation:   1
      Reason:                Accepted
      Status:                True
      Type:                  Accepted
      Last Transition Time:  2025-04-28T06:55:13Z
      Message:               Resolved all the Object references for the Route
      Observed Generation:   1
      Reason:                ResolvedRefs
      Status:                True
      Type:                  ResolvedRefs
    Controller Name:         gateway.envoyproxy.io/gatewayclass-controller
    Parent Ref:
      Group:         gateway.networking.k8s.io
      Kind:          Gateway
      Name:          external
      Namespace:     networking-system
      Section Name:  https-0
    Conditions:
      Last Transition Time:  2025-05-01T10:01:02Z
      Message:               Route is accepted
      Observed Generation:   2
      Reason:                Accepted
      Status:                True
      Type:                  Accepted
      Last Transition Time:  2025-05-01T10:01:02Z
      Message:               Resolved all the Object references for the Route
      Observed Generation:   2
      Reason:                ResolvedRefs
      Status:                True
      Type:                  ResolvedRefs
    Controller Name:         gateway.envoyproxy.io/gatewayclass-controller
    Parent Ref:
      Group:         gateway.networking.k8s.io
      Kind:          Gateway
      Name:          internal
      Namespace:     networking-system
      Section Name:  https-0
```

**What you expected to happen**:
I expect `external-dns` will update change the old record from old gateway address to the new gateway address.

**How to reproduce it (as minimally and precisely as possible)**:
1. Have a HTTPRoute resource and make sure `external-dns` created the record for you.
2. Update the `spec.parentRefs` of the HTTPRoute to another Gateway name.
3. See that you have duplicated records with different addresses created by `external-dns`.

**Anything else we need to know?**:

**Environment**:

- External-DNS version (use `external-dns --version`): 0.16.1
- DNS provider: webhook
- Others:


Hints from issue comments:
@budimanjojo: The workaround that works for me is to delete the entire `HTTPRoute` and recreate it. Deleting the DNS record manually doesn't work because `external-dns` will recreate it again.@kashalls: [This apparently has been a long standing bug since ~2023](https://github.com/cilium/cilium/issues/29407#issuecomment-1829156360)? 

The fix here would be to filter status.parents for items that contain the same observedGeneration that matches metadata.generation. It could be placed anywhere [in this function](https://github.com/kubernetes-sigs/external-dns/blob/master/source/gateway.go#L292).
@kashalls: ```go
meta := rt.Metadata()
for _, rps := range rt.RouteStatus().Parents {
  if rps.Conditions[0].ObservedGeneration != metadata.Generation {
    log.Debugf("Parent reference %s/%s is older than current generation %s", .........)
    continue
  }
......
}
```
@budimanjojo: @kashalls Maybe you should create a PR? Also:
```patch
--if rps.Conditions[0].ObservedGeneration != metadata.Generation {
++if rps.Conditions[0].ObservedGeneration != meta.Generation {
```
?

Solution patch:
diff --git a/source/gateway.go b/source/gateway.go
index 33ef3be3..2a713521 100644
--- a/source/gateway.go
+++ b/source/gateway.go
@@ -54,6 +54,8 @@ type gatewayRoute interface {
 	Metadata() *metav1.ObjectMeta
 	// Hostnames returns the route's specified hostnames.
 	Hostnames() []v1.Hostname
+	// ParentRefs returns the route's parent references as defined in the route spec.
+	ParentRefs() []v1.ParentReference
 	// Protocol returns the route's protocol type.
 	Protocol() v1.ProtocolType
 	// RouteStatus returns the route's common status.
@@ -294,10 +296,24 @@ func (c *gatewayRouteResolver) resolve(rt gatewayRoute) (map[string]endpoint.Tar
 	}
 	hostTargets := make(map[string]endpoint.Targets)
 
+	routeParentRefs := rt.ParentRefs()
+
+	if len(routeParentRefs) == 0 {
+		log.Debugf("No parent references found for %s %s/%s", c.src.rtKind, rt.Metadata().Namespace, rt.Metadata().Name)
+		return hostTargets, nil
+	}
+
 	meta := rt.Metadata()
 	for _, rps := range rt.RouteStatus().Parents {
 		// Confirm the Parent is the standard Gateway kind.
 		ref := rps.ParentRef
+		namespace := strVal((*string)(ref.Namespace), meta.Namespace)
+		// Ensure that the parent reference is in the routeParentRefs list
+		if !gwRouteHasParentRef(routeParentRefs, ref, meta) {
+			log.Debugf("Parent reference %s/%s not found in routeParentRefs for %s %s/%s", namespace, string(ref.Name), c.src.rtKind, meta.Namespace, meta.Name)
+			continue
+		}
+
 		group := strVal((*string)(ref.Group), gatewayGroup)
 		kind := strVal((*string)(ref.Kind), gatewayKind)
 		if group != gatewayGroup || kind != gatewayKind {
@@ -305,7 +321,6 @@ func (c *gatewayRouteResolver) resolve(rt gatewayRoute) (map[string]endpoint.Tar
 			continue
 		}
 		// Lookup the Gateway and its Listeners.
-		namespace := strVal((*string)(ref.Namespace), meta.Namespace)
 		gw, ok := c.gws[namespacedName(namespace, string(ref.Name))]
 		if !ok {
 			log.Debugf("Gateway %s/%s not found for %s %s/%s", namespace, ref.Name, c.src.rtKind, meta.Namespace, meta.Name)
@@ -316,6 +331,7 @@ func (c *gatewayRouteResolver) resolve(rt gatewayRoute) (map[string]endpoint.Tar
 			log.Debugf("Gateway %s/%s does not match %s %s/%s", namespace, ref.Name, c.src.gwName, meta.Namespace, meta.Name)
 			continue
 		}
+
 		// Confirm the Gateway has accepted the Route.
 		if !gwRouteIsAccepted(rps.Conditions) {
 			log.Debugf("Gateway %s/%s has not accepted %s %s/%s", namespace, ref.Name, c.src.rtKind, meta.Namespace, meta.Name)
@@ -458,6 +474,26 @@ func (c *gatewayRouteResolver) routeIsAllowed(gw *v1beta1.Gateway, lis *v1.Liste
 	return false
 }
 
+func gwRouteHasParentRef(routeParentRefs []v1.ParentReference, ref v1.ParentReference, meta *metav1.ObjectMeta) bool {
+	// Ensure that the parent reference is in the routeParentRefs list
+	namespace := strVal((*string)(ref.Namespace), meta.Namespace)
+	group := strVal((*string)(ref.Group), gatewayGroup)
+	kind := strVal((*string)(ref.Kind), gatewayKind)
+	for _, rpr := range routeParentRefs {
+		rprGroup := strVal((*string)(rpr.Group), gatewayGroup)
+		rprKind := strVal((*string)(rpr.Kind), gatewayKind)
+		if rprGroup != group || rprKind != kind {
+			continue
+		}
+		rprNamespace := strVal((*string)(rpr.Namespace), meta.Namespace)
+		if string(rpr.Name) != string(ref.Name) || rprNamespace != namespace {
+			continue
+		}
+		return true
+	}
+	return false
+}
+
 func gwRouteIsAccepted(conds []metav1.Condition) bool {
 	for _, c := range conds {
 		if v1.RouteConditionType(c.Type) == v1.RouteConditionAccepted {
diff --git a/source/gateway_grpcroute.go b/source/gateway_grpcroute.go
index de93a72d..f2253f2d 100644
--- a/source/gateway_grpcroute.go
+++ b/source/gateway_grpcroute.go
@@ -33,11 +33,12 @@ func NewGatewayGRPCRouteSource(clients ClientGenerator, config *Config) (Source,
 
 type gatewayGRPCRoute struct{ route v1.GRPCRoute } // NOTE: Must update TypeMeta in List when changing the APIVersion.
 
-func (rt *gatewayGRPCRoute) Object() kubeObject           { return &rt.route }
-func (rt *gatewayGRPCRoute) Metadata() *metav1.ObjectMeta { return &rt.route.ObjectMeta }
-func (rt *gatewayGRPCRoute) Hostnames() []v1.Hostname     { return rt.route.Spec.Hostnames }
-func (rt *gatewayGRPCRoute) Protocol() v1.ProtocolType    { return v1.HTTPSProtocolType }
-func (rt *gatewayGRPCRoute) RouteStatus() v1.RouteStatus  { return rt.route.Status.RouteStatus }
+func (rt *gatewayGRPCRoute) Object() kubeObject               { return &rt.route }
+func (rt *gatewayGRPCRoute) Metadata() *metav1.ObjectMeta     { return &rt.route.ObjectMeta }
+func (rt *gatewayGRPCRoute) Hostnames() []v1.Hostname         { return rt.route.Spec.Hostnames }
+func (rt *gatewayGRPCRoute) ParentRefs() []v1.ParentReference { return rt.route.Spec.ParentRefs }
+func (rt *gatewayGRPCRoute) Protocol() v1.ProtocolType        { return v1.HTTPSProtocolType }
+func (rt *gatewayGRPCRoute) RouteStatus() v1.RouteStatus      { return rt.route.Status.RouteStatus }
 
 type gatewayGRPCRouteInformer struct {
 	informers_v1.GRPCRouteInformer
diff --git a/source/gateway_httproute.go b/source/gateway_httproute.go
index 4c063d4d..ad3c4fcd 100644
--- a/source/gateway_httproute.go
+++ b/source/gateway_httproute.go
@@ -34,11 +34,12 @@ func NewGatewayHTTPRouteSource(clients ClientGenerator, config *Config) (Source,
 
 type gatewayHTTPRoute struct{ route v1.HTTPRoute } // NOTE: Must update TypeMeta in List when changing the APIVersion.
 
-func (rt *gatewayHTTPRoute) Object() kubeObject           { return &rt.route }
-func (rt *gatewayHTTPRoute) Metadata() *metav1.ObjectMeta { return &rt.route.ObjectMeta }
-func (rt *gatewayHTTPRoute) Hostnames() []v1.Hostname     { return rt.route.Spec.Hostnames }
-func (rt *gatewayHTTPRoute) Protocol() v1.ProtocolType    { return v1.HTTPProtocolType }
-func (rt *gatewayHTTPRoute) RouteStatus() v1.RouteStatus  { return rt.route.Status.RouteStatus }
+func (rt *gatewayHTTPRoute) Object() kubeObject               { return &rt.route }
+func (rt *gatewayHTTPRoute) Metadata() *metav1.ObjectMeta     { return &rt.route.ObjectMeta }
+func (rt *gatewayHTTPRoute) Hostnames() []v1.Hostname         { return rt.route.Spec.Hostnames }
+func (rt *gatewayHTTPRoute) ParentRefs() []v1.ParentReference { return rt.route.Spec.ParentRefs }
+func (rt *gatewayHTTPRoute) Protocol() v1.ProtocolType        { return v1.HTTPProtocolType }
+func (rt *gatewayHTTPRoute) RouteStatus() v1.RouteStatus      { return rt.route.Status.RouteStatus }
 
 type gatewayHTTPRouteInformer struct {
 	informers_v1beta1.HTTPRouteInformer
diff --git a/source/gateway_tcproute.go b/source/gateway_tcproute.go
index 863b4f62..57ca6a55 100644
--- a/source/gateway_tcproute.go
+++ b/source/gateway_tcproute.go
@@ -34,11 +34,12 @@ func NewGatewayTCPRouteSource(clients ClientGenerator, config *Config) (Source,
 
 type gatewayTCPRoute struct{ route v1alpha2.TCPRoute } // NOTE: Must update TypeMeta in List when changing the APIVersion.
 
-func (rt *gatewayTCPRoute) Object() kubeObject           { return &rt.route }
-func (rt *gatewayTCPRoute) Metadata() *metav1.ObjectMeta { return &rt.route.ObjectMeta }
-func (rt *gatewayTCPRoute) Hostnames() []v1.Hostname     { return nil }
-func (rt *gatewayTCPRoute) Protocol() v1.ProtocolType    { return v1.TCPProtocolType }
-func (rt *gatewayTCPRoute) RouteStatus() v1.RouteStatus  { return rt.route.Status.RouteStatus }
+func (rt *gatewayTCPRoute) Object() kubeObject               { return &rt.route }
+func (rt *gatewayTCPRoute) Metadata() *metav1.ObjectMeta     { return &rt.route.ObjectMeta }
+func (rt *gatewayTCPRoute) Hostnames() []v1.Hostname         { return nil }
+func (rt *gatewayTCPRoute) ParentRefs() []v1.ParentReference { return rt.route.Spec.ParentRefs }
+func (rt *gatewayTCPRoute) Protocol() v1.ProtocolType        { return v1.TCPProtocolType }
+func (rt *gatewayTCPRoute) RouteStatus() v1.RouteStatus      { return rt.route.Status.RouteStatus }
 
 type gatewayTCPRouteInformer struct {
 	informers_v1a2.TCPRouteInformer
diff --git a/source/gateway_tlsroute.go b/source/gateway_tlsroute.go
index 0c985588..9e77f3bc 100644
--- a/source/gateway_tlsroute.go
+++ b/source/gateway_tlsroute.go
@@ -34,11 +34,12 @@ func NewGatewayTLSRouteSource(clients ClientGenerator, config *Config) (Source,
 
 type gatewayTLSRoute struct{ route v1alpha2.TLSRoute } // NOTE: Must update TypeMeta in List when changing the APIVersion.
 
-func (rt *gatewayTLSRoute) Object() kubeObject           { return &rt.route }
-func (rt *gatewayTLSRoute) Metadata() *metav1.ObjectMeta { return &rt.route.ObjectMeta }
-func (rt *gatewayTLSRoute) Hostnames() []v1.Hostname     { return rt.route.Spec.Hostnames }
-func (rt *gatewayTLSRoute) Protocol() v1.ProtocolType    { return v1.TLSProtocolType }
-func (rt *gatewayTLSRoute) RouteStatus() v1.RouteStatus  { return rt.route.Status.RouteStatus }
+func (rt *gatewayTLSRoute) Object() kubeObject               { return &rt.route }
+func (rt *gatewayTLSRoute) Metadata() *metav1.ObjectMeta     { return &rt.route.ObjectMeta }
+func (rt *gatewayTLSRoute) Hostnames() []v1.Hostname         { return rt.route.Spec.Hostnames }
+func (rt *gatewayTLSRoute) ParentRefs() []v1.ParentReference { return rt.route.Spec.ParentRefs }
+func (rt *gatewayTLSRoute) Protocol() v1.ProtocolType        { return v1.TLSProtocolType }
+func (rt *gatewayTLSRoute) RouteStatus() v1.RouteStatus      { return rt.route.Status.RouteStatus }
 
 type gatewayTLSRouteInformer struct {
 	informers_v1a2.TLSRouteInformer
diff --git a/source/gateway_udproute.go b/source/gateway_udproute.go
index ffc0987f..82e213c5 100644
--- a/source/gateway_udproute.go
+++ b/source/gateway_udproute.go
@@ -34,11 +34,12 @@ func NewGatewayUDPRouteSource(clients ClientGenerator, config *Config) (Source,
 
 type gatewayUDPRoute struct{ route v1alpha2.UDPRoute } // NOTE: Must update TypeMeta in List when changing the APIVersion.
 
-func (rt *gatewayUDPRoute) Object() kubeObject           { return &rt.route }
-func (rt *gatewayUDPRoute) Metadata() *metav1.ObjectMeta { return &rt.route.ObjectMeta }
-func (rt *gatewayUDPRoute) Hostnames() []v1.Hostname     { return nil }
-func (rt *gatewayUDPRoute) Protocol() v1.ProtocolType    { return v1.UDPProtocolType }
-func (rt *gatewayUDPRoute) RouteStatus() v1.RouteStatus  { return rt.route.Status.RouteStatus }
+func (rt *gatewayUDPRoute) Object() kubeObject               { return &rt.route }
+func (rt *gatewayUDPRoute) Metadata() *metav1.ObjectMeta     { return &rt.route.ObjectMeta }
+func (rt *gatewayUDPRoute) Hostnames() []v1.Hostname         { return nil }
+func (rt *gatewayUDPRoute) ParentRefs() []v1.ParentReference { return rt.route.Spec.ParentRefs }
+func (rt *gatewayUDPRoute) Protocol() v1.ProtocolType        { return v1.UDPProtocolType }
+func (rt *gatewayUDPRoute) RouteStatus() v1.RouteStatus      { return rt.route.Status.RouteStatus }
 
 type gatewayUDPRouteInformer struct {
 	informers_v1a2.UDPRouteInformer



Test patch:
diff --git a/source/gateway_grpcroute_test.go b/source/gateway_grpcroute_test.go
index f92070eb..a794c379 100644
--- a/source/gateway_grpcroute_test.go
+++ b/source/gateway_grpcroute_test.go
@@ -74,6 +74,11 @@ func TestGatewayGRPCRouteSourceEndpoints(t *testing.T) {
 		},
 		Spec: v1.GRPCRouteSpec{
 			Hostnames: []v1.Hostname{"api-hostnames.foobar.internal"},
+			CommonRouteSpec: v1.CommonRouteSpec{
+				ParentRefs: []v1.ParentReference{
+					gwParentRef("default", "internal"),
+				},
+			},
 		},
 		Status: v1.GRPCRouteStatus{
 			RouteStatus: gwRouteStatus(gwParentRef("default", "internal")),
diff --git a/source/gateway_httproute_test.go b/source/gateway_httproute_test.go
index 629a9e58..a09f25bc 100644
--- a/source/gateway_httproute_test.go
+++ b/source/gateway_httproute_test.go
@@ -174,6 +174,12 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 				ObjectMeta: objectMeta("route-namespace", "test"),
 				Spec: v1.HTTPRouteSpec{
 					Hostnames: hostnames("test.example.internal"),
+					CommonRouteSpec: v1.CommonRouteSpec{
+						ParentRefs: []v1.ParentReference{
+							gwParentRef("gateway-namespace", "gateway-name"),
+							gwParentRef("gateway-namespace", "not-gateway-name"),
+						},
+					},
 				},
 				Status: httpRouteStatus( // The route is attached to both gateways.
 					gwParentRef("gateway-namespace", "gateway-name"),
@@ -216,6 +222,12 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 				ObjectMeta: objectMeta("route-namespace", "test"),
 				Spec: v1.HTTPRouteSpec{
 					Hostnames: hostnames("test.example.internal"),
+					CommonRouteSpec: v1.CommonRouteSpec{
+						ParentRefs: []v1.ParentReference{
+							gwParentRef("gateway-namespace", "test"),
+							gwParentRef("not-gateway-namespace", "test"),
+						},
+					},
 				},
 				Status: httpRouteStatus( // The route is attached to both gateways.
 					gwParentRef("gateway-namespace", "test"),
@@ -247,6 +259,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 					ObjectMeta: objectMeta("route-namespace", "test"),
 					Spec: v1.HTTPRouteSpec{
 						Hostnames: hostnames("route-namespace.example.internal"),
+						CommonRouteSpec: v1.CommonRouteSpec{
+							ParentRefs: []v1.ParentReference{
+								gwParentRef("gateway-namespace", "test"),
+							},
+						},
 					},
 					Status: httpRouteStatus(gwParentRef("gateway-namespace", "test")),
 				},
@@ -296,6 +313,12 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 				ObjectMeta: objectMeta("default", "test"),
 				Spec: v1.HTTPRouteSpec{
 					Hostnames: hostnames("test.example.internal"),
+					CommonRouteSpec: v1.CommonRouteSpec{
+						ParentRefs: []v1.ParentReference{
+							gwParentRef("default", "labels-match"),
+							gwParentRef("default", "labels-dont-match"),
+						},
+					},
 				},
 				Status: httpRouteStatus( // The route is attached to both gateways.
 					gwParentRef("default", "labels-match"),
@@ -328,6 +351,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 					},
 					Spec: v1.HTTPRouteSpec{
 						Hostnames: hostnames("labels-match.example.internal"),
+						CommonRouteSpec: v1.CommonRouteSpec{
+							ParentRefs: []v1.ParentReference{
+								gwParentRef("default", "test"),
+							},
+						},
 					},
 					Status: httpRouteStatus(gwParentRef("default", "test")),
 				},
@@ -339,6 +367,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 					},
 					Spec: v1.HTTPRouteSpec{
 						Hostnames: hostnames("labels-dont-match.example.internal"),
+						CommonRouteSpec: v1.CommonRouteSpec{
+							ParentRefs: []v1.ParentReference{
+								gwParentRef("default", "test"),
+							},
+						},
 					},
 					Status: httpRouteStatus(gwParentRef("default", "test")),
 				},
@@ -369,6 +402,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 					},
 					Spec: v1.HTTPRouteSpec{
 						Hostnames: hostnames("annotations-match.example.internal"),
+						CommonRouteSpec: v1.CommonRouteSpec{
+							ParentRefs: []v1.ParentReference{
+								gwParentRef("default", "test"),
+							},
+						},
 					},
 					Status: httpRouteStatus(gwParentRef("default", "test")),
 				},
@@ -380,6 +418,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 					},
 					Spec: v1.HTTPRouteSpec{
 						Hostnames: hostnames("annotations-dont-match.example.internal"),
+						CommonRouteSpec: v1.CommonRouteSpec{
+							ParentRefs: []v1.ParentReference{
+								gwParentRef("default", "test"),
+							},
+						},
 					},
 					Status: httpRouteStatus(gwParentRef("default", "test")),
 				},
@@ -408,6 +451,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 					},
 				},
 				Spec: v1.HTTPRouteSpec{
+					CommonRouteSpec: v1.CommonRouteSpec{
+						ParentRefs: []v1.ParentReference{
+							gwParentRef("default", "test"),
+						},
+					},
 					Hostnames: hostnames("api.example.internal"),
 				},
 				Status: httpRouteStatus(gwParentRef("default", "test")),
@@ -438,6 +486,12 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 				ObjectMeta: objectMeta("default", "test"),
 				Spec: v1.HTTPRouteSpec{
 					Hostnames: hostnames("test.example.internal"),
+					CommonRouteSpec: v1.CommonRouteSpec{
+						ParentRefs: []v1.ParentReference{
+							gwParentRef("default", "one"),
+							gwParentRef("default", "two"),
+						},
+					},
 				},
 				Status: httpRouteStatus(
 					gwParentRef("default", "one"),
@@ -474,6 +528,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 				ObjectMeta: objectMeta("default", "test"),
 				Spec: v1.HTTPRouteSpec{
 					Hostnames: hostnames("*.example.internal"),
+					CommonRouteSpec: v1.CommonRouteSpec{
+						ParentRefs: []v1.ParentReference{
+							gwParentRef("default", "one"),
+						},
+					},
 				},
 				Status: httpRouteStatus(
 					gwParentRef("default", "one"),
@@ -510,6 +569,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 				ObjectMeta: objectMeta("default", "test"),
 				Spec: v1.HTTPRouteSpec{
 					Hostnames: hostnames("*.example.internal"),
+					CommonRouteSpec: v1.CommonRouteSpec{
+						ParentRefs: []v1.ParentReference{
+							gwParentRef("default", "test", withSectionName("foo")),
+						},
+					},
 				},
 				Status: httpRouteStatus(
 					gwParentRef("default", "test", withSectionName("foo")),
@@ -554,6 +618,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 				ObjectMeta: objectMeta("default", "test"),
 				Spec: v1.HTTPRouteSpec{
 					Hostnames: hostnames("*.example.internal"),
+					CommonRouteSpec: v1.CommonRouteSpec{
+						ParentRefs: []v1.ParentReference{
+							gwParentRef("default", "test", withPortNumber(80)),
+						},
+					},
 				},
 				Status: httpRouteStatus(
 					gwParentRef("default", "test", withPortNumber(80)),
@@ -581,6 +650,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 			routes: []*v1beta1.HTTPRoute{{
 				ObjectMeta: objectMeta("default", "no-hostname"),
 				Spec: v1.HTTPRouteSpec{
+					CommonRouteSpec: v1.CommonRouteSpec{
+						ParentRefs: []v1.ParentReference{
+							gwParentRef("default", "test"),
+						},
+					},
 					Hostnames: []v1.Hostname{
 						"foo.example.internal",
 					},
@@ -608,6 +682,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 			routes: []*v1beta1.HTTPRoute{{
 				ObjectMeta: objectMeta("default", "no-hostname"),
 				Spec: v1.HTTPRouteSpec{
+					CommonRouteSpec: v1.CommonRouteSpec{
+						ParentRefs: []v1.ParentReference{
+							gwParentRef("default", "test"),
+						},
+					},
 					Hostnames: []v1.Hostname{
 						"*.example.internal",
 					},
@@ -635,6 +714,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 			routes: []*v1beta1.HTTPRoute{{
 				ObjectMeta: objectMeta("default", "no-hostname"),
 				Spec: v1.HTTPRouteSpec{
+					CommonRouteSpec: v1.CommonRouteSpec{
+						ParentRefs: []v1.ParentReference{
+							gwParentRef("default", "test"),
+						},
+					},
 					Hostnames: []v1.Hostname{
 						"*.example.internal",
 					},
@@ -662,6 +746,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 			routes: []*v1beta1.HTTPRoute{{
 				ObjectMeta: objectMeta("default", "no-hostname"),
 				Spec: v1.HTTPRouteSpec{
+					CommonRouteSpec: v1.CommonRouteSpec{
+						ParentRefs: []v1.ParentReference{
+							gwParentRef("default", "test"),
+						},
+					},
 					Hostnames: nil,
 				},
 				Status: httpRouteStatus(gwParentRef("default", "test")),
@@ -679,6 +768,9 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 				ObjectMeta: objectMeta("default", "test"),
 				Spec: v1.HTTPRouteSpec{
 					Hostnames: hostnames("example.internal"),
+					CommonRouteSpec: v1.CommonRouteSpec{
+						ParentRefs: []v1.ParentReference{},
+					},
 				},
 				Status: httpRouteStatus(),
 			}},
@@ -698,6 +790,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 			routes: []*v1beta1.HTTPRoute{{
 				ObjectMeta: objectMeta("default", "no-hostname"),
 				Spec: v1.HTTPRouteSpec{
+					CommonRouteSpec: v1.CommonRouteSpec{
+						ParentRefs: []v1.ParentReference{
+							gwParentRef("default", "test"),
+						},
+					},
 					Hostnames: nil,
 				},
 				Status: httpRouteStatus(gwParentRef("default", "test")),
@@ -725,6 +822,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 						},
 					},
 					Spec: v1.HTTPRouteSpec{
+						CommonRouteSpec: v1.CommonRouteSpec{
+							ParentRefs: []v1.ParentReference{
+								gwParentRef("default", "test"),
+							},
+						},
 						Hostnames: nil,
 					},
 					Status: httpRouteStatus(gwParentRef("default", "test")),
@@ -738,6 +840,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 						},
 					},
 					Spec: v1.HTTPRouteSpec{
+						CommonRouteSpec: v1.CommonRouteSpec{
+							ParentRefs: []v1.ParentReference{
+								gwParentRef("default", "test"),
+							},
+						},
 						Hostnames: hostnames("with-hostname.internal"),
 					},
 					Status: httpRouteStatus(gwParentRef("default", "test")),
@@ -772,6 +879,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 				},
 				Spec: v1.HTTPRouteSpec{
 					Hostnames: hostnames("with-hostname.internal"),
+					CommonRouteSpec: v1.CommonRouteSpec{
+						ParentRefs: []v1.ParentReference{
+							gwParentRef("default", "test"),
+						},
+					},
 				},
 				Status: httpRouteStatus(gwParentRef("default", "test")),
 			}},
@@ -797,12 +909,22 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 					ObjectMeta: objectMeta("default", "fqdn-with-hostnames"),
 					Spec: v1.HTTPRouteSpec{
 						Hostnames: hostnames("fqdn-with-hostnames.internal"),
+						CommonRouteSpec: v1.CommonRouteSpec{
+							ParentRefs: []v1.ParentReference{
+								gwParentRef("default", "test"),
+							},
+						},
 					},
 					Status: httpRouteStatus(gwParentRef("default", "test")),
 				},
 				{
 					ObjectMeta: objectMeta("default", "fqdn-without-hostnames"),
 					Spec: v1.HTTPRouteSpec{
+						CommonRouteSpec: v1.CommonRouteSpec{
+							ParentRefs: []v1.ParentReference{
+								gwParentRef("default", "test"),
+							},
+						},
 						Hostnames: nil,
 					},
 					Status: httpRouteStatus(gwParentRef("default", "test")),
@@ -832,6 +954,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 			routes: []*v1beta1.HTTPRoute{{
 				ObjectMeta: objectMeta("default", "fqdn-with-hostnames"),
 				Spec: v1.HTTPRouteSpec{
+					CommonRouteSpec: v1.CommonRouteSpec{
+						ParentRefs: []v1.ParentReference{
+							gwParentRef("default", "test"),
+						},
+					},
 					Hostnames: hostnames("fqdn-with-hostnames.internal"),
 				},
 				Status: httpRouteStatus(gwParentRef("default", "test")),
@@ -861,6 +988,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 					},
 					Spec: v1.HTTPRouteSpec{
 						Hostnames: hostnames("valid-ttl.internal"),
+						CommonRouteSpec: v1.CommonRouteSpec{
+							ParentRefs: []v1.ParentReference{
+								gwParentRef("default", "test"),
+							},
+						},
 					},
 					Status: httpRouteStatus(gwParentRef("default", "test")),
 				},
@@ -872,6 +1004,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 					},
 					Spec: v1.HTTPRouteSpec{
 						Hostnames: hostnames("invalid-ttl.internal"),
+						CommonRouteSpec: v1.CommonRouteSpec{
+							ParentRefs: []v1.ParentReference{
+								gwParentRef("default", "test"),
+							},
+						},
 					},
 					Status: httpRouteStatus(gwParentRef("default", "test")),
 				},
@@ -902,6 +1039,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 					},
 				},
 				Spec: v1.HTTPRouteSpec{
+					CommonRouteSpec: v1.CommonRouteSpec{
+						ParentRefs: []v1.ParentReference{
+							gwParentRef("default", "test"),
+						},
+					},
 					Hostnames: hostnames("provider-annotations.com"),
 				},
 				Status: httpRouteStatus(gwParentRef("default", "test")),
@@ -941,6 +1083,12 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 			routes: []*v1beta1.HTTPRoute{{
 				ObjectMeta: objectMeta("default", "test"),
 				Spec: v1.HTTPRouteSpec{
+					CommonRouteSpec: v1.CommonRouteSpec{
+						ParentRefs: []v1.ParentReference{
+							gwParentRef("default", "one"),
+							gwParentRef("default", "two"),
+						},
+					},
 					Hostnames: hostnames("test.one.internal", "test.two.internal"),
 				},
 				Status: httpRouteStatus(
@@ -976,6 +1124,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 					ObjectMeta: objectMeta("same-namespace", "test"),
 					Spec: v1.HTTPRouteSpec{
 						Hostnames: hostnames("same-namespace.example.internal"),
+						CommonRouteSpec: v1.CommonRouteSpec{
+							ParentRefs: []v1.ParentReference{
+								gwParentRef("same-namespace", "test"),
+							},
+						},
 					},
 					Status: httpRouteStatus(gwParentRef("same-namespace", "test")),
 				},
@@ -983,6 +1136,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 					ObjectMeta: objectMeta("other-namespace", "test"),
 					Spec: v1.HTTPRouteSpec{
 						Hostnames: hostnames("other-namespace.example.internal"),
+						CommonRouteSpec: v1.CommonRouteSpec{
+							ParentRefs: []v1.ParentReference{
+								gwParentRef("same-namespace", "test"),
+							},
+						},
 					},
 					Status: httpRouteStatus(gwParentRef("same-namespace", "test")),
 				},
@@ -1035,6 +1193,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 					ObjectMeta: objectMeta("foo", "test"),
 					Spec: v1.HTTPRouteSpec{
 						Hostnames: hostnames("foo.example.internal"),
+						CommonRouteSpec: v1.CommonRouteSpec{
+							ParentRefs: []v1.ParentReference{
+								gwParentRef("default", "test"),
+							},
+						},
 					},
 					Status: httpRouteStatus(gwParentRef("default", "test")),
 				},
@@ -1042,6 +1205,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 					ObjectMeta: objectMeta("bar", "test"),
 					Spec: v1.HTTPRouteSpec{
 						Hostnames: hostnames("bar.example.internal"),
+						CommonRouteSpec: v1.CommonRouteSpec{
+							ParentRefs: []v1.ParentReference{
+								gwParentRef("default", "test"),
+							},
+						},
 					},
 					Status: httpRouteStatus(gwParentRef("default", "test")),
 				},
@@ -1075,6 +1243,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 			routes: []*v1beta1.HTTPRoute{{
 				ObjectMeta: objectMeta("default", "test"),
 				Spec: v1.HTTPRouteSpec{
+					CommonRouteSpec: v1.CommonRouteSpec{
+						ParentRefs: []v1.ParentReference{
+							gwParentRef("default", "test"),
+						},
+					},
 					Hostnames: hostnames("example.internal"),
 				},
 				Status: httpRouteStatus(gwParentRef("default", "test")),
@@ -1109,6 +1282,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 				ObjectMeta: objectMeta("route-namespace", "test"),
 				Spec: v1.HTTPRouteSpec{
 					Hostnames: hostnames("test.example.internal"),
+					CommonRouteSpec: v1.CommonRouteSpec{
+						ParentRefs: []v1.ParentReference{
+							gwParentRef("gateway-namespace", "overriden-gateway"),
+						},
+					},
 				},
 				Status: httpRouteStatus( // The route is attached to both gateways.
 					gwParentRef("gateway-namespace", "overriden-gateway"),
@@ -1156,8 +1334,14 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 				ObjectMeta: objectMeta("route-namespace", "test"),
 				Spec: v1.HTTPRouteSpec{
 					Hostnames: hostnames("test.example.internal"),
+					CommonRouteSpec: v1.CommonRouteSpec{
+						ParentRefs: []v1.ParentReference{
+							gwParentRef("gateway-namespace", "overriden-gateway"),
+							gwParentRef("gateway-namespace", "test"),
+						},
+					},
 				},
-				Status: httpRouteStatus( // The route is attached to both gateways.
+				Status: httpRouteStatus(
 					gwParentRef("gateway-namespace", "overriden-gateway"),
 					gwParentRef("gateway-namespace", "test"),
 				),
@@ -1191,6 +1375,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 					ObjectMeta: objectMeta("default", "one"),
 					Spec: v1.HTTPRouteSpec{
 						Hostnames: hostnames("test.one.internal"),
+						CommonRouteSpec: v1.CommonRouteSpec{
+							ParentRefs: []v1.ParentReference{
+								gwParentRef("default", "one"),
+							},
+						},
 					},
 					Status: httpRouteStatus(
 						gwParentRef("default", "one"),
@@ -1200,6 +1389,11 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 					ObjectMeta: objectMeta("default", "two"),
 					Spec: v1.HTTPRouteSpec{
 						Hostnames: hostnames("test.two.internal"),
+						CommonRouteSpec: v1.CommonRouteSpec{
+							ParentRefs: []v1.ParentReference{
+								gwParentRef("default", "two"),
+							},
+						},
 					},
 					Status: httpRouteStatus(
 						gwParentRef("default", "two"),
@@ -1215,6 +1409,71 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
 				"level=debug msg=\"Endpoints generated from HTTPRoute default/two: [test.two.internal 0 IN A  2.3.4.5 []]\"",
 			},
 		},
+		{
+			title: "NoParentRefs",
+			config: Config{
+				GatewayNamespace: "gateway-namespace",
+			},
+			namespaces: namespaces("gateway-namespace", "route-namespace"),
+			gateways: []*v1beta1.Gateway{
+				{
+					ObjectMeta: objectMeta("gateway-namespace", "test"),
+					Spec: v1.GatewaySpec{
+						Listeners: []v1.Listener{{
+							Protocol:      v1.HTTPProtocolType,
+							AllowedRoutes: allowAllNamespaces,
+						}},
+					},
+					Status: gatewayStatus("1.2.3.4"),
+				},
+			},
+			routes: []*v1beta1.HTTPRoute{{
+				ObjectMeta: objectMeta("route-namespace", "test"),
+				Spec: v1.HTTPRouteSpec{
+					Hostnames: hostnames("test.example.internal"),
+				},
+				Status: httpRouteStatus(gwParentRef("gateway-namespace", "test")),
+			}},
+			endpoints: []*endpoint.Endpoint{},
+			logExpectations: []string{
+				"level=debug msg=\"No parent references found for HTTPRoute route-namespace/test\"",
+			},
+		},
+		{
+			title: "ParentRefsMismatch",
+			config: Config{
+				GatewayNamespace: "gateway-namespace",
+			},
+			namespaces: namespaces("gateway-namespace", "route-namespace"),
+			gateways: []*v1beta1.Gateway{
+				{
+					ObjectMeta: objectMeta("gateway-namespace", "test"),
+					Spec: v1.GatewaySpec{
+						Listeners: []v1.Listener{{
+							Protocol:      v1.HTTPProtocolType,
+							AllowedRoutes: allowAllNamespaces,
+						}},
+					},
+					Status: gatewayStatus("1.2.3.4"),
+				},
+			},
+			routes: []*v1beta1.HTTPRoute{{
+				ObjectMeta: objectMeta("route-namespace", "test"),
+				Spec: v1.HTTPRouteSpec{
+					Hostnames: hostnames("test.example.internal"),
+					CommonRouteSpec: v1.CommonRouteSpec{
+						ParentRefs: []v1.ParentReference{
+							gwParentRef("gateway-namespace", "default-gateway"),
+						},
+					},
+				},
+				Status: httpRouteStatus(gwParentRef("gateway-namespace", "other-gateway")),
+			}},
+			endpoints: []*endpoint.Endpoint{},
+			logExpectations: []string{
+				"level=debug msg=\"Parent reference gateway-namespace/other-gateway not found in routeParentRefs for HTTPRoute route-namespace/test\"",
+			},
+		},
 	}
 	for _, tt := range tests {
 		tt := tt
diff --git a/source/gateway_tcproute_test.go b/source/gateway_tcproute_test.go
index a6039ebf..417ac788 100644
--- a/source/gateway_tcproute_test.go
+++ b/source/gateway_tcproute_test.go
@@ -73,7 +73,13 @@ func TestGatewayTCPRouteSourceEndpoints(t *testing.T) {
 				hostnameAnnotationKey: "api-annotation.foobar.internal",
 			},
 		},
-		Spec: v1alpha2.TCPRouteSpec{},
+		Spec: v1alpha2.TCPRouteSpec{
+			CommonRouteSpec: v1.CommonRouteSpec{
+				ParentRefs: []v1.ParentReference{
+					gwParentRef("default", "internal"),
+				},
+			},
+		},
 		Status: v1alpha2.TCPRouteStatus{
 			RouteStatus: gwRouteStatus(gwParentRef("default", "internal")),
 		},
diff --git a/source/gateway_tlsroute_test.go b/source/gateway_tlsroute_test.go
index e6d4660e..238b6259 100644
--- a/source/gateway_tlsroute_test.go
+++ b/source/gateway_tlsroute_test.go
@@ -75,6 +75,11 @@ func TestGatewayTLSRouteSourceEndpoints(t *testing.T) {
 		},
 		Spec: v1alpha2.TLSRouteSpec{
 			Hostnames: []v1.Hostname{"api-hostnames.foobar.internal"},
+			CommonRouteSpec: v1.CommonRouteSpec{
+				ParentRefs: []v1.ParentReference{
+					gwParentRef("default", "internal"),
+				},
+			},
 		},
 		Status: v1alpha2.TLSRouteStatus{
 			RouteStatus: gwRouteStatus(gwParentRef("default", "internal")),
diff --git a/source/gateway_udproute_test.go b/source/gateway_udproute_test.go
index 570e3d10..161de45f 100644
--- a/source/gateway_udproute_test.go
+++ b/source/gateway_udproute_test.go
@@ -73,7 +73,13 @@ func TestGatewayUDPRouteSourceEndpoints(t *testing.T) {
 				hostnameAnnotationKey: "api-annotation.foobar.internal",
 			},
 		},
-		Spec: v1alpha2.UDPRouteSpec{},
+		Spec: v1alpha2.UDPRouteSpec{
+			CommonRouteSpec: v1.CommonRouteSpec{
+				ParentRefs: []v1.ParentReference{
+					gwParentRef("default", "internal"),
+				},
+			},
+		},
 		Status: v1alpha2.UDPRouteStatus{
 			RouteStatus: gwRouteStatus(gwParentRef("default", "internal")),
 		},