Skip to content

Commit

Permalink
Merge pull request #333 from fasrc/cp_uninvoiced_resource_allocations
Browse files Browse the repository at this point in the history
Uninvoiced resource allocations
  • Loading branch information
claire-peters committed Aug 29, 2024
2 parents 54f78a8 + a0a7223 commit 921f461
Show file tree
Hide file tree
Showing 18 changed files with 172 additions and 80 deletions.
3 changes: 2 additions & 1 deletion coldfront/core/allocation/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,8 @@ def __init__(self, *args, **kwargs):
else:
if allo_resource.resource_type.name == 'Storage Tier':
self.fields['resource'].queryset = Resource.objects.filter(
parent_resource=allo_resource
parent_resource=allo_resource,
is_allocatable=True
)
else:
self.fields['resource'].required = False
Expand Down
8 changes: 8 additions & 0 deletions coldfront/core/allocation/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ def save(self, *args, **kwargs):
def offer_letter_code(self):
return self.get_attribute('Offer Letter Code')

@property
def requires_payment(self):
requires_payment = self.get_attribute('Requires Payment')
if requires_payment == None:
return self.get_parent_resource.requires_payment

@property
def fairshare(self):
return self.get_attribute('FairShare')
Expand Down Expand Up @@ -270,6 +276,8 @@ def cost(self):
return None
except TypeError:
return None
except ObjectDoesNotExist:
return None
size_attr_name = self._return_size_attr_name()
if not size_attr_name:
return None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ <h3><i class="fas fa-list" aria-hidden="true"></i> Allocation Information</h3>
</td>
</tr>
<tr>
<th scope="row" class="text-nowrap">Resource{{ allocation.resources.all|pluralize }} in allocation:</th>
<th scope="row" class="text-nowrap">Allocated Resource{{ allocation.resources.all|pluralize }}:</th>
<td>
{% if allocation.get_resources_as_list %}
{% for resource in allocation.get_resources_as_list %}
Expand Down Expand Up @@ -129,7 +129,7 @@ <h3><i class="fas fa-list" aria-hidden="true"></i> Allocation Information</h3>
</td>
</tr>
{% endif %}
{% if "Storage" in allocation.get_parent_resource.resource_type.name %}
{% if invoice %}
<tr>
<th scope="row" class="text-nowrap">FIINE Expense Codes:</th>
<td>
Expand All @@ -140,7 +140,7 @@ <h3><i class="fas fa-list" aria-hidden="true"></i> Allocation Information</h3>
</tr>
{% endif %}
<tr>
<th scope="row" class="text-nowrap">Total Users in Your Bill:</th>
<th scope="row" class="text-nowrap">Total Users with Usage:</th>
<td>{{ allocation_users.count }}</td>
</tr>
<tr>
Expand All @@ -164,13 +164,15 @@ <h3><i class="fas fa-list" aria-hidden="true"></i> Allocation Information</h3>
<th scope="row" class="text-nowrap">Quota (TB):</th>
<td id="group_quota">{{ allocation.size|floatformat:2 }}</td>
</tr>
<tr>
<th style="background-color:#D3D3D3" bordercolor="red" scope="row" class="text-nowrap">Total Amount Due: </th>
{% cost_tb allocation.size as cost %}
{% if cost %}
<td style="background-color:#D3D3D3">{{ cost }}</td>
{% endif %}
</tr>
{% if invoice %}
<tr>
<th style="background-color:#D3D3D3" bordercolor="red" scope="row" class="text-nowrap">Total Amount Due: </th>
{% cost_tb allocation.size as cost %}
{% if cost %}
<td style="background-color:#D3D3D3">{{ cost }}</td>
{% endif %}
</tr>
{% endif %}
{% endif %}
<tr>
<th scope="row" class="text-nowrap">Start Date:</th>
Expand Down Expand Up @@ -489,7 +491,9 @@ <h3 class="d-inline"><i class="fas fa-users" aria-hidden="true"></i> Users in Al
{% if "Storage" in allocation.get_parent_resource.resource_type.name %}
<th scope="col">Logical Usage</th>
<th scope="col">Percent Usage</th>
<th scope="col">Cost Per User (TB/month)</th>
{% if invoice %}
<th scope="col">Cost Per User (TB/month)</th>
{% endif %}
{% else %}
<th scope="col">CPU Hours</th>
<th scope="col">Percent Usage</th>
Expand Down Expand Up @@ -546,15 +550,16 @@ <h3 class="d-inline"><i class="fas fa-users" aria-hidden="true"></i> Users in Al
<td>{{userusage|div:allocationusage|mul:100|floatformat:2 }}%</td>
{% endif %}

