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 37bf2ea3a feat(portal): repoint
notification/parser/gateway-profile/storage-pref writes to gRPC (Track D, D3)
(#177)
37bf2ea3a is described below
commit 37bf2ea3a0f8472f7509732fa6f0bfa910181fc2
Author: Yasith Jayawardana <[email protected]>
AuthorDate: Mon Jun 8 21:12:18 2026 -0400
feat(portal): repoint notification/parser/gateway-profile/storage-pref
writes to gRPC (Track D, D3) (#177)
Migrate four write families from the Thrift client to the gRPC facades:
- ManageNotificationViewSet create/update/destroy -> research.create_/
update_/delete_notification
- ParserViewSet create/update -> research.save_parser
- CurrentGatewayResourceProfile.put ->
compute.update_gateway_resource_profile
- StoragePreferenceViewSet create/update/destroy -> compute add/update/
delete_gateway_storage_preference
Add the matching grpc_requests reverse adapters: notification (priority
enum), parser (+ParserInput/Output IOType enum), storage_preference,
gateway_resource_profile (+ComputeResourcePreference). The two protocol
enums on ComputeResourcePreference have proto/Thrift NAME divergence
(Thrift CLOUD<->proto JSP_CLOUD, Thrift LOCAL<->proto DATA_MOVEMENT_
PROTOCOL_LOCAL), so they use explicit inverse name maps via _proto_enum_rev
rather than the by-name _proto_enum. Also fix _proto_enum to re-apply a
proto member prefix only when it yields a valid member (proto3 prefixes
only the zero *_UNKNOWN sentinel for these enums) — the mirror of the
read-side _thrift_enum_prefixed.
Verified: manage.py check clean; live notification CRUD round-trips
(POST 201, PUT 200, DELETE 204); offline thrift->proto->thrift round-trips
confirm all four reverse adapters preserve fields and enums (incl. the
divergent protocol enums).
This batch's reverse adapters were drafted by parallel subagents and
integrated + validated here.
---
.../django_airavata/apps/api/grpc_requests.py | 159 ++++++++++++++++++++-
.../django_airavata/apps/api/views.py | 37 +++--
2 files changed, 175 insertions(+), 21 deletions(-)
diff --git a/airavata-django-portal/django_airavata/apps/api/grpc_requests.py
b/airavata-django-portal/django_airavata/apps/api/grpc_requests.py
index fc3ca459d..41b309b98 100644
--- a/airavata-django-portal/django_airavata/apps/api/grpc_requests.py
+++ b/airavata-django-portal/django_airavata/apps/api/grpc_requests.py
@@ -14,10 +14,20 @@ proto3 scalar fields cannot hold ``None``, so optional
Thrift values that may be
import importlib
+from airavata.model.appcatalog.computeresource.ttypes import (
+ JobSubmissionProtocol as _ThriftJobSubmissionProtocol,
+)
from airavata.model.appcatalog.parallelism.ttypes import (
ApplicationParallelismType as _ThriftParallelismType,
)
+from airavata.model.appcatalog.parser.ttypes import IOType as _ThriftIOType
from airavata.model.application.io.ttypes import DataType as _ThriftDataType
+from airavata.model.data.movement.ttypes import (
+ DataMovementProtocol as _ThriftDataMovementProtocol,
+)
+from airavata.model.workspace.ttypes import (
+ NotificationPriority as _ThriftNotificationPriority,
+)
_GEN = "airavata_sdk.generated.org.apache.airavata.model"
@@ -42,7 +52,14 @@ def _proto_enum(proto_enum, thrift_enum, value, prefix=''):
if value is None:
return 0
name = thrift_enum(value).name
- return proto_enum.Value(prefix + name)
+ # proto3 prefixes only members that would otherwise collide in the file —
+ # in practice just the zero *_UNKNOWN sentinel — so real members usually
+ # stay bare. Re-apply the prefix only when it yields a valid proto member
+ # (the mirror of the read-side _thrift_enum_prefixed, which strips it only
+ # when present).
+ if prefix and (prefix + name) in proto_enum.keys():
+ name = prefix + name
+ return proto_enum.Value(name)
def project(t):
@@ -173,3 +190,143 @@ def application_deployment(t):
default_walltime=t.defaultWalltime or 0,
editable_by_user=bool(t.editableByUser),
)
+
+
+def notification(t):
+ """Thrift ``Notification`` -> proto ``Notification`` request message."""
+ return _workspace_pb2().Notification(
+ notification_id=t.notificationId or '',
+ gateway_id=t.gatewayId or '',
+ title=t.title or '',
+ notification_message=t.notificationMessage or '',
+ creation_time=t.creationTime or 0,
+ published_time=t.publishedTime or 0,
+ expiration_time=t.expirationTime or 0,
+ priority=_proto_enum(
+ _workspace_pb2().NotificationPriority, _ThriftNotificationPriority,
+ t.priority, 'NOTIFICATION_PRIORITY_'),
+ )
+
+
+def _parser_input(t):
+ """Thrift ``ParserInput`` -> proto ``ParserInput``."""
+ pp = _pb2("appcatalog.parser.parser_pb2")
+ return pp.ParserInput(
+ id=t.id or '',
+ name=t.name or '',
+ required_input=bool(t.requiredInput),
+ parser_id=t.parserId or '',
+ type=_proto_enum(pp.IOType, _ThriftIOType, t.type, 'IO_TYPE_'),
+ )
+
+
+def _parser_output(t):
+ """Thrift ``ParserOutput`` -> proto ``ParserOutput``."""
+ pp = _pb2("appcatalog.parser.parser_pb2")
+ return pp.ParserOutput(
+ id=t.id or '',
+ name=t.name or '',
+ required_output=bool(t.requiredOutput),
+ parser_id=t.parserId or '',
+ type=_proto_enum(pp.IOType, _ThriftIOType, t.type, 'IO_TYPE_'),
+ )
+
+
+def parser(t):
+ """Thrift ``Parser`` -> proto ``Parser`` request message."""
+ return _pb2("appcatalog.parser.parser_pb2").Parser(
+ id=t.id or '',
+ image_name=t.imageName or '',
+ output_dir_path=t.outputDirPath or '',
+ input_dir_path=t.inputDirPath or '',
+ execution_command=t.executionCommand or '',
+ input_files=[_parser_input(i) for i in (t.inputFiles or [])],
+ output_files=[_parser_output(o) for o in (t.outputFiles or [])],
+ gateway_id=t.gatewayId or '',
+ )
+
+
+# proto-name -> Thrift protocol value maps (mirror grpc_adapters); inverted
+# below to go Thrift value -> proto value, preserving the divergent name pairs
+# (Thrift CLOUD <-> proto JSP_CLOUD; Thrift LOCAL <-> proto
+# DATA_MOVEMENT_PROTOCOL_LOCAL).
+_JOB_SUBMISSION_PROTOCOL_REV = {
+ _ThriftJobSubmissionProtocol.LOCAL: 'LOCAL',
+ _ThriftJobSubmissionProtocol.SSH: 'SSH',
+ _ThriftJobSubmissionProtocol.GLOBUS: 'GLOBUS',
+ _ThriftJobSubmissionProtocol.UNICORE: 'UNICORE',
+ _ThriftJobSubmissionProtocol.CLOUD: 'JSP_CLOUD',
+ _ThriftJobSubmissionProtocol.SSH_FORK: 'SSH_FORK',
+ _ThriftJobSubmissionProtocol.LOCAL_FORK: 'LOCAL_FORK',
+}
+_DATA_MOVEMENT_PROTOCOL_REV = {
+ _ThriftDataMovementProtocol.LOCAL: 'DATA_MOVEMENT_PROTOCOL_LOCAL',
+ _ThriftDataMovementProtocol.SCP: 'SCP',
+ _ThriftDataMovementProtocol.SFTP: 'SFTP',
+ _ThriftDataMovementProtocol.UNICORE_STORAGE_SERVICE:
'UNICORE_STORAGE_SERVICE',
+}
+
+
+def _proto_enum_rev(proto_enum, rev_map, value):
+ """Thrift enum value -> proto enum value via an EXPLICIT inverse name map
+ (for protocol enums whose proto/Thrift member names diverge). None/unmapped
+ -> 0 (proto *_UNKNOWN)."""
+ if value is None:
+ return 0
+ name = rev_map.get(value)
+ return proto_enum.Value(name) if name is not None else 0
+
+
+def _compute_resource_preference(t):
+ """Thrift ``ComputeResourcePreference`` -> proto message."""
+ gp = _pb2("appcatalog.gatewayprofile.gateway_profile_pb2")
+ cr = _pb2("appcatalog.computeresource.compute_resource_pb2")
+ dm = _pb2("data.movement.data_movement_pb2")
+ return gp.ComputeResourcePreference(
+ compute_resource_id=t.computeResourceId or '',
+ override_by_airavata=bool(t.overridebyAiravata),
+ login_user_name=t.loginUserName or '',
+ preferred_job_submission_protocol=_proto_enum_rev(
+ cr.JobSubmissionProtocol, _JOB_SUBMISSION_PROTOCOL_REV,
+ t.preferredJobSubmissionProtocol),
+ preferred_data_movement_protocol=_proto_enum_rev(
+ dm.DataMovementProtocol, _DATA_MOVEMENT_PROTOCOL_REV,
+ t.preferredDataMovementProtocol),
+ preferred_batch_queue=t.preferredBatchQueue or '',
+ scratch_location=t.scratchLocation or '',
+ allocation_project_number=t.allocationProjectNumber or '',
+
resource_specific_credential_store_token=t.resourceSpecificCredentialStoreToken
or '',
+ usage_reporting_gateway_id=t.usageReportingGatewayId or '',
+ quality_of_service=t.qualityOfService or '',
+ reservation=t.reservation or '',
+ reservation_start_time=t.reservationStartTime or 0,
+ reservation_end_time=t.reservationEndTime or 0,
+ ssh_account_provisioner=t.sshAccountProvisioner or '',
+ ssh_account_provisioner_config=dict(t.sshAccountProvisionerConfig or
{}),
+
ssh_account_provisioner_additional_info=t.sshAccountProvisionerAdditionalInfo
or '',
+ )
+
+
+def storage_preference(t):
+ """Thrift ``StoragePreference`` -> proto ``StoragePreference``."""
+ return
_pb2("appcatalog.gatewayprofile.gateway_profile_pb2").StoragePreference(
+ storage_resource_id=t.storageResourceId or '',
+ login_user_name=t.loginUserName or '',
+ file_system_root_location=t.fileSystemRootLocation or '',
+
resource_specific_credential_store_token=t.resourceSpecificCredentialStoreToken
or '',
+ )
+
+
+def gateway_resource_profile(t):
+ """Thrift ``GatewayResourceProfile`` -> proto message."""
+ return
_pb2("appcatalog.gatewayprofile.gateway_profile_pb2").GatewayResourceProfile(
+ gateway_id=t.gatewayID or '',
+ credential_store_token=t.credentialStoreToken or '',
+ compute_resource_preferences=[
+ _compute_resource_preference(p)
+ for p in (t.computeResourcePreferences or [])],
+ storage_preferences=[
+ storage_preference(p) for p in (t.storagePreferences or [])],
+ identity_server_tenant=t.identityServerTenant or '',
+ identity_server_pwd_cred_token=t.identityServerPwdCredToken or '',
+ )
diff --git a/airavata-django-portal/django_airavata/apps/api/views.py
b/airavata-django-portal/django_airavata/apps/api/views.py
index 8f1d3afc2..5eed8df0e 100644
--- a/airavata-django-portal/django_airavata/apps/api/views.py
+++ b/airavata-django-portal/django_airavata/apps/api/views.py
@@ -1464,10 +1464,9 @@ class CurrentGatewayResourceProfile(APIView):
data=request.data, context={'request': request})
if serializer.is_valid():
gateway_resource_profile = serializer.save()
- request.airavata_client.updateGatewayResourceProfile(
- request.authz_token,
+ request.airavata.compute.update_gateway_resource_profile(
settings.GATEWAY_ID,
- gateway_resource_profile)
+
grpc_requests.gateway_resource_profile(gateway_resource_profile))
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
@@ -1526,23 +1525,21 @@ class StoragePreferenceViewSet(APIBackedViewSet):
def perform_create(self, serializer):
storage_preference = serializer.save()
- self.request.airavata_client.addGatewayStoragePreference(
- self.authz_token,
+ self.request.airavata.compute.add_gateway_storage_preference(
settings.GATEWAY_ID,
storage_preference.storageResourceId,
- storage_preference)
+ grpc_requests.storage_preference(storage_preference))
def perform_update(self, serializer):
storage_preference = serializer.save()
- self.request.airavata_client.updateGatewayStoragePreference(
- self.authz_token,
+ self.request.airavata.compute.update_gateway_storage_preference(
settings.GATEWAY_ID,
storage_preference.storageResourceId,
- storage_preference)
+ grpc_requests.storage_preference(storage_preference))
def perform_destroy(self, instance):
- self.request.airavata_client.deleteGatewayStoragePreference(
- self.authz_token, settings.GATEWAY_ID, instance.storageResourceId)
+ self.request.airavata.compute.delete_gateway_storage_preference(
+ settings.GATEWAY_ID, instance.storageResourceId)
class ParserViewSet(mixins.CreateModelMixin,
@@ -1567,11 +1564,12 @@ class ParserViewSet(mixins.CreateModelMixin,
def perform_create(self, serializer):
parser = serializer.save()
- self.request.airavata_client.saveParser(self.authz_token, parser)
+ parser.id = self.request.airavata.research.save_parser(
+ grpc_requests.parser(parser))
def perform_update(self, serializer):
parser = serializer.save()
- self.request.airavata_client.saveParser(self.authz_token, parser)
+
self.request.airavata.research.save_parser(grpc_requests.parser(parser))
class UserStoragePathView(APIView):
@@ -1745,21 +1743,20 @@ class ManageNotificationViewSet(APIBackedViewSet):
]
def perform_destroy(self, instance):
- self.request.airavata_client.deleteNotification(
- self.authz_token, settings.GATEWAY_ID, instance.notificationId)
+ self.request.airavata.research.delete_notification(
+ settings.GATEWAY_ID, instance.notificationId)
def perform_create(self, serializer):
notification = serializer.save(gatewayId=self.gateway_id)
- notificationId = self.request.airavata_client.createNotification(
- self.authz_token, notification)
- notification.notificationId = notificationId
+ notification.notificationId =
self.request.airavata.research.create_notification(
+ grpc_requests.notification(notification))
serializer.update_notification_extension(self.request, notification)
def perform_update(self, serializer):
notification = serializer.save()
- self.request.airavata_client.updateNotification(
- self.authz_token, notification)
+ self.request.airavata.research.update_notification(
+ grpc_requests.notification(notification))
serializer.update_notification_extension(self.request, notification)