diff --git a/coldfront/api/statistics/tests/test_can_submit_job.py b/coldfront/api/statistics/tests/test_can_submit_job.py index 8c16e160f..1bfb04212 100644 --- a/coldfront/api/statistics/tests/test_can_submit_job.py +++ b/coldfront/api/statistics/tests/test_can_submit_job.py @@ -3,10 +3,13 @@ from coldfront.core.allocation.models import AllocationStatusChoice from coldfront.core.allocation.models import AllocationUserAttributeUsage from coldfront.core.allocation.models import AllocationUserStatusChoice +from coldfront.core.resource.models import Resource +from coldfront.core.resource.models import ResourceType from coldfront.core.resource.utils import get_computing_allowance_project_prefixes from coldfront.core.resource.utils import get_primary_compute_resource from coldfront.core.resource.utils_.allowance_utils.computing_allowance import ComputingAllowance from coldfront.core.resource.utils_.allowance_utils.interface import ComputingAllowanceInterface +from coldfront.core.utils.tests.test_base import enable_deployment from decimal import ConversionSyntax from decimal import Decimal from django.conf import settings @@ -120,13 +123,35 @@ def test_compute_allocation_not_expected_always_allowed(self): self.assert_result( job_cost, '0', self.project.name, 200, result, message) - example_exempt_project_names = ( - 'abc', 'alsacc', 'etna', 'nano', 'vector_project', 'vulcan') - for project_name in example_exempt_project_names: + resource_type = ResourceType.objects.get(name='Cluster') + lrc_departmental_cluster_names = ( + 'ALICE', 'ALSACC', 'BALDUR', 'ETNA', 'NANO', 'VULCAN') + for cluster_name in lrc_departmental_cluster_names: + Resource.objects.create( + name=f'{cluster_name} Compute', resource_type=resource_type) + + self.allocation.resources.clear() + + departmental_project_names = ( + 'alice', 'alsacc', 'etna', 'nano', 'vulcan') + for project_name in departmental_project_names: self.project.name = project_name self.project.save() - self.assert_result( - job_cost, '0', self.project.name, 200, True, success_message) + resource = Resource.objects.get( + name=f'{project_name.upper()} Compute') + self.allocation.resources.add(resource) + with enable_deployment('LRC'): + self.assert_result( + job_cost, '0', self.project.name, 200, True, + success_message) + # If the departmental project does not have an Allocation to + # the expected Resource, an error should result. + self.allocation.resources.clear() + message = ( + f'Account {self.project.name} has no active compute ' + f'allocation.') + self.assert_result( + job_cost, '0', self.project.name, 200, False, message) def test_no_active_compute_allocation(self): """Test that requests wherein the account has no active compute diff --git a/coldfront/api/statistics/tests/test_job_view_set.py b/coldfront/api/statistics/tests/test_job_view_set.py index 1241d5a77..630d37523 100644 --- a/coldfront/api/statistics/tests/test_job_view_set.py +++ b/coldfront/api/statistics/tests/test_job_view_set.py @@ -4,6 +4,7 @@ import pytz +from coldfront.api.statistics.serializers import JobSerializer from coldfront.api.statistics.tests.test_job_base import TestJobBase from coldfront.api.statistics.utils import convert_utc_datetime_to_unix_timestamp from coldfront.api.statistics.utils import get_accounting_allocation_objects @@ -20,13 +21,17 @@ from coldfront.core.project.models import ProjectUser from coldfront.core.project.models import ProjectUserRoleChoice from coldfront.core.project.models import ProjectUserStatusChoice +from coldfront.core.resource.models import Resource +from coldfront.core.resource.models import ResourceType from coldfront.core.resource.utils import get_primary_compute_resource from coldfront.core.statistics.models import Job from coldfront.core.user.models import ExpiringToken from coldfront.core.user.models import UserProfile from coldfront.core.utils.common import utc_datetime_to_display_time_zone_date +from coldfront.core.utils.tests.test_base import enable_deployment from django.contrib.auth.models import User +from django.core.management import call_command from django.db.models import Sum from rest_framework.test import APIClient @@ -568,13 +573,71 @@ def test_no_active_compute_allocation(self): name='Expired') self.allocation.save() self.assert_error_message(self.data, message) - # The allocation is active, but is not to the primary compute resource. + # The allocation is active, and is to the expected resource. self.allocation.status = AllocationStatusChoice.objects.get( name='Active') + self.allocation.save() + serializer = JobSerializer(data=self.data) + self.assertTrue(serializer.is_valid()) + # The allocation is active, but is not to any resource. resource = get_primary_compute_resource() self.allocation.resources.remove(resource) self.assert_error_message(self.data, message) + # The allocation is active, but is to an unexpected resource. + resource = Resource.objects.get(name='Vector Compute') + self.allocation.resources.add(resource) + self.assert_error_message(self.data, message) + # The allocation does not exist. + self.allocation.delete() + self.assert_error_message(self.data, message) + + @enable_deployment('LRC') + def test_no_active_compute_allocation_lrc_departmental(self): + """Test that requests wherein the account is for an LRC + departmental cluster and has no active compute allocation + fail.""" + resource_type = ResourceType.objects.get(name='Cluster') + cluster_names = ('ALICE', 'ALSACC', 'BALDUR', 'ETNA', 'NANO', 'VULCAN') + for cluster_name in cluster_names: + Resource.objects.create( + name=f'{cluster_name} Compute', resource_type=resource_type) + + self.allocation.resources.clear() + + departmental_project_names = ( + 'alice', 'alsacc', 'etna', 'nano', 'vulcan') + other_resource = Resource.objects.get(name='BALDUR Compute') + for project_name in departmental_project_names: + message = ( + f'Account {project_name} has no active compute allocation.') + self.project.name = project_name + self.project.save() + self.data['accountid'] = project_name + resource = Resource.objects.get( + name=f'{project_name.upper()} Compute') + self.allocation.resources.add(resource) + # The allocation is expired. + self.allocation.status = AllocationStatusChoice.objects.get( + name='Expired') + self.allocation.save() + self.assert_error_message(self.data, message) + # The allocation is active, and is to the expected resource. + self.allocation.status = AllocationStatusChoice.objects.get( + name='Active') + self.allocation.save() + serializer = JobSerializer(data=self.data) + self.assertTrue(serializer.is_valid()) + # The Allocation is active, but is not to any resource. + self.allocation.resources.clear() + self.assert_error_message(self.data, message) + # The allocation is active, but is to an unexpected resource. + self.allocation.resources.add(other_resource) + self.assert_error_message(self.data, message) + self.allocation.resources.clear() # The allocation does not exist. + message = ( + f'Account {departmental_project_names[-1]} has no active compute ' + f'allocation.') self.allocation.delete() self.assert_error_message(self.data, message) diff --git a/coldfront/api/statistics/utils.py b/coldfront/api/statistics/utils.py index 265271d8f..878ad9363 100644 --- a/coldfront/api/statistics/utils.py +++ b/coldfront/api/statistics/utils.py @@ -7,6 +7,7 @@ from coldfront.core.allocation.models import AllocationUserAttribute from coldfront.core.allocation.models import AllocationUserAttributeUsage from coldfront.core.allocation.models import AllocationUserStatusChoice +from coldfront.core.allocation.utils import get_project_compute_resource_name from coldfront.core.project.models import Project from coldfront.core.project.models import ProjectUser from coldfront.core.project.models import ProjectUserStatusChoice @@ -195,7 +196,7 @@ def get_accounting_allocation_objects(project, user=None, allocation_kwargs = { 'project': project, - 'resources__name': get_primary_compute_resource_name(), + 'resources__name': get_project_compute_resource_name(project), } if enforce_allocation_active: # Check that the project has an active Allocation to the diff --git a/coldfront/api/statistics/views.py b/coldfront/api/statistics/views.py index 360a05d86..9981bd579 100644 --- a/coldfront/api/statistics/views.py +++ b/coldfront/api/statistics/views.py @@ -704,13 +704,6 @@ def client_error(data_message): message = f'No account exists with account_id {account_id}.' return client_error(message) - # Allow all jobs for accounts that are not intended to have - # computing allowances (e.g., departmental cluster-specific accounts). - computing_allowance_project_prefixes = \ - get_computing_allowance_project_prefixes() - if not account.name.startswith(computing_allowance_project_prefixes): - return affirmative - # Validate that needed accounting objects exist. try: allocation_objects = get_accounting_allocation_objects( @@ -743,6 +736,13 @@ def client_error(data_message): logger.error(f'Incorrect input type. Details: {e}') return server_error + # Allow all jobs for accounts that are not intended to have + # computing allowances (e.g., departmental cluster-specific accounts). + computing_allowance_project_prefixes = \ + get_computing_allowance_project_prefixes() + if not account.name.startswith(computing_allowance_project_prefixes): + return affirmative + # Retrieve compute allocation values. account_allocation = Decimal(allocation_objects.allocation_attribute.value) user_account_allocation = Decimal(