Skip to content

Commit 617c1b1

Browse files
committed
hw resolution
1 parent 70f650a commit 617c1b1

10 files changed

Lines changed: 736 additions & 168 deletions

File tree

examples/small_test_clos.yml

Lines changed: 17 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -17,47 +17,18 @@ build:
1717
intra_metro_link:
1818
capacity: 12_800
1919
cost: 1
20-
attrs:
21-
link_type: intra_metro
22-
match:
23-
conditions:
24-
- attr: role
25-
operator: "=="
26-
value: core
27-
- attr: role
28-
operator: "=="
29-
value: leaf
20+
attrs: { link_type: intra_metro }
21+
role_pairs: ["core|leaf"]
3022
inter_metro_link:
3123
capacity: 3_200
3224
cost: 1
33-
attrs:
34-
link_type: inter_metro_corridor
35-
match:
36-
conditions:
37-
- attr: role
38-
operator: "=="
39-
value: core
40-
- attr: role
41-
operator: "=="
42-
value: leaf
43-
logic: "or"
25+
attrs: { link_type: inter_metro_corridor }
26+
role_pairs: ["core|leaf"]
4427
dc_to_pop_link:
4528
capacity: 25_600
4629
cost: 1
47-
attrs:
48-
link_type: dc_to_pop
49-
match:
50-
conditions:
51-
- attr: role
52-
operator: "=="
53-
value: core
54-
- attr: role
55-
operator: "=="
56-
value: dc
57-
- attr: role
58-
operator: "=="
59-
value: leaf
60-
logic: "or"
30+
attrs: { link_type: dc_to_pop }
31+
role_pairs: ["dc|core", "dc|leaf"]
6132
build_overrides:
6233
- metros: [new-york-jersey-city-newark, columbus, washington-arlington]
6334
site_blueprint: Clos_16_8
@@ -79,13 +50,17 @@ build:
7950
respect_min_base_capacity: true
8051

8152
components:
82-
assignments:
83-
core:
84-
hw_component: CoreRouter
85-
leaf:
86-
hw_component: LeafRouter
87-
spine:
88-
hw_component: SpineRouter
53+
hw_component:
54+
core: CoreRouter
55+
leaf: LeafRouter
56+
spine: SpineRouter
57+
dc: {}
58+
optics:
59+
core|core: 800G-ZR+
60+
leaf|leaf: 800G-ZR+
61+
core|leaf: 800G-ZR+
62+
leaf|spine: 800G-DR4
63+
spine|leaf: 1600G-2xDR4
8964

9065
failure_policies:
9166
assignments:

tests/test_components_scenario_builder.py

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44

