Skip to content

Commit 5b4e55f

Browse files
Merge pull request #138 from SDNNetSim/feature/grooming-port
feat: add traffic grooming support for v6.0
2 parents 3ae0480 + 83876bc commit 5b4e55f

35 files changed

Lines changed: 7658 additions & 639 deletions

.DS_Store

10 KB
Binary file not shown.

fusion/configs/schema.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@
4141
"spectrum_priority": str,
4242
"save_step": int,
4343
"save_start_end_slots": str_to_bool,
44+
"is_grooming_enabled": str_to_bool,
45+
"can_partially_serve": str_to_bool,
46+
"transponder_usage_per_node": str_to_bool,
47+
"blocking_type_ci": str_to_bool,
4448
},
4549
"topology_settings": {
4650
"network": str,
@@ -63,6 +67,9 @@
6367
"xt_noise": str_to_bool,
6468
"requested_xt": str,
6569
"phi": str,
70+
"snr_recheck": str_to_bool,
71+
"recheck_adjacent_cores": str_to_bool,
72+
"recheck_crossband": str_to_bool,
6673
},
6774
"file_settings": {
6875
"file_type": str,
@@ -78,6 +85,8 @@
7885
"k_paths": int,
7986
"filter_mods": bool,
8087
"request_distribution": str,
88+
"fragmentation_metrics": str,
89+
"frag_calc_step": int,
8190
},
8291
"topology_settings": {
8392
"bi_directional": str_to_bool,

fusion/configs/templates/cross_platform.ini

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ print_step = 15
2929
save_step = 10
3030
save_start_end_slots = False
3131

32+
# Traffic grooming
33+
is_grooming_enabled = False
34+
can_partially_serve = False
35+
transponder_usage_per_node = False
36+
blocking_type_ci = False
37+
fragmentation_metrics = []
38+
frag_calc_step = 1000
39+
3240
[topology_settings]
3341
network = NSFNet
3442
bw_per_slot = 12.5
@@ -52,6 +60,11 @@ bi_directional = True
5260
xt_noise = False
5361
requested_xt = {"QPSK": -26.19, "16-QAM": -36.69, "64-QAM": -41.69}
5462

63+
# SNR rechecking for grooming
64+
snr_recheck = False
65+
recheck_adjacent_cores = False
66+
recheck_crossband = False
67+
5568
[rl_settings]
5669
obs_space = obs_3
5770
n_trials = 1

fusion/configs/templates/default.ini

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ print_step = 15
3535
save_step = 10
3636
save_start_end_slots = False
3737

38+
# Traffic grooming
39+
is_grooming_enabled = False
40+
can_partially_serve = False
41+
transponder_usage_per_node = False
42+
blocking_type_ci = False
43+
fragmentation_metrics = []
44+
frag_calc_step = 1000
45+
3846
[topology_settings]
3947
# Network topology configuration
4048
network = NSFNet
@@ -61,6 +69,11 @@ bi_directional = True
6169
xt_noise = False
6270
requested_xt = {"QPSK": -26.19, "16-QAM": -36.69, "64-QAM": -41.69}
6371

72+
# SNR rechecking for grooming
73+
snr_recheck = False
74+
recheck_adjacent_cores = False
75+
recheck_crossband = False
76+
6477
[rl_settings]
6578
# Reinforcement learning configuration
6679
obs_space = obs_3

fusion/configs/templates/runtime_config.ini

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ print_step = 15
2929
save_step = 10
3030
save_start_end_slots = False
3131

32+
# Traffic grooming
33+
is_grooming_enabled = False
34+
can_partially_serve = False
35+
transponder_usage_per_node = False
36+
blocking_type_ci = False
37+
fragmentation_metrics = []
38+
frag_calc_step = 1000
39+
3240
[topology_settings]
3341
network = NSFNet
3442
bw_per_slot = 12.5
@@ -52,6 +60,11 @@ bi_directional = True
5260
xt_noise = False
5361
requested_xt = {"QPSK": -26.19, "16-QAM": -36.69, "64-QAM": -41.69}
5462

63+
# SNR rechecking for grooming
64+
snr_recheck = False
65+
recheck_adjacent_cores = False
66+
recheck_crossband = False
67+
5568
[rl_settings]
5669
obs_space = obs_3
5770
n_trials = 1

fusion/configs/templates/xtar_example_config.ini

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,13 @@ pre_calc_mod_selection = False
2626
save_start_end_slots = False
2727
spectrum_priority = CSB
2828

29-
29+
# Traffic grooming
30+
is_grooming_enabled = False
31+
can_partially_serve = False
32+
transponder_usage_per_node = False
33+
blocking_type_ci = False
34+
fragmentation_metrics = []
35+
frag_calc_step = 1000
3036

3137
[topology_settings]
3238
network = Pan-European
@@ -53,6 +59,10 @@ bi_directional = True
5359
xt_noise = False
5460
requested_xt = {"QPSK": -26.19, "16-QAM": -36.69, "64-QAM": -41.69}
5561

62+
# SNR rechecking for grooming
63+
snr_recheck = False
64+
recheck_adjacent_cores = False
65+
recheck_crossband = False
5666

5767
[rl_settings]
5868
obs_space = obs_3

fusion/core/grooming.py

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
"""
2+
Traffic grooming module for optical network simulations.
3+
4+
This module provides traffic grooming functionality to efficiently pack
5+
multiple requests onto existing lightpaths, improving resource utilization.
6+
"""
7+
8+
from typing import Any
9+
10+
from fusion.core.properties import GroomingProps
11+
from fusion.utils.logging_config import get_logger
12+
13+
logger = get_logger(__name__)
14+
15+
16+
class Grooming:
17+
"""
18+
Traffic grooming handler for optical networks.
19+
20+
This class manages the grooming of network requests to existing lightpaths,
21+
including partial grooming and service release operations.
22+
23+
:param engine_props: Engine configuration properties
24+
:type engine_props: dict[str, Any]
25+
:param sdn_props: SDN controller properties
26+
:type sdn_props: Any
27+
"""
28+
29+
def __init__(self, engine_props: dict[str, Any], sdn_props: Any) -> None:
30+
"""
31+
Initialize grooming handler.
32+
33+
:param engine_props: Engine configuration dictionary
34+
:type engine_props: dict[str, Any]
35+
:param sdn_props: SDN properties object
36+
:type sdn_props: Any
37+
"""
38+
self.grooming_props = GroomingProps()
39+
self.engine_props = engine_props
40+
self.sdn_props = sdn_props
41+
42+
logger.debug("Initialized grooming handler")
43+
44+
def _find_path_max_bw(self, light_id: tuple) -> dict[str, Any] | None:
45+
"""
46+
Find the path group with the maximum total remaining bandwidth.
47+
48+
Groups lightpaths by their physical path and returns the group
49+
with the highest total remaining bandwidth. Skips degraded lightpaths.
50+
51+
:param light_id: Tuple of (source, destination) representing the path ID
52+
:type light_id: tuple
53+
:return: Dictionary of the path group with maximum remaining bandwidth or None
54+
:rtype: dict[str, Any] | None
55+
"""
56+
path_groups: dict[tuple, dict[str, Any]] = {}
57+
58+
for lp_id, lp_info in self.sdn_props.lightpath_status_dict[light_id].items():
59+
# Skip lightpaths with degraded SNRs
60+
if lp_info.get("is_degraded", False):
61+
continue
62+
63+
if lp_info["remaining_bandwidth"] > 0:
64+
path_key = tuple(lp_info["path"])
65+
reverse_path_key = tuple(reversed(lp_info["path"]))
66+
67+
# Normalize path key (treat path and reverse as same group)
68+
normalized_path_key = min(path_key, reverse_path_key)
69+
70+
if normalized_path_key not in path_groups:
71+
path_groups[normalized_path_key] = {
72+
"total_remaining_bandwidth": 0,
73+
"lightpaths": [],
74+
"lp_id_list": [],
75+
}
76+
77+
path_groups[normalized_path_key]["total_remaining_bandwidth"] += (
78+
lp_info["remaining_bandwidth"]
79+
)
80+
path_groups[normalized_path_key]["lightpaths"].append((lp_id, lp_info))
81+
path_groups[normalized_path_key]["lp_id_list"].append(lp_id)
82+
83+
# Find the path group with the maximum total remaining bandwidth
84+
if not path_groups:
85+
return None
86+
87+
return max(
88+
path_groups.values(),
89+
key=lambda group: int(group["total_remaining_bandwidth"]),
90+
default=None,
91+
)
92+
93+
def _end_to_end_grooming(self) -> bool:
94+
"""
95+
Groom arrival requests to already established lightpaths.
96+
97+
Attempts to allocate the requested bandwidth using existing lightpaths
98+
between the source and destination. Supports partial grooming where
99+
part of the request is groomed and the remainder requires new lightpaths.
100+
101+
:return: True if the request was fully groomed, False otherwise
102+
:rtype: bool
103+
"""
104+
light_id = tuple(sorted([self.sdn_props.source, self.sdn_props.destination]))
105+
106+
if light_id not in self.sdn_props.lightpath_status_dict:
107+
return False
108+
109+
max_path_group = self._find_path_max_bw(light_id)
110+
if not max_path_group or max_path_group["total_remaining_bandwidth"] == 0:
111+
return False
112+
113+
remaining_bw = int(self.sdn_props.bandwidth)
114+
115+
for lp_id in max_path_group["lp_id_list"]:
116+
lp_info = self.sdn_props.lightpath_status_dict[light_id][lp_id]
117+
118+
if lp_info["remaining_bandwidth"] == 0:
119+
continue
120+
121+
# Determine how much bandwidth to allocate from this lightpath
122+
if lp_info["remaining_bandwidth"] > remaining_bw:
123+
tmp_remaining_bw = remaining_bw
124+
remaining_bw = 0
125+
else:
126+
tmp_remaining_bw = lp_info["remaining_bandwidth"]
127+
remaining_bw -= lp_info["remaining_bandwidth"]
128+
self.sdn_props.is_sliced = True
129+
130+
# Update lightpath status
131+
lp_info["requests_dict"].update(
132+
{self.sdn_props.request_id: tmp_remaining_bw}
133+
)
134+
lp_info["remaining_bandwidth"] -= tmp_remaining_bw
135+
136+
# Calculate utilization percentage
137+
lp_usage = 1 - (
138+
lp_info["remaining_bandwidth"] / lp_info["lightpath_bandwidth"]
139+
)
140+
lp_info["time_bw_usage"].update({self.sdn_props.arrive: lp_usage * 100})
141+
142+
# Update SDN properties with this lightpath's allocation
143+
self.sdn_props.bandwidth_list.append(str(tmp_remaining_bw))
144+
self.sdn_props.core_list.append(lp_info["core"])
145+
self.sdn_props.band_list.append(lp_info["band"])
146+
self.sdn_props.start_slot_list.append(lp_info["start_slot"])
147+
self.sdn_props.end_slot_list.append(lp_info["end_slot"])
148+
self.sdn_props.modulation_list.append(lp_info["mod_format"])
149+
self.sdn_props.path_list = lp_info["path"]
150+
self.sdn_props.crosstalk_list.append(lp_info["snr_cost"])
151+
self.sdn_props.lightpath_bandwidth_list.append(
152+
lp_info["lightpath_bandwidth"]
153+
)
154+
self.sdn_props.lightpath_id_list.append(lp_id)
155+
self.sdn_props.path_weight = lp_info["path_weight"]
156+
157+
if remaining_bw == 0:
158+
# Fully groomed - no new lightpath needed
159+
self.sdn_props.was_routed = True
160+
self.sdn_props.was_groomed = True
161+
self.sdn_props.was_partially_groomed = False
162+
self.sdn_props.number_of_transponders = 0
163+
self.sdn_props.was_new_lp_established = []
164+
self.sdn_props.remaining_bw = "0"
165+
logger.debug(
166+
"Request %s fully groomed using %d lightpaths",
167+
self.sdn_props.request_id,
168+
len(max_path_group["lp_id_list"]),
169+
)
170+
return True
171+
172+
# Partially groomed - some bandwidth still needs allocation
173+
self.sdn_props.was_partially_groomed = True
174+
self.sdn_props.was_groomed = False
175+
self.sdn_props.remaining_bw = remaining_bw
176+
logger.debug(
177+
"Request %s partially groomed, %d bandwidth remaining",
178+
self.sdn_props.request_id,
179+
remaining_bw,
180+
)
181+
return False
182+
183+
def _release_service(self) -> list[int]:
184+
"""
185+
Remove a previously allocated request from the lightpaths.
186+
187+
Frees up bandwidth on lightpaths that were used by this request
188+
and identifies lightpaths that are now completely unused.
189+
190+
:return: List of lightpath IDs that are no longer carrying any requests
191+
:rtype: list[int]
192+
"""
193+
release_lp = []
194+
light_id = tuple(sorted([self.sdn_props.source, self.sdn_props.destination]))
195+
196+
for lp_id in self.sdn_props.lightpath_id_list[:]:
197+
index = self.sdn_props.lightpath_id_list.index(lp_id)
198+
lp_info = self.sdn_props.lightpath_status_dict[light_id][lp_id]
199+
200+
# Get allocated bandwidth for this request
201+
req_bw = lp_info["requests_dict"][self.sdn_props.request_id]
202+
203+
# Remove request from lightpath
204+
lp_info["requests_dict"].pop(self.sdn_props.request_id)
205+
lp_info["remaining_bandwidth"] += req_bw
206+
self.sdn_props.remaining_bw = int(self.sdn_props.remaining_bw) - req_bw
207+
208+
# Clean up tracking lists
209+
self.sdn_props.lightpath_id_list.pop(index)
210+
self.sdn_props.lightpath_bandwidth_list.pop(index)
211+
212+
# Check if lightpath is now completely unused
213+
if lp_info["remaining_bandwidth"] == lp_info["lightpath_bandwidth"]:
214+
release_lp.append(lp_id)
215+
else:
216+
# Update utilization for partially used lightpath
217+
lp_usage = 1 - (
218+
lp_info["remaining_bandwidth"] / lp_info["lightpath_bandwidth"]
219+
)
220+
lp_info["time_bw_usage"].update({self.sdn_props.depart: lp_usage * 100})
221+
222+
logger.debug(
223+
"Released request %s from %d lightpaths, %d lightpaths now empty",
224+
self.sdn_props.request_id,
225+
len(self.sdn_props.lightpath_id_list),
226+
len(release_lp),
227+
)
228+
return release_lp
229+
230+
def handle_grooming(self, request_type: str) -> bool | list[int]:
231+
"""
232+
Control grooming operations based on request type.
233+
234+
Entry point for grooming operations. Routes to appropriate
235+
method based on whether this is an arrival or release request.
236+
237+
:param request_type: Type of request ("arrival" or "release")
238+
:type request_type: str
239+
:return: For arrivals: bool indicating if fully groomed.
240+
For releases: list of lightpath IDs to release.
241+
:rtype: bool | list[int]
242+
"""
243+
if request_type == "release":
244+
return self._release_service()
245+
return self._end_to_end_grooming()

0 commit comments

Comments
 (0)