mirror of
https://github.com/LibreQoE/LibreQoS.git
synced 2025-02-25 18:55:32 -06:00
Update LibreQoS.py
This commit is contained in:
parent
5aab722ab4
commit
2261610723
799
v1.3/LibreQoS.py
799
v1.3/LibreQoS.py
@ -1,5 +1,6 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
# v1.2.1
|
#!/usr/bin/python3
|
||||||
|
# v1.3
|
||||||
|
|
||||||
import csv
|
import csv
|
||||||
import io
|
import io
|
||||||
@ -19,9 +20,13 @@ import shutil
|
|||||||
import binpacking
|
import binpacking
|
||||||
|
|
||||||
from ispConfig import fqOrCAKE, upstreamBandwidthCapacityDownloadMbps, upstreamBandwidthCapacityUploadMbps, \
|
from ispConfig import fqOrCAKE, upstreamBandwidthCapacityDownloadMbps, upstreamBandwidthCapacityUploadMbps, \
|
||||||
interfaceA, interfaceB, enableActualShellCommands, \
|
interfaceA, interfaceB, enableActualShellCommands, useBinPackingToBalanceCPU, \
|
||||||
runShellCommandsAsSudo, generatedPNDownloadMbps, generatedPNUploadMbps, queuesAvailableOverride
|
runShellCommandsAsSudo, generatedPNDownloadMbps, generatedPNUploadMbps, queuesAvailableOverride
|
||||||
|
|
||||||
|
# Automatically account for TCP overhead of plans. For example a 100Mbps plan needs to be set to 109Mbps for the user to ever see that result on a speed test
|
||||||
|
# Does not apply to nodes of any sort, just endpoint devices
|
||||||
|
tcpOverheadFactor = 1.09
|
||||||
|
|
||||||
def shell(command):
|
def shell(command):
|
||||||
if enableActualShellCommands:
|
if enableActualShellCommands:
|
||||||
if runShellCommandsAsSudo:
|
if runShellCommandsAsSudo:
|
||||||
@ -30,12 +35,25 @@ def shell(command):
|
|||||||
commands = command.split(' ')
|
commands = command.split(' ')
|
||||||
proc = subprocess.Popen(commands, stdout=subprocess.PIPE)
|
proc = subprocess.Popen(commands, stdout=subprocess.PIPE)
|
||||||
for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"): # or another encoding
|
for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"): # or another encoding
|
||||||
logging.info(line)
|
if logging.DEBUG <= logging.root.level:
|
||||||
|
print(line)
|
||||||
if ("RTNETLINK answers" in line) or ("We have an error talking to the kernel" in line):
|
if ("RTNETLINK answers" in line) or ("We have an error talking to the kernel" in line):
|
||||||
warnings.warn("Command: '" + command + "' resulted in " + line, stacklevel=2)
|
warnings.warn("Command: '" + command + "' resulted in " + line, stacklevel=2)
|
||||||
else:
|
else:
|
||||||
logging.info(command)
|
logging.info(command)
|
||||||
|
|
||||||
|
def shellTC(command):
|
||||||
|
if enableActualShellCommands:
|
||||||
|
print(command)
|
||||||
|
commands = command.split(' ')
|
||||||
|
proc = subprocess.Popen(commands, stdout=subprocess.PIPE)
|
||||||
|
for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"): # or another encoding
|
||||||
|
if logging.DEBUG <= logging.root.level:
|
||||||
|
print(line)
|
||||||
|
if ("RTNETLINK answers" in line) or ("We have an error talking to the kernel" in line):
|
||||||
|
warnings.warn("Command: '" + command + "' resulted in " + line, stacklevel=2)
|
||||||
|
raise SystemError("Command: '" + command + "' resulted in " + line)
|
||||||
|
|
||||||
def checkIfFirstRunSinceBoot():
|
def checkIfFirstRunSinceBoot():
|
||||||
if os.path.isfile("lastRun.txt"):
|
if os.path.isfile("lastRun.txt"):
|
||||||
with open("lastRun.txt", 'r') as file:
|
with open("lastRun.txt", 'r') as file:
|
||||||
@ -84,6 +102,10 @@ def findQueuesAvailable():
|
|||||||
print("NIC queues (Override):\t\t\t" + str(queuesAvailable))
|
print("NIC queues (Override):\t\t\t" + str(queuesAvailable))
|
||||||
cpuCount = multiprocessing.cpu_count()
|
cpuCount = multiprocessing.cpu_count()
|
||||||
print("CPU cores:\t\t\t" + str(cpuCount))
|
print("CPU cores:\t\t\t" + str(cpuCount))
|
||||||
|
if queuesAvailable < 2:
|
||||||
|
raise SystemError('Only 1 NIC rx/tx queue avaialable. You will need to use a NIC with 2 or more rx/tx queues available.')
|
||||||
|
if queuesAvailable < 2:
|
||||||
|
raise SystemError('Only 1 CPU core avaialable. You will need to use a CPU with 2 or more CPU cores.')
|
||||||
queuesAvailable = min(queuesAvailable,cpuCount)
|
queuesAvailable = min(queuesAvailable,cpuCount)
|
||||||
print("queuesAvailable set to:\t" + str(queuesAvailable))
|
print("queuesAvailable set to:\t" + str(queuesAvailable))
|
||||||
else:
|
else:
|
||||||
@ -121,6 +143,10 @@ def validateNetworkAndDevices():
|
|||||||
seenTheseIPsAlready = []
|
seenTheseIPsAlready = []
|
||||||
for row in commentsRemoved:
|
for row in commentsRemoved:
|
||||||
circuitID, circuitName, deviceID, deviceName, ParentNode, mac, ipv4_input, ipv6_input, downloadMin, uploadMin, downloadMax, uploadMax, comment = row
|
circuitID, circuitName, deviceID, deviceName, ParentNode, mac, ipv4_input, ipv6_input, downloadMin, uploadMin, downloadMax, uploadMax, comment = row
|
||||||
|
# Must have circuitID, it's a unique identifier requried for stateful changes to queue structure
|
||||||
|
if circuitID == '':
|
||||||
|
warnings.warn("No Circuit ID provided in ShapedDevices.csv at row " + str(rowNum), stacklevel=2)
|
||||||
|
devicesValidatedOrNot = False
|
||||||
# Each entry in ShapedDevices.csv can have multiple IPv4s or IPv6s seperated by commas. Split them up and parse each to ensure valid
|
# Each entry in ShapedDevices.csv can have multiple IPv4s or IPv6s seperated by commas. Split them up and parse each to ensure valid
|
||||||
ipv4_subnets_and_hosts = []
|
ipv4_subnets_and_hosts = []
|
||||||
ipv6_subnets_and_hosts = []
|
ipv6_subnets_and_hosts = []
|
||||||
@ -222,6 +248,137 @@ def validateNetworkAndDevices():
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def loadSubscriberCircuits(shapedDevicesFile):
|
||||||
|
# Load Subscriber Circuits & Devices
|
||||||
|
subscriberCircuits = []
|
||||||
|
knownCircuitIDs = []
|
||||||
|
counterForCircuitsWithoutParentNodes = 0
|
||||||
|
dictForCircuitsWithoutParentNodes = {}
|
||||||
|
with open(shapedDevicesFile) as csv_file:
|
||||||
|
csv_reader = csv.reader(csv_file, delimiter=',')
|
||||||
|
# Remove comments if any
|
||||||
|
commentsRemoved = []
|
||||||
|
for row in csv_reader:
|
||||||
|
if not row[0].startswith('#'):
|
||||||
|
commentsRemoved.append(row)
|
||||||
|
# Remove header
|
||||||
|
commentsRemoved.pop(0)
|
||||||
|
for row in commentsRemoved:
|
||||||
|
circuitID, circuitName, deviceID, deviceName, ParentNode, mac, ipv4_input, ipv6_input, downloadMin, uploadMin, downloadMax, uploadMax, comment = row
|
||||||
|
ipv4_subnets_and_hosts = []
|
||||||
|
# Each entry in ShapedDevices.csv can have multiple IPv4s or IPv6s seperated by commas. Split them up and parse each
|
||||||
|
if ipv4_input != "":
|
||||||
|
ipv4_input = ipv4_input.replace(' ','')
|
||||||
|
if "," in ipv4_input:
|
||||||
|
ipv4_list = ipv4_input.split(',')
|
||||||
|
else:
|
||||||
|
ipv4_list = [ipv4_input]
|
||||||
|
for ipEntry in ipv4_list:
|
||||||
|
ipv4_subnets_and_hosts.append(ipEntry)
|
||||||
|
ipv6_subnets_and_hosts = []
|
||||||
|
if ipv6_input != "":
|
||||||
|
ipv6_input = ipv6_input.replace(' ','')
|
||||||
|
if "," in ipv6_input:
|
||||||
|
ipv6_list = ipv6_input.split(',')
|
||||||
|
else:
|
||||||
|
ipv6_list = [ipv6_input]
|
||||||
|
for ipEntry in ipv6_list:
|
||||||
|
ipv6_subnets_and_hosts.append(ipEntry)
|
||||||
|
# If there is something in the circuit ID field
|
||||||
|
if circuitID != "":
|
||||||
|
# Seen circuit before
|
||||||
|
if circuitID in knownCircuitIDs:
|
||||||
|
for circuit in subscriberCircuits:
|
||||||
|
if circuit['circuitID'] == circuitID:
|
||||||
|
if circuit['ParentNode'] != "none":
|
||||||
|
if circuit['ParentNode'] != ParentNode:
|
||||||
|
errorMessageString = "Device " + deviceName + " with deviceID " + deviceID + " had different Parent Node from other devices of circuit ID #" + circuitID
|
||||||
|
raise ValueError(errorMessageString)
|
||||||
|
if ((circuit['minDownload'] != round(int(downloadMin)*tcpOverheadFactor))
|
||||||
|
or (circuit['minUpload'] != round(int(uploadMin)*tcpOverheadFactor))
|
||||||
|
or (circuit['maxDownload'] != round(int(downloadMax)*tcpOverheadFactor))
|
||||||
|
or (circuit['maxUpload'] != round(int(uploadMax)*tcpOverheadFactor))):
|
||||||
|
warnings.warn("Device " + deviceName + " with ID " + deviceID + " had different bandwidth parameters than other devices on this circuit. Will instead use the bandwidth parameters defined by the first device added to its circuit.", stacklevel=2)
|
||||||
|
devicesListForCircuit = circuit['devices']
|
||||||
|
thisDevice = {
|
||||||
|
"deviceID": deviceID,
|
||||||
|
"deviceName": deviceName,
|
||||||
|
"mac": mac,
|
||||||
|
"ipv4s": ipv4_subnets_and_hosts,
|
||||||
|
"ipv6s": ipv6_subnets_and_hosts,
|
||||||
|
"comment": comment
|
||||||
|
}
|
||||||
|
devicesListForCircuit.append(thisDevice)
|
||||||
|
circuit['devices'] = devicesListForCircuit
|
||||||
|
# Have not seen circuit before
|
||||||
|
else:
|
||||||
|
knownCircuitIDs.append(circuitID)
|
||||||
|
if ParentNode == "":
|
||||||
|
ParentNode = "none"
|
||||||
|
ParentNode = ParentNode.strip()
|
||||||
|
deviceListForCircuit = []
|
||||||
|
thisDevice = {
|
||||||
|
"deviceID": deviceID,
|
||||||
|
"deviceName": deviceName,
|
||||||
|
"mac": mac,
|
||||||
|
"ipv4s": ipv4_subnets_and_hosts,
|
||||||
|
"ipv6s": ipv6_subnets_and_hosts,
|
||||||
|
"comment": comment
|
||||||
|
}
|
||||||
|
deviceListForCircuit.append(thisDevice)
|
||||||
|
thisCircuit = {
|
||||||
|
"circuitID": circuitID,
|
||||||
|
"circuitName": circuitName,
|
||||||
|
"ParentNode": ParentNode,
|
||||||
|
"devices": deviceListForCircuit,
|
||||||
|
"minDownload": round(int(downloadMin)*tcpOverheadFactor),
|
||||||
|
"minUpload": round(int(uploadMin)*tcpOverheadFactor),
|
||||||
|
"maxDownload": round(int(downloadMax)*tcpOverheadFactor),
|
||||||
|
"maxUpload": round(int(uploadMax)*tcpOverheadFactor),
|
||||||
|
"classid": '',
|
||||||
|
"comment": comment
|
||||||
|
}
|
||||||
|
if thisCircuit['ParentNode'] == 'none':
|
||||||
|
thisCircuit['idForCircuitsWithoutParentNodes'] = counterForCircuitsWithoutParentNodes
|
||||||
|
dictForCircuitsWithoutParentNodes[counterForCircuitsWithoutParentNodes] = ((round(int(downloadMax)*tcpOverheadFactor))+(round(int(uploadMax)*tcpOverheadFactor)))
|
||||||
|
counterForCircuitsWithoutParentNodes += 1
|
||||||
|
subscriberCircuits.append(thisCircuit)
|
||||||
|
# If there is nothing in the circuit ID field
|
||||||
|
else:
|
||||||
|
# Copy deviceName to circuitName if none defined already
|
||||||
|
if circuitName == "":
|
||||||
|
circuitName = deviceName
|
||||||
|
if ParentNode == "":
|
||||||
|
ParentNode = "none"
|
||||||
|
ParentNode = ParentNode.strip()
|
||||||
|
deviceListForCircuit = []
|
||||||
|
thisDevice = {
|
||||||
|
"deviceID": deviceID,
|
||||||
|
"deviceName": deviceName,
|
||||||
|
"mac": mac,
|
||||||
|
"ipv4s": ipv4_subnets_and_hosts,
|
||||||
|
"ipv6s": ipv6_subnets_and_hosts,
|
||||||
|
}
|
||||||
|
deviceListForCircuit.append(thisDevice)
|
||||||
|
thisCircuit = {
|
||||||
|
"circuitID": circuitID,
|
||||||
|
"circuitName": circuitName,
|
||||||
|
"ParentNode": ParentNode,
|
||||||
|
"devices": deviceListForCircuit,
|
||||||
|
"minDownload": round(int(downloadMin)*tcpOverheadFactor),
|
||||||
|
"minUpload": round(int(uploadMin)*tcpOverheadFactor),
|
||||||
|
"maxDownload": round(int(downloadMax)*tcpOverheadFactor),
|
||||||
|
"maxUpload": round(int(uploadMax)*tcpOverheadFactor),
|
||||||
|
"classid": '',
|
||||||
|
"comment": comment
|
||||||
|
}
|
||||||
|
if thisCircuit['ParentNode'] == 'none':
|
||||||
|
thisCircuit['idForCircuitsWithoutParentNodes'] = counterForCircuitsWithoutParentNodes
|
||||||
|
dictForCircuitsWithoutParentNodes[counterForCircuitsWithoutParentNodes] = ((round(int(downloadMax)*tcpOverheadFactor))+(round(int(uploadMax)*tcpOverheadFactor)))
|
||||||
|
counterForCircuitsWithoutParentNodes += 1
|
||||||
|
subscriberCircuits.append(thisCircuit)
|
||||||
|
return (subscriberCircuits, dictForCircuitsWithoutParentNodes)
|
||||||
|
|
||||||
def refreshShapers():
|
def refreshShapers():
|
||||||
|
|
||||||
# Starting
|
# Starting
|
||||||
@ -237,11 +394,6 @@ def refreshShapers():
|
|||||||
isThisFirstRunSinceBoot = checkIfFirstRunSinceBoot()
|
isThisFirstRunSinceBoot = checkIfFirstRunSinceBoot()
|
||||||
|
|
||||||
|
|
||||||
# Automatically account for TCP overhead of plans. For example a 100Mbps plan needs to be set to 109Mbps for the user to ever see that result on a speed test
|
|
||||||
# Does not apply to nodes of any sort, just endpoint devices
|
|
||||||
tcpOverheadFactor = 1.09
|
|
||||||
|
|
||||||
|
|
||||||
# Files
|
# Files
|
||||||
shapedDevicesFile = 'ShapedDevices.csv'
|
shapedDevicesFile = 'ShapedDevices.csv'
|
||||||
networkJSONfile = 'network.json'
|
networkJSONfile = 'network.json'
|
||||||
@ -249,6 +401,7 @@ def refreshShapers():
|
|||||||
|
|
||||||
# Check validation
|
# Check validation
|
||||||
safeToRunRefresh = False
|
safeToRunRefresh = False
|
||||||
|
print("Validating input files '" + shapedDevicesFile + "' and '" + networkJSONfile + "'")
|
||||||
if (validateNetworkAndDevices() == True):
|
if (validateNetworkAndDevices() == True):
|
||||||
shutil.copyfile('ShapedDevices.csv', 'lastGoodConfig.csv')
|
shutil.copyfile('ShapedDevices.csv', 'lastGoodConfig.csv')
|
||||||
shutil.copyfile('network.json', 'lastGoodConfig.json')
|
shutil.copyfile('network.json', 'lastGoodConfig.json')
|
||||||
@ -267,134 +420,8 @@ def refreshShapers():
|
|||||||
if safeToRunRefresh == True:
|
if safeToRunRefresh == True:
|
||||||
|
|
||||||
# Load Subscriber Circuits & Devices
|
# Load Subscriber Circuits & Devices
|
||||||
subscriberCircuits = []
|
subscriberCircuits, dictForCircuitsWithoutParentNodes = loadSubscriberCircuits(shapedDevicesFile)
|
||||||
knownCircuitIDs = []
|
|
||||||
counterForCircuitsWithoutParentNodes = 0
|
|
||||||
dictForCircuitsWithoutParentNodes = {}
|
|
||||||
with open(shapedDevicesFile) as csv_file:
|
|
||||||
csv_reader = csv.reader(csv_file, delimiter=',')
|
|
||||||
# Remove comments if any
|
|
||||||
commentsRemoved = []
|
|
||||||
for row in csv_reader:
|
|
||||||
if not row[0].startswith('#'):
|
|
||||||
commentsRemoved.append(row)
|
|
||||||
# Remove header
|
|
||||||
commentsRemoved.pop(0)
|
|
||||||
for row in commentsRemoved:
|
|
||||||
circuitID, circuitName, deviceID, deviceName, ParentNode, mac, ipv4_input, ipv6_input, downloadMin, uploadMin, downloadMax, uploadMax, comment = row
|
|
||||||
ipv4_subnets_and_hosts = []
|
|
||||||
# Each entry in ShapedDevices.csv can have multiple IPv4s or IPv6s seperated by commas. Split them up and parse each
|
|
||||||
if ipv4_input != "":
|
|
||||||
ipv4_input = ipv4_input.replace(' ','')
|
|
||||||
if "," in ipv4_input:
|
|
||||||
ipv4_list = ipv4_input.split(',')
|
|
||||||
else:
|
|
||||||
ipv4_list = [ipv4_input]
|
|
||||||
for ipEntry in ipv4_list:
|
|
||||||
ipv4_subnets_and_hosts.append(ipEntry)
|
|
||||||
ipv6_subnets_and_hosts = []
|
|
||||||
if ipv6_input != "":
|
|
||||||
ipv6_input = ipv6_input.replace(' ','')
|
|
||||||
if "," in ipv6_input:
|
|
||||||
ipv6_list = ipv6_input.split(',')
|
|
||||||
else:
|
|
||||||
ipv6_list = [ipv6_input]
|
|
||||||
for ipEntry in ipv6_list:
|
|
||||||
ipv6_subnets_and_hosts.append(ipEntry)
|
|
||||||
# If there is something in the circuit ID field
|
|
||||||
if circuitID != "":
|
|
||||||
# Seen circuit before
|
|
||||||
if circuitID in knownCircuitIDs:
|
|
||||||
for circuit in subscriberCircuits:
|
|
||||||
if circuit['circuitID'] == circuitID:
|
|
||||||
if circuit['ParentNode'] != "none":
|
|
||||||
if circuit['ParentNode'] != ParentNode:
|
|
||||||
errorMessageString = "Device " + deviceName + " with deviceID " + deviceID + " had different Parent Node from other devices of circuit ID #" + circuitID
|
|
||||||
raise ValueError(errorMessageString)
|
|
||||||
if ((circuit['downloadMin'] != round(int(downloadMin)*tcpOverheadFactor))
|
|
||||||
or (circuit['uploadMin'] != round(int(uploadMin)*tcpOverheadFactor))
|
|
||||||
or (circuit['downloadMax'] != round(int(downloadMax)*tcpOverheadFactor))
|
|
||||||
or (circuit['uploadMax'] != round(int(uploadMax)*tcpOverheadFactor))):
|
|
||||||
warnings.warn("Device " + deviceName + " with ID " + deviceID + " had different bandwidth parameters than other devices on this circuit. Will instead use the bandwidth parameters defined by the first device added to its circuit.", stacklevel=2)
|
|
||||||
devicesListForCircuit = circuit['devices']
|
|
||||||
thisDevice = {
|
|
||||||
"deviceID": deviceID,
|
|
||||||
"deviceName": deviceName,
|
|
||||||
"mac": mac,
|
|
||||||
"ipv4s": ipv4_subnets_and_hosts,
|
|
||||||
"ipv6s": ipv6_subnets_and_hosts,
|
|
||||||
"comment": comment
|
|
||||||
}
|
|
||||||
devicesListForCircuit.append(thisDevice)
|
|
||||||
circuit['devices'] = devicesListForCircuit
|
|
||||||
# Have not seen circuit before
|
|
||||||
else:
|
|
||||||
knownCircuitIDs.append(circuitID)
|
|
||||||
if ParentNode == "":
|
|
||||||
ParentNode = "none"
|
|
||||||
ParentNode = ParentNode.strip()
|
|
||||||
deviceListForCircuit = []
|
|
||||||
thisDevice = {
|
|
||||||
"deviceID": deviceID,
|
|
||||||
"deviceName": deviceName,
|
|
||||||
"mac": mac,
|
|
||||||
"ipv4s": ipv4_subnets_and_hosts,
|
|
||||||
"ipv6s": ipv6_subnets_and_hosts,
|
|
||||||
"comment": comment
|
|
||||||
}
|
|
||||||
deviceListForCircuit.append(thisDevice)
|
|
||||||
thisCircuit = {
|
|
||||||
"circuitID": circuitID,
|
|
||||||
"circuitName": circuitName,
|
|
||||||
"ParentNode": ParentNode,
|
|
||||||
"devices": deviceListForCircuit,
|
|
||||||
"downloadMin": round(int(downloadMin)*tcpOverheadFactor),
|
|
||||||
"uploadMin": round(int(uploadMin)*tcpOverheadFactor),
|
|
||||||
"downloadMax": round(int(downloadMax)*tcpOverheadFactor),
|
|
||||||
"uploadMax": round(int(uploadMax)*tcpOverheadFactor),
|
|
||||||
"qdisc": '',
|
|
||||||
"comment": comment
|
|
||||||
}
|
|
||||||
if thisCircuit['ParentNode'] == 'none':
|
|
||||||
thisCircuit['idForCircuitsWithoutParentNodes'] = counterForCircuitsWithoutParentNodes
|
|
||||||
dictForCircuitsWithoutParentNodes[counterForCircuitsWithoutParentNodes] = ((round(int(downloadMax)*tcpOverheadFactor))+(round(int(uploadMax)*tcpOverheadFactor)))
|
|
||||||
counterForCircuitsWithoutParentNodes += 1
|
|
||||||
subscriberCircuits.append(thisCircuit)
|
|
||||||
# If there is nothing in the circuit ID field
|
|
||||||
else:
|
|
||||||
# Copy deviceName to circuitName if none defined already
|
|
||||||
if circuitName == "":
|
|
||||||
circuitName = deviceName
|
|
||||||
if ParentNode == "":
|
|
||||||
ParentNode = "none"
|
|
||||||
ParentNode = ParentNode.strip()
|
|
||||||
deviceListForCircuit = []
|
|
||||||
thisDevice = {
|
|
||||||
"deviceID": deviceID,
|
|
||||||
"deviceName": deviceName,
|
|
||||||
"mac": mac,
|
|
||||||
"ipv4s": ipv4_subnets_and_hosts,
|
|
||||||
"ipv6s": ipv6_subnets_and_hosts,
|
|
||||||
}
|
|
||||||
deviceListForCircuit.append(thisDevice)
|
|
||||||
thisCircuit = {
|
|
||||||
"circuitID": circuitID,
|
|
||||||
"circuitName": circuitName,
|
|
||||||
"ParentNode": ParentNode,
|
|
||||||
"devices": deviceListForCircuit,
|
|
||||||
"downloadMin": round(int(downloadMin)*tcpOverheadFactor),
|
|
||||||
"uploadMin": round(int(uploadMin)*tcpOverheadFactor),
|
|
||||||
"downloadMax": round(int(downloadMax)*tcpOverheadFactor),
|
|
||||||
"uploadMax": round(int(uploadMax)*tcpOverheadFactor),
|
|
||||||
"qdisc": '',
|
|
||||||
"comment": comment
|
|
||||||
}
|
|
||||||
if thisCircuit['ParentNode'] == 'none':
|
|
||||||
thisCircuit['idForCircuitsWithoutParentNodes'] = counterForCircuitsWithoutParentNodes
|
|
||||||
dictForCircuitsWithoutParentNodes[counterForCircuitsWithoutParentNodes] = ((round(int(downloadMax)*tcpOverheadFactor))+(round(int(uploadMax)*tcpOverheadFactor)))
|
|
||||||
counterForCircuitsWithoutParentNodes += 1
|
|
||||||
subscriberCircuits.append(thisCircuit)
|
|
||||||
|
|
||||||
|
|
||||||
# Load network heirarchy
|
# Load network heirarchy
|
||||||
with open(networkJSONfile, 'r') as j:
|
with open(networkJSONfile, 'r') as j:
|
||||||
@ -404,30 +431,45 @@ def refreshShapers():
|
|||||||
# Pull rx/tx queues / CPU cores available
|
# Pull rx/tx queues / CPU cores available
|
||||||
queuesAvailable = findQueuesAvailable()
|
queuesAvailable = findQueuesAvailable()
|
||||||
|
|
||||||
|
|
||||||
# Generate Parent Nodes. Spread ShapedDevices.csv which lack defined ParentNode across these (balance across CPUs)
|
# Generate Parent Nodes. Spread ShapedDevices.csv which lack defined ParentNode across these (balance across CPUs)
|
||||||
|
print("Generating parent nodes")
|
||||||
|
existingPNs = 0
|
||||||
|
for node in network:
|
||||||
|
existingPNs += 1
|
||||||
generatedPNs = []
|
generatedPNs = []
|
||||||
for x in range(queuesAvailable):
|
numberOfGeneratedPNs = queuesAvailable-existingPNs
|
||||||
|
for x in range(numberOfGeneratedPNs):
|
||||||
genPNname = "Generated_PN_" + str(x+1)
|
genPNname = "Generated_PN_" + str(x+1)
|
||||||
network[genPNname] = {
|
network[genPNname] = {
|
||||||
"downloadBandwidthMbps":generatedPNDownloadMbps,
|
"downloadBandwidthMbps":generatedPNDownloadMbps,
|
||||||
"uploadBandwidthMbps":generatedPNUploadMbps
|
"uploadBandwidthMbps":generatedPNUploadMbps
|
||||||
}
|
}
|
||||||
generatedPNs.append(genPNname)
|
generatedPNs.append(genPNname)
|
||||||
bins = binpacking.to_constant_bin_number(dictForCircuitsWithoutParentNodes, queuesAvailable)
|
if useBinPackingToBalanceCPU:
|
||||||
genPNcounter = 0
|
print("Using binpacking module to sort circuits by CPU core")
|
||||||
for binItem in bins:
|
bins = binpacking.to_constant_bin_number(dictForCircuitsWithoutParentNodes, numberOfGeneratedPNs)
|
||||||
sumItem = 0
|
genPNcounter = 0
|
||||||
logging.info(generatedPNs[genPNcounter] + " will contain " + str(len(binItem)) + " circuits")
|
for binItem in bins:
|
||||||
for key in binItem.keys():
|
sumItem = 0
|
||||||
for circuit in subscriberCircuits:
|
logging.info(generatedPNs[genPNcounter] + " will contain " + str(len(binItem)) + " circuits")
|
||||||
if circuit['ParentNode'] == 'none':
|
for key in binItem.keys():
|
||||||
if circuit['idForCircuitsWithoutParentNodes'] == key:
|
for circuit in subscriberCircuits:
|
||||||
circuit['ParentNode'] = generatedPNs[genPNcounter]
|
if circuit['ParentNode'] == 'none':
|
||||||
genPNcounter += 1
|
if circuit['idForCircuitsWithoutParentNodes'] == key:
|
||||||
if genPNcounter >= queuesAvailable:
|
circuit['ParentNode'] = generatedPNs[genPNcounter]
|
||||||
genPNcounter = 0
|
genPNcounter += 1
|
||||||
|
if genPNcounter >= queuesAvailable:
|
||||||
|
genPNcounter = 0
|
||||||
|
print("Finished binpacking")
|
||||||
|
else:
|
||||||
|
genPNcounter = 0
|
||||||
|
for circuit in subscriberCircuits:
|
||||||
|
if circuit['ParentNode'] == 'none':
|
||||||
|
circuit['ParentNode'] = generatedPNs[genPNcounter]
|
||||||
|
genPNcounter += 1
|
||||||
|
if genPNcounter >= queuesAvailable:
|
||||||
|
genPNcounter = 0
|
||||||
|
print("Generated parent nodes created")
|
||||||
|
|
||||||
# Find the bandwidth minimums for each node by combining mimimums of devices lower in that node's heirarchy
|
# Find the bandwidth minimums for each node by combining mimimums of devices lower in that node's heirarchy
|
||||||
def findBandwidthMins(data, depth):
|
def findBandwidthMins(data, depth):
|
||||||
@ -437,8 +479,8 @@ def refreshShapers():
|
|||||||
for elem in data:
|
for elem in data:
|
||||||
for circuit in subscriberCircuits:
|
for circuit in subscriberCircuits:
|
||||||
if elem == circuit['ParentNode']:
|
if elem == circuit['ParentNode']:
|
||||||
minDownload += circuit['downloadMin']
|
minDownload += circuit['minDownload']
|
||||||
minUpload += circuit['uploadMin']
|
minUpload += circuit['minUpload']
|
||||||
if 'children' in data[elem]:
|
if 'children' in data[elem]:
|
||||||
minDL, minUL = findBandwidthMins(data[elem]['children'], depth+1)
|
minDL, minUL = findBandwidthMins(data[elem]['children'], depth+1)
|
||||||
minDownload += minDL
|
minDownload += minDL
|
||||||
@ -446,18 +488,24 @@ def refreshShapers():
|
|||||||
data[elem]['downloadBandwidthMbpsMin'] = minDownload
|
data[elem]['downloadBandwidthMbpsMin'] = minDownload
|
||||||
data[elem]['uploadBandwidthMbpsMin'] = minUpload
|
data[elem]['uploadBandwidthMbpsMin'] = minUpload
|
||||||
return minDownload, minUpload
|
return minDownload, minUpload
|
||||||
|
logging.info("Finding the bandwidth minimums for each node")
|
||||||
minDownload, minUpload = findBandwidthMins(network, 0)
|
minDownload, minUpload = findBandwidthMins(network, 0)
|
||||||
|
logging.info("Found the bandwidth minimums for each node")
|
||||||
|
|
||||||
# Parse network structure and add devices from ShapedDevices.csv
|
# Parse network structure and add devices from ShapedDevices.csv
|
||||||
linuxTCcommands = []
|
|
||||||
xdpCPUmapCommands = []
|
|
||||||
parentNodes = []
|
parentNodes = []
|
||||||
def traverseNetwork(data, depth, major, minor, queue, parentClassID, parentMaxDL, parentMaxUL):
|
minorByCPUpreloaded = {}
|
||||||
|
knownClassIDs = []
|
||||||
|
# Track minor counter by CPU. This way we can have > 32000 hosts (htb has u16 limit to minor handle)
|
||||||
|
for x in range(queuesAvailable):
|
||||||
|
minorByCPUpreloaded[x+1] = 3
|
||||||
|
def traverseNetwork(data, depth, major, minorByCPU, queue, parentClassID, parentMaxDL, parentMaxUL):
|
||||||
for node in data:
|
for node in data:
|
||||||
circuitsForThisNetworkNode = []
|
circuitsForThisNetworkNode = []
|
||||||
nodeClassID = hex(major) + ':' + hex(minor)
|
nodeClassID = hex(major) + ':' + hex(minorByCPU[queue])
|
||||||
data[node]['classid'] = nodeClassID
|
data[node]['classid'] = nodeClassID
|
||||||
|
if depth == 0:
|
||||||
|
parentClassID = hex(major) + ':'
|
||||||
data[node]['parentClassID'] = parentClassID
|
data[node]['parentClassID'] = parentClassID
|
||||||
# Cap based on this node's max bandwidth, or parent node's max bandwidth, whichever is lower
|
# Cap based on this node's max bandwidth, or parent node's max bandwidth, whichever is lower
|
||||||
data[node]['downloadBandwidthMbps'] = min(data[node]['downloadBandwidthMbps'],parentMaxDL)
|
data[node]['downloadBandwidthMbps'] = min(data[node]['downloadBandwidthMbps'],parentMaxDL)
|
||||||
@ -468,31 +516,31 @@ def refreshShapers():
|
|||||||
data[node]['downloadBandwidthMbpsMin'] = round(data[node]['downloadBandwidthMbps']*.95)
|
data[node]['downloadBandwidthMbpsMin'] = round(data[node]['downloadBandwidthMbps']*.95)
|
||||||
data[node]['uploadBandwidthMbpsMin'] = round(data[node]['uploadBandwidthMbps']*.95)
|
data[node]['uploadBandwidthMbpsMin'] = round(data[node]['uploadBandwidthMbps']*.95)
|
||||||
data[node]['classMajor'] = hex(major)
|
data[node]['classMajor'] = hex(major)
|
||||||
data[node]['classMinor'] = hex(minor)
|
data[node]['classMinor'] = hex(minorByCPU[queue])
|
||||||
data[node]['cpuNum'] = hex(queue-1)
|
data[node]['cpuNum'] = hex(queue-1)
|
||||||
thisParentNode = {
|
thisParentNode = {
|
||||||
"parentNodeName": node,
|
"parentNodeName": node,
|
||||||
"classID": nodeClassID,
|
"classID": nodeClassID,
|
||||||
"downloadMax": data[node]['downloadBandwidthMbps'],
|
"maxDownload": data[node]['downloadBandwidthMbps'],
|
||||||
"uploadMax": data[node]['uploadBandwidthMbps'],
|
"maxUpload": data[node]['uploadBandwidthMbps'],
|
||||||
}
|
}
|
||||||
parentNodes.append(thisParentNode)
|
parentNodes.append(thisParentNode)
|
||||||
minor += 1
|
minorByCPU[queue] = minorByCPU[queue] + 1
|
||||||
for circuit in subscriberCircuits:
|
for circuit in subscriberCircuits:
|
||||||
#If a device from ShapedDevices.csv lists this node as its Parent Node, attach it as a leaf to this node HTB
|
#If a device from ShapedDevices.csv lists this node as its Parent Node, attach it as a leaf to this node HTB
|
||||||
if node == circuit['ParentNode']:
|
if node == circuit['ParentNode']:
|
||||||
if circuit['downloadMax'] > data[node]['downloadBandwidthMbps']:
|
if circuit['maxDownload'] > data[node]['downloadBandwidthMbps']:
|
||||||
warnings.warn("downloadMax of Circuit ID [" + circuit['circuitID'] + "] exceeded that of its parent node. Reducing to that of its parent node now.", stacklevel=2)
|
warnings.warn("downloadMax of Circuit ID [" + circuit['circuitID'] + "] exceeded that of its parent node. Reducing to that of its parent node now.", stacklevel=2)
|
||||||
if circuit['uploadMax'] > data[node]['uploadBandwidthMbps']:
|
if circuit['maxUpload'] > data[node]['uploadBandwidthMbps']:
|
||||||
warnings.warn("uploadMax of Circuit ID [" + circuit['circuitID'] + "] exceeded that of its parent node. Reducing to that of its parent node now.", stacklevel=2)
|
warnings.warn("uploadMax of Circuit ID [" + circuit['circuitID'] + "] exceeded that of its parent node. Reducing to that of its parent node now.", stacklevel=2)
|
||||||
parentString = hex(major) + ':'
|
parentString = hex(major) + ':'
|
||||||
flowIDstring = hex(major) + ':' + hex(minor)
|
flowIDstring = hex(major) + ':' + hex(minorByCPU[queue])
|
||||||
circuit['qdisc'] = flowIDstring
|
circuit['classid'] = flowIDstring
|
||||||
# Create circuit dictionary to be added to network structure, eventually output as queuingStructure.json
|
# Create circuit dictionary to be added to network structure, eventually output as queuingStructure.json
|
||||||
maxDownload = min(circuit['downloadMax'],data[node]['downloadBandwidthMbps'])
|
maxDownload = min(circuit['maxDownload'],data[node]['downloadBandwidthMbps'])
|
||||||
maxUpload = min(circuit['uploadMax'],data[node]['uploadBandwidthMbps'])
|
maxUpload = min(circuit['maxUpload'],data[node]['uploadBandwidthMbps'])
|
||||||
minDownload = min(circuit['downloadMin'],maxDownload)
|
minDownload = min(circuit['minDownload'],maxDownload)
|
||||||
minUpload = min(circuit['uploadMin'],maxUpload)
|
minUpload = min(circuit['minUpload'],maxUpload)
|
||||||
thisNewCircuitItemForNetwork = {
|
thisNewCircuitItemForNetwork = {
|
||||||
'maxDownload' : maxDownload,
|
'maxDownload' : maxDownload,
|
||||||
'maxUpload' : maxUpload,
|
'maxUpload' : maxUpload,
|
||||||
@ -502,21 +550,22 @@ def refreshShapers():
|
|||||||
"circuitName": circuit['circuitName'],
|
"circuitName": circuit['circuitName'],
|
||||||
"ParentNode": circuit['ParentNode'],
|
"ParentNode": circuit['ParentNode'],
|
||||||
"devices": circuit['devices'],
|
"devices": circuit['devices'],
|
||||||
"qdisc": flowIDstring,
|
"classid": flowIDstring,
|
||||||
"classMajor": hex(major),
|
"classMajor": hex(major),
|
||||||
"classMinor": hex(minor),
|
"classMinor": hex(minorByCPU[queue]),
|
||||||
"comment": circuit['comment']
|
"comment": circuit['comment']
|
||||||
}
|
}
|
||||||
# Generate TC commands to be executed later
|
# Generate TC commands to be executed later
|
||||||
thisNewCircuitItemForNetwork['devices'] = circuit['devices']
|
thisNewCircuitItemForNetwork['devices'] = circuit['devices']
|
||||||
circuitsForThisNetworkNode.append(thisNewCircuitItemForNetwork)
|
circuitsForThisNetworkNode.append(thisNewCircuitItemForNetwork)
|
||||||
minor += 1
|
minorByCPU[queue] = minorByCPU[queue] + 1
|
||||||
if len(circuitsForThisNetworkNode) > 0:
|
if len(circuitsForThisNetworkNode) > 0:
|
||||||
data[node]['circuits'] = circuitsForThisNetworkNode
|
data[node]['circuits'] = circuitsForThisNetworkNode
|
||||||
# Recursive call this function for children nodes attached to this node
|
# Recursive call this function for children nodes attached to this node
|
||||||
if 'children' in data[node]:
|
if 'children' in data[node]:
|
||||||
# We need to keep tabs on the minor counter, because we can't have repeating class IDs. Here, we bring back the minor counter from the recursive function
|
# We need to keep tabs on the minor counter, because we can't have repeating class IDs. Here, we bring back the minor counter from the recursive function
|
||||||
minor = traverseNetwork(data[node]['children'], depth+1, major, minor+1, queue, nodeClassID, data[node]['downloadBandwidthMbps'], data[node]['uploadBandwidthMbps'])
|
minorByCPU[queue] = minorByCPU[queue] + 1
|
||||||
|
minorByCPU = traverseNetwork(data[node]['children'], depth+1, major, minorByCPU, queue, nodeClassID, data[node]['downloadBandwidthMbps'], data[node]['uploadBandwidthMbps'])
|
||||||
# If top level node, increment to next queue / cpu core
|
# If top level node, increment to next queue / cpu core
|
||||||
if depth == 0:
|
if depth == 0:
|
||||||
if queue >= queuesAvailable:
|
if queue >= queuesAvailable:
|
||||||
@ -525,16 +574,16 @@ def refreshShapers():
|
|||||||
else:
|
else:
|
||||||
queue += 1
|
queue += 1
|
||||||
major += 1
|
major += 1
|
||||||
return minor
|
return minorByCPU
|
||||||
# Here is the actual call to the recursive traverseNetwork() function. finalMinor is not used.
|
# Here is the actual call to the recursive traverseNetwork() function. finalMinor is not used.
|
||||||
finalMinor = traverseNetwork(network, 0, major=1, minor=3, queue=1, parentClassID="1:1", parentMaxDL=upstreamBandwidthCapacityDownloadMbps, parentMaxUL=upstreamBandwidthCapacityUploadMbps)
|
minorByCPU = traverseNetwork(network, 0, major=1, minorByCPU=minorByCPUpreloaded, queue=1, parentClassID=None, parentMaxDL=upstreamBandwidthCapacityDownloadMbps, parentMaxUL=upstreamBandwidthCapacityUploadMbps)
|
||||||
|
|
||||||
|
|
||||||
linuxTCcommands = []
|
linuxTCcommands = []
|
||||||
xdpCPUmapCommands = []
|
xdpCPUmapCommands = []
|
||||||
devicesShaped = []
|
devicesShaped = []
|
||||||
# Root HTB Setup
|
# Root HTB Setup
|
||||||
# Create MQ qdisc for each CPU core / rx-tx queue (XDP method - requires IPv4)
|
# Create MQ qdisc for each CPU core / rx-tx queue. Generate commands to create corresponding HTB and leaf classes. Prepare commands for execution later
|
||||||
thisInterface = interfaceA
|
thisInterface = interfaceA
|
||||||
logging.info("# MQ Setup for " + thisInterface)
|
logging.info("# MQ Setup for " + thisInterface)
|
||||||
command = 'qdisc replace dev ' + thisInterface + ' root handle 7FFF: mq'
|
command = 'qdisc replace dev ' + thisInterface + ' root handle 7FFF: mq'
|
||||||
@ -578,7 +627,7 @@ def refreshShapers():
|
|||||||
# Define lists for hash filters
|
# Define lists for hash filters
|
||||||
def traverseNetwork(data):
|
def traverseNetwork(data):
|
||||||
for node in data:
|
for node in data:
|
||||||
command = 'class add dev ' + interfaceA + ' parent ' + data[node]['parentClassID'] + ' classid ' + data[node]['classMinor'] + ' htb rate '+ str(data[node]['downloadBandwidthMbpsMin']) + 'mbit ceil '+ str(data[node]['downloadBandwidthMbps']) + 'mbit prio 3' + " # Node: " + node
|
command = 'class add dev ' + interfaceA + ' parent ' + data[node]['parentClassID'] + ' classid ' + data[node]['classMinor'] + ' htb rate '+ str(data[node]['downloadBandwidthMbpsMin']) + 'mbit ceil '+ str(data[node]['downloadBandwidthMbps']) + 'mbit prio 3'
|
||||||
linuxTCcommands.append(command)
|
linuxTCcommands.append(command)
|
||||||
command = 'class add dev ' + interfaceB + ' parent ' + data[node]['parentClassID'] + ' classid ' + data[node]['classMinor'] + ' htb rate '+ str(data[node]['uploadBandwidthMbpsMin']) + 'mbit ceil '+ str(data[node]['uploadBandwidthMbps']) + 'mbit prio 3'
|
command = 'class add dev ' + interfaceB + ' parent ' + data[node]['parentClassID'] + ' classid ' + data[node]['classMinor'] + ' htb rate '+ str(data[node]['uploadBandwidthMbpsMin']) + 'mbit ceil '+ str(data[node]['uploadBandwidthMbps']) + 'mbit prio 3'
|
||||||
linuxTCcommands.append(command)
|
linuxTCcommands.append(command)
|
||||||
@ -591,7 +640,7 @@ def refreshShapers():
|
|||||||
if 'devices' in circuit:
|
if 'devices' in circuit:
|
||||||
if 'comment' in circuit['devices'][0]:
|
if 'comment' in circuit['devices'][0]:
|
||||||
comment = comment + '| Comment: ' + circuit['devices'][0]['comment']
|
comment = comment + '| Comment: ' + circuit['devices'][0]['comment']
|
||||||
command = 'class add dev ' + interfaceA + ' parent ' + data[node]['classid'] + ' classid ' + circuit['classMinor'] + ' htb rate '+ str(circuit['minDownload']) + 'mbit ceil '+ str(circuit['maxDownload']) + 'mbit prio 3' + comment
|
command = 'class add dev ' + interfaceA + ' parent ' + data[node]['classid'] + ' classid ' + circuit['classMinor'] + ' htb rate '+ str(circuit['minDownload']) + 'mbit ceil '+ str(circuit['maxDownload']) + 'mbit prio 3'
|
||||||
linuxTCcommands.append(command)
|
linuxTCcommands.append(command)
|
||||||
command = 'qdisc add dev ' + interfaceA + ' parent ' + circuit['classMajor'] + ':' + circuit['classMinor'] + ' ' + fqOrCAKE
|
command = 'qdisc add dev ' + interfaceA + ' parent ' + circuit['classMajor'] + ':' + circuit['classMinor'] + ' ' + fqOrCAKE
|
||||||
linuxTCcommands.append(command)
|
linuxTCcommands.append(command)
|
||||||
@ -602,30 +651,25 @@ def refreshShapers():
|
|||||||
for device in circuit['devices']:
|
for device in circuit['devices']:
|
||||||
if device['ipv4s']:
|
if device['ipv4s']:
|
||||||
for ipv4 in device['ipv4s']:
|
for ipv4 in device['ipv4s']:
|
||||||
if '/' in ipv4:
|
xdpCPUmapCommands.append('./xdp-cpumap-tc/src/xdp_iphash_to_cpu_cmdline --add --ip ' + str(ipv4) + ' --cpu ' + data[node]['cpuNum'] + ' --classid ' + circuit['classid'])
|
||||||
ipv4AddressOnly, prefixOnly = ipv4.split('/')
|
|
||||||
xdpCPUmapCommands.append('./xdp-cpumap-tc/src/xdp_iphash_to_cpu_cmdline --add --ip ' + str(ipv4AddressOnly) + ' --cpu ' + data[node]['cpuNum'] + ' --classid ' + circuit['qdisc'] + ' --prefix ' + prefixOnly)
|
|
||||||
else:
|
|
||||||
xdpCPUmapCommands.append('./xdp-cpumap-tc/src/xdp_iphash_to_cpu_cmdline --add --ip ' + str(ipv4) + ' --cpu ' + data[node]['cpuNum'] + ' --classid ' + circuit['qdisc'])
|
|
||||||
if device['ipv6s']:
|
if device['ipv6s']:
|
||||||
for ipv6 in device['ipv6s']:
|
for ipv6 in device['ipv6s']:
|
||||||
if '/' in ipv6:
|
xdpCPUmapCommands.append('./xdp-cpumap-tc/src/xdp_iphash_to_cpu_cmdline --add --ip ' + str(ipv6) + ' --cpu ' + data[node]['cpuNum'] + ' --classid ' + circuit['classid'])
|
||||||
ipv6AddressOnly, prefixOnly = ipv6.split('/')
|
|
||||||
xdpCPUmapCommands.append('./xdp-cpumap-tc/src/xdp_iphash_to_cpu_cmdline --add --ip ' + str(ipv6AddressOnly) + ' --cpu ' + data[node]['cpuNum'] + ' --classid ' + circuit['qdisc'] + ' --prefix ' + prefixOnly)
|
|
||||||
else:
|
|
||||||
xdpCPUmapCommands.append('./xdp-cpumap-tc/src/xdp_iphash_to_cpu_cmdline --add --ip ' + str(ipv6) + ' --cpu ' + data[node]['cpuNum'] + ' --classid ' + circuit['qdisc'])
|
|
||||||
if device['deviceName'] not in devicesShaped:
|
if device['deviceName'] not in devicesShaped:
|
||||||
devicesShaped.append(device['deviceName'])
|
devicesShaped.append(device['deviceName'])
|
||||||
# Recursive call this function for children nodes attached to this node
|
# Recursive call this function for children nodes attached to this node
|
||||||
if 'children' in data[node]:
|
if 'children' in data[node]:
|
||||||
traverseNetwork(data[node]['children'])
|
traverseNetwork(data[node]['children'])
|
||||||
# Here is the actual call to the recursive traverseNetwork() function. finalResult is not used.
|
# Here is the actual call to the recursive traverseNetwork() function.
|
||||||
traverseNetwork(network)
|
traverseNetwork(network)
|
||||||
|
|
||||||
|
|
||||||
# Save queuingStructure
|
# Save queuingStructure
|
||||||
|
queuingStructure = {}
|
||||||
|
queuingStructure['Network'] = network
|
||||||
|
queuingStructure['lastUsedClassIDCounterByCPU'] = minorByCPU
|
||||||
|
queuingStructure['generatedPNs'] = generatedPNs
|
||||||
with open('queuingStructure.json', 'w') as infile:
|
with open('queuingStructure.json', 'w') as infile:
|
||||||
json.dump(network, infile, indent=4)
|
json.dump(queuingStructure, infile, indent=4)
|
||||||
|
|
||||||
|
|
||||||
# Record start time of actual filter reload
|
# Record start time of actual filter reload
|
||||||
@ -666,6 +710,10 @@ def refreshShapers():
|
|||||||
shell("/sbin/tc -f -b linux_tc.txt")
|
shell("/sbin/tc -f -b linux_tc.txt")
|
||||||
tcEndTime = datetime.now()
|
tcEndTime = datetime.now()
|
||||||
print("Executed " + str(len(linuxTCcommands)) + " linux TC class/qdisc commands")
|
print("Executed " + str(len(linuxTCcommands)) + " linux TC class/qdisc commands")
|
||||||
|
|
||||||
|
|
||||||
|
tcEndTime = datetime.now()
|
||||||
|
print("Executed " + str(len(linuxTCcommands)) + " linux TC class/qdisc commands")
|
||||||
|
|
||||||
|
|
||||||
# Execute actual XDP-CPUMAP-TC filter commands
|
# Execute actual XDP-CPUMAP-TC filter commands
|
||||||
@ -700,6 +748,8 @@ def refreshShapers():
|
|||||||
name, idNum = entry
|
name, idNum = entry
|
||||||
print('DeviceID: ' + idNum + '\t DeviceName: ' + name)
|
print('DeviceID: ' + idNum + '\t DeviceName: ' + name)
|
||||||
|
|
||||||
|
# Save ShapedDevices.csv as ShapedDevices.lastLoaded.csv
|
||||||
|
shutil.copyfile('ShapedDevices.csv', 'ShapedDevices.lastLoaded.csv')
|
||||||
|
|
||||||
# Save for stats
|
# Save for stats
|
||||||
with open('statsByCircuit.json', 'w') as f:
|
with open('statsByCircuit.json', 'w') as f:
|
||||||
@ -728,6 +778,362 @@ def refreshShapers():
|
|||||||
# Done
|
# Done
|
||||||
print("refreshShapers completed on " + datetime.now().strftime("%d/%m/%Y %H:%M:%S"))
|
print("refreshShapers completed on " + datetime.now().strftime("%d/%m/%Y %H:%M:%S"))
|
||||||
|
|
||||||
|
def refreshShapersUpdateOnly():
|
||||||
|
# Starting
|
||||||
|
print("refreshShapers starting at " + datetime.now().strftime("%d/%m/%Y %H:%M:%S"))
|
||||||
|
|
||||||
|
|
||||||
|
# Warn user if enableActualShellCommands is False, because that would mean no actual commands are executing
|
||||||
|
if enableActualShellCommands == False:
|
||||||
|
warnings.warn("enableActualShellCommands is set to False. None of the commands below will actually be executed. Simulated run.", stacklevel=2)
|
||||||
|
|
||||||
|
|
||||||
|
# Files
|
||||||
|
shapedDevicesFile = 'ShapedDevices.csv'
|
||||||
|
networkJSONfile = 'network.json'
|
||||||
|
|
||||||
|
|
||||||
|
# Check validation
|
||||||
|
safeToRunRefresh = False
|
||||||
|
if (validateNetworkAndDevices() == True):
|
||||||
|
shutil.copyfile('ShapedDevices.csv', 'lastGoodConfig.csv')
|
||||||
|
shutil.copyfile('network.json', 'lastGoodConfig.json')
|
||||||
|
print("Backed up good config as lastGoodConfig.csv and lastGoodConfig.json")
|
||||||
|
safeToRunRefresh = True
|
||||||
|
else:
|
||||||
|
warnings.warn("Validation failed. Will now exit.", stacklevel=2)
|
||||||
|
|
||||||
|
if safeToRunRefresh == True:
|
||||||
|
|
||||||
|
# Load queuingStructure
|
||||||
|
with open('queuingStructure.json', 'r') as infile:
|
||||||
|
queuingStructure = json.loads(infile.read())
|
||||||
|
|
||||||
|
|
||||||
|
# Load queuingStructure
|
||||||
|
with open('queuingStructure.json', 'r') as infile:
|
||||||
|
queuingStructure = json.loads(infile.read())
|
||||||
|
|
||||||
|
network = queuingStructure['Network']
|
||||||
|
lastUsedClassIDCounterByCPU = queuingStructure['lastUsedClassIDCounterByCPU']
|
||||||
|
generatedPNs = queuingStructure['generatedPNs']
|
||||||
|
|
||||||
|
newlyUpdatedSubscriberCircuits, newlyUpdatedDictForCircuitsWithoutParentNodes = loadSubscriberCircuits('ShapedDevices.csv')
|
||||||
|
lastLoadedSubscriberCircuits, lastLoadedDictForCircuitsWithoutParentNodes = loadSubscriberCircuits('ShapedDevices.lastLoaded.csv')
|
||||||
|
|
||||||
|
|
||||||
|
# Load stats files
|
||||||
|
with open('statsByParentNode.json', 'r') as j:
|
||||||
|
parentNodes = json.loads(j.read())
|
||||||
|
|
||||||
|
with open('statsByCircuit.json', 'r') as j:
|
||||||
|
subscriberCircuits = json.loads(j.read())
|
||||||
|
|
||||||
|
newlyUpdatedSubscriberCircuitsByID = {}
|
||||||
|
for circuit in newlyUpdatedSubscriberCircuits:
|
||||||
|
circuitid = circuit['circuitID']
|
||||||
|
newlyUpdatedSubscriberCircuitsByID[circuitid] = circuit
|
||||||
|
|
||||||
|
lastLoadedSubscriberCircuitsByID = {}
|
||||||
|
for circuit in lastLoadedSubscriberCircuits:
|
||||||
|
circuitid = circuit['circuitID']
|
||||||
|
lastLoadedSubscriberCircuitsByID[circuitid] = circuit
|
||||||
|
|
||||||
|
|
||||||
|
def removeDeviceIPsFromFilter(circuit):
|
||||||
|
for device in circuit['devices']:
|
||||||
|
for ipv4 in device['ipv4s']:
|
||||||
|
shell('./xdp-cpumap-tc/src/xdp_iphash_to_cpu_cmdline --del --ip ' + str(ipv4))
|
||||||
|
for ipv6 in device['ipv6s']:
|
||||||
|
shell('./xdp-cpumap-tc/src/xdp_iphash_to_cpu_cmdline --del --ip ' + str(ipv6))
|
||||||
|
|
||||||
|
|
||||||
|
def addDeviceIPsToFilter(circuit, cpuNumHex):
|
||||||
|
for device in circuit['devices']:
|
||||||
|
for ipv4 in device['ipv4s']:
|
||||||
|
shell('./xdp-cpumap-tc/src/xdp_iphash_to_cpu_cmdline --add --ip ' + str(ipv4) + ' --cpu ' + cpuNumHex + ' --classid ' + circuit['classid'])
|
||||||
|
for ipv6 in device['ipv6s']:
|
||||||
|
shell('./xdp-cpumap-tc/src/xdp_iphash_to_cpu_cmdline --add --ip ' + str(ipv6) + ' --cpu ' + cpuNumHex + ' --classid ' + circuit['classid'])
|
||||||
|
|
||||||
|
|
||||||
|
def getAllParentNodes(data, allParentNodes):
|
||||||
|
for node in data:
|
||||||
|
if isinstance(node, str):
|
||||||
|
thisParentNode = node
|
||||||
|
if thisParentNode not in allParentNodes:
|
||||||
|
allParentNodes.append(thisParentNode)
|
||||||
|
if 'children' in data[node]:
|
||||||
|
for child in data[node]['children']:
|
||||||
|
result = getAllParentNodes(data[node]['children'][child], allParentNodes)
|
||||||
|
allParentNodes = allParentNodes + result
|
||||||
|
return allParentNodes
|
||||||
|
allParentNodes = getAllParentNodes(network, [])
|
||||||
|
|
||||||
|
|
||||||
|
def getClassIDofParentNodes(data, classIDOfParentNodes):
|
||||||
|
for node in data:
|
||||||
|
if isinstance(node, str):
|
||||||
|
thisParentNode = node
|
||||||
|
classIDOfParentNodes[thisParentNode] = data[node]['classid']
|
||||||
|
if 'children' in data[node]:
|
||||||
|
for child in data[node]['children']:
|
||||||
|
result = getClassIDofParentNodes(data[node]['children'][child], classIDOfParentNodes)
|
||||||
|
classIDOfParentNodes.update(result)
|
||||||
|
return classIDOfParentNodes
|
||||||
|
classIDOfParentNodes = getClassIDofParentNodes(network, {})
|
||||||
|
|
||||||
|
|
||||||
|
def getAllCircuitIDs(data, allCircuitIDs):
|
||||||
|
for node in data:
|
||||||
|
if isinstance(node, str):
|
||||||
|
thisParentNode = node
|
||||||
|
if 'circuits' in data[node]:
|
||||||
|
for circuit in data[node]['circuits']:
|
||||||
|
if circuit['circuitID'] not in allCircuitIDs:
|
||||||
|
allCircuitIDs.append(circuit['circuitID'])
|
||||||
|
if 'children' in data[node]:
|
||||||
|
for child in data[node]['children']:
|
||||||
|
result = getAllCircuitIDs(data[node]['children'][child], allCircuitIDs)
|
||||||
|
for entry in result:
|
||||||
|
if entry not in allCircuitIDs:
|
||||||
|
allCircuitIDs.append(result)
|
||||||
|
return allCircuitIDs
|
||||||
|
allCircuitIDs = getAllCircuitIDs(network, [])
|
||||||
|
|
||||||
|
|
||||||
|
def getClassIDofExistingCircuitID(data, classIDofExistingCircuitID):
|
||||||
|
for node in data:
|
||||||
|
if isinstance(node, str):
|
||||||
|
thisParentNode = node
|
||||||
|
if 'circuits' in data[node]:
|
||||||
|
for circuit in data[node]['circuits']:
|
||||||
|
classIDofExistingCircuitID[circuit['circuitID']] = circuit['classid']
|
||||||
|
if 'children' in data[node]:
|
||||||
|
for child in data[node]['children']:
|
||||||
|
result = getClassIDofExistingCircuitID(data[node]['children'][child], allCircuitIDs)
|
||||||
|
classIDofExistingCircuitID.update(result)
|
||||||
|
return classIDofExistingCircuitID
|
||||||
|
classIDofExistingCircuitID = getClassIDofExistingCircuitID(network, {})
|
||||||
|
|
||||||
|
|
||||||
|
def getParentNodeOfCircuitID(data, parentNodeOfCircuitID, allCircuitIDs):
|
||||||
|
for node in data:
|
||||||
|
if isinstance(node, str):
|
||||||
|
thisParentNode = node
|
||||||
|
if 'circuits' in data[node]:
|
||||||
|
for circuit in data[node]['circuits']:
|
||||||
|
parentNodeOfCircuitID[circuit['circuitID']] = thisParentNode
|
||||||
|
if 'children' in data[node]:
|
||||||
|
for child in data[node]['children']:
|
||||||
|
result = getParentNodeOfCircuitID(data[node]['children'][child], parentNodeOfCircuitID, allCircuitIDs)
|
||||||
|
parentNodeOfCircuitID.update(result)
|
||||||
|
return parentNodeOfCircuitID
|
||||||
|
parentNodeOfCircuitID = getParentNodeOfCircuitID(network, {}, allCircuitIDs)
|
||||||
|
|
||||||
|
|
||||||
|
def getCPUnumOfParentNodes(data, cpuNumOfParentNode):
|
||||||
|
for node in data:
|
||||||
|
if isinstance(node, str):
|
||||||
|
thisParentNode = node
|
||||||
|
cpuNumOfParentNode[thisParentNode] = data[node]['cpuNum']
|
||||||
|
if 'children' in data[node]:
|
||||||
|
for child in data[node]['children']:
|
||||||
|
result = getCPUnumOfParentNodes(data[node]['children'][child], cpuNumOfParentNode)
|
||||||
|
cpuNumOfParentNode.update(result)
|
||||||
|
return cpuNumOfParentNode
|
||||||
|
cpuNumOfParentNodeHex = getCPUnumOfParentNodes(network, {})
|
||||||
|
cpuNumOfParentNodeInt = {}
|
||||||
|
for key, value in cpuNumOfParentNodeHex.items():
|
||||||
|
cpuNumOfParentNodeInt[key] = int(value,16) + 1
|
||||||
|
|
||||||
|
|
||||||
|
def addCircuitHTBandQdisc(circuit, parentNodeClassID):
|
||||||
|
minor = circuit['classid'].split(':')[1]
|
||||||
|
for interface in [interfaceA, interfaceB]:
|
||||||
|
if interface == interfaceA:
|
||||||
|
rate = str(circuit['minDownload'])
|
||||||
|
ceil = str(circuit['maxDownload'])
|
||||||
|
else:
|
||||||
|
rate = str(circuit['minUpload'])
|
||||||
|
ceil = str(circuit['maxUpload'])
|
||||||
|
command = 'tc class add dev ' + interface + ' parent ' + parentNodeClassID + ' classid ' + minor + ' htb rate ' + rate + 'Mbit ceil ' + ceil + 'Mbit'
|
||||||
|
print(command)
|
||||||
|
shell(command)
|
||||||
|
command = 'tc qdisc add dev ' + interface + ' parent ' + classID + ' ' + fqOrCAKE
|
||||||
|
print(command)
|
||||||
|
shell(command)
|
||||||
|
|
||||||
|
|
||||||
|
def delHTBclass(classid):
|
||||||
|
for interface in [interfaceA, interfaceB]:
|
||||||
|
command = 'tc class del dev ' + interface + ' classid ' + classid
|
||||||
|
print(command)
|
||||||
|
shell(command)
|
||||||
|
|
||||||
|
|
||||||
|
generatedPNcounter = 1
|
||||||
|
circuitsIDsToRemove = []
|
||||||
|
circuitsToUpdateByID = {}
|
||||||
|
circuitsToAddByParentNode = {}
|
||||||
|
for circuitID, circuit in lastLoadedSubscriberCircuitsByID.items():
|
||||||
|
# Same circuit, update parameters (bandwidth, devices)
|
||||||
|
bandwidthChanged = False
|
||||||
|
devicesChanged = False
|
||||||
|
if (circuitID in newlyUpdatedSubscriberCircuitsByID) and (circuitID in lastLoadedSubscriberCircuitsByID):
|
||||||
|
if newlyUpdatedSubscriberCircuitsByID[circuitID]['maxDownload'] != lastLoadedSubscriberCircuitsByID[circuitID]['maxDownload']:
|
||||||
|
bandwidthChanged = True
|
||||||
|
if newlyUpdatedSubscriberCircuitsByID[circuitID]['maxUpload'] != lastLoadedSubscriberCircuitsByID[circuitID]['maxUpload']:
|
||||||
|
bandwidthChanged = True
|
||||||
|
if newlyUpdatedSubscriberCircuitsByID[circuitID]['minDownload'] != lastLoadedSubscriberCircuitsByID[circuitID]['minDownload']:
|
||||||
|
bandwidthChanged = True
|
||||||
|
if newlyUpdatedSubscriberCircuitsByID[circuitID]['minUpload'] != lastLoadedSubscriberCircuitsByID[circuitID]['minUpload']:
|
||||||
|
bandwidthChanged = True
|
||||||
|
if newlyUpdatedSubscriberCircuitsByID[circuitID]['devices'] != lastLoadedSubscriberCircuitsByID[circuitID]['devices']:
|
||||||
|
devicesChanged = True
|
||||||
|
if bandwidthChanged == True:
|
||||||
|
if newlyUpdatedSubscriberCircuitsByID[circuitID]['ParentNode'] == lastLoadedSubscriberCircuitsByID[circuitID]['ParentNode']:
|
||||||
|
parentNodeActual = circuit['ParentNode']
|
||||||
|
if parentNodeActual == 'none':
|
||||||
|
parentNodeActual = parentNodeOfCircuitID[circuitID]
|
||||||
|
parentNodeClassID = classIDOfParentNodes[parentNodeActual]
|
||||||
|
classid = classIDofExistingCircuitID[circuitID]
|
||||||
|
minor = classid.split(':')[1]
|
||||||
|
for interface in [interfaceA, interfaceB]:
|
||||||
|
if interface == interfaceA:
|
||||||
|
rate = str(newlyUpdatedSubscriberCircuitsByID[circuitID]['minDownload'])
|
||||||
|
ceil = str(newlyUpdatedSubscriberCircuitsByID[circuitID]['maxDownload'])
|
||||||
|
else:
|
||||||
|
rate = str(newlyUpdatedSubscriberCircuitsByID[circuitID]['minUpload'])
|
||||||
|
ceil = str(newlyUpdatedSubscriberCircuitsByID[circuitID]['maxUpload'])
|
||||||
|
command = 'tc class change dev ' + interface + ' parent ' + parentNodeClassID + ' classid ' + minor + ' htb rate ' + rate + 'Mbit ceil ' + ceil + 'Mbit'
|
||||||
|
shell(command)
|
||||||
|
else:
|
||||||
|
removeDeviceIPsFromFilter(lastLoadedSubscriberCircuitsByID[circuitID])
|
||||||
|
# Delete HTB class, qdisc. Then recreat it.
|
||||||
|
classid = classIDofExistingCircuitID[circuitID]
|
||||||
|
circuit['classid'] = classid
|
||||||
|
delHTBclass(classID)
|
||||||
|
parentNodeClassID = classIDOfParentNodes[parentNodeOfCircuitID[circuitID]]
|
||||||
|
addCircuitHTBandQdisc(circuit, parentNodeClassID)
|
||||||
|
addDeviceIPsToFilter(newlyUpdatedSubscriberCircuitsByID[circuitID], cpuNum)
|
||||||
|
elif devicesChanged:
|
||||||
|
removeDeviceIPsFromFilter(lastLoadedSubscriberCircuitsByID[circuitID])
|
||||||
|
parentNodeActual = lastLoadedSubscriberCircuitsByID[circuitID]['ParentNode']
|
||||||
|
if parentNodeActual == 'none':
|
||||||
|
parentNodeActual = getParentNodeOfCircuitID(network, circuitID)
|
||||||
|
cpuNum, parentNodeClassID = getCPUnumAndClassIDOfParentNode(network, parentNodeActual)
|
||||||
|
addDeviceIPsToFilter(newlyUpdatedSubscriberCircuitsByID[circuitID], cpuNum)
|
||||||
|
|
||||||
|
newlyUpdatedSubscriberCircuitsByID[circuitID]['classid'] = lastLoadedSubscriberCircuitsByID[circuitID]['classid']
|
||||||
|
if (bandwidthChanged) or (devicesChanged):
|
||||||
|
circuitsToUpdateByID[circuitID] = newlyUpdatedSubscriberCircuitsByID[circuitID]
|
||||||
|
|
||||||
|
|
||||||
|
# Removed circuit
|
||||||
|
if (circuitID in lastLoadedSubscriberCircuitsByID) and (circuitID not in newlyUpdatedSubscriberCircuitsByID):
|
||||||
|
circuitsIDsToRemove.append(circuitID)
|
||||||
|
removeDeviceIPsFromFilter(lastLoadedSubscriberCircuitsByID[circuitID])
|
||||||
|
classid = classIDofExistingCircuitID[circuitID]
|
||||||
|
delHTBclass(classid)
|
||||||
|
|
||||||
|
|
||||||
|
# New circuit
|
||||||
|
for circuitID, circuit in newlyUpdatedSubscriberCircuitsByID.items():
|
||||||
|
if (circuitID in newlyUpdatedSubscriberCircuitsByID) and (circuitID not in lastLoadedSubscriberCircuitsByID):
|
||||||
|
if newlyUpdatedSubscriberCircuitsByID[circuitID]['ParentNode'] == 'none':
|
||||||
|
newlyUpdatedSubscriberCircuitsByID[circuitID]['ParentNode'] = 'Generated_PN_' + str(generatedPNcounter)
|
||||||
|
generatedPNcounter += 1
|
||||||
|
if generatedPNcounter > len(generatedPNs):
|
||||||
|
generatedPNcounter = 1
|
||||||
|
cpuNumHex = cpuNumOfParentNodeHex[circuit['ParentNode']]
|
||||||
|
cpuNumInt = cpuNumOfParentNodeInt[circuit['ParentNode']]
|
||||||
|
parentNodeClassID = classIDOfParentNodes[circuit['ParentNode']]
|
||||||
|
classID = parentNodeClassID.split(':')[0] + ':' + hex(lastUsedClassIDCounterByCPU[str(cpuNumInt)])
|
||||||
|
lastUsedClassIDCounterByCPU[str(cpuNumInt)] = lastUsedClassIDCounterByCPU[str(cpuNumInt)] + 1
|
||||||
|
circuit['classid'] = classID
|
||||||
|
# Add HTB class, qdisc
|
||||||
|
addCircuitHTBandQdisc(circuit, parentNodeClassID)
|
||||||
|
addDeviceIPsToFilter(circuit, cpuNumHex)
|
||||||
|
if circuit['ParentNode'] in circuitsToAddByParentNode:
|
||||||
|
temp = circuitsToAddByParentNode[circuit['ParentNode']]
|
||||||
|
temp.append(circuit)
|
||||||
|
circuitsToAddByParentNode[circuit['ParentNode']] = temp
|
||||||
|
else:
|
||||||
|
temp = []
|
||||||
|
temp.append(circuit)
|
||||||
|
circuitsToAddByParentNode[circuit['ParentNode']] = temp
|
||||||
|
|
||||||
|
|
||||||
|
# Update network structure reflecting new circuits, removals, and changes
|
||||||
|
itemsToChange = (circuitsIDsToRemove, circuitsToUpdateByID, circuitsToAddByParentNode)
|
||||||
|
def updateNetworkStructure(data, depth, itemsToChange):
|
||||||
|
circuitsIDsToRemove, circuitsToUpdateByID, circuitsToAddByParentNode = itemsToChange
|
||||||
|
for node in data:
|
||||||
|
if isinstance(node, str):
|
||||||
|
thisParentNode = node
|
||||||
|
if 'circuits' in data[node]:
|
||||||
|
for circuit in data[node]['circuits']:
|
||||||
|
if circuit['circuitID'] in circuitsToUpdateByID:
|
||||||
|
circuit = circuitsToUpdateByID[circuit['circuitID']]
|
||||||
|
print('updated')
|
||||||
|
if circuit['circuitID'] in circuitsIDsToRemove:
|
||||||
|
data[node]['circuits'].remove(circuit)
|
||||||
|
if thisParentNode in circuitsToAddByParentNode:
|
||||||
|
if 'circuits' in data[node]:
|
||||||
|
temp = data[node]['circuits']
|
||||||
|
for circuit in circuitsToAddByParentNode[thisParentNode]:
|
||||||
|
temp.append(circuit)
|
||||||
|
data[node]['circuits'] = temp
|
||||||
|
else:
|
||||||
|
temp = []
|
||||||
|
for circuit in circuitsToAddByParentNode[thisParentNode]:
|
||||||
|
temp.append(circuit)
|
||||||
|
data[node]['circuits'] = temp
|
||||||
|
if 'children' in data[node]:
|
||||||
|
for child in data[node]['children']:
|
||||||
|
result = updateNetworkStructure(data[node]['children'][child], depth+1, itemsToChange)
|
||||||
|
data[node]['children'][child] = result
|
||||||
|
return data
|
||||||
|
network = updateNetworkStructure(network, 0 , itemsToChange)
|
||||||
|
|
||||||
|
|
||||||
|
# Record start time of actual filter reload
|
||||||
|
reloadStartTime = datetime.now()
|
||||||
|
|
||||||
|
|
||||||
|
# Record end time of all reload commands
|
||||||
|
reloadEndTime = datetime.now()
|
||||||
|
|
||||||
|
|
||||||
|
queuingStructure = {}
|
||||||
|
queuingStructure['Network'] = network
|
||||||
|
queuingStructure['lastUsedClassIDCounterByCPU'] = lastUsedClassIDCounterByCPU
|
||||||
|
queuingStructure['generatedPNs'] = generatedPNs
|
||||||
|
# Save queuingStructure
|
||||||
|
with open('queuingStructure.json', 'w') as infile:
|
||||||
|
json.dump(queuingStructure, infile, indent=4)
|
||||||
|
|
||||||
|
|
||||||
|
# copy ShapedDevices.csv and save as ShapedDevices.lastLoaded.csv and lastGoodConfig.csv
|
||||||
|
shutil.copyfile('ShapedDevices.csv', 'ShapedDevices.lastLoaded.csv')
|
||||||
|
shutil.copyfile('ShapedDevices.csv', 'lastGoodConfig.csv')
|
||||||
|
|
||||||
|
|
||||||
|
# Save for stats
|
||||||
|
with open('statsByCircuit.json', 'w') as f:
|
||||||
|
f.write(json.dumps(subscriberCircuits, indent=4))
|
||||||
|
with open('statsByParentNode.json', 'w') as f:
|
||||||
|
f.write(json.dumps(parentNodes, indent=4))
|
||||||
|
|
||||||
|
|
||||||
|
# Report reload time
|
||||||
|
reloadTimeSeconds = ((reloadEndTime - reloadStartTime).seconds) + (((reloadEndTime - reloadStartTime).microseconds) / 1000000)
|
||||||
|
print("Queue and IP filter partial reload completed in " + "{:g}".format(round(reloadTimeSeconds,1)) + " seconds")
|
||||||
|
|
||||||
|
|
||||||
|
# Done
|
||||||
|
print("refreshShapersUpdateOnly completed on " + datetime.now().strftime("%d/%m/%Y %H:%M:%S"))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
@ -741,6 +1147,11 @@ if __name__ == '__main__':
|
|||||||
help="Be verbose",
|
help="Be verbose",
|
||||||
action="store_const", dest="loglevel", const=logging.INFO,
|
action="store_const", dest="loglevel", const=logging.INFO,
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--updateonly',
|
||||||
|
help="Only update to reflect changes in ShapedDevices.csv (partial reload)",
|
||||||
|
action=argparse.BooleanOptionalAction,
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--validate',
|
'--validate',
|
||||||
help="Just validate network.json and ShapedDevices.csv",
|
help="Just validate network.json and ShapedDevices.csv",
|
||||||
@ -758,6 +1169,8 @@ if __name__ == '__main__':
|
|||||||
status = validateNetworkAndDevices()
|
status = validateNetworkAndDevices()
|
||||||
elif args.clearrules:
|
elif args.clearrules:
|
||||||
tearDown(interfaceA, interfaceB)
|
tearDown(interfaceA, interfaceB)
|
||||||
|
elif args.updateonly:
|
||||||
|
refreshShapersUpdateOnly()
|
||||||
else:
|
else:
|
||||||
# Refresh and/or set up queues
|
# Refresh and/or set up queues
|
||||||
refreshShapers()
|
refreshShapers()
|
||||||
|
Loading…
Reference in New Issue
Block a user