Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions conformance/tests/basic/epp_unavailable_fail_open.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,14 @@ var EppUnAvailableFailOpen = suite.ConformanceTest{
k8sutils.InferencePoolMustBeAcceptedByParent(t, s.Client, poolNN)
gwAddr := k8sutils.GetGatewayEndpoint(t, s.Client, s.TimeoutConfig, gatewayNN)

pods, err := k8sutils.GetPodsWithLabel(t, s.Client, appBackendNamespace, backendPodLabels)
pods, err := k8sutils.GetPodsWithLabel(t, s.Client, appBackendNamespace, backendPodLabels, s.TimeoutConfig)
require.NoError(t, err, "Failed to get backend pods")
require.Len(t, pods, expectedPodReplicas, "Expected to find %d backend pod, but found %d.", expectedPodReplicas, len(pods))

targetPodIP := pods[0].Status.PodIP
t.Run("Phase 1: Verify baseline connectivity with EPP available", func(t *testing.T) {
t.Log("Sending request to ensure the Gateway and EPP are working correctly...")
trafficutils.MakeRequestWithRequestParamAndExpectSuccess(
trafficutils.MakeRequestAndExpectSuccess(
t,
s.RoundTripper,
s.TimeoutConfig,
Expand All @@ -97,7 +97,7 @@ var EppUnAvailableFailOpen = suite.ConformanceTest{
require.NoError(t, deleteErr, "Failed to delete the EPP deployment")

t.Log("Sending request again, expecting success to verify fail-open...")
trafficutils.MakeRequestWithRequestParamAndExpectSuccess(
trafficutils.MakeRequestAndExpectSuccess(
t,
s.RoundTripper,
s.TimeoutConfig,
Expand Down
4 changes: 2 additions & 2 deletions conformance/tests/basic/gateway_following_epp_routing.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ var GatewayFollowingEPPRouting = suite.ConformanceTest{
gwAddr := k8sutils.GetGatewayEndpoint(t, s.Client, s.TimeoutConfig, gatewayNN)

t.Logf("Fetching backend pods with labels: %v", backendPodLabels)
pods, err := k8sutils.GetPodsWithLabel(t, s.Client, appBackendNamespace, backendPodLabels)
pods, err := k8sutils.GetPodsWithLabel(t, s.Client, appBackendNamespace, backendPodLabels, s.TimeoutConfig)
require.NoError(t, err, "Failed to get backend pods")
require.Len(t, pods, expectedPodReplicas, "Expected to find %d backend pods, but found %d.", expectedPodReplicas, len(pods))

Expand All @@ -94,7 +94,7 @@ var GatewayFollowingEPPRouting = suite.ConformanceTest{
for i := 0; i < len(pods); i++ {
// Send an initial request targeting a single pod and wait for it to be successful to ensure the Gateway and EPP
// are functioning correctly before running the main test cases.
trafficutils.MakeRequestWithRequestParamAndExpectSuccess(
trafficutils.MakeRequestAndExpectSuccess(
t,
s.RoundTripper,
s.TimeoutConfig,
Expand Down
30 changes: 18 additions & 12 deletions conformance/tests/basic/inferencepool_httproute_port_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,12 @@ var InferencePoolHTTPRoutePortValidation = suite.ConformanceTest{
s.RoundTripper,
s.TimeoutConfig,
gatewayAddr,
hostname,
path,
backendDeploymentName,
appBackendNamespace,
trafficutils.Request{
Host: hostname,
Path: path,
Backend: backendDeploymentName,
Namespace: appBackendNamespace,
},
)
})

Expand All @@ -88,10 +90,12 @@ var InferencePoolHTTPRoutePortValidation = suite.ConformanceTest{
s.RoundTripper,
s.TimeoutConfig,
gatewayAddr,
hostname,
path,
backendDeploymentName,
appBackendNamespace,
trafficutils.Request{
Host: hostname,
Path: path,
Backend: backendDeploymentName,
Namespace: appBackendNamespace,
},
)
})

Expand All @@ -109,10 +113,12 @@ var InferencePoolHTTPRoutePortValidation = suite.ConformanceTest{
s.RoundTripper,
s.TimeoutConfig,
gatewayAddr,
hostname,
path,
backendDeploymentName,
appBackendNamespace,
trafficutils.Request{
Host: hostname,
Path: path,
Backend: backendDeploymentName,
Namespace: appBackendNamespace,
},
)
})
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,13 @@ package basic
import (
"testing"

"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
gwhttp "sigs.k8s.io/gateway-api/conformance/utils/http"
"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"
trafficutils "sigs.k8s.io/gateway-api-inference-extension/conformance/utils/traffic"
)

func init() {
Expand All @@ -52,12 +51,11 @@ var HTTPRouteMultipleRulesDifferentPools = suite.ConformanceTest{
routeName = "httproute-multiple-rules-different-pools"
gatewayName = "conformance-primary-gateway"

backendPrimaryLabelValue = "primary-inference-model-server"
backendSecondaryLabelValue = "secondary-inference-model-server"
backendAppLabelKey = "app"

primaryPath = "/primary"
secondaryPath = "/secondary"

primaryPodBackendPrefix = "primary-inference-model-server"
secondaryPodBackendPrefix = "secondary-inference-model-server"
)

primaryPoolNN := types.NamespacedName{Name: poolPrimaryName, Namespace: appBackendNamespace}
Expand All @@ -71,20 +69,28 @@ var HTTPRouteMultipleRulesDifferentPools = suite.ConformanceTest{
})

t.Run("Traffic should be routed to the correct pool based on path", func(t *testing.T) {
primarySelector := labels.SelectorFromSet(labels.Set{backendAppLabelKey: backendPrimaryLabelValue})
secondarySelector := labels.SelectorFromSet(labels.Set{backendAppLabelKey: backendSecondaryLabelValue})

primaryPod := k8sutils.GetPod(t, s.Client, appBackendNamespace, primarySelector, s.TimeoutConfig.RequestTimeout)
secondaryPod := k8sutils.GetPod(t, s.Client, appBackendNamespace, secondarySelector, s.TimeoutConfig.RequestTimeout)

gwAddr := k8sutils.GetGatewayEndpoint(t, s.Client, s.TimeoutConfig, gatewayNN)

t.Run("request to primary pool", func(t *testing.T) {
trafficutils.MakeRequestAndExpectResponseFromPod(t, s.RoundTripper, s.TimeoutConfig, gwAddr, primaryPath, primaryPod)
gwhttp.MakeRequestAndExpectEventuallyConsistentResponse(t, s.RoundTripper,
s.TimeoutConfig, gwAddr, gwhttp.ExpectedResponse{
Request: gwhttp.Request{
Path: primaryPath,
},
Backend: primaryPodBackendPrefix, // Make sure the request is reaching the primary backend.
Namespace: appBackendNamespace,
})
})

t.Run("request to secondary pool", func(t *testing.T) {
trafficutils.MakeRequestAndExpectResponseFromPod(t, s.RoundTripper, s.TimeoutConfig, gwAddr, secondaryPath, secondaryPod)
gwhttp.MakeRequestAndExpectEventuallyConsistentResponse(t, s.RoundTripper,
s.TimeoutConfig, gwAddr, gwhttp.ExpectedResponse{
Request: gwhttp.Request{
Path: secondaryPath,
},
Backend: secondaryPodBackendPrefix, // Make sure the request is reaching the secondary backend.
Namespace: appBackendNamespace,
})
})
})
},
Expand Down
49 changes: 31 additions & 18 deletions conformance/tests/basic/inferencepool_resolvedrefs_condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package basic

import (
"context"
"net/http"
"testing"
"time"

Expand Down Expand Up @@ -85,21 +86,25 @@ var InferencePoolParentStatus = suite.ConformanceTest{
s.RoundTripper,
s.TimeoutConfig,
gwPrimaryAddr,
hostnamePrimaryGw,
pathPrimaryGw,
backendServicePodName,
appBackendNamespace,
trafficutils.Request{
Host: hostnamePrimaryGw,
Path: pathPrimaryGw,
Backend: backendServicePodName,
Namespace: appBackendNamespace,
},
)

trafficutils.MakeRequestAndExpectSuccess(
t,
s.RoundTripper,
s.TimeoutConfig,
gwSecondaryAddr,
hostnameSecondaryGw,
pathSecondaryGw,
backendServicePodName,
appBackendNamespace,
trafficutils.Request{
Host: hostnameSecondaryGw,
Path: pathSecondaryGw,
Backend: backendServicePodName,
Namespace: appBackendNamespace,
},
)
})

Expand All @@ -121,19 +126,24 @@ var InferencePoolParentStatus = suite.ConformanceTest{
s.RoundTripper,
s.TimeoutConfig,
gwSecondaryAddr,
hostnameSecondaryGw,
pathSecondaryGw,
backendServicePodName,
appBackendNamespace,
trafficutils.Request{
Host: hostnameSecondaryGw,
Path: pathSecondaryGw,
Backend: backendServicePodName,
Namespace: appBackendNamespace,
},
)

trafficutils.MakeRequestAndExpectNotFound(
trafficutils.MakeRequestAndExpectEventuallyConsistentResponse(
t,
s.RoundTripper,
s.TimeoutConfig,
gwPrimaryAddr,
hostnamePrimaryGw,
pathPrimaryGw,
trafficutils.Request{
Host: hostnamePrimaryGw,
Path: pathPrimaryGw,
ExpectedStatusCode: http.StatusNotFound,
},
)
})

Expand All @@ -147,13 +157,16 @@ var InferencePoolParentStatus = suite.ConformanceTest{
k8sutils.InferencePoolMustHaveNoParents(t, s.Client, poolNN)
t.Logf("InferencePool %s correctly shows no parent statuses, indicating it's no longer referenced.", poolNN.String())

trafficutils.MakeRequestAndExpectNotFound(
trafficutils.MakeRequestAndExpectEventuallyConsistentResponse(
t,
s.RoundTripper,
s.TimeoutConfig,
gwSecondaryAddr,
hostnameSecondaryGw,
pathSecondaryGw,
trafficutils.Request{
Host: hostnameSecondaryGw,
Path: pathSecondaryGw,
ExpectedStatusCode: http.StatusNotFound,
},
)
})

Expand Down
59 changes: 16 additions & 43 deletions conformance/utils/kubernetes/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import (
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand Down Expand Up @@ -311,63 +310,37 @@ func GetGatewayEndpoint(t *testing.T, k8sClient client.Client, timeoutConfig gat

// GetPodsWithLabel retrieves a list of Pods.
// It finds pods matching the given labels in a specific namespace.
func GetPodsWithLabel(t *testing.T, c client.Client, namespace string, labels map[string]string) ([]corev1.Pod, error) {
func GetPodsWithLabel(t *testing.T, c client.Client, namespace string, labels map[string]string, timeConfig gatewayapiconfig.TimeoutConfig) ([]corev1.Pod, error) {
t.Helper()

podList := &corev1.PodList{}
pods := &corev1.PodList{}
timeout := timeConfig.RequestTimeout
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()

listOptions := []client.ListOption{
client.InNamespace(namespace),
client.MatchingLabels(labels),
}

t.Logf("Searching for Pods with labels %v in namespace %s", labels, namespace)
if err := c.List(context.Background(), podList, listOptions...); err != nil {
return nil, fmt.Errorf("failed to list pods with labels '%v' in namespace '%s': %w", labels, namespace, err)
}

if len(podList.Items) == 0 {
return nil, fmt.Errorf("no pods found with labels '%v' in namespace '%s'", labels, namespace)
}
return podList.Items, nil
}

// GetPod waits for a Pod matching the specified labels to exist in the given
// namespace and have an IP address assigned. This function returns the first
// matching Pod found if there are multiple matches. It fails the on timeout or error.
// TODO(#1003) combline with GetPodsWithLabel that is being introduced in PR #961
func GetPod(t *testing.T, c client.Client, namespace string, selector labels.Selector, timeout time.Duration) *corev1.Pod {
t.Helper()

var pods corev1.PodList
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()

waitErr := wait.PollUntilContextTimeout(ctx, 1*time.Second, timeout, true, func(ctx context.Context) (bool, error) {
if err := c.List(ctx, &pods, &client.ListOptions{
LabelSelector: selector,
Namespace: namespace,
}); err != nil {
t.Logf("Error listing pods with selector %s: %v. Retrying.", selector.String(), err)
return false, nil
if err := c.List(context.Background(), pods, listOptions...); err != nil {
return false, fmt.Errorf("failed to list pods with labels '%v' in namespace '%s': %w", labels, namespace, err)
}

if len(pods.Items) > 0 {
pod := pods.Items[0]
if pod.Status.PodIP != "" && pod.Status.Phase == corev1.PodRunning {
return true, nil
for _, pod := range pods.Items {
if pod.Status.PodIP == "" || pod.Status.Phase != corev1.PodRunning {
t.Logf("Pod %s found, but not yet running or has no IP. Current phase: %s, IP: '%s'. Retrying.", pod.Name, pod.Status.Phase, pod.Status.PodIP)
return false, nil
}
}
t.Logf("Pod %s found, but not yet running or has no IP. Current phase: %s, IP: '%s'. Retrying.", pod.Name, pod.Status.Phase, pod.Status.PodIP)
} else {
t.Logf("No pods found with selector %s yet. Retrying.", selector.String())
return true, nil
}
t.Logf("No pods found with selector %v yet. Retrying.", labels)
return false, nil
})
require.NoErrorf(t, waitErr, "timed out waiting for Pod with selector %s in namespace %s to be ready", selector.String(), namespace)
require.NotEmpty(t, pods.Items, "expected at least one pod for selector %s in namespace %s, but found none", selector.String(), namespace)

pod := &pods.Items[0]
t.Logf("Successfully found ready Pod %s with IP %s for selector %s", pod.Name, pod.Status.PodIP, selector.String())
return pod
return pods.Items, waitErr
}

// DeleteDeployment deletes the specified Deployment and waits until it is no longer
Expand Down
Loading