-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathapplication.py
More file actions
195 lines (167 loc) · 8.82 KB
/
Copy pathapplication.py
File metadata and controls
195 lines (167 loc) · 8.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# application.py
from packet import Packet
import random
class Application:
def __init__(self, env, app_id, source_host, dest_host, path_selector, flow_config, results_dict, app_registry=None, metrics_collector=None, num_packets=None, packet_size_bytes=1500):
self.env = env
self.app_id = app_id
self.source = source_host
self.destination = dest_host
self.path_selector = path_selector
self.flow_config = flow_config
self.results = results_dict
self.packets_sent = 0
self.source.application = self # Link back for notifications
self.app_registry = app_registry
self.metrics_collector = metrics_collector
self.current_path = None
self.is_path_down = False
self.path_scoring_randomness = random.uniform(0.1, 1)
self.budget = 50 # budget for path selection
self.maintenance_interval = 5000 # interval for periodic maintenance (in ms)
self.flow_name = flow_config.get('name', app_id)
self.num_packets_override = num_packets # Override traffic volume if specified
self.packet_size_bytes = packet_size_bytes
def run(self):
yield self.env.timeout(self.flow_config['start_time_ms'])
print(f"[{self.env.now:.2f}] App {self.app_id}: Starting flow from {self.source.node_id} to {self.destination.node_id}")
#When the application requests a path, it must now "introduce itself" to the algorithm
# so the algorithm can access that specific path_scoring_randomness variable
path = self.path_selector.select_path(self.source.isd_as, self.destination.isd_as, app_instance=self)
if not path:
print(f"[{self.env.now:.2f}] App {self.app_id}: No path found. Stopping.")
return
# Store and register the path
self.current_path = path
if self.app_registry:
self.app_registry.register_path_usage(self, path)
if self.metrics_collector:
self.metrics_collector.record_path_switch(self.flow_name, self.env.now, path)
print(f"[{self.env.now:.2f}] App {self.app_id}: Selected path: {' -> '.join(path)}")
# Start a process to listen for incoming packets
self.env.process(self.receive_handler())
# Start periodic maintenance to re-evaluate path selection
self.env.process(self._periodic_maintenance())
# Send data
# num_packets in flow_config takes precedence (set from experiment config_parameters),
# otherwise derive from data_size_kb.
if 'num_packets' in self.flow_config:
num_packets = self.flow_config['num_packets']
else:
data_to_send_bytes = self.flow_config['data_size_kb'] * 1024
packet_size = 1500 # bytes
num_packets = data_to_send_bytes // packet_size
for i in range(num_packets):
# Check if path is down and attempt re-selection
if self.is_path_down:
self._attempt_path_reselection()
if self.is_path_down:
# Still down after attempt, wait before retry
yield self.env.timeout(10)
continue
# Use current path for packet
packet = Packet(
self.source.node_id,
self.destination.node_id,
self.current_path,
size=self.packet_size_bytes,
flow_name=self.flow_name,
loss_callback=self.notify_loss,
)
packet.creation_time = self.env.now
self.source.send_packet(packet)
self.packets_sent += 1
if self.metrics_collector:
self.metrics_collector.record_packet_sent(self.flow_name, self.env.now, self.current_path, self.packet_size_bytes)
yield self.env.timeout(1) # Send a packet every 1ms
def receive_handler(self):
flow_queue = self.destination.get_incoming_queue(self.flow_name)
while True:
# Receive only from this flow's destination queue.
packet = yield flow_queue.get()
# Ignore control traffic or packets that do not belong to this flow.
if getattr(packet, 'is_beacon', False):
continue
if getattr(packet, 'flow_name', None) not in (None, self.flow_name):
continue
latency = self.env.now - packet.creation_time
self.results["latencies"].append(latency)
if self.metrics_collector:
self.metrics_collector.record_packet_received(self.flow_name, self.env.now, latency)
# --- FEEDBACK LOOP ---
# To close the control loop by feeding real-time Data Plane measurements
# back into the Control Plane (Path Selection Algorithm).
# We check if the current algorithm supports dynamic feedback, in order to make sure the simulation doesn't crash if we switch back to another
# algorithm (like ShortestPath) that doesn't implement 'update_path_feedback'.
if hasattr(self.path_selector, 'update_path_feedback'):
# We pass the physical path used as the key and the measured one-way latency as the value.
# Setting 'is_loss=False' instructs the algorithm to append this latency
# to its sliding window history, which will be used in the path's composite score.
# UMCC: Also pass packet size for throughput tracking
self.path_selector.update_path_feedback(packet.path, latency, is_loss=False, packet_size=packet.size)
print(f"[{self.env.now:.2f}] App {self.app_id}: Received packet/ACK after {latency:.2f}ms")
def notify_loss(self, packet):
self.results["packet_loss"] += 1
if self.metrics_collector:
self.metrics_collector.record_packet_loss(self.flow_name)
# --- FEEDBACK LOOP (FAILURE SIGNAL) ---
#To alert the Control Plane that a path has failed to deliver data.
if hasattr(self.path_selector, 'update_path_feedback'):
# We report the specific path that caused the packet drop.
#Latency is passed as 0 (second argument)
#Sets 'is_loss=True' to increment the loss counter of the algorithm
# UMCC: Pass packet size even for lost packets to track total sent
self.path_selector.update_path_feedback(packet.path, 0, is_loss=True, packet_size=packet.size)
print(f"[{self.env.now:.2f}] App {self.app_id}: Packet loss detected for flow to {packet.destination}")
def on_path_down(self, router_path):
"""
Callback invoked when the current path goes down.
Args:
router_path (list): The failed path
"""
print(f"[{self.env.now:.2f}] App {self.app_id}: Path down notification received")
self.is_path_down = True
self._attempt_path_reselection()
def on_path_up(self, router_path):
"""
Optional callback when a path recovers.
Args:
router_path (list): The recovered path
"""
print(f"[{self.env.now:.2f}] App {self.app_id}: Path up notification received for {router_path}")
def _attempt_path_reselection(self):
"""
Attempt to select a new path after failure.
"""
new_path = self.path_selector.select_path(
self.source.isd_as,
self.destination.isd_as
)
if new_path:
print(f"[{self.env.now:.2f}] App {self.app_id}: Switched to new path: {' -> '.join(new_path)}")
self.current_path = new_path
if self.app_registry:
self.app_registry.register_path_usage(self, new_path)
if self.metrics_collector:
self.metrics_collector.record_path_switch(self.flow_name, self.env.now, new_path)
self.is_path_down = False
else:
print(f"[{self.env.now:.2f}] App {self.app_id}: No alternative path available")
def _periodic_maintenance(self):
"""Periodically re-evaluate path selection"""
yield self.env.timeout(self.maintenance_interval)
while True:
# Call select_path again with current app instance
better_path = self.path_selector.select_path(
self.source.isd_as,
self.destination.isd_as,
app_instance=self
)
# Change the path if different path is selected
if better_path and better_path != self.current_path:
self.current_path = better_path
if self.app_registry:
self.app_registry.register_path_usage(self, better_path)
if self.metrics_collector:
self.metrics_collector.record_path_switch(self.flow_name, self.env.now, better_path)
yield self.env.timeout(self.maintenance_interval)