55
from topogen.blueprints_lib import get_builtin_blueprints
66
from topogen.config import (
7-
ComponentAssignment,
8-
ComponentAssignments,
9-
ComponentsConfig,
107
TopologyConfig,
118
)
129
from topogen.scenario_builder import (
@@ -19,19 +16,21 @@ class TestComponentsScenarioBuilder:
1916
"""Test component-related scenario builder functionality."""
2017

2118
def create_test_config(self) -> TopologyConfig:
22-
"""Create a test configuration with component assignments."""
19+
"""Create a test configuration with streamlined role mappings."""
2320
config = TopologyConfig()
24-
25-
# Set up component assignments
26-
config.components = ComponentsConfig(
27-
assignments=ComponentAssignments(
28-
spine=ComponentAssignment(hw_component="CoreRouter", optics="800G-ZR+"),
29-
leaf=ComponentAssignment(hw_component="CoreRouter", optics="800G-ZR+"),
30-
core=ComponentAssignment(hw_component="CoreRouter", optics="800G-ZR+"),
31-
dc=ComponentAssignment(hw_component="CoreRouter", optics="800G-ZR+"),
32-
),
33-
)
34-
21+
# Streamlined mapping: role -> platform, and role-pair -> optic (source end)
22+
config.components.hw_component = {
23+
"spine": "CoreRouter",
24+
"leaf": "CoreRouter",
25+
"core": "CoreRouter",
26+
"dc": "CoreRouter",
27+
}
28+
config.components.optics = {
29+
"core-core": "800G-ZR+",
30+
# Add a couple to exercise inclusion; actual adjacency tests are elsewhere
31+
"leaf-spine": "800G-DR4",
32+
"spine-leaf": "1600G-2xDR4",
33+
}
3534
return config
3635

3736
def test_build_components_section_basic(self):
@@ -92,8 +91,8 @@ def test_build_components_section_missing_component_warning(self):
9291
"""Test warning when referenced component is not found."""
9392
config = self.create_test_config()
9493

95-
# Reference a non-existent component
96-
config.components.assignments.spine.hw_component = "NonExistentChassis"
94+
# Reference a non-existent component via streamlined mapping
95+
config.components.hw_component["spine"] = "NonExistentChassis"
9796

9897
used_blueprints = {"Clos_64_256"}
9998

tests/test_site_graph_sot.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,12 @@ def test_sites_section_includes_per_site_blueprint_and_components():
3535
cfg.build.build_defaults.dc_region_blueprint = "DCRegion"
3636

3737
# Provide explicit role->component assignments to be serialized per site
38-
cfg.components.assignments.core.hw_component = "CoreRouter"
39-
cfg.components.assignments.leaf.hw_component = "LeafRouter"
40-
cfg.components.assignments.spine.hw_component = "SpineRouter"
41-
cfg.components.assignments.dc.hw_component = "CoreRouter"
38+
cfg.components.hw_component = {
39+
"core": "CoreRouter",
40+
"leaf": "LeafRouter",
41+
"spine": "SpineRouter",
42+
"dc": "CoreRouter",
43+
}
4244

4345
yaml_str = build_scenario(graph, cfg)
4446
data = yaml.safe_load(yaml_str)

topogen/cli.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,14 +116,11 @@ def build_command(args: argparse.Namespace) -> None:
116116
sys.exit(3) # Validation failure
117117
except ValueError as e:
118118
logger.error(f"Validation error: {e}")
119-
print(f"❌ Validation error: {e}")
120119
print("💡 Check input data quality and configuration parameters")
121120
sys.exit(3) # Validation failure
122121
except Exception as e:
123122
logger.error(f"Pipeline failed: {e}")
124-
print(f"❌ ERROR: {e}")
125123
print("💡 Use -v for detailed error information")
126-
127124
sys.exit(1) # Runtime error
128125

129126

topogen/config.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,11 @@ class LinkParams:
130130
cost: int
131131
attrs: dict[str, Any] = field(default_factory=dict)
132132
match: dict[str, Any] = field(default_factory=dict)
133+
# Unordered role-pairs allowed for this link type. Examples:
134+
# ["core|core", "core|leaf"] or [["core", "core"], ["core", "leaf"]]
135+
# The pipeline will auto-render a symmetric match from the union of roles.
136+
role_pairs: list[Any] = field(default_factory=list)
137+
# endpoint_roles removed: explicit direction should not be configured.
133138

134139

135140
@dataclass
@@ -280,6 +285,11 @@ class ComponentsConfig:
280285
"""
281286

282287
assignments: ComponentAssignments = field(default_factory=ComponentAssignments)
288+
# New streamlined mappings (preferred over assignments when provided)
289+
# hw_component: role -> platform component name
290+
# optics: "srcRole-dstRole" -> optic component name (applies to source end)
291+
hw_component: dict[str, str] = field(default_factory=dict)
292+
optics: dict[str, str] = field(default_factory=dict)
283293

284294

285295
@dataclass
@@ -653,6 +663,7 @@ def _from_dict(cls, config_dict: dict[str, Any]) -> TopologyConfig:
653663
**intra_metro_link_dict.get("attrs", {}),
654664
},
655665
match=intra_metro_link_dict.get("match", {}),
666+
role_pairs=intra_metro_link_dict.get("role_pairs", []) or [],
656667
)
657668
inter_metro_link = LinkParams(
658669
capacity=inter_metro_link_dict.get("capacity", 100),
@@ -662,6 +673,7 @@ def _from_dict(cls, config_dict: dict[str, Any]) -> TopologyConfig:
662673
**inter_metro_link_dict.get("attrs", {}),
663674
},
664675
match=inter_metro_link_dict.get("match", {}),
676+
role_pairs=inter_metro_link_dict.get("role_pairs", []) or [],
665677
)
666678
dc_to_pop_link = LinkParams(
667679
capacity=dc_to_pop_link_dict.get("capacity", 400),
@@ -671,6 +683,7 @@ def _from_dict(cls, config_dict: dict[str, Any]) -> TopologyConfig:
671683
**dc_to_pop_link_dict.get("attrs", {}),
672684
},
673685
match=dc_to_pop_link_dict.get("match", {}),
686+
role_pairs=dc_to_pop_link_dict.get("role_pairs", []) or [],
674687
)
675688

676689
# Create BuildDefaults with explicit parameters
@@ -783,7 +796,7 @@ def _from_dict(cls, config_dict: dict[str, Any]) -> TopologyConfig:
783796
tm_sizing=tm_sizing,
784797
)
785798

786-
# Handle optional components configuration (assignments only)
799+
# Handle optional components configuration (streamlined mappings)
787800
components_dict = config_dict.get("components", {})
788801
if not isinstance(components_dict, dict):
789802
raise ValueError("'components' configuration section must be a dictionary")
@@ -806,7 +819,21 @@ def _from_dict(cls, config_dict: dict[str, Any]) -> TopologyConfig:
806819
dc=dc_assignment,
807820
)
808821

809-
components = ComponentsConfig(assignments=assignments)
822+
# New streamlined mappings
823+
hw_component_map = components_dict.get("hw_component", {}) or {}
824+
optics_map = components_dict.get("optics", {}) or {}
825+
if hw_component_map is not None and not isinstance(hw_component_map, dict):
826+
raise ValueError(
827+
"'components.hw_component' must be a mapping when provided"
828+
)
829+
if optics_map is not None and not isinstance(optics_map, dict):
830+
raise ValueError("'components.optics' must be a mapping when provided")
831+
832+
components = ComponentsConfig(
833+
assignments=assignments,
834+
hw_component=hw_component_map if isinstance(hw_component_map, dict) else {},
835+
optics=optics_map if isinstance(optics_map, dict) else {},
836+
)
810837

811838
# Handle optional failure_policies configuration (assignments only)
812839
failure_policies_dict = config_dict.get("failure_policies", {})

0 commit comments

Comments
 (0)