Skip to content

Commit cf5f20b

Browse files
committed
Definition of the input for the events injection part 2
1 parent cacd567 commit cf5f20b

2 files changed

Lines changed: 93 additions & 10 deletions

File tree

src/asyncflow/schemas/event/injection.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,13 @@ def ensure_start_end_compatibility(
7878

7979
expected = start_to_end[model.start.kind]
8080
if model.end.kind != expected:
81-
msg = (f"The event {model.event_id} must have"
81+
msg = (f"The event {model.event_id} must have "
8282
f"as value of kind in end {expected}")
8383
raise ValueError(msg)
8484

8585
# Ensure the time sequence is well defined
8686
if model.start.t_start >= model.end.t_end:
87-
msg=(f"The starting time for the event {model.event_id}"
87+
msg=(f"The starting time for the event {model.event_id} "
8888
"must be smaller than the ending time")
8989
raise ValueError(msg)
9090

src/asyncflow/schemas/payload.py

Lines changed: 91 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from asyncflow.schemas.settings.simulation import SimulationSettings
77
from asyncflow.schemas.topology.graph import TopologyGraph
88
from asyncflow.schemas.workload.rqs_generator import RqsGenerator
9+
from asyncflow.config.constants import EventDescription
910

1011

1112
class SimulationPayload(BaseModel):
@@ -45,29 +46,29 @@ def ensure_components_ids_is_compatible(
4546
if model.events is None:
4647
return model
4748

48-
server_list = model.topology_graph.nodes.servers
49+
servers_list = model.topology_graph.nodes.servers
4950
edges_list = model.topology_graph.edges
5051
valid_ids = (
51-
{server.id for server in server_list}
52+
{server.id for server in servers_list}
5253
| {edge.id for edge in edges_list}
5354
)
5455

5556
for event in model.events:
5657
if event.target_id not in valid_ids:
57-
msg = (f"The target id {event.target_id} related to"
58+
msg = (f"The target id {event.target_id} related to "
5859
f"the event {event.event_id} does not exist")
5960
raise ValueError(msg)
6061

6162
return model
6263

6364
@model_validator(mode="after") # type: ignore[arg-type]
64-
def ensure_event_time_inside_simulatioon_horizon(
65+
def ensure_event_time_inside_simulation_horizon(
6566
cls, # noqa: N805
6667
model: "SimulationPayload",
6768
) -> "SimulationPayload":
6869
"""
69-
The interval of time associated to each events must be
70-
included in the simulation horizon
70+
The time interval associated to each event must be in
71+
the simulation horizon
7172
"""
7273
if model.events is None:
7374
return model
@@ -109,11 +110,93 @@ def ensure_compatibility_event_kind_target_id(
109110
) -> "SimulationPayload":
110111
"""
111112
The kind of the event must be compatible with the target id
112-
type
113+
type, for example we cannot have an event regarding a server
114+
with a target id associated to an edge
113115
"""
114116
if model.events is None:
115117
return model
116-
117118

119+
servers_list = model.topology_graph.nodes.servers
120+
edges_list = model.topology_graph.edges
121+
122+
# We need just the Start or End kind because
123+
# we have a validation for the coherence between
124+
# the starting event kind and the finishing event kind
125+
server_kind = {EventDescription.SERVER_DOWN}
126+
edge_kind = {EventDescription.NETWORK_SPIKE_START}
127+
128+
servers_ids = {server.id for server in servers_list}
129+
edges_ids = {edge.id for edge in edges_list}
130+
131+
for event in model.events:
132+
if event.start.kind in server_kind and event.target_id not in servers_ids:
133+
msg = (f"The event {event.event_id} regarding a server does not have "
134+
"a compatible target id")
135+
raise ValueError(msg)
136+
elif event.start.kind in edge_kind and event.target_id not in edges_ids:
137+
msg = (f"The event {event.event_id} regarding an edge does not have "
138+
"a compatible target id")
139+
raise ValueError(msg)
140+
141+
142+
return model
143+
144+
145+
@model_validator(mode="after") # type: ignore[arg-type]
146+
def ensure_not_all_servers_are_down_simultaneously(
147+
cls, # noqa: N805
148+
model: "SimulationPayload",
149+
) -> "SimulationPayload":
150+
"""
151+
We will not accept the condition to have all server down
152+
at the same moment, always at least one server must be up
153+
and running
154+
"""
155+
156+
if model.events is None:
157+
return model
158+
159+
# First let us build a list of events related to the servers
160+
servers_list = model.topology_graph.nodes.servers
161+
servers_ids = {server.id for server in servers_list}
162+
server_events = [
163+
event for event in model.events
164+
if event.target_id in servers_ids
165+
]
166+
167+
# Helpers needed in the algorithm to define a specific ordering
168+
# procedure
169+
START = "start"
170+
END = "end"
171+
172+
# Let us define a list of tuple as a timeline, this approach ensure
173+
# the possibility to have different servers going up or down at the
174+
# same time, a more elegant approach through an hashmap has been
175+
# considered however it would require an extra assumption that all
176+
# the times had to be different, we thought that this would be too
177+
# strict
178+
timeline = []
179+
for event in server_events:
180+
timeline.append((event.start.t_start, START, event.target_id))
181+
timeline.append((event.end.t_end, END, event.target_id))
182+
183+
# Let us order the timeline by time if there are multiple events at the
184+
# same time process first the end type events
185+
timeline.sort(key=lambda x: (x[0], x[1] == START))
186+
187+
# Definition of a set to verify the condition that at least one server must
188+
# be up
189+
server_down = set()
190+
for time, kind, server_id in timeline:
191+
if kind == START:
192+
server_down.discard(server_id)
193+
else: # "start"
194+
server_down.add(server_id)
195+
if len(server_down) == len(servers_ids):
196+
raise ValueError(
197+
f"At time {time:.6f} all servers are down; keep at least one up."
198+
)
199+
118200
return model
201+
119202

0 commit comments

Comments
 (0)