Skip to content

Commit 9bafa7d

Browse files
author
rchac
committed
changes
1 parent a2e68ef commit 9bafa7d

File tree

2 files changed

+240
-14
lines changed

2 files changed

+240
-14
lines changed

docs/v2.0/configuration-advanced.md

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ In the WebUI, this lives at `Configuration -> Integration - Common` as `Queue Au
108108
How it works:
109109

110110
- `queue_auto_virtualize_threshold_mbps` is the static queue-policy threshold used by `lqos_topology`.
111-
- Integration roots default to static virtual nodes in the runtime tree.
112-
- Site nodes above the threshold may also be virtualized automatically when they are acting as aggregation-only branches.
111+
- `QueueAuto` only hides a node when it is a `Site`, has child branches, and its final effective node rate is at or above the threshold.
112+
- That same threshold rule applies to top-level `QueueAuto` sites and non-top-level `QueueAuto` sites.
113113
- Nodes with directly attached circuits stay queue-visible by default.
114114

115115
This static queue policy is now the primary way to avoid wasting HTB depth or creating artificial aggregate choke points. TreeGuard runtime link virtualization remains available, but is disabled by default.
@@ -120,23 +120,20 @@ This static queue policy is now the primary way to avoid wasting HTB depth or cr
120120

121121
```{mermaid}
122122
flowchart TD
123-
A[Node queue policy = QueueAuto] --> B{Is this a root node?}
124-
B -->|Yes| C[Hide for queueing / promote children]
125-
B -->|No| D{Is this a Site node?}
123+
A[Node queue policy = QueueAuto] --> B{Is this a Site node?}
124+
B -->|No| C[Keep queue-visible]
125+
B -->|Yes| D{Does it have child branches?}
126126
D -->|No| E[Keep queue-visible]
127-
D -->|Yes| F{Does it have child branches?}
127+
D -->|Yes| F{Final effective node rate >= threshold?}
128128
F -->|No| G[Keep queue-visible]
129-
F -->|Yes| H{Final effective node rate >= threshold?}
130-
H -->|No| I[Keep queue-visible]
131-
H -->|Yes| J[Mark static virtual for queueing]
129+
F -->|Yes| H[Mark static virtual for queueing]
132130
```
133131

134132
Current rule summary:
135133

136-
- Root nodes default to queue-hidden or static virtual behavior depending on policy.
137134
- Non-`Site` nodes stay queue-visible under `QueueAuto`.
138135
- A `Site` with no child branches stays queue-visible.
139-
- A `Site` with child branches only becomes static virtual when its final effective node rate is at or above `queue_auto_virtualize_threshold_mbps`.
136+
- A top-level or non-top-level `Site` with child branches only becomes static virtual when its final effective node rate is at or above `queue_auto_virtualize_threshold_mbps`.
140137
- The rate used for this decision is the recompiled runtime-effective rate, not an earlier raw attachment max.
141138

142139
When a node becomes static virtual:

src/rust/lqos_topology/src/lib.rs

Lines changed: 232 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2380,9 +2380,6 @@ fn resolved_queue_visibility_policy(
23802380
TopologyQueueVisibilityPolicy::QueueHiddenPromoteChildren
23812381
}
23822382
TopologyQueueVisibilityPolicy::QueueAuto => {
2383-
if ui_node.current_parent_node_id.is_none() {
2384-
return TopologyQueueVisibilityPolicy::QueueHiddenPromoteChildren;
2385-
}
23862383
let Some(tree_node) = tree_node.and_then(Value::as_object) else {
23872384
return TopologyQueueVisibilityPolicy::QueueVisible;
23882385
};
@@ -5992,6 +5989,238 @@ mod tests {
59925989
assert_eq!(aggregation.get("virtual").and_then(Value::as_bool), None);
59935990
}
59945991

5992+
#[test]
5993+
fn queue_auto_top_level_site_below_threshold_stays_visible() {
5994+
let mut config = Config::default();
5995+
config.uisp_integration.enable_uisp = true;
5996+
config.topology.queue_auto_virtualize_threshold_mbps = 5_000;
5997+
5998+
let editor_state = TopologyEditorStateFile {
5999+
schema_version: 1,
6000+
source: "uisp/full2".to_string(),
6001+
generated_unix: None,
6002+
ingress_identity: None,
6003+
nodes: vec![
6004+
TopologyEditorNode {
6005+
node_id: "site-root".to_string(),
6006+
node_name: "Root Aggregation".to_string(),
6007+
latitude: None,
6008+
longitude: None,
6009+
current_parent_node_id: None,
6010+
current_parent_node_name: None,
6011+
current_attachment_id: None,
6012+
current_attachment_name: None,
6013+
can_move: false,
6014+
allowed_parents: Vec::new(),
6015+
queue_visibility_policy: TopologyQueueVisibilityPolicy::QueueAuto,
6016+
preferred_attachment_id: None,
6017+
preferred_attachment_name: None,
6018+
effective_attachment_id: None,
6019+
effective_attachment_name: None,
6020+
},
6021+
TopologyEditorNode {
6022+
node_id: "site-child".to_string(),
6023+
node_name: "Edge".to_string(),
6024+
latitude: None,
6025+
longitude: None,
6026+
current_parent_node_id: Some("site-root".to_string()),
6027+
current_parent_node_name: Some("Root Aggregation".to_string()),
6028+
current_attachment_id: None,
6029+
current_attachment_name: None,
6030+
can_move: false,
6031+
allowed_parents: Vec::new(),
6032+
queue_visibility_policy: TopologyQueueVisibilityPolicy::QueueVisible,
6033+
preferred_attachment_id: None,
6034+
preferred_attachment_name: None,
6035+
effective_attachment_id: None,
6036+
effective_attachment_name: None,
6037+
},
6038+
],
6039+
};
6040+
6041+
let canonical = json!({
6042+
"Root Aggregation": {
6043+
"children": {
6044+
"Edge": {
6045+
"children": {},
6046+
"downloadBandwidthMbps": 1000,
6047+
"id": "site-child",
6048+
"name": "Edge",
6049+
"type": "Site",
6050+
"uploadBandwidthMbps": 1000
6051+
}
6052+
},
6053+
"downloadBandwidthMbps": 2350,
6054+
"id": "site-root",
6055+
"name": "Root Aggregation",
6056+
"type": "Site",
6057+
"uploadBandwidthMbps": 2350
6058+
}
6059+
});
6060+
6061+
let effective = TopologyEffectiveStateFile {
6062+
schema_version: 1,
6063+
generated_unix: None,
6064+
canonical_generated_unix: None,
6065+
health_generated_unix: None,
6066+
nodes: vec![
6067+
TopologyEffectiveNodeState {
6068+
node_id: "site-root".to_string(),
6069+
logical_parent_node_id: String::new(),
6070+
preferred_attachment_id: None,
6071+
effective_attachment_id: None,
6072+
fallback_reason: None,
6073+
all_attachments_suppressed: false,
6074+
attachments: vec![],
6075+
},
6076+
TopologyEffectiveNodeState {
6077+
node_id: "site-child".to_string(),
6078+
logical_parent_node_id: "site-root".to_string(),
6079+
preferred_attachment_id: None,
6080+
effective_attachment_id: None,
6081+
fallback_reason: None,
6082+
all_attachments_suppressed: false,
6083+
attachments: vec![],
6084+
},
6085+
],
6086+
};
6087+
6088+
let effective_network = apply_effective_topology_to_network_json(
6089+
&config,
6090+
&canonical,
6091+
&editor_state,
6092+
&effective,
6093+
);
6094+
let root = effective_network
6095+
.as_object()
6096+
.expect("effective export should remain an object tree");
6097+
let root_node = root
6098+
.get("Root Aggregation")
6099+
.and_then(Value::as_object)
6100+
.expect("root node should remain exported");
6101+
assert_eq!(root_node.get("virtual").and_then(Value::as_bool), None);
6102+
let children = root_node["children"]
6103+
.as_object()
6104+
.expect("root node should retain its logical children");
6105+
assert!(children.get("Edge").is_some());
6106+
}
6107+
6108+
#[test]
6109+
fn queue_auto_top_level_site_above_threshold_becomes_virtual() {
6110+
let mut config = Config::default();
6111+
config.uisp_integration.enable_uisp = true;
6112+
config.topology.queue_auto_virtualize_threshold_mbps = 5_000;
6113+
6114+
let editor_state = TopologyEditorStateFile {
6115+
schema_version: 1,
6116+
source: "uisp/full2".to_string(),
6117+
generated_unix: None,
6118+
ingress_identity: None,
6119+
nodes: vec![
6120+
TopologyEditorNode {
6121+
node_id: "site-root".to_string(),
6122+
node_name: "Root Aggregation".to_string(),
6123+
latitude: None,
6124+
longitude: None,
6125+
current_parent_node_id: None,
6126+
current_parent_node_name: None,
6127+
current_attachment_id: None,
6128+
current_attachment_name: None,
6129+
can_move: false,
6130+
allowed_parents: Vec::new(),
6131+
queue_visibility_policy: TopologyQueueVisibilityPolicy::QueueAuto,
6132+
preferred_attachment_id: None,
6133+
preferred_attachment_name: None,
6134+
effective_attachment_id: None,
6135+
effective_attachment_name: None,
6136+
},
6137+
TopologyEditorNode {
6138+
node_id: "site-child".to_string(),
6139+
node_name: "Edge".to_string(),
6140+
latitude: None,
6141+
longitude: None,
6142+
current_parent_node_id: Some("site-root".to_string()),
6143+
current_parent_node_name: Some("Root Aggregation".to_string()),
6144+
current_attachment_id: None,
6145+
current_attachment_name: None,
6146+
can_move: false,
6147+
allowed_parents: Vec::new(),
6148+
queue_visibility_policy: TopologyQueueVisibilityPolicy::QueueVisible,
6149+
preferred_attachment_id: None,
6150+
preferred_attachment_name: None,
6151+
effective_attachment_id: None,
6152+
effective_attachment_name: None,
6153+
},
6154+
],
6155+
};
6156+
6157+
let canonical = json!({
6158+
"Root Aggregation": {
6159+
"children": {
6160+
"Edge": {
6161+
"children": {},
6162+
"downloadBandwidthMbps": 1000,
6163+
"id": "site-child",
6164+
"name": "Edge",
6165+
"type": "Site",
6166+
"uploadBandwidthMbps": 1000
6167+
}
6168+
},
6169+
"downloadBandwidthMbps": 7000,
6170+
"id": "site-root",
6171+
"name": "Root Aggregation",
6172+
"type": "Site",
6173+
"uploadBandwidthMbps": 7000
6174+
}
6175+
});
6176+
6177+
let effective = TopologyEffectiveStateFile {
6178+
schema_version: 1,
6179+
generated_unix: None,
6180+
canonical_generated_unix: None,
6181+
health_generated_unix: None,
6182+
nodes: vec![
6183+
TopologyEffectiveNodeState {
6184+
node_id: "site-root".to_string(),
6185+
logical_parent_node_id: String::new(),
6186+
preferred_attachment_id: None,
6187+
effective_attachment_id: None,
6188+
fallback_reason: None,
6189+
all_attachments_suppressed: false,
6190+
attachments: vec![],
6191+
},
6192+
TopologyEffectiveNodeState {
6193+
node_id: "site-child".to_string(),
6194+
logical_parent_node_id: "site-root".to_string(),
6195+
preferred_attachment_id: None,
6196+
effective_attachment_id: None,
6197+
fallback_reason: None,
6198+
all_attachments_suppressed: false,
6199+
attachments: vec![],
6200+
},
6201+
],
6202+
};
6203+
6204+
let effective_network = apply_effective_topology_to_network_json(
6205+
&config,
6206+
&canonical,
6207+
&editor_state,
6208+
&effective,
6209+
);
6210+
let root = effective_network
6211+
.as_object()
6212+
.expect("effective export should remain an object tree");
6213+
let root_node = root
6214+
.get("Root Aggregation")
6215+
.and_then(Value::as_object)
6216+
.expect("root node should remain exported");
6217+
assert_eq!(root_node.get("virtual").and_then(Value::as_bool), Some(true));
6218+
let children = root_node["children"]
6219+
.as_object()
6220+
.expect("root node should retain its logical children");
6221+
assert!(children.get("Edge").is_some());
6222+
}
6223+
59956224
#[test]
59966225
fn runtime_squashing_respects_do_not_squash_sites() {
59976226
let mut config = Config::default();

0 commit comments

Comments
 (0)