Skip to content

Commit e9afe18

Browse files
authored
Feature/event injection input (#19)
* Structure defined for the pydantic model representing Event to be injected * Pydantic validation in the payload for events part 1 * Definition of the input for the events injection part 2 * minor fixes ruff and mypy compliant * full tests and docs added for input event injection * fixed minor bugs
1 parent f57059b commit e9afe18

File tree

9 files changed

+1109
-2
lines changed

9 files changed

+1109
-2
lines changed

docs/api/event-injection.md

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
# EventInjection — Public API Documentation
2+
3+
## Overview
4+
5+
`EventInjection` declares a **time-bounded event** that affects a component in the simulation. Each event targets either a **server** or a **network edge**, and is delimited by a `start` marker and an `end` marker.
6+
7+
Supported families (per code):
8+
9+
* **Server availability**: `SERVER_DOWN``SERVER_UP`
10+
* **Network latency spike (deterministic offset in seconds)**: `NETWORK_SPIKE_START``NETWORK_SPIKE_END`
11+
For network spikes, the `Start` marker carries the amplitude in seconds via `spike_s`.
12+
13+
Strictness:
14+
15+
* Models use `ConfigDict(extra="forbid", frozen=True)`
16+
→ unknown fields are rejected; instances are immutable at runtime.
17+
18+
---
19+
20+
## Data Model
21+
22+
### `Start`
23+
24+
* `kind: Literal[SERVER_DOWN, NETWORK_SPIKE_START]`
25+
Event family selector.
26+
* `t_start: NonNegativeFloat`
27+
Start time in **seconds** from simulation start; **≥ 0.0**.
28+
* `spike_s: PositiveFloat | None`
29+
**Required** and **> 0** **only** when `kind == NETWORK_SPIKE_START`.
30+
**Forbidden** (must be omitted/`None`) for any other kind.
31+
32+
### `End`
33+
34+
* `kind: Literal[SERVER_UP, NETWORK_SPIKE_END]`
35+
Must match the start family (see invariants).
36+
* `t_end: PositiveFloat`
37+
End time in **seconds**; **> 0.0**.
38+
39+
### `EventInjection`
40+
41+
* `event_id: str`
42+
Unique identifier within the simulation payload.
43+
* `target_id: str`
44+
Identifier of the affected component (server or edge) as defined in the topology.
45+
* `start: Start`
46+
Start marker.
47+
* `end: End`
48+
End marker.
49+
50+
---
51+
52+
## Validation & Invariants (as implemented)
53+
54+
### Within `EventInjection`
55+
56+
1. **Family coherence**
57+
58+
* `SERVER_DOWN``SERVER_UP`
59+
* `NETWORK_SPIKE_START``NETWORK_SPIKE_END`
60+
Any other pairing raises:
61+
62+
```
63+
The event {event_id} must have as value of kind in end {expected}
64+
```
65+
2. **Temporal ordering**
66+
67+
* `t_start < t_end` (with `t_start ≥ 0.0`, `t_end > 0.0`)
68+
Error:
69+
70+
```
71+
The starting time for the event {event_id} must be smaller than the ending time
72+
```
73+
3. **Network spike parameter**
74+
75+
* If `start.kind == NETWORK_SPIKE_START``start.spike_s` **must** be provided and be a positive float (seconds).
76+
Error:
77+
78+
```
79+
The field spike_s for the event {event_id} must be defined as a positive float (seconds)
80+
```
81+
* Otherwise (`SERVER_DOWN`) ⇒ `start.spike_s` **must be omitted** / `None`.
82+
Error:
83+
84+
```
85+
Event {event_id}: spike_s must be omitted for non-network events
86+
```
87+
88+
### Enforced at `SimulationPayload` level
89+
90+
4. **Unique event IDs**
91+
Error:
92+
93+
```
94+
The id's representing different events must be unique
95+
```
96+
5. **Target existence & compatibility**
97+
98+
* For server events (`SERVER_DOWN`), `target_id` must refer to a **server**.
99+
* For network spikes (`NETWORK_SPIKE_START`), `target_id` must refer to an **edge**.
100+
Errors:
101+
102+
```
103+
The target id {target_id} related to the event {event_id} does not exist
104+
```
105+
106+
```
107+
The event {event_id} regarding a server does not have a compatible target id
108+
```
109+
110+
```
111+
The event {event_id} regarding an edge does not have a compatible target id
112+
```
113+
6. **Times within simulation horizon** (with `T = sim_settings.total_simulation_time`)
114+
115+
* `t_start >= 0.0` and `t_start <= T`
116+
* `t_end <= T`
117+
Errors:
118+
119+
```
120+
Event '{event_id}': start time t_start={t:.6f} must be >= 0.0
121+
Event '{event_id}': start time t_start={t:.6f} exceeds simulation horizon T={T:.6f}
122+
Event '{event_id}': end time t_end={t:.6f} exceeds simulation horizon T={T:.6f}
123+
```
124+
7. **Global liveness rule (servers)**
125+
The payload is rejected if **all servers are down at the same moment**.
126+
Implementation detail: the timeline is ordered so that, at identical timestamps, **`END` is processed before `START`** to avoid transient all-down states.
127+
Error:
128+
129+
```
130+
At time {time:.6f} all servers are down; keep at least one up
131+
```
132+
133+
---
134+
135+
## Runtime Semantics (summary)
136+
137+
* **Server events**: the targeted server is unavailable between the start and end markers; the system enforces that at least one server remains up at all times.
138+
* **Network spike events**: the targeted edge’s latency sampler is deterministically **shifted by `spike_s` seconds** during the event window (additive congestion model). The underlying distribution is not reshaped—samples are translated by a constant offset.
139+
140+
*(This reflects the agreed model: deterministic additive offset on edges.)*
141+
142+
---
143+
144+
## Units & Precision
145+
146+
* All times and offsets are in **seconds** (floating-point).
147+
* Provide values with the precision your simulator supports; microsecond-level precision is acceptable if needed.
148+
149+
---
150+
151+
## Authoring Guidelines
152+
153+
* **Do not include `spike_s`** for non-network events.
154+
* Use **stable, meaningful `event_id`** values for auditability.
155+
* Keep events within the **simulation horizon**.
156+
* When multiple markers share the same timestamp, rely on the engine’s **END-before-START** ordering for determinism.
157+
158+
---
159+
160+
## Examples
161+
162+
### 1) Valid — Server maintenance window
163+
164+
```yaml
165+
event_id: ev-maint-001
166+
target_id: srv-1
167+
start: { kind: SERVER_DOWN, t_start: 120.0 }
168+
end: { kind: SERVER_UP, t_end: 240.0 }
169+
```
170+
171+
### 2) Valid — Network spike on an edge (+8 ms)
172+
173+
```yaml
174+
event_id: ev-spike-008ms
175+
target_id: edge-12
176+
start: { kind: NETWORK_SPIKE_START, t_start: 10.0, spike_s: 0.008 }
177+
end: { kind: NETWORK_SPIKE_END, t_end: 25.0 }
178+
```
179+
180+
### 3) Invalid — Missing `spike_s` for a network spike
181+
182+
```yaml
183+
event_id: ev-missing-spike
184+
target_id: edge-5
185+
start: { kind: NETWORK_SPIKE_START, t_start: 5.0 }
186+
end: { kind: NETWORK_SPIKE_END, t_end: 15.0 }
187+
```
188+
189+
Error:
190+
191+
```
192+
The field spike_s for the event ev-missing-spike must be defined as a positive float (seconds)
193+
```
194+
195+
### 4) Invalid — `spike_s` present for a server event
196+
197+
```yaml
198+
event_id: ev-bad-spike
199+
target_id: srv-2
200+
start: { kind: SERVER_DOWN, t_start: 50.0, spike_s: 0.005 }
201+
end: { kind: SERVER_UP, t_end: 60.0 }
202+
```
203+
204+
Error:
205+
206+
```
207+
Event ev-bad-spike: spike_s must be omitted for non-network events
208+
```
209+
210+
### 5) Invalid — Mismatched families
211+
212+
```yaml
213+
event_id: ev-bad-kinds
214+
target_id: edge-1
215+
start: { kind: NETWORK_SPIKE_START, t_start: 5.0, spike_s: 0.010 }
216+
end: { kind: SERVER_UP, t_end: 15.0 }
217+
```
218+
219+
Error:
220+
221+
```
222+
The event ev-bad-kinds must have as value of kind in end NETWORK_SPIKE_END
223+
```
224+
225+
### 6) Invalid — Start not before End
226+
227+
```yaml
228+
event_id: ev-bad-time
229+
target_id: srv-2
230+
start: { kind: SERVER_DOWN, t_start: 300.0 }
231+
end: { kind: SERVER_UP, t_end: 300.0 }
232+
```
233+
234+
Error:
235+
236+
```
237+
The starting time for the event ev-bad-time must be smaller than the ending time
238+
```
239+
240+
---
241+
242+
## Notes for Consumers
243+
244+
* The schema is **strict**: misspelled fields (e.g., `t_strat`) are rejected.
245+
* The engine may combine multiple active network spikes on the same edge by **summing** their `spike_s` values while they overlap (handled by runtime bookkeeping).
246+
* This document describes exactly what is present in the provided code and validators; no additional fields or OpenAPI metadata are assumed.

src/asyncflow/components/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Public components: re-exports Pydantic schemas (topology)."""
22
from __future__ import annotations
33

4+
from asyncflow.schemas.event.injection import EventInjection
45
from asyncflow.schemas.topology.edges import Edge
56
from asyncflow.schemas.topology.endpoint import Endpoint
67
from asyncflow.schemas.topology.nodes import (
@@ -10,6 +11,14 @@
1011
ServerResources,
1112
)
1213

13-
__all__ = ["Client", "Edge", "Endpoint", "LoadBalancer", "Server", "ServerResources"]
14+
__all__ = [
15+
"Client",
16+
"Edge",
17+
"Endpoint",
18+
"EventInjection",
19+
"LoadBalancer",
20+
"Server",
21+
"ServerResources",
22+
]
1423

1524

src/asyncflow/config/constants.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,19 @@ class SystemEdges(StrEnum):
175175

176176
NETWORK_CONNECTION = "network_connection"
177177

178+
# ======================================================================
179+
# CONSTANTS FOR THE EVENT TO INJECT IN THE SIMULATION
180+
# ======================================================================
181+
182+
class EventDescription(StrEnum):
183+
"""Description for the events you may inject during the simulation"""
184+
185+
SERVER_UP = "server_up"
186+
SERVER_DOWN = "server_down"
187+
NETWORK_SPIKE_START = "network_spike_start"
188+
NETWORK_SPIKE_END = "network_spike_end"
189+
190+
178191
# ======================================================================
179192
# CONSTANTS FOR SAMPLED METRICS
180193
# ======================================================================

0 commit comments

Comments
 (0)