4b6817381b
Benchmark image — build + push to ECR (any adapter) / build + push (push) Waiting to run
CI / quality (ubuntu-latest) (push) Waiting to run
CI / test (tools-runtime) (push) Waiting to run
CI / test (e2e-general) (push) Waiting to run
CI / test (cli-runtime) (push) Waiting to run
CI / test (e2e-provider-and-openclaw) (push) Waiting to run
CI / test (integrations-and-misc) (push) Waiting to run
CI / coverage-report (push) Blocked by required conditions
CI / test-kubernetes (push) Waiting to run
CI / should-run-thorough (push) Waiting to run
CI / test-thorough (cloudwatch-demo) (push) Blocked by required conditions
CI / test-thorough (flink-ecs) (push) Blocked by required conditions
CI / test-thorough (upstream-lambda) (push) Blocked by required conditions
CI / test-thorough (prefect-ecs-fargate) (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Blocked by required conditions
Release / publish-release (push) Blocked by required conditions
Release / publish-main-release (push) Blocked by required conditions
Release / prepare (push) Waiting to run
Release / verify (push) Blocked by required conditions
Release / build-python-dist (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Blocked by required conditions
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Waiting to run
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
214 lines
6.9 KiB
Python
214 lines
6.9 KiB
Python
#!/usr/bin/env python3
|
|
"""End-to-end agent investigation test for upstream/downstream pipeline.
|
|
|
|
Triggers a failure in the pipeline and tests if the agent can correctly investigate and diagnose it.
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
import time
|
|
from datetime import UTC, datetime
|
|
|
|
import boto3
|
|
import requests
|
|
|
|
from platform.observability.trace.hook import traceable
|
|
from surfaces.cli.investigation import run_investigation_cli
|
|
from tests.utils.alert_factory import create_alert
|
|
from tests.utils.conftest import UPSTREAM_DOWNSTREAM_CONFIG
|
|
|
|
|
|
def _pick_field(payload: dict, keys: list[str]) -> str | None:
|
|
for key in keys:
|
|
value = payload.get(key)
|
|
if isinstance(value, str) and value:
|
|
return value
|
|
data = payload.get("data")
|
|
if isinstance(data, dict):
|
|
for key in keys:
|
|
value = data.get(key)
|
|
if isinstance(value, str) and value:
|
|
return value
|
|
return None
|
|
|
|
|
|
def _parse_ingester_payload(response: requests.Response) -> dict:
|
|
try:
|
|
result = response.json()
|
|
except ValueError as exc:
|
|
raise RuntimeError(f"Ingester API did not return JSON: {response.text}") from exc
|
|
|
|
payload: object = result
|
|
if isinstance(result, dict) and "statusCode" in result and "body" in result:
|
|
status_code = result.get("statusCode")
|
|
if status_code != 200:
|
|
raise RuntimeError(f"Pipeline trigger failed: {result}")
|
|
payload = result.get("body")
|
|
if isinstance(payload, str):
|
|
try:
|
|
payload = json.loads(payload)
|
|
except json.JSONDecodeError as exc:
|
|
raise RuntimeError(f"Ingester body was not valid JSON: {payload}") from exc
|
|
|
|
if not isinstance(payload, dict):
|
|
raise RuntimeError(f"Unexpected ingester payload: {payload}")
|
|
if payload.get("error"):
|
|
raise RuntimeError(f"Pipeline trigger failed: {payload}")
|
|
|
|
s3_key = _pick_field(payload, ["s3_key", "s3Key", "key"])
|
|
bucket = _pick_field(payload, ["s3_bucket", "s3Bucket", "bucket"])
|
|
if not s3_key or not bucket:
|
|
raise RuntimeError(f"Ingester response missing s3 fields: {payload}")
|
|
|
|
return {"s3_key": s3_key, "bucket": bucket, "payload": payload}
|
|
|
|
|
|
def trigger_pipeline_failure() -> dict:
|
|
"""Trigger a pipeline failure and return alert data."""
|
|
print("=" * 60)
|
|
print("Triggering Pipeline Failure")
|
|
print("=" * 60)
|
|
|
|
# Trigger failure via HTTP
|
|
correlation_id = f"alert-local-{datetime.now(UTC).strftime('%Y%m%d-%H%M%S')}"
|
|
|
|
print(f"\nTriggering pipeline with schema error (correlation_id={correlation_id})...")
|
|
response = requests.post(
|
|
UPSTREAM_DOWNSTREAM_CONFIG["ingester_api_url"],
|
|
json={"correlation_id": correlation_id, "inject_schema_change": True},
|
|
timeout=10,
|
|
)
|
|
|
|
parsed = _parse_ingester_payload(response)
|
|
s3_key = parsed["s3_key"]
|
|
bucket = parsed["bucket"]
|
|
|
|
print(f"✓ Bad data written to: s3://{bucket}/{s3_key}")
|
|
print("Waiting 10s for Mock DAG to process and fail...")
|
|
time.sleep(10)
|
|
|
|
# Get error from CloudWatch logs
|
|
logs_client = boto3.client("logs")
|
|
log_group = f"/aws/lambda/{UPSTREAM_DOWNSTREAM_CONFIG['mock_dag_function_name']}"
|
|
|
|
print(f"Checking logs in: {log_group}")
|
|
response = logs_client.filter_log_events(
|
|
logGroupName=log_group,
|
|
startTime=int((time.time() - 120) * 1000),
|
|
filterPattern=correlation_id,
|
|
)
|
|
|
|
error_message = "Schema validation failed"
|
|
for event in response["events"]:
|
|
if "PIPELINE FAILED" in event["message"]:
|
|
error_message = event["message"].split("Error: ")[-1].split("\n")[0]
|
|
break
|
|
|
|
print(f"✓ Error detected: {error_message}")
|
|
|
|
return {
|
|
"correlation_id": correlation_id,
|
|
"s3_key": s3_key,
|
|
"bucket": bucket,
|
|
"error_message": error_message,
|
|
"log_group": log_group,
|
|
}
|
|
|
|
|
|
def test_agent_investigation(failure_data: dict) -> bool:
|
|
"""Test agent can investigate the pipeline failure."""
|
|
print("\n" + "=" * 60)
|
|
print("Testing Agent Investigation")
|
|
print("=" * 60)
|
|
|
|
pipeline_name = "upstream_downstream_pipeline"
|
|
run_id = f"run_{datetime.now(UTC).strftime('%Y%m%d_%H%M%S')}"
|
|
|
|
# Create alert
|
|
raw_alert = create_alert(
|
|
pipeline_name=pipeline_name,
|
|
run_name=run_id,
|
|
status="failed",
|
|
timestamp=datetime.now(UTC).isoformat(),
|
|
annotations={
|
|
"s3_bucket": failure_data["bucket"],
|
|
"s3_key": failure_data["s3_key"],
|
|
"correlation_id": failure_data["correlation_id"],
|
|
"error": failure_data["error_message"],
|
|
"lambda_log_group": failure_data["log_group"],
|
|
"function_name": UPSTREAM_DOWNSTREAM_CONFIG["mock_dag_function_name"],
|
|
"landing_bucket": UPSTREAM_DOWNSTREAM_CONFIG["landing_bucket_name"],
|
|
"processed_bucket": UPSTREAM_DOWNSTREAM_CONFIG["processed_bucket_name"],
|
|
"mock_api_url": UPSTREAM_DOWNSTREAM_CONFIG["mock_api_url"],
|
|
"ingester_function": UPSTREAM_DOWNSTREAM_CONFIG["ingester_function_name"],
|
|
"mock_dag_function": UPSTREAM_DOWNSTREAM_CONFIG["mock_dag_function_name"],
|
|
"context_sources": "s3,lambda,cloudwatch",
|
|
},
|
|
)
|
|
|
|
print("\nAlert created:")
|
|
print(f" Alert ID: {raw_alert['alert_id']}")
|
|
print(f" Pipeline: {pipeline_name}")
|
|
print(f" S3 Key: {failure_data['s3_key']}")
|
|
print(f" Correlation ID: {failure_data['correlation_id']}")
|
|
|
|
print("\nRunning agent investigation...")
|
|
|
|
@traceable(
|
|
run_type="chain",
|
|
name=f"test_lambda_upstream - {raw_alert['alert_id'][:8]}",
|
|
metadata={
|
|
"alert_id": raw_alert["alert_id"],
|
|
"pipeline_name": pipeline_name,
|
|
"correlation_id": failure_data["correlation_id"],
|
|
"s3_key": failure_data["s3_key"],
|
|
"lambda_function": failure_data.get("mock_dag_function"),
|
|
},
|
|
)
|
|
def run_investigation():
|
|
return run_investigation_cli(raw_alert=raw_alert)
|
|
|
|
try:
|
|
result = run_investigation()
|
|
|
|
print("\n✓ Investigation complete")
|
|
print(f" Root cause: {result.get('root_cause', 'Unknown')}")
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
print(f"\n✗ Investigation failed: {e}")
|
|
import traceback
|
|
|
|
traceback.print_exc()
|
|
return False
|
|
|
|
|
|
def main():
|
|
"""Main test flow."""
|
|
print("=" * 60)
|
|
print("Upstream/Downstream Pipeline - Agent E2E Test")
|
|
print("=" * 60)
|
|
print()
|
|
|
|
# Step 1: Trigger failure
|
|
failure_data = trigger_pipeline_failure()
|
|
|
|
# Step 2: Test agent investigation
|
|
success = test_agent_investigation(failure_data)
|
|
|
|
if success:
|
|
print("\n" + "=" * 60)
|
|
print("✓ AGENT E2E TEST PASSED")
|
|
print("=" * 60)
|
|
return 0
|
|
else:
|
|
print("\n" + "=" * 60)
|
|
print("✗ AGENT E2E TEST FAILED")
|
|
print("=" * 60)
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|