Skip to content
14 changes: 7 additions & 7 deletions conformance/conformance.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import (
"io/fs"
"os"
"testing"
"time"

"github.com/stretchr/testify/require"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
Expand Down Expand Up @@ -64,6 +63,7 @@ import (

// Import the Inference Extension API types
inferencev1alpha2 "sigs.k8s.io/gateway-api-inference-extension/api/v1alpha2"
inferenceconfig "sigs.k8s.io/gateway-api-inference-extension/conformance/utils/config"
)

// Constants for the shared Gateway
Expand Down Expand Up @@ -245,16 +245,16 @@ func ensureGatewayAvailableAndReady(t *testing.T, k8sClient client.Client, opts
t.Logf("Attempting to fetch Gateway %s/%s.", gatewayNN.Namespace, gatewayNN.Name)
gw := &gatewayv1.Gateway{} // This gw instance will be populated by the poll function

// Define polling interval
// TODO: Make this configurable using a local TimeoutConfig (from ConformanceOptions perhaps)
pollingInterval := 5 * time.Second
// Use the GatewayMustHaveAddress timeout from the suite's TimeoutConfig for the Gateway object to appear
waitForGatewayCreationTimeout := opts.TimeoutConfig.GatewayMustHaveAddress
// Use extension-specific config for the polling interval defined in timeout.go.
extTimeoutConf := inferenceconfig.DefaultInferenceExtensionTimeoutConfig()

// Use the GatewayMustHaveAddress timeout from the suite's base TimeoutConfig for the Gateway object to appear.
waitForGatewayCreationTimeout := extTimeoutConf.TimeoutConfig.GatewayMustHaveAddress

logDebugf(t, opts.Debug, "Waiting up to %v for Gateway object %s/%s to appear after manifest application...", waitForGatewayCreationTimeout, gatewayNN.Namespace, gatewayNN.Name)

ctx := context.TODO()
pollErr := wait.PollUntilContextTimeout(ctx, pollingInterval, waitForGatewayCreationTimeout, true, func(pollCtx context.Context) (bool, error) {
pollErr := wait.PollUntilContextTimeout(ctx, extTimeoutConf.GatewayObjectPollInterval, waitForGatewayCreationTimeout, true, func(pollCtx context.Context) (bool, error) {
fetchErr := k8sClient.Get(pollCtx, gatewayNN, gw)
if fetchErr == nil {
t.Logf("Successfully fetched Gateway %s/%s. Spec.GatewayClassName: %s",
Expand Down
2 changes: 1 addition & 1 deletion conformance/tests/basic/inferencepool_accepted.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ var InferencePoolAccepted = suite.ConformanceTest{
Status: metav1.ConditionTrue,
Reason: "", // "" means we don't strictly check the Reason for this basic test.
}
infrakubernetes.InferencePoolMustHaveCondition(t, s.Client, s.TimeoutConfig, poolNN, acceptedCondition)
infrakubernetes.InferencePoolMustHaveCondition(t, s.Client, poolNN, acceptedCondition)
})
},
}
49 changes: 49 additions & 0 deletions conformance/utils/config/timing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
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 config

import (
"time"

// Import the upstream Gateway API timeout config
gatewayconfig "sigs.k8s.io/gateway-api/conformance/utils/config"
)

// InferenceExtensionTimeoutConfig embeds the upstream TimeoutConfig and adds
// extension-specific timeout values.
type InferenceExtensionTimeoutConfig struct {
// All fields from gatewayconfig.TimeoutConfig will be available directly.
gatewayconfig.TimeoutConfig

// InferencePoolMustHaveConditionTimeout represents the maximum time to wait for an InferencePool to have a specific condition.
InferencePoolMustHaveConditionTimeout time.Duration

// InferencePoolMustHaveConditionInterval represents the polling interval for checking an InferencePool's condition.
InferencePoolMustHaveConditionInterval time.Duration

// GatewayObjectPollInterval is the polling interval used when waiting for a Gateway object to appear.
GatewayObjectPollInterval time.Duration
}

func DefaultInferenceExtensionTimeoutConfig() InferenceExtensionTimeoutConfig {
return InferenceExtensionTimeoutConfig{
TimeoutConfig: gatewayconfig.DefaultTimeoutConfig(),
InferencePoolMustHaveConditionTimeout: 300 * time.Second,
InferencePoolMustHaveConditionInterval: 10 * time.Second,
GatewayObjectPollInterval: 5 * time.Second,
}
}
62 changes: 32 additions & 30 deletions conformance/utils/kubernetes/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"fmt"
"reflect"
"testing"
"time"

"github.com/stretchr/testify/require"
apierrors "k8s.io/apimachinery/pkg/api/errors"
Expand All @@ -36,7 +35,7 @@ import (
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
"sigs.k8s.io/gateway-api/conformance/utils/config"
"sigs.k8s.io/gateway-api-inference-extension/conformance/utils/config"
)

// checkCondition is a helper function similar to findConditionInList or CheckCondition
Expand Down Expand Up @@ -67,45 +66,48 @@ func checkCondition(t *testing.T, conditions []metav1.Condition, expectedConditi
// InferencePoolMustHaveCondition waits for the specified InferencePool resource
// to exist and report the expected status condition within one of its parent statuses.
// It polls the InferencePool's status until the condition is met or the timeout occurs.
func InferencePoolMustHaveCondition(t *testing.T, c client.Client, timeoutConfig config.TimeoutConfig, poolNN types.NamespacedName, expectedCondition metav1.Condition) {
func InferencePoolMustHaveCondition(t *testing.T, c client.Client, poolNN types.NamespacedName, expectedCondition metav1.Condition) {
t.Helper() // Marks this function as a test helper

var timeoutConfig config.InferenceExtensionTimeoutConfig = config.DefaultInferenceExtensionTimeoutConfig()
var lastObservedPool *inferenceapi.InferencePool
var lastError error
var conditionFound bool
var interval time.Duration = 5 * time.Second // pull interval for status checks.

// TODO: Make retry interval configurable.
waitErr := wait.PollUntilContextTimeout(context.Background(), interval, timeoutConfig.DefaultTestTimeout, true, func(ctx context.Context) (bool, error) {
pool := &inferenceapi.InferencePool{} // This is the type instance used for Get
err := c.Get(ctx, poolNN, pool)
if err != nil {
if apierrors.IsNotFound(err) {
t.Logf("InferencePool %s not found yet. Retrying.", poolNN.String())

waitErr := wait.PollUntilContextTimeout(
context.Background(),
timeoutConfig.InferencePoolMustHaveConditionInterval,
timeoutConfig.InferencePoolMustHaveConditionTimeout,
true, func(ctx context.Context) (bool, error) {
pool := &inferenceapi.InferencePool{} // This is the type instance used for Get
err := c.Get(ctx, poolNN, pool)
if err != nil {
if apierrors.IsNotFound(err) {
t.Logf("InferencePool %s not found yet. Retrying.", poolNN.String())
lastError = err
return false, nil
}
t.Logf("Error fetching InferencePool %s (type: %s): %v. Retrying.", poolNN.String(), reflect.TypeOf(pool).String(), err)
lastError = err
return false, nil
}
t.Logf("Error fetching InferencePool %s (type: %s): %v. Retrying.", poolNN.String(), reflect.TypeOf(pool).String(), err)
lastError = err
return false, nil
}
lastObservedPool = pool
lastError = nil
conditionFound = false
lastObservedPool = pool
lastError = nil
conditionFound = false

if len(pool.Status.Parents) == 0 {
t.Logf("InferencePool %s has no parent statuses reported yet.", poolNN.String())
return false, nil
}
if len(pool.Status.Parents) == 0 {
t.Logf("InferencePool %s has no parent statuses reported yet.", poolNN.String())
return false, nil
}

for _, parentStatus := range pool.Status.Parents {
if checkCondition(t, parentStatus.Conditions, expectedCondition) {
conditionFound = true
return true, nil
for _, parentStatus := range pool.Status.Parents {
if checkCondition(t, parentStatus.Conditions, expectedCondition) {
conditionFound = true
return true, nil
}
}
}
return false, nil
})
return false, nil
})

if waitErr != nil || !conditionFound {
debugMsg := ""
Expand Down