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()

Reply via email to