mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-09 00:58:16 -06:00
Fixes #3377: Recursive power calculation
This commit is contained in:
parent
7b8e82f321
commit
6eef5cab03
@ -1,3 +1,11 @@
|
|||||||
|
# v2.6.13 (FUTURE)
|
||||||
|
|
||||||
|
## Enhancements
|
||||||
|
|
||||||
|
* [#3377](https://github.com/netbox-community/netbox/issues/3377) - Include children devices when calculating power utilization
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
# v2.6.12 (2020-01-13)
|
# v2.6.12 (2020-01-13)
|
||||||
|
|
||||||
## Enhancements
|
## Enhancements
|
||||||
|
@ -8,7 +8,7 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from django.contrib.postgres.fields import ArrayField, JSONField
|
from django.contrib.postgres.fields import ArrayField, JSONField
|
||||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
from django.db import connection, models
|
||||||
from django.db.models import Count, F, ProtectedError, Q, Sum
|
from django.db.models import Count, F, ProtectedError, Q, Sum
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from mptt.models import MPTTModel, TreeForeignKey
|
from mptt.models import MPTTModel, TreeForeignKey
|
||||||
@ -25,6 +25,35 @@ from .exceptions import LoopDetected
|
|||||||
from .fields import ASNField, MACAddressField
|
from .fields import ASNField, MACAddressField
|
||||||
from .managers import InterfaceManager
|
from .managers import InterfaceManager
|
||||||
|
|
||||||
|
QUERY_POWER_DRAW_BASE = """
|
||||||
|
WITH RECURSIVE power_connection(outlet_id, port_id, allocated_draw, maximum_draw, _path, _cycle) AS (
|
||||||
|
-- Non-recursive term: get all outlets and connected port pairs for a given root_powerport.
|
||||||
|
SELECT outlet.id, connected_powerport.id, connected_powerport.allocated_draw, connected_powerport.maximum_draw,
|
||||||
|
ARRAY[outlet.id], false
|
||||||
|
FROM dcim_powerport AS root_powerport
|
||||||
|
JOIN dcim_poweroutlet AS outlet ON outlet.device_id = root_powerport.device_id
|
||||||
|
JOIN dcim_powerport AS connected_powerport ON connected_powerport._connected_poweroutlet_id=outlet.id
|
||||||
|
{}
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
-- Recursive term: for each row in the previous iteration (initially the non-recursive term), get connections.
|
||||||
|
SELECT outlet.id, connected_powerport.id, connected_powerport.allocated_draw, connected_powerport.maximum_draw,
|
||||||
|
_path || outlet.id, outlet.id = ANY(_path)
|
||||||
|
FROM power_connection
|
||||||
|
JOIN dcim_powerport AS root_powerport ON root_powerport.id = power_connection.port_id
|
||||||
|
JOIN dcim_poweroutlet AS outlet ON outlet.device_id = root_powerport.device_id
|
||||||
|
JOIN dcim_powerport AS connected_powerport ON connected_powerport._connected_poweroutlet_id=outlet.id
|
||||||
|
WHERE NOT _cycle
|
||||||
|
)
|
||||||
|
-- Any cycle-causing rows are kept in the results, though they are not used in next iteration.
|
||||||
|
SELECT SUM(allocated_draw) as total_allocated_draw, SUM(maximum_draw) as total_maximum_draw
|
||||||
|
FROM power_connection
|
||||||
|
WHERE NOT _cycle;
|
||||||
|
"""
|
||||||
|
QUERY_POWER_DRAW_PORT = QUERY_POWER_DRAW_BASE.format('WHERE root_powerport.id = %s')
|
||||||
|
QUERY_POWER_DRAW_PORT_LEG = QUERY_POWER_DRAW_BASE.format('WHERE root_powerport.id = %s AND outlet.feed_leg = %s')
|
||||||
|
|
||||||
|
|
||||||
class ComponentTemplateModel(models.Model):
|
class ComponentTemplateModel(models.Model):
|
||||||
|
|
||||||
@ -754,19 +783,16 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
|
|||||||
"""
|
"""
|
||||||
Determine the utilization rate of power in the rack and return it as a percentage.
|
Determine the utilization rate of power in the rack and return it as a percentage.
|
||||||
"""
|
"""
|
||||||
power_stats = PowerFeed.objects.filter(
|
# Sum up all of the available power from the power feeds assigned to the rack
|
||||||
rack=self
|
available_power = PowerFeed.objects.filter(rack=self).aggregate(total=Sum('available_power'))
|
||||||
).annotate(
|
|
||||||
allocated_draw_total=Sum('connected_endpoint__poweroutlets__connected_endpoint__allocated_draw'),
|
|
||||||
).values(
|
|
||||||
'allocated_draw_total',
|
|
||||||
'available_power'
|
|
||||||
)
|
|
||||||
|
|
||||||
if power_stats:
|
# Get the power draw of the power ports from the power feeds assigned to the rack
|
||||||
allocated_draw_total = sum(x['allocated_draw_total'] for x in power_stats)
|
feeds_stats = [x.get_power_draw() for x in PowerPort.objects.filter(_connected_powerfeed__rack=self)]
|
||||||
available_power_total = sum(x['available_power'] for x in power_stats)
|
|
||||||
return int(allocated_draw_total / available_power_total * 100) or 0
|
if available_power.get('total') and feeds_stats:
|
||||||
|
available_power_total = available_power.get('total')
|
||||||
|
allocated_draw_total = sum([x.get('allocated') or 0 for x in feeds_stats])
|
||||||
|
return round(allocated_draw_total / available_power_total * 100)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@ -2041,34 +2067,34 @@ class PowerPort(CableTermination, ComponentModel):
|
|||||||
"""
|
"""
|
||||||
# Calculate aggregate draw of all child power outlets if no numbers have been defined manually
|
# Calculate aggregate draw of all child power outlets if no numbers have been defined manually
|
||||||
if self.allocated_draw is None and self.maximum_draw is None:
|
if self.allocated_draw is None and self.maximum_draw is None:
|
||||||
outlet_ids = PowerOutlet.objects.filter(power_port=self).values_list('pk', flat=True)
|
cursor = connection.cursor()
|
||||||
utilization = PowerPort.objects.filter(_connected_poweroutlet_id__in=outlet_ids).aggregate(
|
try:
|
||||||
maximum_draw_total=Sum('maximum_draw'),
|
cursor.execute(QUERY_POWER_DRAW_PORT, [self.pk])
|
||||||
allocated_draw_total=Sum('allocated_draw'),
|
allocated_draw_total, maximum_draw_total = cursor.fetchone()
|
||||||
)
|
|
||||||
ret = {
|
ret = {
|
||||||
'allocated': utilization['allocated_draw_total'] or 0,
|
'allocated': allocated_draw_total or 0,
|
||||||
'maximum': utilization['maximum_draw_total'] or 0,
|
'maximum': maximum_draw_total or 0,
|
||||||
'outlet_count': len(outlet_ids),
|
'outlet_count': PowerOutlet.objects.filter(power_port=self).count(),
|
||||||
'legs': [],
|
'legs': [],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Calculate per-leg aggregates for three-phase feeds
|
# Calculate per-leg aggregates for three-phase feeds
|
||||||
if self._connected_powerfeed and self._connected_powerfeed.phase == POWERFEED_PHASE_3PHASE:
|
if self._connected_powerfeed and self._connected_powerfeed.phase == POWERFEED_PHASE_3PHASE:
|
||||||
for leg, leg_name in POWERFEED_LEG_CHOICES:
|
for leg, leg_name in POWERFEED_LEG_CHOICES:
|
||||||
outlet_ids = PowerOutlet.objects.filter(power_port=self, feed_leg=leg).values_list('pk', flat=True)
|
cursor.execute(QUERY_POWER_DRAW_PORT_LEG, [self.pk, leg])
|
||||||
utilization = PowerPort.objects.filter(_connected_poweroutlet_id__in=outlet_ids).aggregate(
|
allocated_draw_total, maximum_draw_total = cursor.fetchone()
|
||||||
maximum_draw_total=Sum('maximum_draw'),
|
|
||||||
allocated_draw_total=Sum('allocated_draw'),
|
|
||||||
)
|
|
||||||
ret['legs'].append({
|
ret['legs'].append({
|
||||||
'name': leg_name,
|
'name': leg_name,
|
||||||
'allocated': utilization['allocated_draw_total'] or 0,
|
'allocated': allocated_draw_total or 0,
|
||||||
'maximum': utilization['maximum_draw_total'] or 0,
|
'maximum': maximum_draw_total or 0,
|
||||||
'outlet_count': len(outlet_ids),
|
'outlet_count': PowerOutlet.objects.filter(power_port=self, feed_leg=leg).count(),
|
||||||
})
|
})
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
finally:
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
# Default to administratively defined values
|
# Default to administratively defined values
|
||||||
return {
|
return {
|
||||||
|
Loading…
Reference in New Issue
Block a user