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.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.db.models import Sum
from django.db.models import Q, Sum
from django.urls import reverse
from mptt.models import MPTTModel, TreeForeignKey
@ -156,6 +156,12 @@ class LinkTermination(models.Model):
def parent_object(self):
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
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)."
})
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):
"""
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
if self.allocated_draw is None and self.maximum_draw is None:
poweroutlet_ct = ContentType.objects.get_for_model(PowerOutlet)
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(
utilization = self.get_downstream_powerports().aggregate(
maximum_draw_total=Sum('maximum_draw'),
allocated_draw_total=Sum('allocated_draw'),
)
ret = {
'allocated': utilization['allocated_draw_total'] or 0,
'maximum': utilization['maximum_draw_total'] or 0,
'outlet_count': len(outlet_ids),
'outlet_count': self.poweroutlets.count(),
'legs': [],
}
# Calculate per-leg aggregates for three-phase feeds
if getattr(self._link_peer, 'phase', None) == PowerFeedPhaseChoices.PHASE_3PHASE:
# Calculate per-leg aggregates for three-phase power feeds
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:
outlet_ids = PowerOutlet.objects.filter(power_port=self, feed_leg=leg).values_list('pk', flat=True)
utilization = PowerPort.objects.filter(
_link_peer_type=poweroutlet_ct,
_link_peer_id__in=outlet_ids
).aggregate(
utilization = self.get_downstream_powerports(leg=leg).aggregate(
maximum_draw_total=Sum('maximum_draw'),
allocated_draw_total=Sum('allocated_draw'),
)
@ -370,7 +389,7 @@ class PowerPort(ModularComponentModel, LinkTermination, PathEndpoint):
'name': leg_name,
'allocated': utilization['allocated_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
@ -379,7 +398,7 @@ class PowerPort(ModularComponentModel, LinkTermination, PathEndpoint):
return {
'allocated': self.allocated_draw or 0,
'maximum': self.maximum_draw or 0,
'outlet_count': PowerOutlet.objects.filter(power_port=self).count(),
'outlet_count': self.poweroutlets.count(),
'legs': [],
}
@ -422,9 +441,7 @@ class PowerOutlet(ModularComponentModel, LinkTermination, PathEndpoint):
# Validate power port assignment
if self.power_port and self.power_port.device != self.device:
raise ValidationError(
"Parent power port ({}) must belong to the same device".format(self.power_port)
)
raise ValidationError(f"Parent power port ({self.power_port}) must belong to the same device")
#

View File

@ -429,20 +429,22 @@ class Rack(NetBoxModel):
"""
powerfeeds = PowerFeed.objects.filter(rack=self)
available_power_total = sum(pf.available_power for pf in powerfeeds)
print(f'available_power_total: {available_power_total}')
if not available_power_total:
return 0
pf_powerports = PowerPort.objects.filter(
_link_peer_type=ContentType.objects.get_for_model(PowerFeed),
_link_peer_id__in=powerfeeds.values_list('id', flat=True)
)
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
powerports = []
for powerfeed in powerfeeds:
powerports.extend([
peer for peer in powerfeed.link_peers if isinstance(peer, PowerPort)
])
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):