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,