Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/reference/api-full.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ For a curated, example-driven API guide, see **[api.md](api.md)**.
> - **[CLI Reference](cli.md)** - Command-line interface
> - **[DSL Reference](dsl.md)** - YAML syntax guide

**Generated from source code on:** August 06, 2025 at 21:33 UTC
**Generated from source code on:** August 07, 2025 at 00:14 UTC

**Modules auto-discovered:** 53

Expand Down
32 changes: 32 additions & 0 deletions docs/reference/dsl.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ The scenario YAML file is organized around a **core foundation** that defines yo

The main sections of a scenario YAML file work together to define a complete network simulation:

- `vars`: **[Optional]** Defines YAML anchors and variables for reuse throughout the scenario file.
- `network`: **[Required]** Describes the actual network topology - nodes, links, and their connections.
- `blueprints`: **[Optional]** Defines reusable network templates that can be instantiated multiple times within the network.
- `components`: **[Optional]** A library of hardware and optics definitions with attributes like power consumption.
Expand Down Expand Up @@ -442,6 +443,37 @@ failure_policy_set:
- If only one policy exists and no `default` is specified, that policy becomes the default
- Multiple policies allow testing different failure scenarios in the same network

## `vars` - YAML Anchors and Variables

The `vars` section provides a designated space for YAML anchor definitions. YAML anchors (`&name`) and aliases (`*name`) follow the YAML 1.1 specification and are processed by PyYAML during parsing, before NetGraph validation.

**Anchor Types:**

- **Scalar anchors**: Reference primitive values (strings, numbers, booleans)
- **Sequence anchors**: Reference arrays/lists
- **Mapping anchors**: Reference objects/dictionaries
- **Merge keys (`<<`)**: Merge mapping properties with override capability

**Minimal Example:**

```yaml
vars:
default_cap: &cap 10000
base_attrs: &attrs {cost: 100, region: "dc1"}

network:
nodes:
N1: {attrs: {<<: *attrs, capacity: *cap}}
N2: {attrs: {<<: *attrs, capacity: *cap, region: "dc2"}}
```

**Processing Behavior:**

- Anchors are resolved during YAML parsing, before schema validation
- The `vars` section itself is ignored by NetGraph runtime logic
- Anchors can be defined in any section, not just `vars`
- Merge operations follow YAML 1.1 semantics (later keys override earlier ones)

## `workflow` - Execution Steps

A list of operations to perform on the network. Each step has a `step_type` and specific arguments. This section defines the analysis workflow to be executed.
Expand Down
3 changes: 3 additions & 0 deletions ngraph/scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ def from_yaml(
with a default ComponentsLibrary if provided.

Top-level YAML keys can include:
- vars
- blueprints
- network
- failure_policy_set
Expand All @@ -92,6 +93,7 @@ def from_yaml(
If 'failure_policy_set' is omitted, scenario.failure_policy_set is empty.
If 'components' is provided, it is merged with default_components.
If 'seed' is provided, it enables reproducible random operations.
If 'vars' is provided, it can contain YAML anchors and aliases for reuse.
If any unrecognized top-level key is found, a ValueError is raised.

Args:
Expand All @@ -115,6 +117,7 @@ def from_yaml(

# Ensure only recognized top-level keys are present.
recognized_keys = {
"vars",
"blueprints",
"network",
"failure_policy_set",
Expand Down
4 changes: 4 additions & 0 deletions schemas/scenario.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
"description": "JSON Schema for NetGraph network scenario YAML files",
"type": "object",
"properties": {
"vars": {
"type": "object",
"description": "YAML anchors and variable definitions for reuse throughout the scenario"
},
"seed": {
"type": "integer",
"description": "Master seed for reproducible random operations across the scenario"
Expand Down
84 changes: 84 additions & 0 deletions tests/test_scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -645,3 +645,87 @@ def test_failure_policy_docstring_yaml_full_scenario_integration():
# N2, N3 should not fail (different electric grids)
assert "N2" not in failed
assert "N3" not in failed


def test_yaml_anchors_and_aliases():
"""Test that YAML anchors and aliases work correctly with the vars section."""
scenario_yaml = """
vars:
default_capacity: &default_cap 100
common_attrs: &common_attrs
region: "datacenter1"
type: "switch"

network:
name: "anchor_test"
nodes:
N1:
attrs:
<<: *common_attrs
capacity: *default_cap
N2:
attrs:
<<: *common_attrs
capacity: *default_cap
type: "router" # Override the type
links:
- source: N1
target: N2
link_params:
capacity: *default_cap

traffic_matrix_set:
default: []
"""

# Should load without errors
scenario = Scenario.from_yaml(scenario_yaml)

# Verify the anchors were properly expanded
n1_attrs = scenario.network.nodes["N1"].attrs
n2_attrs = scenario.network.nodes["N2"].attrs

# Both nodes should have the common attributes
assert n1_attrs["region"] == "datacenter1"
assert n2_attrs["region"] == "datacenter1"
assert n1_attrs["capacity"] == 100
assert n2_attrs["capacity"] == 100

# N1 should have the original type from the anchor
assert n1_attrs["type"] == "switch"
# N2 should have the overridden type
assert n2_attrs["type"] == "router"

# The link should also use the anchored capacity
link = list(scenario.network.links.values())[0]
assert link.capacity == 100


def test_yaml_anchors_without_vars_section():
"""Test that YAML anchors work even without an explicit vars section."""
scenario_yaml = """
network:
name: "anchor_test"
nodes:
N1: &common_node
attrs:
region: "datacenter1"
type: "switch"
N2:
<<: *common_node
attrs:
region: "datacenter2"
type: "router"
links: []

traffic_matrix_set:
default: []
"""

# Should load without errors
scenario = Scenario.from_yaml(scenario_yaml)

# Verify the network was created correctly
assert len(scenario.network.nodes) == 2
assert "N1" in scenario.network.nodes
assert "N2" in scenario.network.nodes
13 changes: 13 additions & 0 deletions tests/test_schema_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,19 @@ def test_schema_requires_risk_group_name(self, schema):
with pytest.raises(jsonschema.ValidationError):
jsonschema.validate(invalid_data, schema)

def test_schema_validates_vars_section(self, schema):
"""Test that the schema validates the vars section for YAML anchors."""
valid_data = {
"vars": {
"default_capacity": 100,
"common_attrs": {"region": "datacenter1", "type": "switch"},
},
"network": {"nodes": {}, "links": []},
}

# Should not raise any validation errors
jsonschema.validate(valid_data, schema)

def test_schema_validates_link_risk_groups(self, schema):
"""Test that the schema validates risk_groups in link_params."""
valid_data = {
Expand Down