Repository: kubernetes-sigs/gateway-api-inference-extension

Issue:
conformance: Add HTTP calls to validate E2E routing for inferencepool_resolvedrefs_condition test.
This is a tracking issue for work item to update the Conformance test "inferencepool_resolvedrefs_condition" to add E2E HTTP calls that ensure the routing is actually taking place as expected. 

Discussion:
@robscott: /assign @SinaChavoshi @SinaChavoshi: fixedby #866

Solution patch:
diff --git a/conformance/resources/manifests/manifests.yaml b/conformance/resources/manifests/manifests.yaml
index 190a884..b2ca489 100644
--- a/conformance/resources/manifests/manifests.yaml
+++ b/conformance/resources/manifests/manifests.yaml
@@ -23,7 +23,7 @@ metadata:
     gateway-conformance: backend
 
 ---
-# Namespace for simple web server backends. This is expected by 
+# Namespace for simple web server backends. This is expected by
 # the upstream conformance suite's Setup method.
 apiVersion: v1
 kind: Namespace
@@ -50,8 +50,27 @@ spec:
     protocol: HTTP
     allowedRoutes:
       namespaces:
-        from: All 
+        from: All
       kinds:
       # Allows HTTPRoutes to attach, which can then reference InferencePools.
       - group: gateway.networking.k8s.io
         kind: HTTPRoute
+
+---
+# --- Conformance Secondary Gateway Definition ---
+# A second generic Gateway resource for tests requiring multiple Gateways.
+apiVersion: gateway.networking.k8s.io/v1
+kind: Gateway
+metadata:
+  name: conformance-secondary-gateway
+  namespace: gateway-conformance-infra
+spec:
+  gatewayClassName: "{GATEWAY_CLASS_NAME}"
+  listeners:
+  - name: http
+    port: 80
+    protocol: HTTP
+    hostname: "secondary.example.com" # Distinct hostname to differentiate from conformance-gateway
+    allowedRoutes:
+      namespaces:
+        from: All
diff --git a/conformance/utils/kubernetes/helpers.go b/conformance/utils/kubernetes/helpers.go
index fbe24b5..a862cfb 100644
--- a/conformance/utils/kubernetes/helpers.go
+++ b/conformance/utils/kubernetes/helpers.go
@@ -34,8 +34,12 @@ import (
 	// Import the Inference Extension API types
 	inferenceapi "sigs.k8s.io/gateway-api-inference-extension/api/v1alpha2" // Adjust if your API version is different
 
-	// Import necessary utilities from the core Gateway API conformance suite
+	// Import local config for Inference Extension
 	"sigs.k8s.io/gateway-api-inference-extension/conformance/utils/config"
+	// Import necessary utilities from the core Gateway API conformance suite
+	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
+	gatewayapiconfig "sigs.k8s.io/gateway-api/conformance/utils/config"
+	gatewayk8sutils "sigs.k8s.io/gateway-api/conformance/utils/kubernetes"
 )
 
 // checkCondition is a helper function similar to findConditionInList or CheckCondition
@@ -152,3 +156,103 @@ func InferencePoolMustHaveCondition(t *testing.T, c client.Client, poolNN types.
 	}
 	t.Log(logMsg)
 }
