mirror of
https://github.com/LibreQoE/LibreQoS.git
synced 2026-10-25 14:20:05 -05:00
changes
This commit is contained in:
1 parent
823450f1d2
commit
86f758d79a
4 files changed
+44
-2
No files matched your search
@@ -15,6 +15,7 @@ Use esta integración cuando Sonar sea su fuente de verdad CRM/NMS.
|
||||
- La integración refresca automáticamente los datos importados y de shaping que LibreQoS usa con Sonar.
|
||||
- `network.json` queda para despliegues DIY o manuales.
|
||||
- La integración importa IPs desde equipos de inventario, cuentas Radius e IPs asignadas directamente a la cuenta Sonar. Las IPs de Radius y de cuenta se agregan como dispositivos suplementarios de shaping y se deduplican contra las IPs de inventario.
|
||||
- Las asignaciones IPv4 e IPv6 se separan en los campos correctos de LibreQoS. Los prefijos IPv6 de Sonar, como redes delegadas `/56`, se importan como asignaciones IPv6.
|
||||
- Use la WebUI para confirmar que la importación terminó correctamente.
|
||||
|
||||
## Referencia completa
|
||||
|
||||
@@ -8,6 +8,7 @@ Current behavior notes:
|
||||
- Paginated Sonar GraphQL requests now use a split connect/read timeout and retry transient read timeouts before failing the import.
|
||||
- Emitted Sonar identities are namespaced (for example `sonar:account:<id>` and `sonar:device:<id>`) so they remain stable across overrides and downstream tooling.
|
||||
- Account device discovery now preserves inventory-item IP handling and also imports Radius account and account-level IP assignments when they exist. Inventory-backed MACs are still used for AP mapping; Radius-only and account-level IPs are added as supplemental shaping devices and overlapping subnets are de-duplicated.
|
||||
- IPv4 and IPv6 assignments are split into the correct LibreQoS fields. Sonar IPv6 prefixes such as delegated `/56` networks are imported as IPv6 assignments, not IPv4 device entries.
|
||||
- Sonar `child_accounts` are also imported when they expose their own service and usable IP data. If a child account lacks its own address, LibreQoS falls back to the parent account address so the child can still be emitted as its own circuit.
|
||||
- Sonar settings now support ISP-specific recurring-service fallback rates plus a recurring-service exclusion list. LibreQoS still prefers active `DATA` services first; recurring mappings are only used when an account has no usable `DATA` service.
|
||||
- If Sonar returns non-JSON content or GraphQL errors, the integration now raises a more specific error message showing the endpoint and a short response preview.
|
||||
|
||||
+28
-1
@@ -1,5 +1,6 @@
|
||||
from pythonCheck import checkPythonVersion
|
||||
checkPythonVersion()
|
||||
import ipaddress
|
||||
import requests
|
||||
import subprocess
|
||||
import time
|
||||
@@ -227,6 +228,23 @@ def dedupeSubnets(subnets):
|
||||
return deduped
|
||||
|
||||
|
||||
def splitIpVersions(subnets):
|
||||
ipv4 = []
|
||||
ipv6 = []
|
||||
invalid = []
|
||||
for subnet in subnets or []:
|
||||
try:
|
||||
network = ipaddress.ip_network(subnet, strict=False)
|
||||
except ValueError:
|
||||
invalid.append(subnet)
|
||||
continue
|
||||
if network.version == 4:
|
||||
ipv4.append(subnet)
|
||||
else:
|
||||
ipv6.append(subnet)
|
||||
return ipv4, ipv6, invalid
|
||||
|
||||
|
||||
def normalizeServiceName(name):
|
||||
return (name or "").strip().casefold()
|
||||
|
||||
@@ -773,6 +791,8 @@ def buildMacToApMap(aps):
|
||||
def sonarImportSummary(sites, aps, accounts):
|
||||
total_devices = 0
|
||||
devices_with_ips = 0
|
||||
ipv4_assignments = 0
|
||||
ipv6_assignments = 0
|
||||
account_level_ip_devices = 0
|
||||
accounts_with_parent = 0
|
||||
for account in accounts:
|
||||
@@ -782,12 +802,16 @@ def sonarImportSummary(sites, aps, accounts):
|
||||
total_devices += 1
|
||||
if len(device.get('ips') or []) > 0:
|
||||
devices_with_ips += 1
|
||||
ipv4, ipv6, _invalid = splitIpVersions(device.get('ips', []))
|
||||
ipv4_assignments += len(ipv4)
|
||||
ipv6_assignments += len(ipv6)
|
||||
if device.get('source') == 'account_ip_assignments':
|
||||
account_level_ip_devices += 1
|
||||
return (
|
||||
f"Sonar import summary: sites={len(sites)} aps={len(aps)} "
|
||||
f"accounts={len(accounts)} accounts_with_ap_parent={accounts_with_parent} "
|
||||
f"devices={total_devices} devices_with_ips={devices_with_ips} "
|
||||
f"ipv4_assignments={ipv4_assignments} ipv6_assignments={ipv6_assignments} "
|
||||
f"account_level_ip_devices={account_level_ip_devices}"
|
||||
)
|
||||
|
||||
@@ -836,7 +860,10 @@ def createShaper():
|
||||
net.addRawNode(customer)
|
||||
|
||||
for device in account['devices']:
|
||||
libre_device = NetworkNode(id=device['id'], displayName=device['name'],parentId=account['id'],type=NodeType.device, ipv4=device['ips'],ipv6=[],mac=device['mac'])
|
||||
ipv4, ipv6, invalid_ips = splitIpVersions(device.get('ips', []))
|
||||
if invalid_ips:
|
||||
print(f"Skipping invalid Sonar IP assignment(s) for {device['id']}: {invalid_ips}")
|
||||
libre_device = NetworkNode(id=device['id'], displayName=device['name'],parentId=account['id'],type=NodeType.device, ipv4=ipv4,ipv6=ipv6,mac=device['mac'])
|
||||
net.addRawNode(libre_device)
|
||||
|
||||
net.prepareTree()
|
||||
|
||||
@@ -180,6 +180,17 @@ class TestIntegrationSonarDevices(unittest.TestCase):
|
||||
self.assertEqual(devices[1]["mac"], "")
|
||||
self.assertEqual(devices[1]["source"], "account_ip_assignments")
|
||||
|
||||
def test_split_ip_versions_preserves_ipv6_prefixes(self):
|
||||
ipv4, ipv6, invalid = integrationSonar.splitIpVersions([
|
||||
"100.64.8.80",
|
||||
"2605:40c1:300::/56",
|
||||
"not an ip",
|
||||
])
|
||||
|
||||
self.assertEqual(ipv4, ["100.64.8.80"])
|
||||
self.assertEqual(ipv6, ["2605:40c1:300::/56"])
|
||||
self.assertEqual(invalid, ["not an ip"])
|
||||
|
||||
def test_account_level_ips_are_deduped_against_inventory_and_radius_ips(self):
|
||||
devices = integrationSonar.buildAccountDevices(
|
||||
sonar_account(
|
||||
@@ -429,7 +440,7 @@ class TestIntegrationSonarDevices(unittest.TestCase):
|
||||
},
|
||||
{
|
||||
"id": "sonar:account-ip-assignments:1",
|
||||
"ips": ["100.64.11.11"],
|
||||
"ips": ["100.64.11.11", "2605:40c1:300::/56"],
|
||||
"source": "account_ip_assignments",
|
||||
},
|
||||
],
|
||||
@@ -447,6 +458,8 @@ class TestIntegrationSonarDevices(unittest.TestCase):
|
||||
self.assertIn("accounts_with_ap_parent=1", summary)
|
||||
self.assertIn("devices=2", summary)
|
||||
self.assertIn("devices_with_ips=1", summary)
|
||||
self.assertIn("ipv4_assignments=1", summary)
|
||||
self.assertIn("ipv6_assignments=1", summary)
|
||||
self.assertIn("account_level_ip_devices=1", summary)
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user