netgraph-dsl
NetGraph scenario DSL for defining network topologies, traffic demands, failure policies, and analysis workflows in YAML. Use when: creating or editing .yaml/.yml network scenarios, defining nodes/links/groups, writing link rules with patterns, configuring selectors or blueprints, setting up traffic demands or failure policies, running scenarios and interpreting results, debugging DSL syntax or validation errors, or asking about NetGraph scenario structure.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install networmix-netgraph-netgraph-dsl
Repository
Skill path: .claude/skills/netgraph-dsl
NetGraph scenario DSL for defining network topologies, traffic demands, failure policies, and analysis workflows in YAML. Use when: creating or editing .yaml/.yml network scenarios, defining nodes/links/groups, writing link rules with patterns, configuring selectors or blueprints, setting up traffic demands or failure policies, running scenarios and interpreting results, debugging DSL syntax or validation errors, or asking about NetGraph scenario structure.
Open repositoryBest for
Primary workflow: Write Technical Docs.
Technical facets: Full Stack, Tech Writer, Testing.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: networmix.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install netgraph-dsl into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/networmix/NetGraph before adding netgraph-dsl to shared team environments
- Use netgraph-dsl for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: netgraph-dsl
description: >
NetGraph scenario DSL for defining network topologies, traffic demands, failure policies,
and analysis workflows in YAML. Use when: creating or editing .yaml/.yml network scenarios,
defining nodes/links/groups, writing link rules with patterns, configuring selectors or blueprints,
setting up traffic demands or failure policies, running scenarios and interpreting results,
debugging DSL syntax or validation errors, or asking about NetGraph scenario structure.
---
# NetGraph DSL
Define network simulation scenarios in YAML format.
> **Quick Start**: See [Minimal Example](#minimal-example) below.
> **Complete Reference**: See [references/REFERENCE.md](references/REFERENCE.md) for full documentation.
## Instructions
When working with NetGraph scenarios:
1. **Creating new scenarios**: Start with the [Minimal Example](#minimal-example), then add sections as needed
2. **Editing existing scenarios**: Identify the relevant section (network, demands, failures, etc.)
3. **Understanding selection**: Review [Selection Models](#selection-models) to understand path-based vs condition-based selection
4. **Debugging issues**: Check [Common Pitfalls](#common-pitfalls) and [Validation Checklist](#validation-checklist)
5. **Complex topologies**: Use [Blueprints](#blueprints) for reusable patterns
6. **Failure simulation**: Define [Risk Groups](#risk-groups) before creating failure policies
Refer to specific sections below for detailed syntax and examples.
## Quick Reference
| Section | Purpose |
|---------|---------|
| `network` | Topology: nodes, links (required) |
| `blueprints` | Reusable topology templates |
| `components` | Hardware library for cost/power modeling |
| `risk_groups` | Failure correlation groups |
| `vars` | YAML anchors for value reuse |
| `demands` | Traffic demand definitions |
| `failures` | Failure simulation rules |
| `workflow` | Analysis execution steps |
| `seed` | Master seed for reproducibility |
## Minimal Example
```yaml
network:
nodes:
A: {}
B: {}
links:
- source: A
target: B
capacity: 100
cost: 1
```
## Core Patterns
### Selection Models
The DSL implements two distinct selection patterns:
**1. Path-based Node Selection** (link rules, traffic demands, workflow steps)
- Uses regex patterns on hierarchical node names
- Supports capture group-based grouping
- Supports attribute-based grouping (`group_by`)
- Supports attribute filtering (`match` conditions)
- Supports `active_only` filtering
**2. Condition-based Entity Selection** (failure rules, membership rules, risk group generation)
- Works on nodes/links; failure + membership can also target risk_groups
- Optional regex `path` filter on entity names/IDs (no capture grouping)
- Attribute filtering via `match.conditions` (`match.logic` defaults vary by context)
These patterns share common primitives (condition evaluation, match specification) but serve different purposes and should not be confused.
> **For comprehensive details** on entity creation flows, processing steps, and comparison tables, see the [Entity Creation Architecture](references/REFERENCE.md#entity-creation-architecture) section in the full reference.
### Nodes and Links
```yaml
network:
nodes:
Seattle:
attrs: # Custom attributes go here
role: core
risk_groups: ["RG1"]
disabled: false
Portland:
attrs:
role: edge
links:
- source: Seattle
target: Portland
capacity: 100 # Direct properties (no wrapper)
cost: 10
attrs:
distance_km: 280
count: 2 # Parallel links
```
### Node Groups
```yaml
network:
nodes:
leaf:
count: 4
template: "leaf-{n}"
attrs:
role: leaf
```
Creates: `leaf/leaf-1`, `leaf/leaf-2`, `leaf/leaf-3`, `leaf/leaf-4`
### Template Syntaxes
| Syntax | Example | Context |
|--------|---------|---------|
| `[1-3]` | `dc[1-3]/rack` | Group names, risk groups |
| `$var`/`${var}` | `pod${p}/leaf` | Links, rules, demands |
| `{n}` | `srv-{n}` | `template` field |
These are NOT interchangeable. See [REFERENCE.md](references/REFERENCE.md) for details.
### Bracket Expansion
```yaml
network:
nodes:
dc[1-3]/rack[a,b]: # Cartesian product
count: 4
template: "srv-{n}"
```
Creates: `dc1/racka`, `dc1/rackb`, `dc2/racka`, `dc2/rackb`, `dc3/racka`, `dc3/rackb`
**Scope**: Bracket expansion works in group names, risk group definitions (including children), and risk group membership arrays. Component names and other fields treat brackets as literal characters.
### Link Patterns
```yaml
network:
links:
- source: /leaf
target: /spine
pattern: mesh # Every source to every target
capacity: 100
- source: /group_a # 4 nodes
target: /group_b # 2 nodes
pattern: one_to_one # Pairwise with modulo wrap (sizes must have multiple factor)
```
### Selectors with Conditions
```yaml
network:
links:
- source:
path: "/datacenter"
match:
logic: and # "and" or "or"; defaults vary by context (see below)
conditions:
- attr: role
op: "=="
value: leaf
target: /spine
pattern: mesh
```
**Operators**: `==`, `!=`, `<`, `<=`, `>`, `>=`, `contains`, `not_contains`, `in`, `not_in`, `exists`, `not_exists`
**Logic defaults by context (for `match.logic`)**:
| Context | Default `logic` | Rationale |
|---------|-----------------|-----------|
| Link `match` | `"or"` | Inclusive: match any condition |
| Demand `match` | `"or"` | Inclusive: match any condition |
| Membership rules | `"and"` | Precise: must match all conditions |
| Failure rules | `"or"` | Inclusive: match any condition |
### Capturing Groups for Grouping
```yaml
# Single capture group creates groups by captured value
source: "^(dc[1-3])/.*" # Groups: dc1, dc2, dc3
# Multiple capture groups join with |
source: "^(dc\\d+)/(spine|leaf)/.*" # Groups: dc1|spine, dc1|leaf, etc.
```
### Variable Expansion
```yaml
links:
- source: "plane${p}/rack"
target: "spine${s}"
expand:
vars:
p: [1, 2]
s: [1, 2, 3]
mode: cartesian # or "zip" (equal-length lists required)
pattern: mesh
```
**Expansion limit**: Maximum 10,000 expansions per template. Exceeding this raises an error.
### Blueprints
```yaml
blueprints:
clos_pod:
nodes:
leaf:
count: 4
template: "leaf-{n}"
spine:
count: 2
template: "spine-{n}"
links:
- source: /leaf
target: /spine
pattern: mesh
capacity: 100
network:
nodes:
pod[1-2]:
blueprint: clos_pod
params:
leaf.count: 6 # Override defaults
```
**Alternative: Inline nested nodes** (no blueprint needed):
```yaml
network:
nodes:
datacenter:
nodes: # Inline hierarchy
rack1:
count: 2
template: "srv-{n}"
```
### Node and Link Rules
Modify entities after creation with optional attribute filtering:
```yaml
network:
node_rules:
- path: "^pod1/.*"
match: # Optional: filter by attributes
conditions:
- {attr: role, op: "==", value: compute}
disabled: true
link_rules:
- source: "^pod1/.*"
target: "^pod2/.*"
link_match: # Optional: filter by link attributes
conditions:
- {attr: capacity, op: ">=", value: 400}
cost: 99
```
Rules also support `expand` for variable-based application.
### Traffic Demands
```yaml
demands:
production:
- source: "^dc1/.*"
target: "^dc2/.*"
volume: 1000
mode: pairwise # or "combine"
flow_policy: SHORTEST_PATHS_ECMP
```
**Flow policies**: `SHORTEST_PATHS_ECMP`, `SHORTEST_PATHS_WCMP`, `TE_WCMP_UNLIM`, `TE_ECMP_16_LSP`, `TE_ECMP_UP_TO_256_LSP`
### Failure Policies
```yaml
failures:
single_link:
expand_groups: false # Expand to shared-risk entities
modes: # Weighted modes (one selected per iteration)
- weight: 1.0
rules:
- scope: link # Required: node, link, or risk_group
mode: choice # all, choice, or random
count: 1
# Optional: weight_by: capacity # Weighted sampling by attribute
```
**Rule modes**: `all` (select all matches), `choice` (sample `count`), `random` (each with `probability`)
### Risk Groups
Risk groups model failure correlation (shared infrastructure, geographic regions, vendor dependencies, or any custom domain). Three methods:
**Direct definition:**
```yaml
risk_groups:
- name: "RG1" # Full form
- "RG2" # String shorthand (equivalent to {name: "RG2"})
```
**Membership rules** (assign entities by attribute matching; optional `path` filter):
```yaml
risk_groups:
- name: HighCapacityLinks
membership:
scope: link # Required: node, link, or risk_group
match:
logic: and # "and" or "or" (default: "and" for membership)
conditions:
- attr: capacity
op: ">="
value: 1000
```
**Generate blocks** (create groups from unique attribute values; optional `path` filter):
```yaml
risk_groups:
- generate:
scope: node # Required: node or link only
path: "^prod_.*" # Optional: narrow entities before grouping
group_by: region # Any attribute to group by
name: "Region_${value}"
```
**Validation:** Risk group references are validated at load time (undefined references and circular hierarchies detected).
See [REFERENCE.md](references/REFERENCE.md) for complete details.
### Workflow
```yaml
workflow:
- type: NetworkStats
name: stats
- type: MaximumSupportedDemand
name: msd
demand_set: production
alpha_start: 1.0
resolution: 0.05
- type: TrafficMatrixPlacement
name: placement
demand_set: production
failure_policy: single_link
iterations: 1000
alpha_from_step: msd # Reference MSD result
alpha_from_field: data.alpha_star
- type: MaxFlow
source: "^(dc[1-3])$"
target: "^(dc[1-3])$"
mode: pairwise
failure_policy: single_link
iterations: 1000
seed: 42 # Optional: for reproducibility
```
**Step types**: `BuildGraph`, `NetworkStats`, `MaxFlow`, `TrafficMatrixPlacement`, `MaximumSupportedDemand`, `CostPower`
## Common Pitfalls
### 1. Custom fields must go in `attrs`
```yaml
# WRONG
nodes:
A:
my_field: value # Error!
# CORRECT
nodes:
A:
attrs:
my_field: value
```
### 2. Link properties are flattened (no wrapper)
```yaml
# WRONG
links:
- source: A
target: B
link_params: # Error! No wrapper
capacity: 100
# CORRECT
links:
- source: A
target: B
capacity: 100 # Direct property
```
### 3. `one_to_one` requires compatible sizes
Sizes must have a multiple factor (4-to-2 OK, 3-to-2 ERROR).
### 4. Path patterns are anchored at start
```yaml
path: "leaf" # Only matches names STARTING with "leaf"
path: ".*leaf.*" # Matches "leaf" anywhere
```
**Note**: Leading `/` is stripped and has no effect. `/leaf` and `leaf` are equivalent. All paths are relative to the current scope (blueprint instantiation path or network root).
### 5. Variable syntax uses `$` prefix
```yaml
# WRONG (conflicts with regex {m,n})
source: "{dc}/leaf"
# CORRECT
source: "${dc}/leaf"
```
### 6. `zip` requires equal-length lists
```yaml
# WRONG
expand:
vars:
a: [1, 2]
b: [x, y, z] # Length mismatch!
mode: zip
```
### 7. Processing order matters
1. Groups and direct nodes created
2. Node rules applied
3. Blueprint links expanded
4. Top-level links expanded (including direct links)
5. Link rules applied
Rules only affect entities that exist at their processing stage.
## Validation Checklist
- [ ] Custom fields inside `attrs`
- [ ] Link properties directly on link (no wrapper)
- [ ] Referenced blueprints exist
- [ ] Node names in direct links exist
- [ ] `one_to_one` sizes have multiple factor
- [ ] `zip` lists have equal length
- [ ] Selectors have at least one of: `path`, `group_by`, `match`
## More Information
- [Full DSL Reference](references/REFERENCE.md) - Complete field documentation, all operators, workflow steps
- [Working Examples](references/EXAMPLES.md) - 22 complete scenarios from simple to advanced
## Running Scenarios
**Always validate before running:**
```bash
./venv/bin/ngraph inspect scenario.yaml && ./venv/bin/ngraph run scenario.yaml
```
### CLI Commands
| Command | Purpose |
|---------|---------|
| `ngraph inspect <file>` | Validate and summarize scenario |
| `ngraph inspect -d <file>` | Detailed view with node/link tables |
| `ngraph run <file>` | Execute and write `<file>.results.json` |
| `ngraph run <file> --stdout` | Execute and print results to stdout |
| `ngraph run <file> --profile` | Execute with CPU profiling |
### Success Indicators
**inspect success:**
```
✓ YAML file loaded successfully
✓ Scenario structure is valid
```
**run success:**
```
✅ Scenario execution completed
✅ Results written to: scenario.results.json
```
Exit code 0, results JSON created.
### Failure Indicators
**Schema validation error:**
```
❌ ERROR: Failed to inspect scenario
ValidationError: Additional properties are not allowed ('bad_field' was unexpected)
On instance['network']['nodes']['A']:
{'bad_field': 'x'}
```
Fix: Move custom fields inside `attrs: {}`.
**Missing node reference:**
```
ValueError: Source node 'X' not found in network
```
Fix: Check node name spelling or pattern matching.
**Empty selector match:**
```
WARNING: No nodes matched selector
```
Fix: Verify regex pattern matches actual node names.
### Interpreting Results
Results JSON structure:
```json
{
"steps": {
"<step_name>": {
"metadata": { "duration_sec": 0.5 },
"data": { ... }
}
}
}
```
**Key metrics by step type:**
| Step | Key Field | Good Value | Problem |
|------|-----------|------------|---------|
| MaximumSupportedDemand | `alpha_star` | >= 1.0 | < 1.0 means network undersized |
| TrafficMatrixPlacement | `flow_results[].summary.total_dropped` | 0 | > 0 means congestion |
| MaxFlow | `flow_results[].summary.total_placed` | = total_demand | < demand means bottleneck |
**Quick validation after run:**
```bash
# Check alpha_star (should be >= 1.0)
grep -o '"alpha_star": [0-9.]*' scenario.results.json
# Check for dropped traffic (should be 0)
grep -o '"total_dropped": [0-9.]*' scenario.results.json | head -5
```
### Common Errors and Fixes
| Error | Cause | Fix |
|-------|-------|-----|
| `Additional properties are not allowed` | Custom field outside `attrs` | Move to `attrs: {field: value}` |
| `Source node 'X' not found` | Link references non-existent node | Fix node name or create the node |
| `one_to_one pattern requires sizes with multiple factor` | Mismatched group sizes | Use sizes like 4-to-2, not 3-to-2 |
| `Variable '$x' not found in expand.vars` | Missing variable definition | Add to `expand: {vars: {x: [...]}}` |
| `zip expansion requires equal-length lists` | Lists have different lengths | Make lists same length or use `cartesian` |
### Profiling Scenarios
When performance matters:
```bash
./venv/bin/ngraph run scenario.yaml --profile --profile-memory
```
Output shows:
- **Step timing**: Time per workflow step
- **Bottlenecks**: Steps taking >10% of total time
- **Memory**: Peak memory per step (with `--profile-memory`)
- **Recommendations**: Optimization suggestions
### Iteration Pattern
1. Write scenario YAML
2. `./venv/bin/ngraph inspect scenario.yaml` - fix any errors
3. `./venv/bin/ngraph run scenario.yaml`
4. Check results: `alpha_star`, `total_dropped`
5. If results bad -> adjust topology/demands -> repeat
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### references/REFERENCE.md
```markdown
# NetGraph DSL Reference
Complete reference documentation for the NetGraph scenario DSL.
> For a quick start guide and common patterns, see the main [SKILL.md](../SKILL.md).
> For complete working examples, see [EXAMPLES.md](EXAMPLES.md).
## Syntax Overview
### Template and Expansion Syntaxes
NetGraph DSL uses three distinct template syntaxes in different contexts:
| Syntax | Example | Where | Purpose |
|--------|---------|-------|---------|
| **Brackets** `[1-3]` | `dc[1-3]/rack[a,b]` | Group names, risk groups | Generate multiple entities |
| **Variables** `$var` | `pod${p}/leaf` | Links, rules, demands | Template expansion |
| **Format** `{n}` | `srv-{n}` | `template` | Node naming |
**Important**: These syntaxes are NOT interchangeable:
- `[1-3]` works in group names and risk groups (definitions and memberships), not components
- `${var}` requires `expand.vars` dict; works in link `source`/`target`, demand `source`/`target`, and node/link rules (via `expand` blocks)
- `{n}` is the only placeholder available in `template` (Python format syntax)
### Endpoint Naming Conventions
| Context | Fields | Terminology |
|---------|--------|-------------|
| Links, link_rules | `source`, `target` | Graph edge |
| Traffic demands, workflow steps | `source`, `target` | Max-flow |
### Expansion Controls in Traffic Demands
Traffic demands have three expansion-related fields:
| Field | Values | Default | Purpose |
|-------|--------|---------|---------|
| `mode` | `combine`, `pairwise` | `combine` | How source/target nodes pair |
| `group_mode` | `flatten`, `per_group`, `group_pairwise` | `flatten` | How grouped nodes expand |
| `expand.mode` | `cartesian`, `zip` | `cartesian` | How `expand.vars` combine |
See detailed sections below for each mechanism.
## Entity Creation Architecture
The DSL implements two fundamentally different selection patterns optimized for different use cases. Understanding these patterns is essential for effective scenario authoring.
### Two Selection Models
The DSL uses distinct selection strategies depending on the operation:
**1. Path-Based Node Selection** (link rules, traffic demands, workflow steps)
- Uses regex patterns on hierarchical node names
- Supports capture group-based grouping
- Supports attribute-based grouping (`group_by`)
- Supports attribute filtering (`match` conditions)
- Supports `active_only` filtering
**2. Condition-Based Entity Selection** (failure rules, membership rules, risk group generation)
- Works on nodes/links; failure + membership can also target risk_groups
- Optional regex `path` filter on entity names/IDs (no capture grouping)
- Attribute filtering via `match.conditions` for failure/membership; generate uses `group_by` only
These patterns share common primitives (condition evaluation, match specification) but serve different purposes and should not be confused.
### Link Creation Flow
Link definitions create links between nodes using path-based selection with optional filtering:
```mermaid
flowchart TD
Start[Link Definition] --> VarExpand{Has expand.vars?}
VarExpand -->|Yes| VarSubst[Variable Substitution]
VarSubst --> PathFilter
VarExpand -->|No| PathFilter[1. Path-Based Selection]
PathFilter --> PathDesc[Select nodes via regex pattern<br/>Groups by capture groups]
PathDesc --> MatchFilter{Has match conditions?}
MatchFilter -->|Yes| AttrFilter[2. Attribute Filtering]
MatchFilter -->|No| ActiveFilter
AttrFilter --> AttrDesc[Filter by attribute conditions<br/>using logic and/or]
AttrDesc --> ActiveFilter[3. Active/Excluded Filtering]
ActiveFilter --> GroupBy{Has group_by?}
GroupBy -->|Yes| Regroup[4. Re-group by Attribute]
GroupBy -->|No| Pattern
Regroup --> Pattern[5. Apply Pattern]
Pattern --> PatternDesc[mesh or one_to_one<br/>Creates links between groups]
```
**Processing Steps:**
1. **Path Selection**: Regex pattern matches nodes by hierarchical name
- Capture groups create initial grouping
- If no path specified, selects all nodes
2. **Attribute Filtering**: Optional `match` conditions filter nodes
- Uses `logic: "and"` or `"or"` (default: `"or"`)
- Supports operators: `==`, `!=`, `<`, `>`, `contains`, `in`, etc.
3. **Active Filtering**: Filters disabled nodes based on context
- Link default: `active_only=false` (creates links to disabled nodes)
4. **Attribute Grouping**: Optional `group_by` overrides regex capture grouping
5. **Pattern Application**: Creates links between selected node groups
- `mesh`: Every source to every target
- `one_to_one`: Pairwise with wrap-around
**Key Characteristics:**
- `default_active_only=False` (links are created to disabled nodes)
- `match.logic` defaults to `"or"` (inclusive matching)
- Supports variable expansion via `expand.vars`
### Traffic Demand Creation Flow
Traffic demands follow a similar pattern but with important differences:
```mermaid
flowchart TD
Start[Traffic Demand Spec] --> VarExpand{Has expand.vars?}
VarExpand -->|Yes| VarSubst[Variable Substitution<br/>Creates multiple demand specs]
VarSubst --> Process
VarExpand -->|No| Process[Process Single Demand]
Process --> SrcSelect[1. Select Source Nodes]
SrcSelect --> TgtSelect[2. Select Target Nodes]
TgtSelect --> SrcDesc[Uses same path + match + group_by<br/>selection as links]
SrcDesc --> Mode{Demand Mode?}
Mode -->|pairwise| Pairwise[3a. Pairwise Expansion]
Mode -->|combine| Combine[3b. Combine Expansion]
Pairwise --> PairDesc[Create demand for each src-dst pair<br/>Volume distributed evenly<br/>No pseudo nodes]
Combine --> CombDesc[Create pseudo-source and pseudo-target<br/>Single aggregated demand<br/>Augmentation edges connect real nodes]
```
**Key Differences from Links:**
1. **Active-only default**: `default_active_only=True` (only active nodes participate)
2. **Two selection phases**: Source nodes first, then target nodes (both use same selector logic)
3. **Expansion modes**:
- **Pairwise**: Creates individual demands for each (source, target) pair
- **Combine**: Creates pseudo nodes and a single aggregated demand
4. **Group modes**: Additional layer (`flatten`, `per_group`, `group_pairwise`) for handling grouped selections
**Processing Steps:**
1. Select source nodes using unified selector (path + match + group_by)
2. Select target nodes using unified selector
3. Apply mode-specific expansion:
- **Pairwise**: Volume evenly distributed across all pairs
- **Combine**: Single demand with pseudo nodes for aggregation
### Risk Group Creation Flow
Risk groups use the condition-based selection model:
```mermaid
flowchart TD
Start[Risk Groups Definition] --> Three[Three Creation Methods]
Three --> Direct[1. Direct Definition]
Three --> Member[2. Membership Rules]
Three --> Generate[3. Generate Blocks]
Direct --> DirectDesc[Simply name the risk group<br/>Entities reference it explicitly]
Member --> MemberScope[Specify scope<br/>node, link, or risk_group]
MemberScope --> MemberCond[Optional path filter<br/>Match conditions (logic defaults to and)]
MemberCond --> MemberExec[Scan entities of that scope<br/>Apply path + match]
Generate --> GenScope[Specify scope<br/>node or link only]
GenScope --> GenGroupBy[Specify group_by attribute]
GenGroupBy --> GenExec[Collect unique values<br/>Create risk group for each value<br/>Add entities with that value]
```
**Creation Methods:**
1. **Direct Definition**: Explicitly name risk groups, entities reference them
2. **Membership Rules**: Auto-assign entities based on attribute matching
3. **Generate Blocks**: Auto-create risk groups from unique attribute values
**Key Characteristics:**
- **Optional path filter**: Regex `path` narrows entities before matching
- **Membership uses `match.conditions`**; generate uses `group_by` only
- **`match.logic` defaults to "and"** for membership (stricter matching)
- **Hierarchical support**: Risk groups can contain other risk groups as children
### Comparison Table
| Feature | Links | Traffic Demands | Risk Groups |
|---------|-------|----------------|-------------|
| Selection Type | Path-based | Path-based | Condition-based |
| Regex Patterns | Yes | Yes | Yes (path filter) |
| Capture Groups | Yes | Yes | No |
| `group_by` | Yes | Yes | Yes (generate only) |
| `match` Conditions | Yes | Yes | Yes (membership/failure) |
| `active_only` Default | False | True | N/A |
| `match.logic` Default | "or" | "or" | "and" (membership) |
| Variable Expansion | Yes | Yes | No |
| Entity Scope | Nodes only | Nodes only | Nodes, links, risk_groups (generate: node/link) |
### Shared Evaluation Primitives
All selection mechanisms share common evaluation primitives:
1. **Condition evaluation**: `evaluate_condition()` handles all operators
- Comparison: `==`, `!=`, `<`, `<=`, `>`, `>=`
- String/collection: `contains`, `not_contains`, `in`, `not_in`
- Existence: `exists`, `not_exists`
2. **Condition combining**: `evaluate_conditions()` applies `"and"`/`"or"` logic
3. **Attribute flattening**: Unified access to entity attributes
- `flatten_node_attrs()`: Merges node.attrs with top-level fields
- `flatten_link_attrs()`: Merges link.attrs with top-level fields
- `flatten_risk_group_attrs()`: Merges risk_group.attrs with top-level fields
4. **Dot-notation support**: `resolve_attr_path()` handles nested attributes
- Example: `hardware.vendor` resolves to `attrs["hardware"]["vendor"]`
5. **Variable expansion**: `expand_templates()` handles `$var` and `${var}` substitution
- Supports `cartesian` and `zip` expansion modes
### Context-Aware Defaults
The DSL uses context-aware defaults to optimize for common use cases:
| Context | Selection Type | Active Only | Match Logic | Rationale |
|---------|---------------|-------------|-------------|-----------|
| Links | Path-based | False | "or" | Create links to all nodes, including disabled |
| Demands | Path-based | True | "or" | Only route traffic through active nodes |
| Node Rules | Path-based | False | "or" | Modify all matching nodes |
| Workflow Steps | Path-based | True | "or" | Analyze only active topology |
| Membership Rules | Condition-based | N/A | "and" | Precise matching for risk assignment |
| Failure Rules | Condition-based | N/A | "or" | Inclusive matching for failure scenarios |
| Generate Blocks | Condition-based | N/A | N/A | No conditions, groups by values |
These defaults ensure intuitive behavior while remaining overridable when needed.
## Top-Level Keys
| Key | Required | Purpose |
|-----|----------|---------|
| `network` | Yes | Network topology (nodes, links) |
| `blueprints` | No | Reusable topology templates |
| `components` | No | Hardware component library |
| `risk_groups` | No | Failure correlation groups |
| `vars` | No | YAML anchors for value reuse |
| `demands` | No | Traffic demand definitions |
| `failures` | No | Failure simulation policies |
| `workflow` | No | Analysis execution steps |
| `seed` | No | Master seed for reproducible random operations |
## Network Metadata
```yaml
network:
name: "My Network" # Optional: network name
version: "1.0" # Optional: version string or number
nodes: ...
links: ...
```
## Network Topology
### Direct Node Definitions
```yaml
network:
nodes:
Seattle:
disabled: false # Optional: disable node
risk_groups: ["RG1"] # Optional: failure correlation
attrs: # Optional: custom attributes
coords: [47.6062, -122.3321]
role: core
hardware:
component: "SpineRouter"
count: 1
```
**Allowed node keys**: `disabled`, `attrs`, `risk_groups`, `count`, `template`, `blueprint`, `params`, `nodes`
### Direct Link Definitions
```yaml
network:
links:
- source: Seattle
target: Portland
capacity: 100.0 # Direct property
cost: 10
disabled: false
risk_groups: ["RG_Seattle_Portland"]
attrs:
distance_km: 280
media_type: fiber
hardware:
source:
component: "800G-ZR+"
count: 1
exclusive: false # Optional: unsharable usage (rounds up)
target:
component: "800G-ZR+"
count: 1
count: 2 # Optional: parallel links
```
**Allowed link keys**: `source`, `target`, `pattern`, `count`, `capacity`, `cost`, `disabled`, `risk_groups`, `attrs`, `expand`
**Link hardware per-end fields**: `component`, `count`, `exclusive`
### Node Groups
Groups create multiple nodes from a template (distinguished by having `count` field):
```yaml
network:
nodes:
servers:
count: 4
template: "srv-{n}"
disabled: false
risk_groups: ["RG_Servers"]
attrs:
role: compute
```
Creates: `servers/srv-1`, `servers/srv-2`, `servers/srv-3`, `servers/srv-4`
**Group-specific keys**: `count`, `template`
### Nested Inline Nodes
Create hierarchical structures without blueprints using inline `nodes`:
```yaml
network:
nodes:
datacenter:
attrs:
region: west
nodes:
rack1:
count: 2
template: "srv-{n}"
attrs:
role: compute
rack2:
count: 2
template: "srv-{n}"
```
Creates: `datacenter/rack1/srv-1`, `datacenter/rack1/srv-2`, `datacenter/rack2/srv-1`, `datacenter/rack2/srv-2`
**Key points:**
- Child nodes inherit parent `attrs`, `disabled`, and `risk_groups`
- Child-specific values override inherited ones
- Can be nested to any depth
- Useful for simple hierarchies without reusable blueprints
**Allowed keys for nested containers**: `nodes`, `attrs`, `disabled`, `risk_groups`
### Bracket Expansion
Create multiple similar groups using bracket notation:
```yaml
network:
nodes:
dc[1-3]/rack[a,b]:
count: 4
template: "srv-{n}"
```
**Expansion types**:
- Numeric ranges: `[1-4]` -> 1, 2, 3, 4
- Explicit lists: `[a,b,c]` -> a, b, c
- Mixed: `[1,3,5-7]` -> 1, 3, 5, 6, 7
Multiple brackets create Cartesian product.
**Scope**: Bracket expansion applies to:
- **Group names** under `network.nodes` and `blueprints.*.nodes`
- **Risk group names** in top-level `risk_groups` definitions (including children)
- **Risk group membership arrays** on nodes, links, and groups
It does NOT apply to: component names, direct node names without `count`, or other string fields.
**Risk group expansion examples**:
```yaml
# Definition expansion - creates DC1_Power, DC2_Power, DC3_Power
risk_groups:
- name: "DC[1-3]_Power"
# Membership expansion - assigns to RG1, RG2, RG3
network:
nodes:
Server:
risk_groups: ["RG[1-3]"]
```
### Path Patterns
Path patterns in selectors and rules are **regex patterns** matched against node names using `re.match()` (anchored at start).
**Key behaviors**:
- Paths are matched from the **start** of the node name (no implicit `.*` prefix)
- Node names are hierarchical: `group/subgroup/node1`
- Leading `/` is stripped before matching (has no functional effect)
- All paths are relative to the current scope
**Examples**:
| Pattern | Matches | Does NOT Match |
|---------|---------|----------------|
| `leaf` | `leaf/leaf1`, `leaf/leaf2` | `pod1/leaf/leaf1` |
| `pod1/leaf` | `pod1/leaf/leaf1` | `pod2/leaf/leaf1` |
| `.*leaf` | `leaf/leaf1`, `pod1/leaf/leaf1` | (matches any path containing "leaf") |
| `pod[12]/leaf` | `pod1/leaf/leaf1`, `pod2/leaf/leaf1` | `pod3/leaf/leaf1` |
**Path scoping**:
- **At top-level** (`network.links`): Parent path is empty, so patterns match against full node names. `/leaf` and `leaf` are equivalent.
- **In blueprints**: Paths are relative to instantiation path. If `pod1` uses a blueprint with `source: /leaf`, the pattern becomes `pod1/leaf`.
### Link Rules (with patterns)
```yaml
network:
links:
- source: /leaf
target: /spine
pattern: mesh
capacity: 100
cost: 1
count: 1
```
**Patterns**:
- `mesh`: Full connectivity (every source to every target)
- `one_to_one`: Pairwise with modulo wrap. Sizes must have multiple factor (4-to-2 OK, 3-to-2 ERROR)
### Link Selectors
Filter nodes using attribute conditions:
```yaml
network:
links:
- source:
path: "/datacenter"
match:
logic: and # "and" or "or" (default varies by context)
conditions:
- attr: role
op: "=="
value: leaf
- attr: tier
op: ">="
value: 2
target:
path: "/datacenter"
match:
conditions:
- attr: role
op: "=="
value: spine
pattern: mesh
capacity: 100
```
**Condition operators**:
| Operator | Description |
|----------|-------------|
| `==` | Equal |
| `!=` | Not equal |
| `<`, `<=`, `>`, `>=` | Numeric comparison |
| `contains` | String/list contains value |
| `not_contains` | String/list does not contain |
| `in` | Value in list |
| `not_in` | Value not in list |
| `exists` | Attribute exists and is not None |
| `not_exists` | Attribute missing or None |
### Variable Expansion in Links
Use `$var` or `${var}` syntax in link `source`/`target` fields:
```yaml
network:
links:
- source: "plane${p}/rack"
target: "spine${s}"
expand:
vars:
p: [1, 2]
s: [1, 2, 3]
mode: cartesian
pattern: mesh
capacity: 100
```
**Expansion modes**:
- `cartesian` (default): All combinations (2 * 3 = 6 expansions)
- `zip`: Pair by index (lists must have equal length)
**Expansion limit**: Maximum 10,000 expansions per template. Exceeding this raises an error.
## Blueprints
Reusable topology templates:
```yaml
blueprints:
clos_pod:
nodes:
leaf:
count: 4
template: "leaf-{n}"
attrs:
role: leaf
spine:
count: 2
template: "spine-{n}"
attrs:
role: spine
links:
- source: /leaf
target: /spine
pattern: mesh
capacity: 100
cost: 1
```
### Blueprint Usage
```yaml
network:
nodes:
pod1:
blueprint: clos_pod
attrs: # Merged into all subgroup nodes
location: datacenter_east
params: # Override blueprint defaults
leaf.count: 6
spine.template: "core{n}"
leaf.attrs.priority: high
```
Creates: `pod1/leaf/leaf1`, `pod1/spine/spine1`, etc.
**Parameter override syntax**: `<group>.<field>` or `<group>.attrs.<nested_key>`
### Blueprint Path Scoping
All paths are relative to the current scope. In blueprints, paths resolve relative to the instantiation path:
```yaml
blueprints:
my_bp:
links:
- source: /leaf # Becomes pod1/leaf when instantiated as pod1
target: spine # Also becomes pod1/spine (leading / is optional)
pattern: mesh
```
**Note**: Leading `/` is stripped and has no functional effect. Both `/leaf` and `leaf` produce the same result. The `/` serves as a visual convention indicating "from scope root".
## Node and Link Rules
Modify nodes/links after initial creation:
```yaml
network:
node_rules:
- path: "^pod1/spine/.*$" # Regex pattern
disabled: true
risk_groups: ["Maintenance"]
attrs:
maintenance_mode: active
link_rules:
- source: "^pod1/leaf/.*$"
target: "^pod1/spine/.*$"
bidirectional: true # Match both directions (default: true)
capacity: 200
attrs:
upgraded: true
```
### Node Rule Fields
- `path`: Regex pattern for matching node names (default: `".*"`)
- `match`: Optional attribute conditions to filter nodes (see below)
- `expand`: Optional variable expansion (see [Variable Expansion](#variable-expansion-in-rules))
- `disabled`, `risk_groups`, `attrs`: Properties to set on matching nodes
**Node rule with match conditions:**
```yaml
node_rules:
- path: ".*"
match:
logic: and # "and" or "or" (default: "or")
conditions:
- {attr: role, op: "==", value: compute}
- {attr: tier, op: ">=", value: 2}
disabled: true
```
### Link Rule Fields
- `source`, `target`: Regex patterns for matching link endpoints
- `bidirectional`: If `true` (default), matches both A→B and B→A directions
- `link_match`: Optional conditions to filter by the link's own attributes
- `expand`: Optional variable expansion (see [Variable Expansion](#variable-expansion-in-rules))
- Direct properties: `capacity`, `cost`, `disabled`, `risk_groups`, `attrs`
**Link rule with link_match:**
```yaml
link_rules:
- source: "^pod1/.*$"
target: "^pod2/.*$"
link_match:
logic: and
conditions:
- {attr: capacity, op: ">=", value: 400}
- {attr: type, op: "==", value: fiber}
cost: 99 # Only high-capacity fiber links updated
```
### Variable Expansion in Rules
Use `expand` to apply a rule across multiple patterns:
```yaml
node_rules:
- path: "${dc}_srv1"
expand:
vars:
dc: [dc1, dc2, dc3]
mode: cartesian
attrs:
tagged: true
link_rules:
- source: "${src}_srv"
target: "${tgt}_srv"
expand:
vars:
src: [dc1, dc2]
tgt: [dc2, dc3]
mode: zip # Pairs by index: dc1->dc2, dc2->dc3
capacity: 200
```
**Processing order**:
1. Groups and direct nodes created
2. **Node rules applied**
3. Blueprint links and network links expanded
4. Direct links created
5. **Link rules applied**
## Components Library
Define hardware components for cost/power modeling:
```yaml
components:
SpineRouter:
component_type: chassis
description: "64-port spine router"
capex: 50000.0 # Cost per instance
power_watts: 2500.0 # Typical power usage
power_watts_max: 3000.0 # Peak power usage
capacity: 64000.0 # Gbps
ports: 64
attrs:
vendor: "Example Corp"
children:
LineCard400G:
component_type: linecard
capex: 8000.0
power_watts: 400.0
capacity: 12800.0
ports: 32
count: 4
Optic400G:
component_type: optic
description: "400G pluggable optic"
capex: 2500.0
power_watts: 12.0
capacity: 400.0
```
**Component fields**:
| Field | Description |
|-------|-------------|
| `component_type` | Category: `chassis`, `linecard`, `optic`, etc. |
| `description` | Human-readable description |
| `capex` | Cost per instance |
| `power_watts` | Typical power consumption (watts) |
| `power_watts_max` | Peak power consumption (watts) |
| `capacity` | Capacity in Gbps |
| `ports` | Number of ports |
| `count` | Instance count (for children) |
| `attrs` | Additional metadata |
| `children` | Nested child components |
**Usage in nodes/links**:
```yaml
network:
nodes:
spine1:
attrs:
hardware:
component: "SpineRouter"
count: 2
```
## Risk Groups
Risk groups define hierarchical failure correlation using three methods: direct definition, membership rules, and dynamic generation. Groups can model any failure domain: physical infrastructure, geographic regions, vendor dependencies, or custom correlation patterns.
### Direct Definition
```yaml
risk_groups:
# Full object form with hierarchy
- name: "Region_West"
disabled: false # Optional: disable on load
attrs:
type: geographic
children:
- name: "Site_Seattle"
children:
- name: "Cluster_SEA_01"
- name: "Site_Portland"
# String shorthand (equivalent to {name: "CustomGroup"})
- "CustomGroup"
```
**Risk group fields**: `name` (required), `disabled`, `attrs`, `children`, `membership`, `generate`
### Membership Rules
Dynamically assign entities to risk groups based on attribute conditions:
```yaml
risk_groups:
- name: HighCapacityLinks
membership:
scope: link # Required: node, link, or risk_group
match:
logic: and # "and" or "or" (default: "and")
conditions:
- attr: capacity
op: ">="
value: 1000
- name: CoreNodes
membership:
scope: node
match:
logic: and
conditions:
- attr: role
op: "=="
value: core
- attr: tier
op: ">="
value: 2
```
**Key points:**
- `scope`: Type of entities to match (`node`, `link`, or `risk_group`)
- `match.logic`: Defaults to `"and"` (stricter than other contexts)
- `match.conditions`: Uses same operators as selectors
- Entities are added to risk group during network build
- Supports dot-notation for nested attributes (e.g., `hardware.vendor`)
### Generate Blocks
Automatically create risk groups from unique attribute values:
```yaml
risk_groups:
- generate:
scope: link # Required: node or link (not risk_group)
group_by: connection_type # Attribute to group by (supports dot-notation)
name: "LinkType_${value}"
attrs: # Optional: static attrs for generated groups
generated: true
- generate:
scope: node
group_by: region
name: "Region_${value}"
```
**Generate block fields:**
| Field | Required | Description |
|-------|----------|-------------|
| `scope` | Yes | `node` or `link` (cannot generate from risk_groups) |
| `group_by` | Yes | Attribute name (supports dot-notation) |
| `name` | Yes | Template with `${value}` placeholder |
| `path` | No | Regex to filter entities before grouping |
| `attrs` | No | Static attributes for generated groups |
**Using path to filter entities:**
```yaml
risk_groups:
- generate:
scope: node
path: "^prod_.*" # Only production nodes
group_by: env
name: "Env_${value}"
```
This creates risk groups only from nodes matching the path pattern. For example, if you have `prod_srv1`, `prod_srv2` (env: production), and `dev_srv1` (env: development), only `Env_production` is created because `dev_srv1` doesn't match `^prod_.*`.
**Key points:**
- Creates one risk group per unique attribute value
- Entities with null/missing attribute are skipped
- Generated groups are created during network build
- Use `path` to narrow scope before grouping
### Validation
Risk group references are validated at scenario load time:
**Undefined References:** All risk group names referenced by nodes and links must exist in the `risk_groups` section. Validation errors list affected entities and undefined groups:
```yaml
# This will fail validation
network:
nodes:
Router1:
risk_groups: ["PowerZone_A"] # References undefined risk group
risk_groups:
- name: "PowerZone_B" # Only PowerZone_B is defined
```
**Circular Hierarchies:** Parent-child relationships cannot form cycles:
```yaml
# This will fail validation
risk_groups:
- name: "GroupA"
children:
- name: "GroupB"
children:
- name: "GroupA" # Error: circular reference
```
## Traffic Demands
```yaml
demands:
production:
- source: "^dc1/.*"
target: "^dc2/.*"
volume: 1000
mode: combine
group_mode: flatten # How to handle grouped nodes
priority: 1
flow_policy: SHORTEST_PATHS_ECMP
attrs:
service: web
- source:
path: "^datacenter/.*"
match:
conditions:
- attr: role
op: "=="
value: leaf
target:
group_by: metro
volume: 500
mode: pairwise
priority: 2
```
### Traffic Modes
| Mode | Description |
|------|-------------|
| `combine` | Single aggregate flow between source/target groups via pseudo nodes |
| `pairwise` | Individual flows between all source-target node pairs |
### Group Modes
When selectors use `group_by`, `group_mode` controls how grouped nodes produce demands:
| Group Mode | Description |
|------------|-------------|
| `flatten` | Flatten all groups into single source/target sets (default) |
| `per_group` | Create separate demands for each group |
| `group_pairwise` | Create pairwise demands between groups |
### Flow Policies
Flow policies can be specified as preset strings or inline configuration objects.
**Preset strings:**
| Preset | Description |
|--------|-------------|
| `SHORTEST_PATHS_ECMP` | IP/IGP routing with equal-split ECMP |
| `SHORTEST_PATHS_WCMP` | IP/IGP routing with weighted ECMP (by capacity) |
| `TE_WCMP_UNLIM` | MPLS-TE / SDN with unlimited tunnels |
| `TE_ECMP_16_LSP` | MPLS-TE with 16 ECMP LSPs per demand |
| `TE_ECMP_UP_TO_256_LSP` | MPLS-TE with up to 256 ECMP LSPs |
**Inline configuration objects:**
For advanced scenarios, you can specify a custom flow policy as an inline object:
```yaml
demands:
custom:
- source: A
target: B
volume: 100
flow_policy:
path_alg: SPF
flow_placement: PROPORTIONAL
```
Inline objects are preserved and passed to the analysis engine. The supported fields depend on the underlying NetGraph-Core FlowPolicyConfig. Common fields include:
- `path_alg`: Path algorithm (`SPF`, etc.)
- `flow_placement`: Flow distribution strategy (`PROPORTIONAL`, `EQUAL_BALANCED`)
**Note:** Preset strings are recommended for most use cases. Inline objects provide flexibility for specialized routing behaviors but require knowledge of the underlying configuration options.
### Variable Expansion in Demands
```yaml
demands:
inter_dc:
- source: "^${src_dc}/.*"
target: "^${dst_dc}/.*"
volume: 100
expand:
vars:
src_dc: [dc1, dc2]
dst_dc: [dc2, dc3]
mode: cartesian
```
## Failure Policies
Failure policies define how nodes, links, and risk groups fail during Monte Carlo simulations.
### Structure
```yaml
failures:
policy_name:
attrs: {} # Optional metadata
expand_groups: false # Expand to shared-risk entities
expand_children: false # Fail child risk groups recursively
modes: # Required: weighted failure modes
- weight: 1.0 # Mode selection weight
attrs: {} # Optional mode metadata
rules: [] # Rules applied when mode is selected
```
### Mode Selection
Exactly one mode is selected per failure iteration based on normalized weights:
```yaml
modes:
- weight: 0.3 # 30% probability of selection
rules: [...]
- weight: 0.5 # 50% probability
rules: [...]
- weight: 0.2 # 20% probability
rules: [...]
```
- Modes with zero or negative weight are never selected
- If all weights are non-positive, falls back to the first mode
### Rule Structure
```yaml
rules:
- scope: link # Required: node, link, or risk_group
path: "^edge/.*" # Optional regex on entity name/id
match:
conditions: [] # Optional: filter conditions
logic: or # Condition logic: and | or (default: or)
mode: all # Selection: all | choice | random (default: all)
probability: 1.0 # For random: [0.0, 1.0]
count: 1 # For choice: number to select
weight_by: null # For choice: attribute for weighted sampling
```
### Rule Modes
| Mode | Description | Parameters |
|------|-------------|------------|
| `all` | Select all matching entities | None |
| `choice` | Random sample from matches | `count`, optional `weight_by` |
| `random` | Each match selected with probability | `probability` in [0, 1] |
### Condition Logic
When multiple `match.conditions` are specified:
| Logic | Behavior |
|-------|----------|
| `or` | Entity matches if **any** condition is true |
| `and` | Entity matches if **all** conditions are true |
If `match` is omitted or `match.conditions` is empty, all entities of the given scope match.
**Context-specific defaults**:
| Context | Default `logic` | Rationale |
|---------|-----------------|-----------|
| Link `match` | `"or"` | Inclusive: match any condition |
| Demand `match` | `"or"` | Inclusive: match any condition |
| Membership rules | `"and"` | Precise: must match all conditions |
| Failure rules | `"or"` | Inclusive: match any condition |
### Weighted Sampling (choice mode)
When `weight_by` is set for `mode: choice`:
```yaml
- scope: link
mode: choice
count: 2
weight_by: capacity # Sample proportional to capacity attribute
```
- Uses Efraimidis-Spirakis algorithm for weighted sampling without replacement
- Entities with non-positive or missing weights are sampled uniformly after positive-weight items
- Falls back to uniform sampling if all weights are non-positive
### Risk Group Expansion
```yaml
expand_groups: true
```
When enabled, after initial failures are selected, expands to fail all entities that share a risk group with any failed entity (BFS traversal).
```yaml
expand_children: true
```
When enabled and a risk_group is marked as failed, recursively fails all child risk groups.
### Complete Example
```yaml
failures:
weighted_modes:
attrs:
description: "Balanced failure simulation"
expand_groups: true
expand_children: false
modes:
# 30% chance: fail 1 risk group weighted by distance
- weight: 0.3
rules:
- scope: risk_group
mode: choice
count: 1
weight_by: distance_km
# 50% chance: fail 1 non-core node weighted by capacity
- weight: 0.5
rules:
- scope: node
mode: choice
count: 1
match:
logic: and
conditions:
- attr: role
op: "!="
value: core
weight_by: attached_capacity_gbps
# 20% chance: random link failures with 1% probability each
- weight: 0.2
rules:
- scope: link
mode: random
probability: 0.01
```
### Entity Scopes
| Scope | Description |
|-------|-------------|
| `node` | Match against node attributes |
| `link` | Match against link attributes |
| `risk_group` | Match against risk group names/attributes |
## Workflow Steps
```yaml
workflow:
- type: NetworkStats
name: baseline_stats
- type: MaximumSupportedDemand
name: msd_baseline
demand_set: production
acceptance_rule: hard
alpha_start: 1.0
growth_factor: 2.0
resolution: 0.05
- type: TrafficMatrixPlacement
name: tm_placement
demand_set: production
failure_policy: weighted_modes
iterations: 1000
parallelism: 8
alpha_from_step: msd_baseline
alpha_from_field: data.alpha_star
- type: MaxFlow
name: capacity_matrix
source: "^(dc[1-3])$"
target: "^(dc[1-3])$"
mode: pairwise
failure_policy: single_link
iterations: 500
- type: CostPower
name: cost_analysis
include_disabled: true
aggregation_level: 2
```
### Step Types
| Type | Description |
|------|-------------|
| `BuildGraph` | Export graph to JSON (node-link format) |
| `NetworkStats` | Compute basic statistics (node/link counts, degrees) |
| `MaxFlow` | Monte Carlo capacity analysis between node groups |
| `TrafficMatrixPlacement` | Monte Carlo demand placement for a named matrix |
| `MaximumSupportedDemand` | Search for maximum supportable demand scaling (`alpha_star`) |
| `CostPower` | Cost and power estimation from components |
### BuildGraph Parameters
```yaml
- type: BuildGraph
name: build_graph
add_reverse: true # Add reverse edges for bidirectional connectivity
```
### NetworkStats Parameters
```yaml
- type: NetworkStats
name: stats
include_disabled: false # Include disabled nodes/links in stats
excluded_nodes: [] # Optional: temporary node exclusions
excluded_links: [] # Optional: temporary link exclusions
```
### MaxFlow Parameters
Baseline (no failures) is always run first as a reference. The `iterations` parameter specifies how many failure scenarios to run.
```yaml
- type: MaxFlow
name: capacity_analysis
source: "^servers/.*"
target: "^storage/.*"
mode: combine # combine | pairwise
failure_policy: policy_name
iterations: 1000
parallelism: auto # or integer
seed: 42 # Optional: for reproducibility
shortest_path: false # Restrict to shortest paths only
require_capacity: true # Path selection considers capacity
flow_placement: PROPORTIONAL # PROPORTIONAL | EQUAL_BALANCED
store_failure_patterns: false
include_flow_details: false # Cost distribution per flow
include_min_cut: false # Min-cut edge list per flow
```
### TrafficMatrixPlacement Parameters
Baseline (no failures) is always run first as a reference. The `iterations` parameter specifies how many failure scenarios to run.
```yaml
- type: TrafficMatrixPlacement
name: tm_placement
demand_set: default
failure_policy: policy_name
iterations: 100
parallelism: auto
placement_rounds: auto # or integer
seed: 42 # Optional: for reproducibility
include_flow_details: true
include_used_edges: false
store_failure_patterns: false
# Alpha scaling options
alpha: 1.0 # Explicit scaling factor
# Or reference another step's output:
alpha_from_step: msd_step_name
alpha_from_field: data.alpha_star
```
### MaximumSupportedDemand Parameters
```yaml
- type: MaximumSupportedDemand
name: msd
demand_set: default
acceptance_rule: hard # Currently only "hard" supported
alpha_start: 1.0 # Starting alpha for search
growth_factor: 2.0 # Growth factor for bracketing (> 1.0)
alpha_min: 0.000001 # Minimum alpha bound
alpha_max: 1000000000.0 # Maximum alpha bound
resolution: 0.01 # Convergence resolution
max_bracket_iters: 32
max_bisect_iters: 32
seeds_per_alpha: 1 # Seeds per alpha (majority vote)
placement_rounds: auto
```
### CostPower Parameters
```yaml
- type: CostPower
name: cost_power
include_disabled: false # Include disabled nodes/links
aggregation_level: 2 # Hierarchy level for aggregation (split by /)
```
## Selector Reference
Selectors work across links, demands, and workflows.
### Selection Patterns
The DSL uses two distinct selection patterns:
**Path-based Node Selection** (links, demands, workflows):
- Works on node entities
- Uses regex patterns on hierarchical node names (`path`)
- Supports capture group-based grouping
- Supports attribute-based grouping (`group_by`)
- Supports attribute filtering (`match` conditions)
- Supports `active_only` filtering
**Condition-based Entity Selection** (failure rules, membership rules):
- Works on nodes, links, or risk_groups (`scope`)
- Uses only attribute-based filtering (`conditions`)
- No path/regex patterns (operates on all entities of specified type)
- See Failure Policies section for details
### String Pattern (Regex)
```yaml
source: "^dc1/spine/.*$"
```
Patterns use Python `re.match()`, anchored at start.
### Selector Object
```yaml
source:
path: "^dc1/.*" # Regex on node.name
group_by: metro # Group by attribute value
match: # Filter by conditions
logic: and
conditions:
- attr: role
op: "=="
value: spine
active_only: true # Exclude disabled nodes
```
At least one of `path`, `group_by`, or `match` must be specified.
### Context-Aware Defaults for active_only
The `active_only` field has context-dependent defaults:
| Context | Default | Rationale |
|---------|---------|-----------|
| `links` | `false` | Links to disabled nodes are created |
| `node_rules` | `false` | Rules can target disabled nodes |
| `demand` | `true` | Traffic only between active nodes |
| `workflow` | `true` | Analysis uses active nodes only |
### Capture Groups for Labeling
```yaml
# Single capture group
source: "^(dc[1-3])/.*" # Groups: dc1, dc2, dc3
# Multiple capture groups join with |
source: "^(dc\\d+)/(spine|leaf)/.*" # Groups: dc1|spine, dc1|leaf
```
## YAML Anchors
Use `vars` section for reusable values:
```yaml
vars:
default_cap: &cap 10000
base_attrs: &attrs {cost: 100, region: "dc1"}
spine_config: &spine_cfg
hardware:
component: "SpineRouter"
count: 1
network:
nodes:
spine1: {attrs: {<<: *attrs, <<: *spine_cfg, capacity: *cap}}
spine2: {attrs: {<<: *attrs, <<: *spine_cfg, capacity: *cap, region: "dc2"}}
```
Anchors are resolved during YAML parsing, before schema validation.
```
### references/EXAMPLES.md
```markdown
# NetGraph DSL Examples
Complete working examples for common use cases.
> For quick patterns and pitfalls, see the main [SKILL.md](../SKILL.md).
> For detailed field reference, see [REFERENCE.md](REFERENCE.md).
## Example 1: Simple Data Center
A basic leaf-spine topology with traffic analysis.
```yaml
network:
nodes:
leaf:
count: 4
template: "leaf{n}"
attrs:
role: leaf
spine:
count: 2
template: "spine{n}"
attrs:
role: spine
links:
- source: /leaf
target: /spine
pattern: mesh
capacity: 100
cost: 1
demands:
default:
- source: "^leaf/.*"
target: "^leaf/.*"
volume: 50
mode: pairwise
failures:
single_link:
modes:
- weight: 1.0
rules:
- scope: link
mode: choice
count: 1
workflow:
- type: TrafficMatrixPlacement
name: placement
demand_set: default
failure_policy: single_link
iterations: 100
```
**Result**: 6 nodes (4 leaf + 2 spine), 8 links (4x2 mesh)
## Example 2: Multi-Pod with Blueprint
Two pods sharing a blueprint, connected via spine layer.
```yaml
blueprints:
clos_pod:
nodes:
leaf:
count: 4
template: "leaf{n}"
attrs:
role: leaf
spine:
count: 2
template: "spine{n}"
attrs:
role: spine
links:
- source: /leaf
target: /spine
pattern: mesh
capacity: 100
network:
nodes:
pod[1-2]:
blueprint: clos_pod
links:
- source:
path: "pod1/spine"
match:
conditions:
- attr: role
op: "=="
value: spine
target:
path: "pod2/spine"
pattern: mesh
capacity: 400
```
**Result**: 12 nodes (2 pods x 6 nodes), 20 links (16 internal + 4 inter-pod)
## Example 3: Backbone with Risk Groups
Wide-area network with shared-risk link groups.
```yaml
network:
nodes:
NewYork: {attrs: {site_type: core}}
Chicago: {attrs: {site_type: core}}
LosAngeles: {attrs: {site_type: core}}
links:
# Parallel diverse paths
- source: NewYork
target: Chicago
capacity: 100
cost: 10
risk_groups: [RG_NY_CHI]
- source: NewYork
target: Chicago
capacity: 100
cost: 10
# Single path
- source: Chicago
target: LosAngeles
capacity: 100
cost: 15
risk_groups: [RG_CHI_LA]
risk_groups:
- name: RG_NY_CHI
attrs:
corridor: NYC-Chicago
distance_km: 1200
- name: RG_CHI_LA
attrs:
corridor: Chicago-LA
distance_km: 2800
failures:
srlg_failure:
modes:
- weight: 1.0
rules:
- scope: risk_group
mode: choice
count: 1
```
**Result**: 3 nodes, 3 links, 2 risk groups
## Example 4: Variable Expansion at Scale
Large fabric using variable expansion.
```yaml
network:
nodes:
plane[1-4]/rack[1-8]:
count: 48
template: "server{n}"
attrs:
role: compute
fabric/spine[1-4]:
count: 1
template: "spine"
attrs:
role: spine
links:
- source: "plane${p}/rack${r}"
target: "fabric/spine${s}"
expand:
vars:
p: [1, 2, 3, 4]
r: [1, 2, 3, 4, 5, 6, 7, 8]
s: [1, 2, 3, 4]
mode: cartesian
pattern: mesh
capacity: 100
```
**Result**: 1540 nodes (4x8x48 compute + 4 spine), 6144 links
## Example 5: Full Mesh Topology
Simple 4-node full mesh for testing.
```yaml
seed: 42
network:
nodes:
N1: {}
N2: {}
N3: {}
N4: {}
links:
- source: N1
target: N2
capacity: 2.0
cost: 1.0
- source: N1
target: N3
capacity: 1.0
cost: 1.0
- source: N1
target: N4
capacity: 2.0
cost: 1.0
- source: N2
target: N3
capacity: 2.0
cost: 1.0
- source: N2
target: N4
capacity: 1.0
cost: 1.0
- source: N3
target: N4
capacity: 2.0
cost: 1.0
failures:
single_link_failure:
modes:
- weight: 1.0
rules:
- scope: link
mode: choice
count: 1
demands:
baseline:
- source: "^N([1-4])$"
target: "^N([1-4])$"
volume: 12.0
mode: pairwise
workflow:
- type: MaximumSupportedDemand
name: msd
demand_set: baseline
acceptance_rule: hard
alpha_start: 1.0
resolution: 0.05
- type: MaxFlow
name: capacity_matrix
source: "^(N[1-4])$"
target: "^(N[1-4])$"
mode: pairwise
failure_policy: single_link_failure
iterations: 1000
seed: 42
```
## Example 6: Attribute-Based Selectors
Using match conditions to filter nodes.
```yaml
network:
nodes:
servers:
count: 4
template: "srv{n}"
attrs:
role: compute
rack: "rack-1"
servers_b:
count: 2
template: "srvb{n}"
attrs:
role: compute
rack: "rack-9"
switches:
count: 2
template: "sw{n}"
attrs:
tier: spine
links:
- source:
path: "/servers"
match:
logic: and
conditions:
- attr: role
op: "=="
value: compute
- attr: rack
op: "!="
value: "rack-9"
target:
path: "/switches"
match:
conditions:
- attr: tier
op: "=="
value: spine
pattern: mesh
capacity: 10
cost: 1
```
**Result**: 8 nodes, 8 links (only rack-1 servers connect to switches)
## Example 7: Blueprint with Parameter Overrides
Customizing blueprint instances.
```yaml
blueprints:
bp1:
nodes:
leaf:
count: 1
attrs:
some_field:
nested_key: 111
network:
nodes:
Main:
blueprint: bp1
params:
leaf.attrs.some_field.nested_key: 999
```
**Result**: Node `Main/leaf/leaf1` has `attrs.some_field.nested_key = 999`
## Example 8: Node and Link Rules
Modifying topology after creation.
```yaml
blueprints:
test_bp:
nodes:
switches:
count: 3
template: "switch{n}"
network:
nodes:
group1:
count: 2
template: "node{n}"
group2:
count: 2
template: "node{n}"
my_clos1:
blueprint: test_bp
links:
- source: /group1
target: /group2
pattern: mesh
capacity: 100
cost: 10
node_rules:
- path: "^my_clos1/switches/switch(1|3)$"
disabled: true
attrs:
maintenance_mode: active
hw_type: newer_model
link_rules:
- source: "^group1/node1$"
target: "^group2/node1$"
capacity: 200
cost: 5
```
**Result**: Switches 1 and 3 disabled, specific link upgraded to 200 capacity
## Example 9: Complete Traffic Analysis
Full workflow with MSD and placement analysis.
```yaml
seed: 42
blueprints:
Clos_L16_S4:
nodes:
spine:
count: 4
template: spine{n}
attrs:
role: spine
leaf:
count: 16
template: leaf{n}
attrs:
role: leaf
links:
- source: /leaf
target: /spine
pattern: mesh
capacity: 3200
cost: 1
network:
nodes:
metro1/pop[1-2]:
blueprint: Clos_L16_S4
attrs:
metro_name: new-york
node_type: pop
demands:
baseline:
- source: "^metro1/pop1/.*"
target: "^metro1/pop2/.*"
volume: 15000.0
mode: pairwise
flow_policy: TE_WCMP_UNLIM
failures:
single_link:
modes:
- weight: 1.0
rules:
- scope: link
mode: choice
count: 1
workflow:
- type: NetworkStats
name: network_statistics
- type: MaximumSupportedDemand
name: msd_baseline
demand_set: baseline
acceptance_rule: hard
alpha_start: 1.0
growth_factor: 2.0
resolution: 0.05
- type: TrafficMatrixPlacement
name: tm_placement
seed: 42
demand_set: baseline
failure_policy: single_link
iterations: 1000
parallelism: 7
include_flow_details: true
alpha_from_step: msd_baseline
alpha_from_field: data.alpha_star
```
## Example 10: Group-By Selectors
Grouping nodes by attribute for demand generation.
```yaml
network:
nodes:
dc1_srv1: {attrs: {dc: dc1, role: server}}
dc1_srv2: {attrs: {dc: dc1, role: server}}
dc2_srv1: {attrs: {dc: dc2, role: server}}
dc2_srv2: {attrs: {dc: dc2, role: server}}
links:
- source: dc1_srv1
target: dc2_srv1
capacity: 100
- source: dc1_srv2
target: dc2_srv2
capacity: 100
demands:
inter_dc:
- source:
group_by: dc
target:
group_by: dc
volume: 100
mode: pairwise
```
**Result**: Traffic flows grouped by datacenter attribute
## Example 11: Advanced Failure Policies
Multiple weighted failure modes with conditions and weighted sampling.
```yaml
network:
nodes:
core1: {attrs: {role: core, capacity_gbps: 1000}}
core2: {attrs: {role: core, capacity_gbps: 1000}}
edge1: {attrs: {role: edge, capacity_gbps: 400, region: west}}
edge2: {attrs: {role: edge, capacity_gbps: 400, region: east}}
edge3: {attrs: {role: edge, capacity_gbps: 200, region: west}}
links:
- source: core1
target: core2
capacity: 1000
risk_groups: [RG_core]
- source: core1
target: edge1
capacity: 400
risk_groups: [RG_west]
- source: core1
target: edge3
capacity: 200
risk_groups: [RG_west]
- source: core2
target: edge2
capacity: 400
risk_groups: [RG_east]
risk_groups:
- name: RG_core
attrs: {tier: core, distance_km: 50}
- name: RG_west
attrs: {tier: edge, distance_km: 500}
- name: RG_east
attrs: {tier: edge, distance_km: 800}
failures:
mixed_failures:
expand_groups: true # Expand to shared-risk entities
expand_children: false
modes:
# 40% chance: fail 1 edge node weighted by capacity
- weight: 0.4
attrs: {scenario: edge_failure}
rules:
- scope: node
mode: choice
count: 1
match:
logic: and
conditions:
- attr: role
op: "=="
value: edge
weight_by: capacity_gbps
# 35% chance: fail 1 risk group weighted by distance
- weight: 0.35
attrs: {scenario: srlg_failure}
rules:
- scope: risk_group
mode: choice
count: 1
weight_by: distance_km
# 15% chance: fail all west-region nodes
- weight: 0.15
attrs: {scenario: regional_outage}
rules:
- scope: node
mode: all
match:
conditions:
- attr: region
op: "=="
value: west
# 10% chance: random link failures (5% each)
- weight: 0.1
attrs: {scenario: random_link}
rules:
- scope: link
mode: random
probability: 0.05
workflow:
- type: MaxFlow
name: failure_analysis
source: "^(edge[1-3])$"
target: "^(edge[1-3])$"
mode: pairwise
failure_policy: mixed_failures
iterations: 1000
seed: 42
```
**Result**: 5 nodes, 4 links, 3 risk groups, failure policy with 4 weighted modes
## Example 12: Hardware Components and Cost Analysis
Using the components library for cost/power modeling.
```yaml
components:
SpineRouter:
component_type: chassis
description: "64-port spine switch"
capex: 55000.0
power_watts: 2000.0
power_watts_max: 3000.0
capacity: 102400.0
ports: 64
LeafRouter:
component_type: chassis
description: "48-port leaf switch"
capex: 25000.0
power_watts: 800.0
power_watts_max: 1200.0
capacity: 38400.0
ports: 48
Optic400G:
component_type: optic
description: "400G DR4 pluggable"
capex: 3000.0
power_watts: 16.0
capacity: 400.0
network:
name: "datacenter-fabric"
version: "2.0"
nodes:
spine:
count: 2
template: "spine{n}"
attrs:
hardware:
component: SpineRouter
count: 1
leaf:
count: 4
template: "leaf{n}"
attrs:
hardware:
component: LeafRouter
count: 1
links:
- source: /leaf
target: /spine
pattern: mesh
count: 2 # 2 parallel links per pair
capacity: 800
cost: 1
attrs:
hardware:
source:
component: Optic400G
count: 2
target:
component: Optic400G
count: 2
exclusive: true # Dedicated optics (rounds up count)
workflow:
- type: NetworkStats
name: stats
- type: CostPower
name: cost_analysis
include_disabled: false
aggregation_level: 1 # Aggregate by top-level group
```
**Result**: 6 nodes, 16 links (4x2x2), component-based cost/power analysis
## Example 13: YAML Anchors for Reuse
Using `vars` section for DRY configuration.
```yaml
vars:
default_link: &link_cfg
capacity: 100
cost: 1
spine_attrs: &spine_attrs
role: spine
tier: 2
leaf_attrs: &leaf_attrs
role: leaf
tier: 1
network:
nodes:
spine:
count: 2
template: "spine{n}"
attrs:
<<: *spine_attrs # Merge anchor
region: east
leaf:
count: 4
template: "leaf{n}"
attrs:
<<: *leaf_attrs
region: east
links:
- source: /leaf
target: /spine
pattern: mesh
<<: *link_cfg # Reuse link config
attrs:
link_type: fabric
```
**Result**: Anchors resolved during YAML parsing; cleaner, less repetitive config
## Example 14: One-to-One Pattern and Zip Expansion
Demonstrating pairwise connectivity patterns.
```yaml
network:
nodes:
# 4 servers, 2 switches - compatible for one_to_one (4 is multiple of 2)
server[1-4]:
count: 1
template: "srv"
switch[1-2]:
count: 1
template: "sw"
links:
# one_to_one: server1->switch1, server2->switch2, server3->switch1, server4->switch2
- source: /server
target: /switch
pattern: one_to_one
capacity: 100
# zip expansion: pairs variables by index (equal-length lists required)
- source: "server${idx}"
target: "switch${sw}"
expand:
vars:
idx: [1, 2]
sw: [1, 2]
mode: zip # server1->switch1, server2->switch2
pattern: one_to_one
capacity: 50
cost: 2
```
**Result**: Demonstrates one_to_one modulo wrap and zip expansion mode
## Example 15: Traffic Demands with Variable Expansion and Group Modes
Advanced demand configuration.
```yaml
network:
nodes:
dc1_leaf1: {attrs: {dc: dc1, role: leaf}}
dc1_leaf2: {attrs: {dc: dc1, role: leaf}}
dc2_leaf1: {attrs: {dc: dc2, role: leaf}}
dc2_leaf2: {attrs: {dc: dc2, role: leaf}}
dc3_leaf1: {attrs: {dc: dc3, role: leaf}}
links:
- {source: dc1_leaf1, target: dc2_leaf1, capacity: 100}
- {source: dc1_leaf2, target: dc2_leaf2, capacity: 100}
- {source: dc2_leaf1, target: dc3_leaf1, capacity: 100}
demands:
# Variable expansion in demands
inter_dc:
- source: "^${src}/.*"
target: "^${dst}/.*"
volume: 50
expand:
vars:
src: [dc1, dc2]
dst: [dc2, dc3]
mode: zip # dc1->dc2, dc2->dc3
# Group modes with group_by
grouped:
- source:
group_by: dc
target:
group_by: dc
volume: 100
mode: pairwise
group_mode: per_group # Separate demand per group pair
priority: 1
flow_policy: SHORTEST_PATHS_WCMP
```
**Result**: Shows variable expansion in demands, group_mode, and priority
## Example 16: Hierarchical Risk Groups
Nested risk group structure with children.
```yaml
network:
nodes:
rack1_srv1: {risk_groups: [Rack1_Card1]}
rack1_srv2: {risk_groups: [Rack1_Card1]}
rack1_srv3: {risk_groups: [Rack1_Card2]}
rack2_srv1: {risk_groups: [Rack2]}
links:
- {source: rack1_srv1, target: rack2_srv1, capacity: 100}
- {source: rack1_srv2, target: rack2_srv1, capacity: 100}
- {source: rack1_srv3, target: rack2_srv1, capacity: 100}
risk_groups:
- name: Rack1
attrs: {location: "DC1-Row1"}
children:
- name: Rack1_Card1
attrs: {slot: 1}
- name: Rack1_Card2
attrs: {slot: 2}
- name: Rack2
disabled: false
attrs: {location: "DC1-Row2"}
failures:
hierarchical:
expand_groups: true
expand_children: true # Failing Rack1 also fails Card1, Card2
modes:
- weight: 1.0
rules:
- scope: risk_group
mode: choice
count: 1
match:
conditions:
- attr: location
op: contains # String contains
value: "DC1"
```
**Result**: Hierarchical risk groups with recursive child failure expansion
## Example 17: Risk Group Membership Rules
Dynamically assign entities based on attributes.
```yaml
network:
nodes:
core1: {attrs: {role: core, tier: 3, datacenter: dc1}}
core2: {attrs: {role: core, tier: 3, datacenter: dc2}}
edge1: {attrs: {role: edge, tier: 1, datacenter: dc1}}
edge2: {attrs: {role: edge, tier: 1, datacenter: dc2}}
links:
- source: core1
target: core2
capacity: 1000
attrs:
route_type: backbone
path_id: primary
- source: core1
target: edge1
capacity: 400
risk_groups:
# Assign all core tier-3 nodes
- name: CoreTier3
membership:
scope: node
match:
logic: and # Must match ALL conditions
conditions:
- attr: role
op: "=="
value: core
- attr: tier
op: "=="
value: 3
# Assign links by route type
- name: BackboneLinks
membership:
scope: link
match:
logic: and
conditions:
- attr: route_type # Dot-notation for nested attrs
op: "=="
value: backbone
# String shorthand for simple groups
- "ManualGroup1"
```
**Result**: Nodes and links automatically assigned to risk groups based on attributes
## Example 18: Generated Risk Groups
Create risk groups from unique attribute values.
```yaml
network:
nodes:
srv1: {attrs: {datacenter: dc1, rack: r1}}
srv2: {attrs: {datacenter: dc1, rack: r2}}
srv3: {attrs: {datacenter: dc2, rack: r1}}
links:
- source: srv1
target: srv2
capacity: 100
attrs:
connection_type: intra_dc
- source: srv2
target: srv3
capacity: 100
attrs:
connection_type: inter_dc
risk_groups:
# Generate risk group per datacenter (from nodes)
- generate:
scope: node
group_by: datacenter
name: "DC_${value}"
attrs:
generated: true
type: location
# Generate risk group per rack (from nodes)
- generate:
scope: node
group_by: rack
name: "Rack_${value}"
# Generate risk group per connection type (from links)
- generate:
scope: link
group_by: connection_type
name: "Links_${value}"
```
**Result**: Creates 6 risk groups:
- `DC_dc1` (srv1, srv2)
- `DC_dc2` (srv3)
- `Rack_r1` (srv1, srv3)
- `Rack_r2` (srv2)
- `Links_intra_dc` (link srv1→srv2)
- `Links_inter_dc` (link srv2→srv3)
## Example 19: Additional Selector Operators
Demonstrating all condition operators.
```yaml
network:
nodes:
srv1: {attrs: {tier: 1, tags: [prod, web], region: null}}
srv2: {attrs: {tier: 2, tags: [prod, db], region: east}}
srv3: {attrs: {tier: 3, tags: [dev], region: west}}
srv4: {attrs: {tier: 2}}
links:
- {source: srv1, target: srv2, capacity: 100}
- {source: srv2, target: srv3, capacity: 100}
- {source: srv3, target: srv4, capacity: 100}
demands:
filtered:
# Tier comparison operators
- source:
match:
conditions:
- attr: tier
op: ">="
value: 2
target:
match:
conditions:
- attr: tier
op: "<"
value: 3
volume: 50
mode: pairwise
# List membership operators
- source:
match:
conditions:
- attr: region
op: in
value: [east, west]
target:
match:
conditions:
- attr: tags
op: contains # List contains value
value: prod
volume: 25
mode: combine
# Existence operators
- source:
match:
conditions:
- attr: region
op: exists # Attribute exists and not null
target:
match:
conditions:
- attr: region
op: not_exists # Attribute missing or null
volume: 10
mode: pairwise
```
**Result**: Demonstrates `>=`, `<`, `in`, `contains`, `exists`, `not_exists` operators
## Example 20: link_match and Rule Expansion
Using `link_match` to filter link rules by the link's own attributes, and `expand` for variable-based rule application.
```yaml
network:
nodes:
dc1_srv: {}
dc2_srv: {}
dc3_srv: {}
links:
- {source: dc1_srv, target: dc2_srv, capacity: 100, cost: 1, attrs: {type: fiber}}
- {source: dc1_srv, target: dc2_srv, capacity: 500, cost: 1, attrs: {type: fiber}}
- {source: dc2_srv, target: dc3_srv, capacity: 500, cost: 1, attrs: {type: copper}}
# Update only high-capacity fiber links
link_rules:
- source: ".*"
target: ".*"
link_match:
logic: and
conditions:
- {attr: capacity, op: ">=", value: 400}
- {attr: type, op: "==", value: fiber}
cost: 99
attrs:
priority: high
# Apply node rules using variable expansion
node_rules:
- path: "${dc}_srv"
expand:
vars:
dc: [dc1, dc2]
mode: cartesian
attrs:
tagged: true
```
**Result**: Only the 500-capacity fiber link (dc1_srv -> dc2_srv) gets cost 99. Nodes dc1_srv and dc2_srv are tagged.
## Example 21: Nested Inline Nodes (No Blueprint)
Creating hierarchical topology structure without using blueprints.
```yaml
network:
nodes:
datacenter:
attrs:
region: west
tier: 1
nodes:
rack1:
attrs:
rack_id: 1
nodes:
tor:
count: 1
template: "sw{n}"
attrs:
role: switch
servers:
count: 4
template: "srv{n}"
attrs:
role: compute
rack2:
attrs:
rack_id: 2
nodes:
tor:
count: 1
template: "sw{n}"
attrs:
role: switch
servers:
count: 4
template: "srv{n}"
attrs:
role: compute
links:
# Connect servers to their TOR switch in each rack
- source:
path: "datacenter/rack1/servers"
target:
path: "datacenter/rack1/tor"
pattern: mesh
capacity: 25
- source:
path: "datacenter/rack2/servers"
target:
path: "datacenter/rack2/tor"
pattern: mesh
capacity: 25
# Connect TOR switches
- source: datacenter/rack1/tor/sw1
target: datacenter/rack2/tor/sw1
capacity: 100
```
**Result**: Creates 10 nodes (2 switches + 8 servers) in a two-rack hierarchy. All nodes inherit `region: west` and `tier: 1` from the datacenter parent. Each rack's nodes get the appropriate `rack_id`.
## Example 22: path Filter in Generate Blocks
Using `path` to narrow entities before generating risk groups.
```yaml
network:
nodes:
prod_web1: {attrs: {env: production, service: web}}
prod_web2: {attrs: {env: production, service: web}}
prod_db1: {attrs: {env: production, service: database}}
dev_web1: {attrs: {env: development, service: web}}
dev_db1: {attrs: {env: development, service: database}}
links:
- {source: prod_web1, target: prod_db1, capacity: 100, attrs: {link_type: internal}}
- {source: prod_web2, target: prod_db1, capacity: 100, attrs: {link_type: internal}}
- {source: dev_web1, target: dev_db1, capacity: 50, attrs: {link_type: internal}}
risk_groups:
# Generate env-based risk groups only for production nodes
- generate:
scope: node
path: "^prod_.*"
group_by: env
name: "Env_${value}"
attrs:
generated: true
critical: true
# Generate service-based risk groups for all nodes
- generate:
scope: node
group_by: service
name: "Service_${value}"
# Generate link risk groups only for production links
- generate:
scope: link
path: ".*prod.*"
group_by: link_type
name: "ProdLinks_${value}"
demands:
baseline:
- source: "^prod_web.*"
target: "^prod_db.*"
volume: 50
mode: pairwise
flow_policy: SHORTEST_PATHS_ECMP
failures:
production_failure:
expand_groups: true
modes:
- weight: 1.0
rules:
- scope: risk_group
path: "^Env_.*"
mode: choice
count: 1
```
**Result**: Creates the following risk groups:
- `Env_production` (only production nodes due to path filter)
- `Service_web` (prod_web1, prod_web2, dev_web1)
- `Service_database` (prod_db1, dev_db1)
- `ProdLinks_internal` (only production links due to path filter)
Note: `Env_development` is NOT created because dev nodes don't match `^prod_.*`.
```