+
+// InferencePoolMustHaveNoParents waits for the specified InferencePool resource
+// to exist and report that it has no parent references in its status.
+// This typically indicates it is no longer referenced by any Gateway API resources.
+func InferencePoolMustHaveNoParents(t *testing.T, c client.Client, poolNN types.NamespacedName) {
+	t.Helper()
+
+	var lastObservedPool *inferenceapi.InferencePool
+	var lastError error
+	var timeoutConfig config.InferenceExtensionTimeoutConfig = config.DefaultInferenceExtensionTimeoutConfig()
+
+	ctx := context.Background()
+	waitErr := wait.PollUntilContextTimeout(
+		ctx,
+
+		timeoutConfig.InferencePoolMustHaveConditionInterval,
+		timeoutConfig.InferencePoolMustHaveConditionTimeout,
+		true,
+		func(pollCtx context.Context) (bool, error) {
+			pool := &inferenceapi.InferencePool{}
+			err := c.Get(pollCtx, poolNN, pool)
+			if err != nil {
+				if apierrors.IsNotFound(err) {
+					t.Logf("InferencePool %s not found. Considering this as having no parents.", poolNN.String())
+					lastError = nil
+					return true, nil
+				}
+				t.Logf("Error fetching InferencePool %s: %v. Retrying.", poolNN.String(), err)
+				lastError = err
+				return false, nil
+			}
+			lastObservedPool = pool
+			lastError = nil
+
+			if len(pool.Status.Parents) == 0 {
+				t.Logf("InferencePool %s successfully has no parent statuses.", poolNN.String())
+				return true, nil
+			}
+			t.Logf("InferencePool %s still has %d parent statuses. Waiting...", poolNN.String(), len(pool.Status.Parents))
+			return false, nil
+		})
+
+	if waitErr != nil {
+		debugMsg := fmt.Sprintf("Timed out waiting for InferencePool %s to have no parent statuses.", poolNN.String())
+		if lastError != nil {
+			debugMsg += fmt.Sprintf(" Last error during fetching: %v.", lastError)
+		}
+		if lastObservedPool != nil && len(lastObservedPool.Status.Parents) > 0 {
+			debugMsg += fmt.Sprintf(" Last observed InferencePool still had %d parent(s):", len(lastObservedPool.Status.Parents))
+		} else if lastError == nil && (lastObservedPool == nil || len(lastObservedPool.Status.Parents) == 0) {
+			debugMsg += " Polling completed without timeout, but an unexpected waitErr occurred."
+		}
+		require.FailNow(t, debugMsg, waitErr)
+	}
+	t.Logf("Successfully verified that InferencePool %s has no parent statuses.", poolNN.String())
+}
+
+// HTTPRouteMustBeAcceptedAndResolved waits for the specified HTTPRoute
+// to be Accepted and have its references resolved by the specified Gateway.
+// It uses the upstream Gateway API's HTTPRouteMustHaveCondition helper.
+func HTTPRouteMustBeAcceptedAndResolved(t *testing.T, c client.Client, timeoutConfig gatewayapiconfig.TimeoutConfig, routeNN, gatewayNN types.NamespacedName) {
+	t.Helper()
+
+	acceptedCondition := metav1.Condition{
+		Type:   string(gatewayv1.RouteConditionAccepted),
+		Status: metav1.ConditionTrue,
+		Reason: string(gatewayv1.RouteReasonAccepted),
+	}
+
+	resolvedRefsCondition := metav1.Condition{
+		Type:   string(gatewayv1.RouteConditionResolvedRefs),
+		Status: metav1.ConditionTrue,
+		Reason: string(gatewayv1.RouteReasonResolvedRefs),
+	}
+
+	t.Logf("Waiting for HTTPRoute %s to be Accepted by Gateway %s", routeNN.String(), gatewayNN.String())
+	gatewayk8sutils.HTTPRouteMustHaveCondition(t, c, timeoutConfig, routeNN, gatewayNN, acceptedCondition)
+
+	t.Logf("Waiting for HTTPRoute %s to have ResolvedRefs by Gateway %s", routeNN.String(), gatewayNN.String())
+	gatewayk8sutils.HTTPRouteMustHaveCondition(t, c, timeoutConfig, routeNN, gatewayNN, resolvedRefsCondition)
+
+	t.Logf("HTTPRoute %s is now Accepted and has ResolvedRefs by Gateway %s", routeNN.String(), gatewayNN.String())
+}
+
+// InferencePoolMustBeAcceptedByParent waits for the specified InferencePool
+// to report an Accepted condition with status True and reason "Accepted"
+// from at least one of its parent Gateways.
+func InferencePoolMustBeAcceptedByParent(t *testing.T, c client.Client, poolNN types.NamespacedName) {
+	t.Helper()
+
+	acceptedByParentCondition := metav1.Condition{
+		Type:   string(gatewayv1.GatewayConditionAccepted),
+		Status: metav1.ConditionTrue,
+		Reason: string(gatewayv1.GatewayReasonAccepted), // Expecting the standard "Accepted" reason
+	}
+
+	t.Logf("Waiting for InferencePool %s to be Accepted by a parent Gateway (Reason: %s)", poolNN.String(), gatewayv1.GatewayReasonAccepted)
+	InferencePoolMustHaveCondition(t, c, poolNN, acceptedByParentCondition)
+	t.Logf("InferencePool %s is Accepted by a parent Gateway (Reason: %s)", poolNN.String(), gatewayv1.GatewayReasonAccepted)
+}



