mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-16 04:02:52 -06:00
Update power utilization calculations for new cabling model
This commit is contained in:
parent
25ed3390cb
commit
fcd1daaf79
@ -3,7 +3,7 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Sum
|
from django.db.models import Q, Sum
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from mptt.models import MPTTModel, TreeForeignKey
|
from mptt.models import MPTTModel, TreeForeignKey
|
||||||
|
|
||||||
@ -156,6 +156,12 @@ class LinkTermination(models.Model):
|
|||||||
def parent_object(self):
|
def parent_object(self):
|
||||||
raise NotImplementedError(f"{self.__class__.__name__} models must declare a parent_object property")
|
raise NotImplementedError(f"{self.__class__.__name__} models must declare a parent_object property")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def opposite_cable_end(self):
|
||||||
|
if not self.cable_end:
|
||||||
|
return None
|
||||||
|
return CableEndChoices.SIDE_A if self.cable_end == CableEndChoices.SIDE_B else CableEndChoices.SIDE_B
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def link(self):
|
def link(self):
|
||||||
"""
|
"""
|
||||||
@ -333,36 +339,49 @@ class PowerPort(ModularComponentModel, LinkTermination, PathEndpoint):
|
|||||||
'allocated_draw': f"Allocated draw cannot exceed the maximum draw ({self.maximum_draw}W)."
|
'allocated_draw': f"Allocated draw cannot exceed the maximum draw ({self.maximum_draw}W)."
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def get_downstream_powerports(self, leg=None):
|
||||||
|
"""
|
||||||
|
Return a queryset of all PowerPorts connected via cable to a child PowerOutlet.
|
||||||
|
"""
|
||||||
|
poweroutlets = self.poweroutlets.filter(cable__isnull=False)
|
||||||
|
if leg:
|
||||||
|
poweroutlets = poweroutlets.filter(feed_leg=leg)
|
||||||
|
if not poweroutlets:
|
||||||
|
return PowerPort.objects.none()
|
||||||
|
|
||||||
|
q = Q()
|
||||||
|
for poweroutlet in poweroutlets:
|
||||||
|
q |= Q(
|
||||||
|
cable=poweroutlet.cable,
|
||||||
|
cable_end=poweroutlet.opposite_cable_end
|
||||||
|
)
|
||||||
|
|
||||||
|
return PowerPort.objects.filter(q)
|
||||||
|
|
||||||
def get_power_draw(self):
|
def get_power_draw(self):
|
||||||
"""
|
"""
|
||||||
Return the allocated and maximum power draw (in VA) and child PowerOutlet count for this PowerPort.
|
Return the allocated and maximum power draw (in VA) and child PowerOutlet count for this PowerPort.
|
||||||
"""
|
"""
|
||||||
|
from dcim.models import PowerFeed
|
||||||
|
|
||||||
# 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:
|
||||||
poweroutlet_ct = ContentType.objects.get_for_model(PowerOutlet)
|
utilization = self.get_downstream_powerports().aggregate(
|
||||||
outlet_ids = PowerOutlet.objects.filter(power_port=self).values_list('pk', flat=True)
|
|
||||||
utilization = PowerPort.objects.filter(
|
|
||||||
_link_peer_type=poweroutlet_ct,
|
|
||||||
_link_peer_id__in=outlet_ids
|
|
||||||
).aggregate(
|
|
||||||
maximum_draw_total=Sum('maximum_draw'),
|
maximum_draw_total=Sum('maximum_draw'),
|
||||||
allocated_draw_total=Sum('allocated_draw'),
|
allocated_draw_total=Sum('allocated_draw'),
|
||||||
)
|
)
|
||||||
ret = {
|
ret = {
|
||||||
'allocated': utilization['allocated_draw_total'] or 0,
|
'allocated': utilization['allocated_draw_total'] or 0,
|
||||||
'maximum': utilization['maximum_draw_total'] or 0,
|
'maximum': utilization['maximum_draw_total'] or 0,
|
||||||
'outlet_count': len(outlet_ids),
|
'outlet_count': self.poweroutlets.count(),
|
||||||
'legs': [],
|
'legs': [],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Calculate per-leg aggregates for three-phase feeds
|
# Calculate per-leg aggregates for three-phase power feeds
|
||||||
if getattr(self._link_peer, 'phase', None) == PowerFeedPhaseChoices.PHASE_3PHASE:
|
if len(self.link_peers) == 1 and isinstance(self.link_peers[0], PowerFeed) and \
|
||||||
|
self.link_peers[0].phase == PowerFeedPhaseChoices.PHASE_3PHASE:
|
||||||
for leg, leg_name in PowerOutletFeedLegChoices:
|
for leg, leg_name in PowerOutletFeedLegChoices:
|
||||||
outlet_ids = PowerOutlet.objects.filter(power_port=self, feed_leg=leg).values_list('pk', flat=True)
|
utilization = self.get_downstream_powerports(leg=leg).aggregate(
|
||||||
utilization = PowerPort.objects.filter(
|
|
||||||
_link_peer_type=poweroutlet_ct,
|
|
||||||
_link_peer_id__in=outlet_ids
|
|
||||||
).aggregate(
|
|
||||||
maximum_draw_total=Sum('maximum_draw'),
|
maximum_draw_total=Sum('maximum_draw'),
|
||||||
allocated_draw_total=Sum('allocated_draw'),
|
allocated_draw_total=Sum('allocated_draw'),
|
||||||
)
|
)
|
||||||
@ -370,7 +389,7 @@ class PowerPort(ModularComponentModel, LinkTermination, PathEndpoint):
|
|||||||
'name': leg_name,
|
'name': leg_name,
|
||||||
'allocated': utilization['allocated_draw_total'] or 0,
|
'allocated': utilization['allocated_draw_total'] or 0,
|
||||||
'maximum': utilization['maximum_draw_total'] or 0,
|
'maximum': utilization['maximum_draw_total'] or 0,
|
||||||
'outlet_count': len(outlet_ids),
|
'outlet_count': self.poweroutlets.filter(feed_leg=leg).count(),
|
||||||
})
|
})
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
@ -379,7 +398,7 @@ class PowerPort(ModularComponentModel, LinkTermination, PathEndpoint):
|
|||||||
return {
|
return {
|
||||||
'allocated': self.allocated_draw or 0,
|
'allocated': self.allocated_draw or 0,
|
||||||
'maximum': self.maximum_draw or 0,
|
'maximum': self.maximum_draw or 0,
|
||||||
'outlet_count': PowerOutlet.objects.filter(power_port=self).count(),
|
'outlet_count': self.poweroutlets.count(),
|
||||||
'legs': [],
|
'legs': [],
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -422,9 +441,7 @@ class PowerOutlet(ModularComponentModel, LinkTermination, PathEndpoint):
|
|||||||
|
|
||||||
# Validate power port assignment
|
# Validate power port assignment
|
||||||
if self.power_port and self.power_port.device != self.device:
|
if self.power_port and self.power_port.device != self.device:
|
||||||
raise ValidationError(
|
raise ValidationError(f"Parent power port ({self.power_port}) must belong to the same device")
|
||||||
"Parent power port ({}) must belong to the same device".format(self.power_port)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -429,20 +429,22 @@ class Rack(NetBoxModel):
|
|||||||
"""
|
"""
|
||||||
powerfeeds = PowerFeed.objects.filter(rack=self)
|
powerfeeds = PowerFeed.objects.filter(rack=self)
|
||||||
available_power_total = sum(pf.available_power for pf in powerfeeds)
|
available_power_total = sum(pf.available_power for pf in powerfeeds)
|
||||||
|
print(f'available_power_total: {available_power_total}')
|
||||||
if not available_power_total:
|
if not available_power_total:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
pf_powerports = PowerPort.objects.filter(
|
powerports = []
|
||||||
_link_peer_type=ContentType.objects.get_for_model(PowerFeed),
|
for powerfeed in powerfeeds:
|
||||||
_link_peer_id__in=powerfeeds.values_list('id', flat=True)
|
powerports.extend([
|
||||||
)
|
peer for peer in powerfeed.link_peers if isinstance(peer, PowerPort)
|
||||||
poweroutlets = PowerOutlet.objects.filter(power_port_id__in=pf_powerports)
|
])
|
||||||
allocated_draw_total = PowerPort.objects.filter(
|
|
||||||
_link_peer_type=ContentType.objects.get_for_model(PowerOutlet),
|
|
||||||
_link_peer_id__in=poweroutlets.values_list('id', flat=True)
|
|
||||||
).aggregate(Sum('allocated_draw'))['allocated_draw__sum'] or 0
|
|
||||||
|
|
||||||
return int(allocated_draw_total / available_power_total * 100)
|
allocated_draw = 0
|
||||||
|
for powerport in powerports:
|
||||||
|
allocated_draw += powerport.get_power_draw()['allocated']
|
||||||
|
print(f'allocated_draw: {allocated_draw}')
|
||||||
|
|
||||||
|
return int(allocated_draw / available_power_total * 100)
|
||||||
|
|
||||||
|
|
||||||
class RackReservation(NetBoxModel):
|
class RackReservation(NetBoxModel):
|
||||||
|
Loading…
Reference in New Issue
Block a user