Update power utilization calculations for new cabling model

This commit is contained in:
jeremystretch 2022-06-27 14:34:08 -04:00
parent 25ed3390cb
commit fcd1daaf79
2 changed files with 49 additions and 30 deletions

View File

@ -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)
)
# #

View File

@ -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):