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 ae630730a feat(portal): de-Thrift per-protocol interface detail views 
to gRPC (Track D) (#188)
ae630730a is described below

commit ae630730af4fb9783fdebb76de50355990a12b3d
Author: Yasith Jayawardana <[email protected]>
AuthorDate: Tue Jun 9 01:27:02 2026 -0400

    feat(portal): de-Thrift per-protocol interface detail views to gRPC (Track 
D) (#188)
    
    Repoint the admin-only per-protocol job-submission / data-movement detail
    APIViews from the legacy Thrift client to the gRPC compute/storage facades:
    
    - LocalJobSubmissionView, SshJobSubmissionView, UnicoreJobSubmissionView,
      CloudJobSubmissionView -> 
compute.get_{local,ssh,unicore,cloud}_job_submission
    - LocalDataMovementView, ScpDataMovementView, GridFtpDataMovementView ->
      storage.get_{local,scp,grid_ftp}_data_movement
    
    New grpc_adapters cover each model (proto -> the auto-generated serializer's
    Thrift attribute shape). Enum nuances bridged by NAME: SecurityProtocol /
    ResourceJobManagerType / ProviderName are prefix-aligned 
(_thrift_enum_prefixed,
    proto *_UNKNOWN -> None); MonitorMode names diverge (proto MONITOR_FORK/
    MONITOR_LOCAL vs Thrift FORK/LOCAL) via an explicit map; 
ResourceJobManager's
    two enum-keyed map<int32,string> fields (jobManagerCommands keyed by
    JobManagerCommand, parallelismPrefix by ApplicationParallelismType) bridge 
each
    key to the Thrift IntEnum member by name via a new _enum_keyed_map helper, 
so the
    DictField renders the same enum-member keys the Thrift map produced.
    
    Two views can't migrate and stay on Thrift, each flagged with a TODO:
    - GlobusJobSubmissionView: the compute facade has no Globus getter and the
      existing Thrift call (getClo) is already broken.
    - UnicoreDataMovementView: the storage facade has no UNICORE data-movement 
getter.
    
    REST/JSON contract unchanged. Validated: manage.py check clean; for all 7
    migrated models the gRPC adapter output through the serializer is 
byte-for-byte
    identical to the native-Thrift serializer output (scalars, divergent enums, 
and
    the enum-keyed maps), and unset enums/maps render as None/empty as before.
---
 .../django_airavata/apps/api/grpc_adapters.py      | 152 +++++++++++++++++++++
 .../django_airavata/apps/api/views.py              |  33 +++--
 2 files changed, 171 insertions(+), 14 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 29ebf862a..9d9111946 100644
--- a/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
+++ b/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
@@ -14,7 +14,11 @@ serializers are made protobuf-native.
 from types import SimpleNamespace
 
 from airavata.model.appcatalog.computeresource.ttypes import (
+    JobManagerCommand as _ThriftJobManagerCommand,
     JobSubmissionProtocol as _ThriftJobSubmissionProtocol,
+    MonitorMode as _ThriftMonitorMode,
+    ProviderName as _ThriftProviderName,
+    ResourceJobManagerType as _ThriftResourceJobManagerType,
 )
 from airavata.model.appcatalog.groupresourceprofile.ttypes import (
     ResourceType as _ThriftResourceType,
@@ -32,6 +36,7 @@ from airavata.model.data.replica.ttypes import (
 )
 from airavata.model.data.movement.ttypes import (
     DataMovementProtocol as _ThriftDataMovementProtocol,
+    SecurityProtocol as _ThriftSecurityProtocol,
 )
 from airavata.model.experiment.ttypes import (
     ExperimentType as _ThriftExperimentType,
@@ -1007,3 +1012,150 @@ def parser(pb):
         outputFiles=[_parser_output(o) for o in pb.output_files],
         gatewayId=pb.gateway_id,
     )
+
+
+# --- Per-protocol job-submission / data-movement interface details ----------
+# These admin-only detail views render a single protocol's submission/movement
+# model via the auto-generated serializer, so the adapter exposes Thrift 
attribute
+# names. SecurityProtocol/ResourceJobManagerType/ProviderName are 
prefix-aligned
+# (proto *_UNKNOWN sentinel -> None). MonitorMode NAMES diverge (proto 
MONITOR_FORK
+# / MONITOR_LOCAL vs Thrift FORK / LOCAL) so it needs an explicit map. The
+# ResourceJobManager carries two enum-keyed map<int32,string> fields whose int 
keys
+# hold proto enum values, bridged to the Thrift enum int by name (like 
_file_systems).
+
+# proto MonitorMode member name -> Thrift MonitorMode value (names diverge for 
the
+# FORK/LOCAL members, which proto prefixes with MONITOR_).
+_MONITOR_MODE = {
+    'POLL_JOB_MANAGER': _ThriftMonitorMode.POLL_JOB_MANAGER,
+    'CLOUD_JOB_MONITOR': _ThriftMonitorMode.CLOUD_JOB_MONITOR,
+    'JOB_EMAIL_NOTIFICATION_MONITOR': 
_ThriftMonitorMode.JOB_EMAIL_NOTIFICATION_MONITOR,
+    'XSEDE_AMQP_SUBSCRIBE': _ThriftMonitorMode.XSEDE_AMQP_SUBSCRIBE,
+    'MONITOR_FORK': _ThriftMonitorMode.FORK,
+    'MONITOR_LOCAL': _ThriftMonitorMode.LOCAL,
+}
+
+
+def _enum_keyed_map(pb_map, proto_enum, thrift_enum):
+    """proto map<int32, string> whose int key holds a ``proto_enum`` value ->
+    {Thrift enum member: value}, bridging the key by NAME (proto and Thrift 
assign
+    different ints to the same member). The Thrift model declared these as
+    enum-keyed maps, so the serializer's ``DictField`` rendered ``str(member)``
+    (e.g. ``'JobManagerCommand.SUBMISSION'``) -> keep the Thrift IntEnum 
member as
+    the key to reproduce that exact representation. Unknown keys (e.g. the zero
+    sentinel) are dropped.
+    """
+    result = {}
+    for k, v in pb_map.items():
+        name = proto_enum.DESCRIPTOR.values_by_number.get(k)
+        if name is None:
+            continue
+        thrift_member = getattr(thrift_enum, name.name, None)
+        if thrift_member is not None:
+            result[thrift_member] = v
+    return result
+
+
+def _resource_job_manager(pb):
+    """gRPC ``ResourceJobManager`` -> auto-generated serializer shape."""
+    from 
airavata_sdk.generated.org.apache.airavata.model.appcatalog.computeresource 
import (  # noqa: E501
+        compute_resource_pb2,
+    )
+    from airavata_sdk.generated.org.apache.airavata.model.parallelism import (
+        parallelism_pb2,
+    )
+    return SimpleNamespace(
+        resourceJobManagerId=pb.resource_job_manager_id or None,
+        resourceJobManagerType=_thrift_enum_prefixed(
+            pb, 'resource_job_manager_type', _ThriftResourceJobManagerType,
+            'RESOURCE_JOB_MANAGER_TYPE_'),
+        pushMonitoringEndpoint=pb.push_monitoring_endpoint or None,
+        jobManagerBinPath=pb.job_manager_bin_path or None,
+        jobManagerCommands=_enum_keyed_map(
+            pb.job_manager_commands, compute_resource_pb2.JobManagerCommand,
+            _ThriftJobManagerCommand),
+        parallelismPrefix=_enum_keyed_map(
+            pb.parallelism_prefix, parallelism_pb2.ApplicationParallelismType,
+            _ThriftParallelismType),
+    )
+
+
+def local_job_submission(pb):
+    """gRPC ``LOCALSubmission`` -> auto-generated serializer shape."""
+    return SimpleNamespace(
+        jobSubmissionInterfaceId=pb.job_submission_interface_id,
+        resourceJobManager=_resource_job_manager(pb.resource_job_manager),
+        securityProtocol=_thrift_enum_prefixed(
+            pb, 'security_protocol', _ThriftSecurityProtocol,
+            'SECURITY_PROTOCOL_'),
+    )
+
+
+def ssh_job_submission(pb):
+    """gRPC ``SSHJobSubmission`` -> auto-generated serializer shape."""
+    return SimpleNamespace(
+        jobSubmissionInterfaceId=pb.job_submission_interface_id,
+        securityProtocol=_thrift_enum_prefixed(
+            pb, 'security_protocol', _ThriftSecurityProtocol,
+            'SECURITY_PROTOCOL_'),
+        resourceJobManager=_resource_job_manager(pb.resource_job_manager),
+        alternativeSSHHostName=pb.alternative_ssh_host_name or None,
+        sshPort=pb.ssh_port or None,
+        monitorMode=_thrift_enum_mapped(pb, 'monitor_mode', _MONITOR_MODE),
+        batchQueueEmailSenders=list(pb.batch_queue_email_senders),
+    )
+
+
+def cloud_job_submission(pb):
+    """gRPC ``CloudJobSubmission`` -> auto-generated serializer shape."""
+    return SimpleNamespace(
+        jobSubmissionInterfaceId=pb.job_submission_interface_id,
+        securityProtocol=_thrift_enum_prefixed(
+            pb, 'security_protocol', _ThriftSecurityProtocol,
+            'SECURITY_PROTOCOL_'),
+        nodeId=pb.node_id or None,
+        executableType=pb.executable_type or None,
+        providerName=_thrift_enum_prefixed(
+            pb, 'provider_name', _ThriftProviderName, 'PROVIDER_NAME_'),
+        userAccountName=pb.user_account_name or None,
+    )
+
+
+def unicore_job_submission(pb):
+    """gRPC ``UnicoreJobSubmission`` -> auto-generated serializer shape."""
+    return SimpleNamespace(
+        jobSubmissionInterfaceId=pb.job_submission_interface_id,
+        securityProtocol=_thrift_enum_prefixed(
+            pb, 'security_protocol', _ThriftSecurityProtocol,
+            'SECURITY_PROTOCOL_'),
+        unicoreEndPointURL=pb.unicore_end_point_url or None,
+    )
+
+
+def local_data_movement(pb):
+    """gRPC ``LOCALDataMovement`` -> auto-generated serializer shape."""
+    return SimpleNamespace(
+        dataMovementInterfaceId=pb.data_movement_interface_id,
+    )
+
+
+def scp_data_movement(pb):
+    """gRPC ``SCPDataMovement`` -> auto-generated serializer shape."""
+    return SimpleNamespace(
+        dataMovementInterfaceId=pb.data_movement_interface_id,
+        securityProtocol=_thrift_enum_prefixed(
+            pb, 'security_protocol', _ThriftSecurityProtocol,
+            'SECURITY_PROTOCOL_'),
+        alternativeSCPHostName=pb.alternative_scp_host_name or None,
+        sshPort=pb.ssh_port or None,
+    )
+
+
+def grid_ftp_data_movement(pb):
+    """gRPC ``GridFTPDataMovement`` -> auto-generated serializer shape."""
+    return SimpleNamespace(
+        dataMovementInterfaceId=pb.data_movement_interface_id,
+        securityProtocol=_thrift_enum_prefixed(
+            pb, 'security_protocol', _ThriftSecurityProtocol,
+            'SECURITY_PROTOCOL_'),
+        gridFTPEndPoints=list(pb.grid_ftp_end_points),
+    )
diff --git a/airavata-django-portal/django_airavata/apps/api/views.py 
b/airavata-django-portal/django_airavata/apps/api/views.py
index d17bf30a8..1a3a5bd5b 100644
--- a/airavata-django-portal/django_airavata/apps/api/views.py
+++ b/airavata-django-portal/django_airavata/apps/api/views.py
@@ -731,8 +731,8 @@ class LocalJobSubmissionView(APIView):
 
     def get(self, request, format=None):
         job_submission_id = request.query_params["id"]
-        local_job_submission = request.airavata_client.getLocalJobSubmission(
-            request.authz_token, job_submission_id)
+        local_job_submission = grpc_adapters.local_job_submission(
+            
request.airavata.compute.get_local_job_submission(job_submission_id))
         return Response(
             thrift_utils.create_serializer(
                 LOCALSubmission,
@@ -744,8 +744,8 @@ class CloudJobSubmissionView(APIView):
 
     def get(self, request, format=None):
         job_submission_id = request.query_params["id"]
-        job_submission = request.airavata_client.getCloudJobSubmission(
-            request.authz_token, job_submission_id)
+        job_submission = grpc_adapters.cloud_job_submission(
+            
request.airavata.compute.get_cloud_job_submission(job_submission_id))
         return Response(
             thrift_utils.create_serializer(
                 CloudJobSubmission,
@@ -756,6 +756,9 @@ class GlobusJobSubmissionView(APIView):
     renderer_classes = (JSONRenderer,)
 
     def get(self, request, format=None):
+        # TODO: the gRPC compute facade has no Globus job-submission getter and
+        # the legacy Thrift call here (`getClo`) is broken, so this admin-only
+        # detail view stays unmigrated until backend support lands.
         job_submission_id = request.query_params["id"]
         job_submission = request.airavata_client.getClo(
             request.authz_token, job_submission_id)
@@ -770,8 +773,8 @@ class SshJobSubmissionView(APIView):
 
     def get(self, request, format=None):
         job_submission_id = request.query_params["id"]
-        job_submission = request.airavata_client.getSSHJobSubmission(
-            request.authz_token, job_submission_id)
+        job_submission = grpc_adapters.ssh_job_submission(
+            request.airavata.compute.get_ssh_job_submission(job_submission_id))
         return Response(
             thrift_utils.create_serializer(
                 SSHJobSubmission,
@@ -783,8 +786,8 @@ class UnicoreJobSubmissionView(APIView):
 
     def get(self, request, format=None):
         job_submission_id = request.query_params["id"]
-        job_submission = request.airavata_client.getUnicoreJobSubmission(
-            request.authz_token, job_submission_id)
+        job_submission = grpc_adapters.unicore_job_submission(
+            
request.airavata.compute.get_unicore_job_submission(job_submission_id))
         return Response(
             thrift_utils.create_serializer(
                 UnicoreJobSubmission,
@@ -796,8 +799,8 @@ class GridFtpDataMovementView(APIView):
 
     def get(self, request, format=None):
         data_movement_id = request.query_params["id"]
-        data_movement = request.airavata_client.getGridFTPDataMovement(
-            request.authz_token, data_movement_id)
+        data_movement = grpc_adapters.grid_ftp_data_movement(
+            
request.airavata.storage.get_grid_ftp_data_movement(data_movement_id))
         return Response(
             thrift_utils.create_serializer(
                 GridFTPDataMovement,
@@ -809,8 +812,8 @@ class ScpDataMovementView(APIView):
 
     def get(self, request, format=None):
         data_movement_id = request.query_params["id"]
-        data_movement = request.airavata_client.getSCPDataMovement(
-            request.authz_token, data_movement_id)
+        data_movement = grpc_adapters.scp_data_movement(
+            request.airavata.storage.get_scp_data_movement(data_movement_id))
         return Response(
             thrift_utils.create_serializer(
                 SCPDataMovement,
@@ -821,6 +824,8 @@ class UnicoreDataMovementView(APIView):
     renderer_classes = (JSONRenderer,)
 
     def get(self, request, format=None):
+        # TODO: the gRPC storage facade has no UNICORE data-movement getter 
yet,
+        # so this admin-only detail view stays on Thrift until the SDK adds 
one.
         data_movement_id = request.query_params["id"]
         data_movement = request.airavata_client.getUnicoreDataMovement(
             request.authz_token, data_movement_id)
@@ -835,8 +840,8 @@ class LocalDataMovementView(APIView):
 
     def get(self, request, format=None):
         data_movement_id = request.query_params["id"]
-        data_movement = request.airavata_client.getLocalDataMovement(
-            request.authz_token, data_movement_id)
+        data_movement = grpc_adapters.local_data_movement(
+            request.airavata.storage.get_local_data_movement(data_movement_id))
         return Response(
             thrift_utils.create_serializer(
                 LOCALDataMovement,

Reply via email to