{% if "Storage" in allocation.get_parent_resource.resource_type.name %}
{% if invoice %}
{% cost_bytes userusage as cost %}
{% if cost %}
<td>{{ cost }}</td>
{% endif %}
{% else %}
<td>{{ user.effectvusage }}</td>
<td>{{ user.normshares }}</td>
<td>{{ user.fairshare }}</td>
{% endif %}
{% if 'Cluster' in allocation.get_parent_resource.resource_type.name %}
<td>{{ user.effectvusage }}</td>
<td>{{ user.normshares }}</td>
<td>{{ user.fairshare }}</td>
{% endif %}
</tr>
{% endfor %}
Expand Down
39 changes: 38 additions & 1 deletion coldfront/core/allocation/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
Allocation,
AllocationUserNote,
AllocationAttribute,
AllocationAttributeType,
AllocationStatusChoice,
AllocationChangeRequest,
)
from coldfront.core.resource.models import Resource
from coldfront.core.test_helpers.factories import (
setup_models,
UserFactory,
Expand Down Expand Up @@ -74,7 +77,7 @@ def setUpTestData(cls):
AllocationFactory() for i in list(range(50))
]
for allocation in cls.additional_allocations:
allocation.resources.add(ResourceFactory(name='holylfs09/tier1', id=2))
allocation.resources.add(Resource.objects.get(name='holylfs09/tier1'))
cls.nonproj_nonallocation_user = UserFactory(username='rdrake')

def test_allocation_list_access_admin(self):
Expand Down Expand Up @@ -351,6 +354,40 @@ def test_allocationuser_button_visibility(self):
)


class AllocationDetailViewPostTest(AllocationViewBaseTest):
def setUp(self):
self.new_allocation = AllocationFactory(
project=self.project, quantity=20, justification='test new allocation',
status=AllocationStatusChoice.objects.get(name='New')
)
self.new_allocation.resources.add(Resource.objects.get(name='holylfs09/tier1'))
quotatb = AllocationAttributeType.objects.get(name='Storage Quota (TB)')
self.new_allocation.allocationattribute_set.create(
value=20, allocation_attribute_type=quotatb
)
self.url = f'/allocation/{self.new_allocation.pk}/'

def test_allocationdetail_approval_post(self):
"""test approval of new allocation"""
self.client.force_login(self.admin_user)
form_data = {
'status': AllocationStatusChoice.objects.get(name='Active').pk,
#'start_date': self.new_allocation.start_date,
#'end_date': self.new_allocation.end_date,
'description': "description test",
'is_locked': False,
'is_changeable': True,
'resource': self.new_allocation.resources.first().pk,
'action': 'approve',
'auto_create_opts': '1',
}
response = self.client.post(self.url, data=form_data, follow=True)
# confirm that messages in response contains "Allocation Activated"
self.assertContains(response, 'Allocation Activated')
self.new_allocation.refresh_from_db()
self.assertEqual(self.new_allocation.status.name, 'Active')


class AllocationCreateViewTest(AllocationViewBaseTest):
"""Tests for the AllocationCreateView"""

Expand Down
11 changes: 11 additions & 0 deletions coldfront/core/allocation/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,11 @@ def get_context_data(self, **kwargs):
context['expense_codes'] = expense_codes

offer_letter_code_type = AllocationAttributeType.objects.get(name="Expense Code")
context['invoice'] = (
allocation_obj.requires_payment
and allocation_obj.status.name in ACTIVE_ALLOCATION_STATUSES
and "Storage" in allocation_obj.get_parent_resource.resource_type.name
)
context['expense_code'] = allocation_obj.allocationattribute_set.filter(
allocation_attribute_type=offer_letter_code_type
)
Expand Down Expand Up @@ -269,6 +274,7 @@ def post(self, request, *args, **kwargs):
err = 'You do not have permission to update the allocation'
messages.error(request, err)
return HttpResponseRedirect(reverse('allocation-detail', kwargs={'pk': pk}))

