mirror of
https://github.com/LibreQoE/LibreQoS.git
synced 2026-10-25 06:17:39 -05:00
changes
This commit is contained in:
1 parent
1f9d442bd4
commit
a2365ca78a
7 files changed
+217
-16
No files matched your search
@@ -178,6 +178,7 @@ Notas:
|
||||
- Durante el shaping, los nodos virtuales se eliminan y sus hijos se promueven al ancestro no virtual más cercano (revisa `queuingStructure.json` para el plan físico activo).
|
||||
- `ShapedDevices.csv` aún puede usar un nodo virtual como `Parent Node` para mostrar/agrupar; LibreQoS adjuntará esos circuitos para shaping al ancestro no virtual más cercano (si el nodo virtual está en el nivel superior y no tiene ancestro no virtual, se tratará como sin nodo padre para el shaping).
|
||||
- Evita colisiones de nombres después de la promoción (dos nodos con el mismo nombre terminando al mismo nivel).
|
||||
- Queue Auto también puede virtualizar ramas de alta capacidad de agregación, uplink o AP representadas como nodos `Site` o `AP` cuando superan `queue_auto_virtualize_threshold_mbps` y tienen ramas de cola hijas. Esto evita que una rama lógica grande se convierta en un cuello de botella de una sola cola de CPU.
|
||||
|
||||
#### Consideraciones de CPU
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ Después, ejecute una sincronización manual y valide salidas antes de aumentar
|
||||
- Use la WebUI para confirmar que la importación y la profundidad del árbol son las esperadas.
|
||||
- Use WebUI para ajustes operativos diarios.
|
||||
- Cuando Splynx no puede inferir el padre de un circuito desde `access_device` o los metadatos del router, LibreQoS deja ese circuito sin padre para shaping en lugar de adjuntarlo a un sitio o AP sintético. Corrija el padre en Splynx o Topology Manager si debe aparecer bajo un sitio o AP real.
|
||||
- En modo `ap_site`, LibreQoS trata los Network Sites de Splynx como contenedores de sitio y las filas de monitoreo con `access_device = 1` como AP o nodos de acceso. Los contenedores de sitio grandes pueden virtualizarse automáticamente cuando superan `queue_auto_virtualize_threshold_mbps`, tienen ramas de cola hijas y no tienen circuitos conectados directamente al ID del sitio. Defina una anulación virtual del nodo en `false` cuando un sitio específico deba permanecer como cola física.
|
||||
- En modo `ap_site`, LibreQoS trata los Network Sites de Splynx como contenedores de sitio y las filas de monitoreo con `access_device = 1` como AP o nodos de acceso. Las ramas grandes de sitios y APs, incluidas ramas de uplink o agregación representadas como nodos `Site` o `AP`, pueden virtualizarse automáticamente cuando superan `queue_auto_virtualize_threshold_mbps`, tienen ramas de cola hijas y no tienen circuitos conectados directamente al ID del nodo. Defina una anulación virtual del nodo en `false` cuando un nodo específico deba permanecer como cola física.
|
||||
|
||||
## Validación en 5 minutos después de cambios Splynx
|
||||
|
||||
|
||||
@@ -119,8 +119,8 @@ In the WebUI, this lives at `Configuration -> Integration - Common` as `Queue Au
|
||||
How it works:
|
||||
|
||||
- `queue_auto_virtualize_threshold_mbps` is the static queue-policy threshold used by `lqos_topology`.
|
||||
- `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.
|
||||
- That same threshold rule applies to top-level `QueueAuto` sites and non-top-level `QueueAuto` sites.
|
||||
- Queue Auto can hide a `Site` or `AP` node when it has child branches and its final effective node rate is at or above the threshold.
|
||||
- That same threshold rule applies to top-level and non-top-level eligible nodes.
|
||||
- Nodes with directly attached circuits stay queue-visible by default.
|
||||
|
||||
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.
|
||||
@@ -131,7 +131,7 @@ This static queue policy is now the primary way to avoid wasting HTB depth or cr
|
||||
|
||||
```{mermaid}
|
||||
flowchart TD
|
||||
A[Node queue policy = QueueAuto] --> B{Is this a Site node?}
|
||||
A[Node queue policy allows auto visibility] --> B{Is this a Site or AP node?}
|
||||
B -->|No| C[Keep queue-visible]
|
||||
B -->|Yes| D{Does it have child branches?}
|
||||
D -->|No| E[Keep queue-visible]
|
||||
@@ -142,9 +142,9 @@ flowchart TD
|
||||
|
||||
Current rule summary:
|
||||
|
||||
- Non-`Site` nodes stay queue-visible under `QueueAuto`.
|
||||
- A `Site` with no child branches stays queue-visible.
|
||||
- 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`.
|
||||
- Node kinds other than `Site` and `AP` stay queue-visible under Queue Auto.
|
||||
- An eligible node with no child branches stays queue-visible.
|
||||
- A top-level or non-top-level eligible node with child branches only becomes static virtual when its final effective node rate is at or above `queue_auto_virtualize_threshold_mbps`.
|
||||
- The rate used for this decision is the recompiled runtime-effective rate, not an earlier raw attachment max.
|
||||
|
||||
When a node becomes static virtual:
|
||||
@@ -334,6 +334,7 @@ Notes:
|
||||
- During shaping, virtual nodes are removed and their children are promoted to the nearest non-virtual ancestor (see `queuingStructure.json` for the active physical plan).
|
||||
- `ShapedDevices.csv` can still use a virtual node as a `Parent Node` for display/grouping; LibreQoS will attach those circuits for shaping to the nearest non-virtual ancestor (top-level virtual nodes will be treated as unparented for shaping).
|
||||
- Avoid name collisions after promotion (two nodes with the same name ending up at the same level).
|
||||
- Queue Auto can also virtualize high-capacity aggregation, uplink, or AP branches represented as `Site` or `AP` nodes when they exceed `queue_auto_virtualize_threshold_mbps` and have child queue branches. This keeps one large logical branch from becoming a single CPU queue bottleneck.
|
||||
|
||||
#### CPU Considerations
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ LibreQoS supports multiple topology strategies for Splynx integration to balance
|
||||
|
||||
When Splynx cannot infer a circuit parent from `access_device` or router metadata, LibreQoS leaves the circuit unparented for shaping instead of attaching it to a synthetic site or AP. Fix the parent in Splynx or Topology Manager if the circuit should appear under a real site or AP.
|
||||
|
||||
In `ap_site` mode, LibreQoS treats Splynx Network Sites as site containers and monitoring rows with `access_device = 1` as AP/access nodes. Large site containers can be virtualized automatically when they exceed `queue_auto_virtualize_threshold_mbps`, have child queue branches, and have no circuits directly attached to the site node ID. Set a node virtual override to `false` when a specific site must remain as a physical queue.
|
||||
In `ap_site` mode, LibreQoS treats Splynx Network Sites as site containers and monitoring rows with `access_device = 1` as AP/access nodes. Large site and AP branches, including uplink or aggregation branches represented as `Site` or `AP` nodes, can be virtualized automatically when they exceed `queue_auto_virtualize_threshold_mbps`, have child queue branches, and have no circuits directly attached to the node ID. Set a node virtual override to `false` when a specific node must remain as a physical queue.
|
||||
|
||||
Configure the shared topology mode in `/etc/lqos.conf` under the `[topology]` section:
|
||||
|
||||
|
||||
@@ -16,8 +16,9 @@ pub struct TopologyConfig {
|
||||
|
||||
/// Practical per-node queue budget used by `queue_auto` visibility policy.
|
||||
///
|
||||
/// Site-like aggregation nodes at or above this threshold may be hidden from the
|
||||
/// queue-visible runtime tree when LibreQoS can safely promote their children.
|
||||
/// Eligible high-capacity `Site` and `AP` branches at or above this threshold may
|
||||
/// be hidden from the queue-visible runtime tree when LibreQoS can safely promote
|
||||
/// their children.
|
||||
#[serde(default = "default_queue_auto_virtualize_threshold_mbps")]
|
||||
pub queue_auto_virtualize_threshold_mbps: u64,
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ pub enum TopologyAttachmentRole {
|
||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TopologyQueueVisibilityPolicy {
|
||||
/// The node remains visible in the baseline queue topology.
|
||||
/// The node remains visible unless automatic high-capacity branch rules hide it.
|
||||
#[default]
|
||||
QueueVisible,
|
||||
/// The node remains logical-only for queueing and its children are promoted one level.
|
||||
|
||||
@@ -2754,11 +2754,7 @@ fn resolved_auto_queue_visibility_policy(
|
||||
let Some(tree_node) = tree_node.and_then(Value::as_object) else {
|
||||
return TopologyQueueVisibilityPolicy::QueueVisible;
|
||||
};
|
||||
let is_site = tree_node
|
||||
.get("type")
|
||||
.and_then(Value::as_str)
|
||||
.is_some_and(|kind| kind.eq_ignore_ascii_case("site"));
|
||||
if !is_site {
|
||||
if !queue_auto_node_kind_can_hide(tree_node) {
|
||||
return TopologyQueueVisibilityPolicy::QueueVisible;
|
||||
}
|
||||
if child_branch_counts
|
||||
@@ -2780,6 +2776,13 @@ fn resolved_auto_queue_visibility_policy(
|
||||
}
|
||||
}
|
||||
|
||||
fn queue_auto_node_kind_can_hide(tree_node: &Map<String, Value>) -> bool {
|
||||
tree_node
|
||||
.get("type")
|
||||
.and_then(Value::as_str)
|
||||
.is_some_and(|kind| matches!(kind.to_ascii_lowercase().as_str(), "site" | "ap"))
|
||||
}
|
||||
|
||||
fn apply_effective_topology_reparenting_only(
|
||||
canonical_network: &Value,
|
||||
ui_state: &TopologyEditorStateFile,
|
||||
@@ -6869,6 +6872,201 @@ mod tests {
|
||||
assert!(aggregation_children.get("Edge POP").is_some());
|
||||
}
|
||||
|
||||
fn ap_branch_fixture() -> (
|
||||
Config,
|
||||
Value,
|
||||
TopologyEditorStateFile,
|
||||
TopologyEffectiveStateFile,
|
||||
) {
|
||||
let mut config = Config::default();
|
||||
config.uisp_integration.enable_uisp = true;
|
||||
config.topology.queue_auto_virtualize_threshold_mbps = 5_000;
|
||||
|
||||
let editor_state = TopologyEditorStateFile {
|
||||
schema_version: 1,
|
||||
source: "uisp/full2".to_string(),
|
||||
generated_unix: None,
|
||||
ingress_identity: None,
|
||||
nodes: vec![
|
||||
TopologyEditorNode {
|
||||
node_id: "site-root".to_string(),
|
||||
node_name: "Core".to_string(),
|
||||
latitude: None,
|
||||
longitude: None,
|
||||
current_parent_node_id: None,
|
||||
current_parent_node_name: None,
|
||||
current_attachment_id: None,
|
||||
current_attachment_name: None,
|
||||
can_move: false,
|
||||
allowed_parents: Vec::new(),
|
||||
queue_visibility_policy: TopologyQueueVisibilityPolicy::QueueVisible,
|
||||
preferred_attachment_id: None,
|
||||
preferred_attachment_name: None,
|
||||
effective_attachment_id: None,
|
||||
effective_attachment_name: None,
|
||||
},
|
||||
TopologyEditorNode {
|
||||
node_id: "ap-agg".to_string(),
|
||||
node_name: "Aggregation Switch".to_string(),
|
||||
latitude: None,
|
||||
longitude: None,
|
||||
current_parent_node_id: Some("site-root".to_string()),
|
||||
current_parent_node_name: Some("Core".to_string()),
|
||||
current_attachment_id: None,
|
||||
current_attachment_name: None,
|
||||
can_move: false,
|
||||
allowed_parents: Vec::new(),
|
||||
queue_visibility_policy: TopologyQueueVisibilityPolicy::QueueVisible,
|
||||
preferred_attachment_id: None,
|
||||
preferred_attachment_name: None,
|
||||
effective_attachment_id: None,
|
||||
effective_attachment_name: None,
|
||||
},
|
||||
TopologyEditorNode {
|
||||
node_id: "ap-child".to_string(),
|
||||
node_name: "Access AP".to_string(),
|
||||
latitude: None,
|
||||
longitude: None,
|
||||
current_parent_node_id: Some("ap-agg".to_string()),
|
||||
current_parent_node_name: Some("Aggregation Switch".to_string()),
|
||||
current_attachment_id: None,
|
||||
current_attachment_name: None,
|
||||
can_move: false,
|
||||
allowed_parents: Vec::new(),
|
||||
queue_visibility_policy: TopologyQueueVisibilityPolicy::QueueVisible,
|
||||
preferred_attachment_id: None,
|
||||
preferred_attachment_name: None,
|
||||
effective_attachment_id: None,
|
||||
effective_attachment_name: None,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let canonical = json!({
|
||||
"Core": {
|
||||
"children": {
|
||||
"Aggregation Switch": {
|
||||
"children": {
|
||||
"Access AP": {
|
||||
"children": {},
|
||||
"downloadBandwidthMbps": 1000,
|
||||
"id": "ap-child",
|
||||
"name": "Access AP",
|
||||
"type": "AP",
|
||||
"uploadBandwidthMbps": 1000
|
||||
}
|
||||
},
|
||||
"downloadBandwidthMbps": 10000,
|
||||
"id": "ap-agg",
|
||||
"name": "Aggregation Switch",
|
||||
"type": "AP",
|
||||
"uploadBandwidthMbps": 10000
|
||||
}
|
||||
},
|
||||
"downloadBandwidthMbps": 20000,
|
||||
"id": "site-root",
|
||||
"name": "Core",
|
||||
"type": "Site",
|
||||
"uploadBandwidthMbps": 20000
|
||||
}
|
||||
});
|
||||
|
||||
let effective = TopologyEffectiveStateFile {
|
||||
schema_version: 1,
|
||||
generated_unix: None,
|
||||
canonical_generated_unix: None,
|
||||
health_generated_unix: None,
|
||||
nodes: vec![
|
||||
TopologyEffectiveNodeState {
|
||||
node_id: "site-root".to_string(),
|
||||
logical_parent_node_id: String::new(),
|
||||
preferred_attachment_id: None,
|
||||
effective_attachment_id: None,
|
||||
fallback_reason: None,
|
||||
all_attachments_suppressed: false,
|
||||
attachments: vec![],
|
||||
},
|
||||
TopologyEffectiveNodeState {
|
||||
node_id: "ap-agg".to_string(),
|
||||
logical_parent_node_id: "site-root".to_string(),
|
||||
preferred_attachment_id: None,
|
||||
effective_attachment_id: None,
|
||||
fallback_reason: None,
|
||||
all_attachments_suppressed: false,
|
||||
attachments: vec![],
|
||||
},
|
||||
TopologyEffectiveNodeState {
|
||||
node_id: "ap-child".to_string(),
|
||||
logical_parent_node_id: "ap-agg".to_string(),
|
||||
preferred_attachment_id: None,
|
||||
effective_attachment_id: None,
|
||||
fallback_reason: None,
|
||||
all_attachments_suppressed: false,
|
||||
attachments: vec![],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
(config, canonical, editor_state, effective)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn queue_auto_marks_large_ap_branch_virtual_without_treeguard() {
|
||||
let (config, canonical, editor_state, effective) = ap_branch_fixture();
|
||||
|
||||
let effective_network = apply_effective_topology_to_network_json(
|
||||
&config,
|
||||
&canonical,
|
||||
&editor_state,
|
||||
&effective,
|
||||
);
|
||||
let root = effective_network
|
||||
.as_object()
|
||||
.expect("effective export should remain an object tree");
|
||||
let aggregation = root["Core"]["children"]["Aggregation Switch"]
|
||||
.as_object()
|
||||
.expect("Aggregation Switch should remain visible as a virtual node");
|
||||
assert_eq!(
|
||||
aggregation.get("virtual").and_then(Value::as_bool),
|
||||
Some(true)
|
||||
);
|
||||
let aggregation_children = aggregation["children"]
|
||||
.as_object()
|
||||
.expect("Aggregation Switch should retain its logical children");
|
||||
assert!(aggregation_children.get("Access AP").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn queue_auto_keeps_large_ap_branch_visible_with_direct_circuit() {
|
||||
let (config, canonical, editor_state, effective) = ap_branch_fixture();
|
||||
let virtualization = QueueVirtualizationContext {
|
||||
direct_circuit_node_ids: HashSet::from(["ap-agg".to_string()]),
|
||||
forced_visible_node_names: HashSet::new(),
|
||||
};
|
||||
let canonical_state = TopologyCanonicalStateFile::from_editor_and_network(
|
||||
&editor_state,
|
||||
&canonical,
|
||||
TopologyCanonicalIngressKind::NativeIntegration,
|
||||
);
|
||||
|
||||
let effective_network = apply_effective_topology_to_network_json_from_canonical(
|
||||
&config,
|
||||
&canonical,
|
||||
&canonical_state,
|
||||
&editor_state,
|
||||
&effective,
|
||||
&virtualization,
|
||||
);
|
||||
let root = effective_network
|
||||
.as_object()
|
||||
.expect("effective export should remain an object tree");
|
||||
let aggregation = root["Core"]["children"]["Aggregation Switch"]
|
||||
.as_object()
|
||||
.expect("Aggregation Switch should remain exported");
|
||||
|
||||
assert_eq!(aggregation.get("virtual").and_then(Value::as_bool), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn queue_auto_uses_recompiled_effective_rate_before_virtualizing() {
|
||||
let mut config = Config::default();
|
||||
|
||||
Reference in new issue
Block a user