feat: expand bgp observability surfaces
This commit is contained in:
@@ -16,7 +16,11 @@ from app.services.collectors.bgp_common import (
|
||||
save_bgp_observations_for_batch,
|
||||
)
|
||||
from app.services.bgp_enrichment import enrich_bgp_events_for_batch, extract_bgp_network_fields
|
||||
from app.services.bgp_incidents import create_bgp_incidents_for_anomalies
|
||||
from app.services.bgp_incidents import (
|
||||
create_bgp_incidents_for_anomalies,
|
||||
infer_related_infrastructure,
|
||||
)
|
||||
from app.services.bgp_collectors import build_bgp_collector_coverage
|
||||
from app.models.bgp_anomaly import BGPAnomaly
|
||||
from app.models.collected_data import CollectedData
|
||||
from app.models.bgp_incident import BGPIncident
|
||||
@@ -331,13 +335,17 @@ async def test_create_bgp_incidents_for_anomalies_aggregates_regions_and_collect
|
||||
},
|
||||
)
|
||||
|
||||
created = await create_bgp_incidents_for_anomalies(
|
||||
db,
|
||||
source="ris_live_bgp",
|
||||
snapshot_id=1,
|
||||
task_id=2,
|
||||
anomalies=[anomaly],
|
||||
)
|
||||
with patch(
|
||||
"app.services.bgp_incidents.infer_related_infrastructure",
|
||||
new=AsyncMock(return_value={"related_cables": [], "related_ixps": []}),
|
||||
):
|
||||
created = await create_bgp_incidents_for_anomalies(
|
||||
db,
|
||||
source="ris_live_bgp",
|
||||
snapshot_id=1,
|
||||
task_id=2,
|
||||
anomalies=[anomaly],
|
||||
)
|
||||
|
||||
assert created == 1
|
||||
assert db.commits == 1
|
||||
@@ -348,6 +356,91 @@ async def test_create_bgp_incidents_for_anomalies_aggregates_regions_and_collect
|
||||
assert incident.affected_regions[0]["city"] == "Amsterdam"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_infer_related_infrastructure_links_nearby_cables():
|
||||
landing = CollectedData(
|
||||
source="arcgis_landing_points",
|
||||
name="Amsterdam Landing",
|
||||
data_type="landing_point",
|
||||
extra_data={
|
||||
"city_id": 10,
|
||||
"country": "Netherlands",
|
||||
"city": "Amsterdam",
|
||||
"latitude": 52.3676,
|
||||
"longitude": 4.9041,
|
||||
},
|
||||
)
|
||||
relation = CollectedData(
|
||||
source="arcgis_cable_landing_relation",
|
||||
name="rel-1",
|
||||
data_type="landing_relation",
|
||||
extra_data={"city_id": 10, "cable_id": 20},
|
||||
)
|
||||
cable = CollectedData(
|
||||
source="arcgis_cables",
|
||||
name="AEConnect-1",
|
||||
data_type="cable",
|
||||
extra_data={"cable_id": 20},
|
||||
)
|
||||
db = _FakeAsyncSession([[landing], [relation], [cable]])
|
||||
|
||||
result = await infer_related_infrastructure(
|
||||
db,
|
||||
[
|
||||
{
|
||||
"collector": "rrc00",
|
||||
"country": "Netherlands",
|
||||
"city": "Amsterdam",
|
||||
"latitude": 52.36,
|
||||
"longitude": 4.90,
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
assert len(result["related_cables"]) == 1
|
||||
assert result["related_cables"][0]["landing_point"] == "Amsterdam Landing"
|
||||
assert result["related_cables"][0]["cable_names"] == ["AEConnect-1"]
|
||||
assert result["related_ixps"][0]["name"] == "Amsterdam, Netherlands"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_build_bgp_collector_coverage_summarizes_observations():
|
||||
obs_one = BGPObservation(
|
||||
source="ris_live_bgp",
|
||||
collector="rrc00",
|
||||
prefix="203.0.113.0/24",
|
||||
origin_asn=64496,
|
||||
peer_asn=3333,
|
||||
event_type="announcement",
|
||||
observed_at=datetime(2026, 3, 30, 10, 0, tzinfo=UTC),
|
||||
collector_geo={"city": "Amsterdam", "country": "Netherlands"},
|
||||
)
|
||||
obs_two = BGPObservation(
|
||||
source="ris_live_bgp",
|
||||
collector="rrc00",
|
||||
prefix="198.51.100.0/24",
|
||||
origin_asn=64497,
|
||||
peer_asn=3334,
|
||||
event_type="withdrawal",
|
||||
observed_at=datetime(2026, 3, 30, 10, 5, tzinfo=UTC),
|
||||
collector_geo={"city": "Amsterdam", "country": "Netherlands"},
|
||||
)
|
||||
db = _FakeAsyncSession([[obs_one, obs_two]])
|
||||
|
||||
coverage = await build_bgp_collector_coverage(db, source_filter=BGP_SOURCES)
|
||||
|
||||
first = next(item for item in coverage if item["collector"] == "rrc00")
|
||||
assert first["observation_count"] == 2
|
||||
assert first["recent_24h_observation_count"] == 2
|
||||
assert first["recent_7d_observation_count"] == 2
|
||||
assert first["prefix_count"] == 2
|
||||
assert first["recent_24h_prefix_count"] == 2
|
||||
assert first["origin_asn_count"] == 2
|
||||
assert first["latest_event_type"] == "withdrawal"
|
||||
assert first["baseline_scope"]["countries"] == ["Netherlands"]
|
||||
assert first["baseline_scope"]["cities"] == ["Amsterdam"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_bgp_observations_for_batch_adds_rows():
|
||||
db = _FakeAsyncSession([])
|
||||
@@ -643,3 +736,41 @@ async def test_bgp_event_summary_api_returns_aggregates():
|
||||
assert payload["prefix_count"] == 2
|
||||
assert payload["by_type"]["announcement"] == 1
|
||||
assert payload["by_type"]["withdrawal"] == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bgp_collectors_api_returns_coverage():
|
||||
observation = BGPObservation(
|
||||
id=1,
|
||||
source="ris_live_bgp",
|
||||
collector="rrc00",
|
||||
peer_asn=3333,
|
||||
prefix="203.0.113.0/24",
|
||||
event_type="announcement",
|
||||
origin_asn=64496,
|
||||
observed_at=datetime(2026, 3, 30, 10, 0, tzinfo=UTC),
|
||||
collector_geo={"city": "Amsterdam", "country": "Netherlands"},
|
||||
)
|
||||
db = _FakeAsyncSession([[observation], [observation]])
|
||||
client = await _bgp_test_client(db)
|
||||
|
||||
try:
|
||||
list_response = await client.get("/api/v1/bgp/collectors")
|
||||
summary_response = await client.get("/api/v1/bgp/collectors/summary")
|
||||
finally:
|
||||
await client.aclose()
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
assert list_response.status_code == 200
|
||||
list_payload = list_response.json()
|
||||
assert list_payload["total"] >= 1
|
||||
target = next(item for item in list_payload["data"] if item["collector"] == "rrc00")
|
||||
assert target["observation_count"] == 1
|
||||
assert target["prefix_count"] == 1
|
||||
|
||||
assert summary_response.status_code == 200
|
||||
summary_payload = summary_response.json()
|
||||
assert summary_payload["active_collectors"] >= 1
|
||||
assert summary_payload["observed_prefixes"] >= 1
|
||||
assert summary_payload["recent_24h_events"] >= 1
|
||||
assert summary_payload["recent_7d_events"] >= 1
|
||||
|
||||
Reference in New Issue
Block a user