initial_data = {
'status': allocation_obj.status,
'end_date': allocation_obj.end_date,
Expand Down Expand Up @@ -356,6 +362,11 @@ def post(self, request, *args, **kwargs):
allocation_obj.resources.add(resource)

allocation_obj.status = AllocationStatusChoice.objects.get(name='Active')
AllocationAttribute.objects.get_or_create(
allocation=allocation_obj,
allocation_attribute_type=AllocationAttributeType.objects.get(name='RequiresPayment'),
defaults={'value': resource.requires_payment}
)

elif action == 'deny':
allocation_obj.status = AllocationStatusChoice.objects.get(name='Denied')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ <h3 class="d-inline"><i class="fas fa-users" aria-hidden="true"></i>Quarterly Cl
<th scope="col">Project</th>
<th scope="col">Allocation</th>
<th scope="col">Users</th>
<th scope="col">Usage</th>
<th scope="col">Usage (CPU Hours)</th>
<!-- <th scope="col">Quarterly Cost to Date</th> -->
</tr>
</thead>
Expand Down
5 changes: 4 additions & 1 deletion coldfront/core/department/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,10 @@ def get_context_data(self, **kwargs):
status__name='Active',
)
p.storage_allocs = p.allocs.filter(
resources__resource_type__name='Storage')
resources__resource_type__name='Storage',
allocationattribute__allocation_attribute_type__name='RequiresPayment',
allocationattribute__value='True'
)
p.compute_allocs = p.allocs.filter(
resources__resource_type__name='Cluster')
storage_pi_dict[p.pi].extend(list(p.storage_allocs))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ <h4>Storage &nbsp;
<td>{{ allocation.allocationuser_set.count }}</td>
<td>{{ allocation.size|floatformat:1 }} {{ allocation.get_parent_resource.unit }}</td>
<td>{{ allocation.usage|floatformat:1 }}</td>
<td>${{allocation.cost|floatformat:2 }}</td>
<td>{% if allocation_obj.requires_payment %}${{allocation.cost|floatformat:2 }}{% endif %}</td>
<td>
<a href="{% url 'allocation-detail' allocation.pk %}">
<span class="badge badge-success"><i class="fas fa-search" aria-hidden="true"></i>
Expand Down
2 changes: 1 addition & 1 deletion coldfront/core/project/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ def get_context_data(self, **kwargs):
compute_allocations = allocations.filter(resources__resource_type__name='Cluster')
allocation_total = {'allocation_user_count': 0, 'size': 0, 'cost': 0, 'usage':0}
for allocation in storage_allocations:
if allocation.cost:
if allocation.cost and allocation.requires_payment:
allocation_total['cost'] += allocation.cost
if allocation.size:
allocation_total['size'] += allocation.size
Expand Down
2 changes: 1 addition & 1 deletion coldfront/core/resource/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class ResourceAdmin(SimpleHistoryAdmin):
# readonly_fields_change = ('resource_type', )
fields_change = ('resource_type', 'parent_resource', 'is_allocatable', 'name', 'description', 'is_available',
'is_public', 'requires_payment', 'allowed_groups', 'allowed_users', 'linked_resources')
list_display = ('pk', 'name', 'description', 'parent_resource', 'is_allocatable', 'resource_type_name',
list_display = ('pk', 'name', 'description', 'parent_resource', 'is_allocatable', 'requires_payment', 'resource_type_name',
'is_available', 'is_public', 'created', 'modified', )
search_fields = ('name', 'description', 'resource_type__name')
list_filter = ('resource_type__name', 'is_allocatable', 'is_available', 'is_public', 'requires_payment' )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def handle(self, *args, **options):
('used_tb', 'Float'),
('file_count', 'Int'),
('allocated_tb', 'Float'),
('ChangeableAllocations', 'Yes/No'),
# ('Core Count', 'Int'),
# ('expiry_time', 'Int'),
# ('fee_applies', 'Yes/No'),
Expand Down Expand Up @@ -75,33 +76,44 @@ def handle(self, *args, **options):
default_value_type = ResourceAttributeType.objects.get(name='quantity_default_value')
label_type = ResourceAttributeType.objects.get(name='quantity_label')

for name, desc, is_public, rtype, parent_name, default_value in (
('Tier 0', 'Bulk - Lustre', True, storage_tier, None, 1),
('Tier 1', 'Enterprise - Isilon', True, storage_tier, None, 1),
('Tier 2', 'CEPH storage', True, storage_tier, None, 1),
('Tier 3', 'Attic Storage - Tape', True, storage_tier, None, 20),
('holylfs04/tier0', 'Holyoke data center lustre storage', True, storage, 'Tier 0', 1),
('holylfs05/tier0', 'Holyoke data center lustre storage', True, storage, 'Tier 0', 1),
('nesetape/tier3', 'Cold storage for past projects', True, storage, 'Tier 3', 20),
('holy-isilon/tier1', 'Tier1 storage with snapshots and disaster recovery copy', True, storage, 'Tier 1', 1),
('bos-isilon/tier1', 'Tier1 storage for on-campus storage mounting', True, storage, 'Tier 1', 1),
('holystore01/tier0', 'Luster storage under Tier0', True, storage, 'Tier 0', 1),
('b-nfs02-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1),
('b-nfs03-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1),
('b-nfs04-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1),
('b-nfs05-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1),
('b-nfs06-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1),
('b-nfs07-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1),
('b-nfs08-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1),
('b-nfs09-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1),
('h-nfs16-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1),
('h-nfs17-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1),
('h-nfs18-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1),
('h-nfs19-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1),
for name, desc, is_public, rtype, parent_name, default_value, reqspayment, is_allocatable in (
('Tier 0', 'Bulk - Lustre', True, storage_tier, None, 1, True, True),
('Tier 1', 'Enterprise - Isilon', True, storage_tier, None, 1, True, True),
('Tier 2', 'CEPH storage', True, storage_tier, None, 1, True, True),
('Tier 3', 'Attic Storage - Tape', True, storage_tier, None, 20, True, True),
('holylfs04/tier0', 'Holyoke data center lustre storage', True, storage, 'Tier 0', 1, True, True),
('holylfs05/tier0', 'Holyoke data center lustre storage', True, storage, 'Tier 0', 1, True, True),
('nesetape/tier3', 'Cold storage for past projects', True, storage, 'Tier 3', 20, True, True),
('holy-isilon/tier1', 'Tier1 storage with snapshots and disaster recovery copy', True, storage, 'Tier 1', 1, True, True),
('bos-isilon/tier1', 'Tier1 storage for on-campus storage mounting', True, storage, 'Tier 1', 1, True, True),
('holystore01/tier0', 'Luster storage under Tier0', True, storage, 'Tier 0', 1, True, True),
('b-nfs02-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1, False, True),
('b-nfs03-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1, False, True),
('b-nfs04-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1, False, True),
('b-nfs05-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1, False, True),
('b-nfs06-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1, False, True),
('b-nfs07-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1, False, True),
('b-nfs08-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1, False, True),
('b-nfs09-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1, False, True),
('h-nfs11-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1, False, True),
('h-nfs12-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1, False, True),
('h-nfs13-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1, False, True),
('h-nfs14-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1, False, True),
('h-nfs15-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1, False, True),
('h-nfs16-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1, False, True),
('h-nfs17-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1, False, True),
('h-nfs18-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1, False, True),
('h-nfs19-p/tier2', 'Tier2 CEPH storage', True, storage, 'Tier 2', 1, False, True),
('boslfs02', 'complimentary lab storage', True, storage, 'Tier 0', 1, False, False),
('holylabs', 'complimentary lab storage', True, storage, 'Tier 0', 1, False, False),
):

resource_defaults = {
'description':desc, 'is_public':is_public, 'resource_type':rtype,
'description': desc,
'is_public': is_public,
'resource_type': rtype,
'requires_payment': reqspayment,
'is_allocatable': is_allocatable,
}
if parent_name:
resource_defaults['parent_resource'] = Resource.objects.get(name=parent_name)
Expand All @@ -113,6 +125,10 @@ def handle(self, *args, **options):
resource_attribute_type=default_value_type,
defaults={'value': default_value}
)
resource_obj.resourceattribute_set.update_or_create(
resource_attribute_type=default_value_type,
defaults={'value': default_value}
)

quantity_label = "TB"
if default_value == 20:
Expand Down
2 changes: 2 additions & 0 deletions coldfront/core/test_helpers/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ def setup_models(test_case):
ResourceTypeFactory(name=resource_type)
for resource, r_id, rtype in [
('holylfs10/tier1', 1, 'Storage'),
('holylfs09/tier1', 2, 'Storage'),
('Test Cluster', 3, 'Cluster')
]:
ResourceFactory(name=resource, id=r_id, resource_type__name=rtype)
Expand All @@ -361,6 +362,7 @@ def setup_models(test_case):
('High Security', 'Yes/No', False, False),
('DUA', 'Yes/No', False, False),
('External Sharing', 'Yes/No', False, False),
('RequiresPayment', 'Yes/No', False, False),
):
AllocationAttributeTypeFactory(
name=name,
Expand Down
Loading

0 comments on commit 921f461

Please sign in to comment.