Files
tracer-cloud--opensre/tests/synthetic/mock_aws_backend/backend.py
T
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

355 lines
13 KiB
Python

"""AWSBackend Protocol and FixtureAWSBackend for synthetic EC2/ELB/RDS testing.
The Protocol defines the minimal surface the EC2/RDS investigation agent
uses to query AWS topology and instance state. FixtureAWSBackend satisfies
it by serving scenario fixture data in the exact shape the tools under
``tools/EC2InstancesByTagTool/``, ``tools/ELBTargetHealthTool/``,
``tools/RDSDescribeInstanceTool/``, and ``tools/RDSEventsTool/``
return — no boto3 calls, no AWS credentials required.
Usage
-----
resolved_integrations = {
"aws": {
"region": "us-east-1",
"ec2_backend": FixtureAWSBackend(fixture),
}
}
The synthetic source builder reads ``ec2_backend`` and exposes it on
``available_sources["ec2"]["_backend"]`` (and ``available_sources["rds"]``)
without colliding with the EKS slot ``available_sources["eks"]["_backend"]``.
RDS describe semantics
----------------------
``describe_db_instances`` is synthesized from ``scenario.yml`` metadata
(engine, version, instance class, region, db identifier). Every RDS scenario
declares these fields, so this method is always available — it is treated as
foundational metadata, not a separate evidence channel. ``describe_events``,
in contrast, is gated on ``aws_rds_events`` being declared in
``available_evidence``: it is a data feed, and calling it on a scenario that
didn't opt in is a synthetic-suite bug worth surfacing loudly.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
from integrations.aws.topology_helper import build_ec2_summary, build_elb_summary
if TYPE_CHECKING:
from tests.synthetic.rds_postgres.scenario_loader import ScenarioFixture
_DEFAULT_ENGINE_PORTS: dict[str, int] = {
"postgres": 5432,
"postgresql": 5432,
"aurora-postgresql": 5432,
"mysql": 3306,
"mariadb": 3306,
"aurora-mysql": 3306,
}
@runtime_checkable
class AWSBackend(Protocol):
"""Minimal AWS interface used by the EC2/RDS investigation agent."""
def describe_instances_by_tag(
self,
tier: str = "",
instance_ids: list[str] | None = None,
vpc_id: str = "",
**kwargs: Any,
) -> dict[str, Any]:
"""Return a response matching ``ec2_instances_by_tag``."""
def describe_target_health(
self,
target_group_arns: list[str] | None = None,
target_group_arn: str = "",
load_balancer_arn: str = "",
**kwargs: Any,
) -> dict[str, Any]:
"""Return a response matching ``get_elb_target_health``."""
def describe_db_instances(
self,
db_instance_identifier: str,
region: str = "",
**kwargs: Any,
) -> dict[str, Any]:
"""Return a response matching ``describe_rds_instance``."""
def describe_db_events(
self,
db_instance_identifier: str,
duration_minutes: int = 60,
region: str = "",
**kwargs: Any,
) -> dict[str, Any]:
"""Return a response matching ``describe_rds_events``."""
def lookup_events(
self,
lookup_attributes: list[dict[str, str]] | None = None,
duration_minutes: int = 60,
max_results: int = 50,
region: str = "",
next_token: str = "",
**kwargs: Any,
) -> dict[str, Any]:
"""Return a response matching ``lookup_cloudtrail_events``."""
class FixtureAWSBackend:
"""AWSBackend implementation backed by a ScenarioFixture.
Each method wraps the corresponding fixture file in the envelope that the
real tool function returns. Calling a method for an evidence source that
the scenario did not declare in ``available_evidence`` raises ValueError.
"""
def __init__(self, fixture: ScenarioFixture) -> None:
self._fixture = fixture
# ------------------------------------------------------------------ EC2
def describe_instances_by_tag(
self,
tier: str = "",
instance_ids: list[str] | None = None,
vpc_id: str = "",
**_: Any,
) -> dict[str, Any]:
ec2_fixture = self._fixture.evidence.ec2_instances_by_tag
if ec2_fixture is None:
raise ValueError(
f"{self._fixture.scenario_id}: describe_instances_by_tag called but "
"'ec2_instances_by_tag' is not declared in available_evidence"
)
all_instances = list(ec2_fixture.get("instances", []))
instances = all_instances
if instance_ids:
id_set = set(instance_ids)
instances = [i for i in all_instances if i.get("instance_id", "") in id_set]
elif tier:
instances = [i for i in all_instances if (i.get("tier", "") or "") == tier]
if vpc_id:
# Strict equality — real EC2 API never returns instances from other
# VPCs, and missing/empty vpc_id is a fixture error worth surfacing.
instances = [i for i in instances if i.get("vpc_id", "") == vpc_id]
by_tier: dict[str, list[str]] = {}
for inst in instances:
bucket = by_tier.setdefault(inst.get("tier") or "untagged", [])
iid = inst.get("instance_id") or ""
if iid:
bucket.append(iid)
return {
"source": "ec2",
"available": True,
"tier": tier,
"vpc_id": vpc_id,
"total_instances": len(instances),
"instances": instances,
"by_tier": by_tier,
"tiers_detected": sorted(by_tier.keys()),
"summary": build_ec2_summary(instances, by_tier),
"error": None,
}
# ------------------------------------------------------------------ ELB
def describe_target_health(
self,
target_group_arns: list[str] | None = None,
target_group_arn: str = "",
load_balancer_arn: str = "",
**_: Any,
) -> dict[str, Any]:
elb_fixture = self._fixture.evidence.elb_target_health
if elb_fixture is None:
raise ValueError(
f"{self._fixture.scenario_id}: describe_target_health called but "
"'elb_target_health' is not declared in available_evidence"
)
target_groups = list(elb_fixture.get("target_groups", []))
targets = list(elb_fixture.get("targets", []))
# Accept the canonical plural list or the singular convenience alias.
arns = set(target_group_arns or [])
if target_group_arn:
arns.add(target_group_arn)
if arns:
target_groups = [tg for tg in target_groups if tg.get("TargetGroupArn") in arns]
targets = [t for t in targets if t.get("target_group_arn") in arns]
elif load_balancer_arn:
target_groups = [
tg
for tg in target_groups
if load_balancer_arn in (tg.get("LoadBalancerArns") or [])
]
allowed_arns = {tg.get("TargetGroupArn", "") for tg in target_groups}
targets = [t for t in targets if t.get("target_group_arn", "") in allowed_arns]
healthy = [t for t in targets if t.get("state") == "healthy"]
unhealthy = [t for t in targets if t.get("state") != "healthy"]
instance_ids = [t.get("instance_id", "") for t in targets if t.get("instance_id")]
return {
"source": "ec2",
"available": True,
"target_groups": target_groups,
"healthy_targets": healthy,
"unhealthy_targets": unhealthy,
"instance_ids": list(dict.fromkeys(instance_ids)),
"summary": build_elb_summary(target_groups, healthy, unhealthy),
"error": None,
}
# ------------------------------------------------------------------ RDS
def describe_db_instances(
self,
db_instance_identifier: str,
region: str = "",
**_: Any,
) -> dict[str, Any]:
"""Synthesize a ``describe_rds_instance`` response from scenario metadata.
Every RDS scenario fixture supplies its instance identity in
``scenario.yml`` (engine, version, class, region, identifier). We
treat that as the canonical answer to ``describe_db_instances`` so
all 16+ scenarios are served uniformly without per-scenario fixture
files. Mismatched identifiers (the agent asks about a DB the scenario
doesn't define) return the same ``available=False`` envelope the real
tool uses for ``DBInstanceNotFound``, keeping the failure mode honest.
"""
meta = self._fixture.metadata
if db_instance_identifier and db_instance_identifier != meta.db_instance_identifier:
return {
"source": "rds",
"available": False,
"db_instance_identifier": db_instance_identifier,
"error": "No RDS instance found with the given identifier.",
}
engine = (meta.engine or "").lower()
port = _DEFAULT_ENGINE_PORTS.get(engine, 5432)
resolved_region = region or meta.region or "us-east-1"
endpoint_address = (
f"{meta.db_instance_identifier}.synthetic.{resolved_region}.rds.amazonaws.com"
)
return {
"source": "rds",
"available": True,
"db_instance_identifier": meta.db_instance_identifier,
"status": "available",
"engine": meta.engine,
"engine_version": meta.engine_version,
"instance_class": meta.instance_class,
"multi_az": False,
"publicly_accessible": False,
"storage_type": "gp3",
"allocated_storage_gb": 200,
"endpoint": {"address": endpoint_address, "port": port},
"availability_zone": f"{resolved_region}a",
"preferred_backup_window": "03:00-04:00",
"backup_retention_period": 7,
"error": None,
}
def describe_db_events(
self,
db_instance_identifier: str,
duration_minutes: int = 60,
**_: Any,
) -> dict[str, Any]:
"""Serve ``describe_rds_events`` from the ``aws_rds_events`` fixture.
Gated on the scenario declaring ``aws_rds_events`` in
``available_evidence``. Calling without that opt-in is a synthetic
suite contract violation and raises so it surfaces during scenario
development rather than producing empty (and misleading) "no events"
results that the agent would treat as evidence of absence.
``region`` is accepted via ``**kwargs`` to keep the Protocol surface
symmetric with ``describe_db_instances`` even though fixture events
are scenario-scoped and don't vary by region.
"""
events_fixture = self._fixture.evidence.aws_rds_events
if events_fixture is None:
raise ValueError(
f"{self._fixture.scenario_id}: describe_db_events called but "
"'aws_rds_events' is not declared in available_evidence"
)
meta = self._fixture.metadata
if db_instance_identifier and db_instance_identifier != meta.db_instance_identifier:
return {
"source": "rds",
"available": True,
"db_instance_identifier": db_instance_identifier,
"duration_minutes": duration_minutes,
"total_events": 0,
"events": [],
"error": None,
}
events = [
{
"date": event.get("date"),
"message": event.get("message"),
"categories": list(event.get("event_categories", []) or []),
"source_type": event.get("source_type"),
}
for event in events_fixture
]
return {
"source": "rds",
"available": True,
"db_instance_identifier": meta.db_instance_identifier,
"duration_minutes": duration_minutes,
"total_events": len(events),
"events": events,
"error": None,
}
# ------------------------------------------------------------------ CloudTrail
def lookup_events(
self,
lookup_attributes: list[dict[str, str]] | None = None,
duration_minutes: int = 60,
region: str = "",
**_: Any,
) -> dict[str, Any]:
"""Serve ``lookup_cloudtrail_events`` from fixture data.
No scenario currently declares CloudTrail evidence, so this returns an
empty (but well-shaped) account-wide result. Its purpose is to keep
synthetic runs off real AWS — never leak a boto3 ``lookup_events`` call —
while giving the agent a valid "no recent changes" answer. ``filter``
echoes the single LookupAttribute the tool sent, mirroring the real tool.
"""
attributes = lookup_attributes or []
return {
"source": "cloudtrail",
"available": True,
"region": region,
"duration_minutes": duration_minutes,
"filter": attributes[0] if attributes else None,
"total_events": 0,
"truncated": False,
"next_token": None,
"events": [],
"error": None,
}