Test patch:
diff --git a/conformance/tests/basic/inferencepool_accepted.go b/conformance/tests/basic/inferencepool_accepted.go
index 442dd22..d74f738 100644
--- a/conformance/tests/basic/inferencepool_accepted.go
+++ b/conformance/tests/basic/inferencepool_accepted.go
@@ -41,7 +41,10 @@ var InferencePoolAccepted = suite.ConformanceTest{
 	ShortName:   "InferencePoolAccepted",
 	Description: "A minimal InferencePool resource should be accepted by the controller and report an Accepted condition",
 	Manifests:   []string{"tests/basic/inferencepool_accepted.yaml"},
-	Features:    []features.FeatureName{},
+	Features: []features.FeatureName{
+		features.FeatureName("SupportInferencePool"),
+		features.SupportGateway,
+	},
 	Test: func(t *testing.T, s *suite.ConformanceTestSuite) {
 		// created by the associated manifest file.
 		poolNN := types.NamespacedName{Name: "inferencepool-basic-accepted", Namespace: "gateway-conformance-app-backend"}
diff --git a/conformance/tests/basic/inferencepool_resolvedrefs_condition.go b/conformance/tests/basic/inferencepool_resolvedrefs_condition.go
new file mode 100644
index 0000000..86ca542
--- /dev/null
+++ b/conformance/tests/basic/inferencepool_resolvedrefs_condition.go
@@ -0,0 +1,104 @@
+/*
+Copyright 2025 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package basic
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
+	"sigs.k8s.io/gateway-api/conformance/utils/suite"
+	"sigs.k8s.io/gateway-api/pkg/features"
+
+	"sigs.k8s.io/gateway-api-inference-extension/conformance/tests"
+	k8sutils "sigs.k8s.io/gateway-api-inference-extension/conformance/utils/kubernetes"
+)
+
+func init() {
+	tests.ConformanceTests = append(tests.ConformanceTests, InferencePoolParentStatus)
+}
+
+var InferencePoolParentStatus = suite.ConformanceTest{
+	ShortName:   "InferencePoolResolvedRefsCondition",
+	Description: "Verify that an InferencePool correctly updates its parent-specific status (e.g., Accepted condition) when referenced by HTTPRoutes attached to shared Gateways, and clears parent statuses when no longer referenced.",
+	Manifests:   []string{"tests/basic/inferencepool_resolvedrefs_condition.yaml"},
+	Features: []features.FeatureName{
+		features.FeatureName("SupportInferencePool"),
+		features.SupportGateway,
+	},
+	Test: func(t *testing.T, s *suite.ConformanceTestSuite) {
+		const (
+			appBackendNamespace = "gateway-conformance-app-backend"
+			infraNamespace      = "gateway-conformance-infra"
+			poolName            = "multi-gateway-pool"
+			sharedGateway1Name  = "conformance-gateway"
+			sharedGateway2Name  = "conformance-secondary-gateway"
+			httpRoute1Name      = "httproute-for-gw1"
+			httpRoute2Name      = "httproute-for-gw2"
+		)
+
+		poolNN := types.NamespacedName{Name: poolName, Namespace: appBackendNamespace}
+		httpRoute1NN := types.NamespacedName{Name: httpRoute1Name, Namespace: appBackendNamespace}
+		httpRoute2NN := types.NamespacedName{Name: httpRoute2Name, Namespace: appBackendNamespace}
+		gateway1NN := types.NamespacedName{Name: sharedGateway1Name, Namespace: infraNamespace}
+		gateway2NN := types.NamespacedName{Name: sharedGateway2Name, Namespace: infraNamespace}
+
+		k8sutils.HTTPRouteMustBeAcceptedAndResolved(t, s.Client, s.TimeoutConfig, httpRoute1NN, gateway1NN)
+		k8sutils.HTTPRouteMustBeAcceptedAndResolved(t, s.Client, s.TimeoutConfig, httpRoute2NN, gateway2NN)
+
+		t.Run("InferencePool should show Accepted:True by parents when referenced by multiple HTTPRoutes", func(t *testing.T) {
+			k8sutils.InferencePoolMustBeAcceptedByParent(t, s.Client, poolNN)
+			// TODO(#865) ensure requests are correctly routed to this InferencePool.
+			t.Logf("InferencePool %s has parent status Accepted:True as expected with two references.", poolNN.String())
+		})
+
+		t.Run("Delete httproute-for-gw1", func(t *testing.T) {
+			httproute1 := &gatewayv1.HTTPRoute{
+				ObjectMeta: metav1.ObjectMeta{Name: httpRoute1NN.Name, Namespace: httpRoute1NN.Namespace},
+			}
+			t.Logf("Deleting HTTPRoute %s", httpRoute1NN.String())
+			require.NoError(t, s.Client.Delete(context.TODO(), httproute1), "failed to delete httproute-for-gw1")
+			time.Sleep(s.TimeoutConfig.GatewayMustHaveCondition)
+		})
+
+		t.Run("InferencePool should still show Accepted:True by parent after one HTTPRoute is deleted", func(t *testing.T) {
+			k8sutils.InferencePoolMustBeAcceptedByParent(t, s.Client, poolNN)
+			// TODO(#865) ensure requests are correctly routed to this InferencePool.
+			t.Logf("InferencePool %s still has parent status Accepted:True as expected with one reference remaining.", poolNN.String())
+		})
+
+		t.Run("Delete httproute-for-gw2", func(t *testing.T) {
+			httproute2 := &gatewayv1.HTTPRoute{
+				ObjectMeta: metav1.ObjectMeta{Name: httpRoute2NN.Name, Namespace: httpRoute2NN.Namespace},
+			}
+			t.Logf("Deleting HTTPRoute %s", httpRoute2NN.String())
+			require.NoError(t, s.Client.Delete(context.TODO(), httproute2), "failed to delete httproute-for-gw2")
+		})
+
+		t.Run("InferencePool should have no parent statuses after all HTTPRoutes are deleted", func(t *testing.T) {
+			t.Logf("Waiting for InferencePool %s to have no parent statuses.", poolNN.String())
+			k8sutils.InferencePoolMustHaveNoParents(t, s.Client, poolNN)
+			t.Logf("InferencePool %s correctly shows no parent statuses, indicating it's no longer referenced.", poolNN.String())
+		})
+
+		t.Logf("InferencePoolResolvedRefsCondition completed.")
+	},
+}
diff --git a/conformance/tests/basic/inferencepool_resolvedrefs_condition.yaml b/conformance/tests/basic/inferencepool_resolvedrefs_condition.yaml
new file mode 100644
index 0000000..0088931
--- /dev/null
+++ b/conformance/tests/basic/inferencepool_resolvedrefs_condition.yaml
@@ -0,0 +1,131 @@
+# conformance/tests/basic/inferencepool_resolvedrefs_condition.yaml
+
+# This manifest defines the initial resources for the
+# inferencepool_resolvedrefs_condition.go conformance test.
+
+# --- Backend Deployment (using agnhost echo server) ---
+# This Deployment provides Pods for the InferencePool to select.
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: infra-backend-deployment
+  namespace: gateway-conformance-app-backend
+  labels:
+    app: infra-backend
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: infra-backend
+  template:
+    metadata:
+      labels:
+        app: infra-backend
+    spec:
+      containers:
+      - name: agnhost-echo
+        image: k8s.gcr.io/e2e-test-images/agnhost:2.39
+        args:
+        - serve-hostname
+        - --port=8080
+        ports:
+        - name: http
+          containerPort: 8080
+        readinessProbe:
+          httpGet:
+            path: /
+            port: 8080
+          initialDelaySeconds: 3
+          periodSeconds: 5
+          failureThreshold: 2
+
+---
+# --- Backend Service ---
+# Service for the infra-backend-deployment.
+apiVersion: v1
+kind: Service
+metadata:
+  name: infra-backend-svc
+  namespace: gateway-conformance-app-backend
+spec:
+  selector:
+    app: infra-backend
+  ports:
+  - name: http
+    protocol: TCP
+    port: 8080
+    targetPort: 8080
+  - name: epp
+    port: 9002
+    targetPort: 9002
+
+---
+# --- InferencePool Definition ---
+apiVersion: inference.networking.x-k8s.io/v1alpha2
+kind: InferencePool
+metadata:
+  name: multi-gateway-pool # Name used in the Go test
+  namespace: gateway-conformance-app-backend # Defined in base manifests.yaml
+spec:
+  # --- Selector (Required) ---
+  # Selects the Pods belonging to this pool.
+  selector:
+    app: "infra-backend"
+  # --- Target Port (Required) ---
+  targetPortNumber: 8080
+  extensionRef:
+    name: infra-backend-svc
+
+---
+# --- HTTPRoute for Gateway 1 (conformance-gateway) ---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: httproute-for-gw1
+  namespace: gateway-conformance-app-backend
+spec:
+  parentRefs:
+  - group: gateway.networking.k8s.io
+    kind: Gateway
+    name: conformance-gateway
+    namespace: gateway-conformance-infra
+    sectionName: http
+  hostnames:
+  - "gw1.example.com"
+  rules:
+  - backendRefs:
+    - group: inference.networking.x-k8s.io
+      kind: InferencePool
+      name: multi-gateway-pool
+      port: 8080
+    matches:
+    - path:
+        type: PathPrefix
+        value: /conformance-gateway-test
+
+---
+# --- HTTPRoute for Gateway 2 (conformance-secondary-gateway) ---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: httproute-for-gw2
+  namespace: gateway-conformance-app-backend
+spec:
+  parentRefs:
+  - group: gateway.networking.k8s.io
+    kind: Gateway
+    name: conformance-secondary-gateway
+    namespace: gateway-conformance-infra
+    sectionName: http
+  hostnames:
+  - "secondary.example.com"
+  rules:
+  - backendRefs:
+    - group: inference.networking.x-k8s.io
+      kind: InferencePool
+      name: multi-gateway-pool
+      port: 8080
+    matches:
+    - path:
+        type: PathPrefix
+        value: /gateway-2-test