Skip to content
32 changes: 30 additions & 2 deletions test/e2e/clients/python/framework/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from test.e2e.clients.python.framework.predicates import (
deployment_ready,
warmpool_ready,
gateway_address_ready,
)
import subprocess
from urllib3.exceptions import ReadTimeoutError
Expand Down Expand Up @@ -179,7 +180,7 @@ def wait_for_warmpool_ready(
namespace: Optional[str] = None,
timeout=DEFAULT_TIMEOUT_SECONDS,
):
"""Waits for a SandboxWarmPool to have at least min_ready ready sandboxes"""
"""Waits for a SandboxWarmPool to have all the required number ready sandboxes"""
if namespace is None:
namespace = self.namespace
if not namespace:
Expand All @@ -200,6 +201,33 @@ def wait_for_warmpool_ready(
timeout,
)

def wait_for_gateway_address(
Comment thread
janetkuo marked this conversation as resolved.
self,
name: str,
namespace: Optional[str] = None,
timeout=DEFAULT_TIMEOUT_SECONDS,
):
"""Waits for a Gateway to have an address in its status"""
if namespace is None:
namespace = self.namespace
if not namespace:
raise ValueError("Namespace must be provided.")

custom_objects_api = self.get_custom_objects_api()

return self.wait_for_object(
functools.partial(
custom_objects_api.list_namespaced_custom_object,
group="gateway.networking.k8s.io",
version="v1",
plural="gateways",
),
name,
namespace,
gateway_address_ready(),
timeout,
)


if __name__ == "__main__":
# Example Usage
Expand All @@ -214,4 +242,4 @@ def wait_for_warmpool_ready(
finally:
if tc and tc.namespace:
print(f"Cleaning up namespace: {tc.namespace}")
# tc.delete_namespace()
# tc.delete_namespace()
16 changes: 15 additions & 1 deletion test/e2e/clients/python/framework/predicates.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def check(obj: kubernetes.client.V1Pod) -> bool:

def warmpool_ready():
"""
Predicate to check if a SandboxWarmPool (CR) has at least min_ready ready sandboxes.
Predicate to check if a SandboxWarmPool (CR) has all the required number of ready sandboxes.
"""

def check(obj: Dict[str, Any]) -> bool:
Expand All @@ -59,3 +59,17 @@ def check(obj: Dict[str, Any]) -> bool:
return ready_replicas == replicas

return check


def gateway_address_ready():
"""Predicate to check if a Gateway has an address."""

def check(obj: Dict[str, Any]) -> bool:
if not isinstance(obj, dict):
return False

status = obj.get("status") or {}
addresses = status.get("addresses") or []
return len(addresses) > 0

return check
60 changes: 60 additions & 0 deletions test/e2e/clients/python/test_e2e_python_sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from test.e2e.clients.python.framework.context import TestContext

import pytest
import yaml
from agentic_sandbox import SandboxClient

TEST_MANIFESTS_DIR = "test/e2e/clients/python/test_manifests"
Expand All @@ -25,6 +26,10 @@
ROUTER_YAML_PATH = (
"clients/python/agentic-sandbox-client/sandbox-router/sandbox_router.yaml"
)
GATEWAY_YAML_PATH = (
"clients/python/agentic-sandbox-client/gateway-kind/gateway-kind.yaml"
)
GATEWAY_NAME = "kind-gateway"


@pytest.fixture(scope="module")
Expand Down Expand Up @@ -70,6 +75,18 @@ def deploy_router(tc, temp_namespace):
tc.wait_for_deployment_ready("sandbox-router-deployment", namespace=temp_namespace)


@pytest.fixture(scope="function")
def deploy_gateway(tc, temp_namespace):
"""Deploys the sandbox gateway into the test namespace"""
with open(GATEWAY_YAML_PATH, "r") as f:
manifest = f.read()

print(f"Applying gateway manifest to namespace: {temp_namespace}")
tc.apply_manifest_text(manifest, namespace=temp_namespace)
print("Waiting for gateway to get an address...")
tc.wait_for_gateway_address(GATEWAY_NAME, namespace=temp_namespace)


@pytest.fixture(scope="function")
def sandbox_template(tc, temp_namespace):
"""Deploys the sandbox template into the test namespace"""
Expand Down Expand Up @@ -144,3 +161,46 @@ def test_python_sdk_router_mode_warmpool(

except Exception as e:
pytest.fail(f"SDK test with warmpool failed: {e}")


def test_python_sdk_gateway_mode(
tc, temp_namespace, sandbox_template, deploy_router, deploy_gateway
):
"""Tests the Python SDK in Production mode (with Gateway and Router) without warmpool."""
try:
with SandboxClient(
template_name=sandbox_template,
namespace=temp_namespace,
gateway_name=GATEWAY_NAME,
gateway_namespace=temp_namespace,
) as sandbox:
print("\n--- Running SDK tests without warmpool ---")
run_sdk_tests(sandbox)
print("SDK test without warmpool passed!")

except Exception as e:
pytest.fail(f"SDK test without warmpool failed: {e}")


def test_python_sdk_gateway_mode_warmpool(
tc,
temp_namespace,
sandbox_template,
deploy_router,
sandbox_warmpool,
deploy_gateway,
):
"""Tests the Python SDK in Production mode (with gateway and router) with warmpool."""
try:
with SandboxClient(
template_name=sandbox_template,
namespace=temp_namespace,
gateway_name=GATEWAY_NAME,
gateway_namespace=temp_namespace,
) as sandbox:
print("\n--- Running SDK tests with warmpool ---")
run_sdk_tests(sandbox)
print("SDK test with warmpool passed!")

except Exception as e:
pytest.fail(f"SDK test with warmpool failed: {e}")
7 changes: 4 additions & 3 deletions test/e2e/framework/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import (
)

const (
// DefaultTimeout is the default timeout for WaitForObject.
// DefaultTimeout is the default timeout for WaitForObject and WaitForObjectNotFound.
DefaultTimeout = 60 * time.Second
)

Expand Down Expand Up @@ -178,8 +178,9 @@ func (cl *ClusterClient) WaitForObject(ctx context.Context, obj client.Object, p
// WaitForObjectNotFound waits for the specified object to not exist.
func (cl *ClusterClient) WaitForObjectNotFound(ctx context.Context, obj client.Object) error {
cl.Helper()
// Static 30 second timeout, this can be adjusted if needed
timeoutCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
// Static 1 minute timeout, this can be adjusted if needed
timeoutCtx, cancel := context.WithTimeout(ctx, DefaultTimeout)
Comment thread
shrutiyam-glitch marked this conversation as resolved.

defer cancel()
start := time.Now()
nn := types.NamespacedName{Name: obj.GetName(), Namespace: obj.GetNamespace()}
Expand Down