This is an automated email from the ASF dual-hosted git repository.
yasithdev pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airavata-portals.git
The following commit(s) were added to refs/heads/main by this push:
new b7404ec5b feat(portal): repoint group resource profile reads to gRPC
(Track D, D2) (#169)
b7404ec5b is described below
commit b7404ec5b6ae27ec5acca12780a6bd5451995dd1
Author: Yasith Jayawardana <[email protected]>
AuthorDate: Mon Jun 8 19:50:39 2026 -0400
feat(portal): repoint group resource profile reads to gRPC (Track D, D2)
(#169)
Migrate GroupResourceProfileViewSet.get_list/get_instance from the Thrift
client to the gRPC compute facade (compute.get_group_resource_list /
get_group_resource_profile). Write actions stay on Thrift pending D3.
GroupResourceProfile is the most deeply nested read model. The new
group_resource_profile adapter recursively adapts:
- computePreferences (GroupComputeResourcePreference), each carrying a
proto-oneof EnvironmentSpecificPreferences union -> {slurm, aws} (one
set, chosen via WhichOneof), with the SLURM branch's reservations and
groupSSHAccountProvisionerConfigs;
- computeResourcePolicies (ComputeResourcePolicy) and
batchQueueResourcePolicies (BatchQueueResourcePolicy).
Three enums render as raw integers and are bridged by name to the Thrift
integer: preferredJobSubmissionProtocol and preferredDataMovementProtocol
reuse the existing name-divergent maps (JSP_CLOUD->CLOUD, LOCAL prefixes),
and resourceType uses a new ResourceType map (SLURM/AWS align by name,
off by one in value). Empty credential-store tokens map to None so the
serializer's userHasWriteAccess token READ check ('token is None or ...')
skips unset tokens; that check and the WRITE check migrate to the gRPC
sharing helper.
Verified: manage.py check clean; empty list returns 200 live; offline
serializer render with a full SLURM-union profile (reservations, SSH
provisioner configs, both policy types, all three enum bridges, the
allocationProjectNumber flattening, and the multi-token sharing check)
renders byte-for-byte correct. (The dev backend's create path needs user
context the service-account token doesn't satisfy, so the nested live
read was validated via the offline render against real protobuf.)
---
.../django_airavata/apps/api/grpc_adapters.py | 140 +++++++++++++++++++++
.../django_airavata/apps/api/serializers.py | 8 +-
.../django_airavata/apps/api/views.py | 10 +-
3 files changed, 149 insertions(+), 9 deletions(-)
diff --git a/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
b/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
index ca9942a57..57b7333ad 100644
--- a/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
+++ b/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
@@ -16,6 +16,9 @@ from types import SimpleNamespace
from airavata.model.appcatalog.computeresource.ttypes import (
JobSubmissionProtocol as _ThriftJobSubmissionProtocol,
)
+from airavata.model.appcatalog.groupresourceprofile.ttypes import (
+ ResourceType as _ThriftResourceType,
+)
from airavata.model.appcatalog.parallelism.ttypes import (
ApplicationParallelismType as _ThriftParallelismType,
)
@@ -370,3 +373,140 @@ def application_deployment(pb):
defaultWalltime=pb.default_walltime or None,
editableByUser=pb.editable_by_user,
)
+
+
+# proto ResourceType member name -> Thrift ResourceType value (names align,
+# ints differ: proto SLURM=1 vs Thrift SLURM=0).
+_RESOURCE_TYPE = {
+ 'SLURM': _ThriftResourceType.SLURM,
+ 'AWS': _ThriftResourceType.AWS,
+}
+
+
+def _compute_resource_reservation(pb):
+ """gRPC ``ComputeResourceReservation`` ->
``ComputeResourceReservationSerializer`` shape."""
+ return SimpleNamespace(
+ reservationId=pb.reservation_id,
+ reservationName=pb.reservation_name,
+ queueNames=list(pb.queue_names),
+ # serializer overrides start/end with nullable UTC fields.
+ startTime=pb.start_time or None,
+ endTime=pb.end_time or None,
+ )
+
+
+def _group_account_ssh_provisioner_config(pb):
+ """gRPC ``GroupAccountSSHProvisionerConfig`` -> auto-generated serializer
shape."""
+ return SimpleNamespace(
+ resourceId=pb.resource_id,
+ groupResourceProfileId=pb.group_resource_profile_id,
+ configName=pb.config_name,
+ configValue=pb.config_value,
+ )
+
+
+def _slurm_compute_resource_preference(pb):
+ """gRPC ``SlurmComputeResourcePreference`` -> auto-generated serializer
shape."""
+ return SimpleNamespace(
+ allocationProjectNumber=pb.allocation_project_number,
+ preferredBatchQueue=pb.preferred_batch_queue,
+ qualityOfService=pb.quality_of_service,
+ usageReportingGatewayId=pb.usage_reporting_gateway_id,
+ sshAccountProvisioner=pb.ssh_account_provisioner,
+ groupSSHAccountProvisionerConfigs=[
+ _group_account_ssh_provisioner_config(c)
+ for c in pb.group_ssh_account_provisioner_configs],
+
sshAccountProvisionerAdditionalInfo=pb.ssh_account_provisioner_additional_info,
+ reservations=[_compute_resource_reservation(r) for r in
pb.reservations],
+ )
+
+
+def _aws_compute_resource_preference(pb):
+ """gRPC ``AwsComputeResourcePreference`` -> auto-generated serializer
shape."""
+ return SimpleNamespace(
+ region=pb.region,
+ preferredAmiId=pb.preferred_ami_id,
+ preferredInstanceType=pb.preferred_instance_type,
+ )
+
+
+def _environment_specific_preferences(pb):
+ """proto oneof ``EnvironmentSpecificPreferences`` -> {slurm, aws} (one
set)."""
+ which = pb.WhichOneof('preferences')
+ return SimpleNamespace(
+ slurm=(_slurm_compute_resource_preference(pb.slurm)
+ if which == 'slurm' else None),
+ aws=(_aws_compute_resource_preference(pb.aws)
+ if which == 'aws' else None),
+ )
+
+
+def _group_compute_resource_preference(pb):
+ """gRPC ``GroupComputeResourcePreference`` -> auto-generated serializer
shape."""
+ return SimpleNamespace(
+ computeResourceId=pb.compute_resource_id,
+ groupResourceProfileId=pb.group_resource_profile_id,
+ overridebyAiravata=pb.override_by_airavata,
+ loginUserName=pb.login_user_name,
+ scratchLocation=pb.scratch_location,
+ # rendered as raw ints; bridge by name (incl. the JSP_CLOUD/LOCAL
+ # divergences) to the Thrift integer the frontend expects.
+ preferredJobSubmissionProtocol=_thrift_enum_mapped(
+ pb, 'preferred_job_submission_protocol', _JOB_SUBMISSION_PROTOCOL),
+ preferredDataMovementProtocol=_thrift_enum_mapped(
+ pb, 'preferred_data_movement_protocol', _DATA_MOVEMENT_PROTOCOL),
+ # empty token -> None so the serializer's userHasWriteAccess token READ
+ # check (``token is None or ...``) skips unset tokens.
+ resourceSpecificCredentialStoreToken=(
+ pb.resource_specific_credential_store_token or None),
+ resourceType=_thrift_enum_mapped(pb, 'resource_type', _RESOURCE_TYPE),
+ specificPreferences=(
+ _environment_specific_preferences(pb.specific_preferences)
+ if pb.HasField('specific_preferences') else None),
+ )
+
+
+def _compute_resource_policy(pb):
+ """gRPC ``ComputeResourcePolicy`` -> auto-generated serializer shape."""
+ return SimpleNamespace(
+ resourcePolicyId=pb.resource_policy_id,
+ computeResourceId=pb.compute_resource_id,
+ groupResourceProfileId=pb.group_resource_profile_id,
+ allowedBatchQueues=list(pb.allowed_batch_queues),
+ )
+
+
+def _batch_queue_resource_policy(pb):
+ """gRPC ``BatchQueueResourcePolicy`` -> auto-generated serializer shape."""
+ return SimpleNamespace(
+ resourcePolicyId=pb.resource_policy_id,
+ computeResourceId=pb.compute_resource_id,
+ groupResourceProfileId=pb.group_resource_profile_id,
+ queuename=pb.queuename,
+ maxAllowedNodes=pb.max_allowed_nodes,
+ maxAllowedCores=pb.max_allowed_cores,
+ maxAllowedWalltime=pb.max_allowed_walltime,
+ )
+
+
+def group_resource_profile(pb):
+ """gRPC ``GroupResourceProfile`` -> ``GroupResourceProfileSerializer``
shape.
+
+ Recursively adapts the compute preferences (each carrying a slurm/aws
+ union of specific preferences with reservations) and the compute /
+ batch-queue resource policies.
+ """
+ return SimpleNamespace(
+ gatewayId=pb.gateway_id,
+ groupResourceProfileId=pb.group_resource_profile_id,
+ groupResourceProfileName=pb.group_resource_profile_name,
+ computePreferences=[
+ _group_compute_resource_preference(p) for p in
pb.compute_preferences],
+ computeResourcePolicies=[
+ _compute_resource_policy(p) for p in pb.compute_resource_policies],
+ batchQueueResourcePolicies=[
+ _batch_queue_resource_policy(p) for p in
pb.batch_queue_resource_policies],
+ creationTime=pb.creation_time or None,
+ updatedTime=pb.updated_time or None,
+ defaultCredentialStoreToken=pb.default_credential_store_token or None,
+ )
diff --git a/airavata-django-portal/django_airavata/apps/api/serializers.py
b/airavata-django-portal/django_airavata/apps/api/serializers.py
index 6d99f5c48..55179966a 100644
--- a/airavata-django-portal/django_airavata/apps/api/serializers.py
+++ b/airavata-django-portal/django_airavata/apps/api/serializers.py
@@ -1628,9 +1628,8 @@ class GroupResourceProfileSerializer(
def get_userHasWriteAccess(self, groupResourceProfile):
request = self.context['request']
- write_access = request.airavata_client.userHasAccess(
- request.authz_token, groupResourceProfile.groupResourceProfileId,
- ResourcePermissionType.WRITE)
+ write_access = user_has_access(
+ request, groupResourceProfile.groupResourceProfileId, "WRITE")
if not write_access:
return False
# Check that user has READ access to all tokens in this
@@ -1640,8 +1639,7 @@ class GroupResourceProfileSerializer(
for cp in groupResourceProfile.computePreferences])
def check_token(token):
- return token is None or request.airavata_client.userHasAccess(
- request.authz_token, token, ResourcePermissionType.READ)
+ return token is None or user_has_access(request, token, "READ")
return all(map(check_token, tokens))
diff --git a/airavata-django-portal/django_airavata/apps/api/views.py
b/airavata-django-portal/django_airavata/apps/api/views.py
index 7c273d867..13c365c08 100644
--- a/airavata-django-portal/django_airavata/apps/api/views.py
+++ b/airavata-django-portal/django_airavata/apps/api/views.py
@@ -954,12 +954,14 @@ class GroupResourceProfileViewSet(APIBackedViewSet):
lookup_field = 'group_resource_profile_id'
def get_list(self):
- return self.request.airavata_client.getGroupResourceList(
- self.authz_token, self.gateway_id)
+ return [
+ grpc_adapters.group_resource_profile(p)
+ for p in self.request.airavata.compute.get_group_resource_list()
+ ]
def get_instance(self, lookup_value):
- return self.request.airavata_client.getGroupResourceProfile(
- self.authz_token, lookup_value)
+ return grpc_adapters.group_resource_profile(
+
self.request.airavata.compute.get_group_resource_profile(lookup_value))
def perform_create(self, serializer):
group_resource_profile = serializer.save()