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 f299283b5 refactor(portal): remove the legacy Airavata Thrift client 
and its dead admin views (#192)
f299283b5 is described below

commit f299283b59c037da2244341d8f4ce64a4d6e8812
Author: Yasith Jayawardana <[email protected]>
AuthorDate: Tue Jun 9 03:07:04 2026 -0400

    refactor(portal): remove the legacy Airavata Thrift client and its dead 
admin views (#192)
    
    With the functional Thrift->gRPC migration complete, nothing reads
    request.airavata_client except two admin-only per-protocol detail views that
    never worked on the gRPC stack:
    - GlobusJobSubmissionView called request.airavata_client.getClo, a typo that
      always AttributeError'd (the view was already broken).
    - UnicoreDataMovementView used getUnicoreDataMovement, for which the gRPC 
SDK
      has no facade getter.
    
    Delete both views (+ their URL routes and the now-unused 
GlobusJobSubmission /
    UnicoreDataMovement Thrift-model imports), which removes the last
    request.airavata_client consumers. Then remove the legacy Thrift client 
itself:
    
    - AiravataClientMiddleware (and its registration in settings.MIDDLEWARE)
    - the entire django_airavata/utils.py Thrift-client module (the airavata API
      Thrift pool, transports, connection helpers) -- nothing imports it anymore
    - the thrift_connector dependency and the now-unused 
THRIFT_CLIENT_POOL_KEEPALIVE
      setting
    
    What stays (interim, removed in the later serializer/terminology pass): the
    thrift_utils-generated DRF serializers + grpc_adapters/grpc_requests still 
read
    Thrift model TYPES (airavata.model.*.ttypes), so the 
airavata-python-sdk==2.2.7
    and thrift libs remain for the model types only -- no Thrift 
client/transport.
    
    manage.py check clean; the request pipeline serves gRPC requests without
    AiravataClientMiddleware (verified live); no remaining 
request.airavata_client
    references; deleted views gone with sibling per-protocol views intact.
---
 .../django_airavata/airavata_grpc.py               |  15 +-
 .../django_airavata/apps/api/urls.py               |   4 -
 .../django_airavata/apps/api/views.py              |  35 +---
 .../django_airavata/middleware.py                  |  59 +-----
 airavata-django-portal/django_airavata/settings.py |  13 +-
 airavata-django-portal/django_airavata/utils.py    | 231 ---------------------
 airavata-django-portal/requirements.txt            |   1 -
 7 files changed, 16 insertions(+), 342 deletions(-)

diff --git a/airavata-django-portal/django_airavata/airavata_grpc.py 
b/airavata-django-portal/django_airavata/airavata_grpc.py
index 203d80e80..4f281063e 100644
--- a/airavata-django-portal/django_airavata/airavata_grpc.py
+++ b/airavata-django-portal/django_airavata/airavata_grpc.py
@@ -1,12 +1,9 @@
-"""New-stack gRPC Airavata client (airavata-python-sdk ``AiravataClient``).
-
-Track D: the portal is migrating from the legacy Thrift API to the new Airavata
-gRPC/REST server. This module builds the gRPC ``AiravataClient`` from a 
request's
-Keycloak access token. It is intentionally additive — the gRPC client
-(``request.airavata``) coexists with the legacy Thrift client
-(``request.airavata_client``) while ``apps/api`` views are repointed resource
-family by resource family. The Thrift client and ``thrift_utils`` are removed 
once
-nothing depends on them.
+"""gRPC Airavata client (airavata-python-sdk ``AiravataClient``).
+
+Builds the gRPC ``AiravataClient`` (``request.airavata``) from a request's 
Keycloak
+access token. The ``apps/api`` views/serializers talk to Airavata through this
+client's facade sub-clients; the interim ``thrift_utils`` serializer layer 
(which
+still reads Thrift model types) is removed in the later serializer/terminology 
pass.
 
 Per the migration principle, the "talk to Airavata + transform" grunt belongs 
in
 ``airavata-python-sdk`` (the facade sub-clients on ``AiravataClient``), 
keeping the
diff --git a/airavata-django-portal/django_airavata/apps/api/urls.py 
b/airavata-django-portal/django_airavata/apps/api/urls.py
index 5b864b578..a83e67bf3 100644
--- a/airavata-django-portal/django_airavata/apps/api/urls.py
+++ b/airavata-django-portal/django_airavata/apps/api/urls.py
@@ -62,8 +62,6 @@ urlpatterns = [
             name="local_job_submission"),
     re_path(r'^job/submission/cloud', views.CloudJobSubmissionView.as_view(),
             name="cloud_job_submission"),
-    re_path(r'^job/submission/globus', views.GlobusJobSubmissionView.as_view(),
-            name="globus_job_submission"),
     re_path(r'^job/submission/ssh', views.SshJobSubmissionView.as_view(),
             name="ssh_job_submission"),
     re_path(r'^job/submission/unicore', 
views.UnicoreJobSubmissionView.as_view(),
@@ -72,8 +70,6 @@ urlpatterns = [
             name="grid_ftp_data_movement"),
     re_path(r'^data/movement/local', views.LocalDataMovementView.as_view(),
             name="local_ftp_data_movement"),
-    re_path(r'^data/movement/unicore', views.UnicoreDataMovementView.as_view(),
-            name="unicore_ftp_data_movement"),
     re_path(r'^data/movement/scp', views.ScpDataMovementView.as_view(),
             name="scp_ftp_data_movement"),
     re_path(r'^gateway-resource-profile',
diff --git a/airavata-django-portal/django_airavata/apps/api/views.py 
b/airavata-django-portal/django_airavata/apps/api/views.py
index 79a2ef19c..2de39dd25 100644
--- a/airavata-django-portal/django_airavata/apps/api/views.py
+++ b/airavata-django-portal/django_airavata/apps/api/views.py
@@ -9,7 +9,6 @@ from urllib.parse import quote
 
 from airavata.model.appcatalog.computeresource.ttypes import (
     CloudJobSubmission,
-    GlobusJobSubmission,
     LOCALSubmission,
     SSHJobSubmission,
     UnicoreJobSubmission
@@ -19,8 +18,7 @@ from airavata.model.credential.store.ttypes import SummaryType
 from airavata.model.data.movement.ttypes import (
     GridFTPDataMovement,
     LOCALDataMovement,
-    SCPDataMovement,
-    UnicoreDataMovement
+    SCPDataMovement
 )
 from airavata.model.experiment.ttypes import (
     ExperimentModel,
@@ -803,22 +801,6 @@ class CloudJobSubmissionView(APIView):
                 instance=job_submission).data)
 
 
-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)
-        return Response(
-            thrift_utils.create_serializer(
-                GlobusJobSubmission,
-                instance=job_submission).data)
-
-
 class SshJobSubmissionView(APIView):
     renderer_classes = (JSONRenderer,)
 
@@ -871,21 +853,6 @@ class ScpDataMovementView(APIView):
                 instance=data_movement).data)
 
 
-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)
-        return Response(
-            thrift_utils.create_serializer(
-                UnicoreDataMovement,
-                instance=data_movement).data)
-
-
 class LocalDataMovementView(APIView):
     renderer_classes = (JSONRenderer,)
 
diff --git a/airavata-django-portal/django_airavata/middleware.py 
b/airavata-django-portal/django_airavata/middleware.py
index 74506d692..d2006f3de 100644
--- a/airavata-django-portal/django_airavata/middleware.py
+++ b/airavata-django-portal/django_airavata/middleware.py
@@ -1,63 +1,16 @@
 import logging
 
-import thrift
-import thrift.transport.TTransport
-from django.shortcuts import render
-
-from . import utils
-
 logger = logging.getLogger(__name__)
 
 
-class AiravataClientMiddleware:
-    def __init__(self, get_response):
-        self.get_response = get_response
-
-    def __call__(self, request):
-        # Track D: attach the Thrift client lazily so requests that only use 
the
-        # gRPC client (request.airavata) never open a Thrift connection. The
-        # connection is acquired from the pool on first access and released 
after
-        # the response. Views/serializers still on Thrift work unchanged.
-        from django.utils.functional import SimpleLazyObject
-
-        opened = []
-
-        def _client():
-            ctx = utils.airavata_api_client_pool.connection()
-            client = ctx.__enter__()
-            opened.append(ctx)
-            return client
-
-        request.airavata_client = SimpleLazyObject(_client)
-        try:
-            return self.get_response(request)
-        finally:
-            for ctx in opened:
-                ctx.__exit__(None, None, None)
-
-    def process_exception(self, request, exception):
-        if isinstance(exception, 
thrift.transport.TTransport.TTransportException):
-            return render(
-                request,
-                'django_airavata/error_page.html',
-                status=500,
-                context={
-                    'title': 'Airavata is down',
-                    'text': """The Airavata API server is not reachable. 
Please try again."""})
-        else:
-            return None
-
-
 def airavata_grpc_client(get_response):
-    """Attach the new-stack gRPC ``AiravataClient`` as ``request.airavata``.
+    """Attach the gRPC ``AiravataClient`` as ``request.airavata``.
 
-    Track D: additive — coexists with the legacy Thrift 
``request.airavata_client``
-    while ``apps/api`` views are repointed from Thrift to gRPC. 
``request.airavata``
-    is a lazy object: the client (and the ``airavata_sdk`` import) is built 
only
-    when a view first accesses it, carrying the user's Keycloak token from
-    ``request.authz_token``. The channel is closed after the response if it was
-    used. Views that never touch ``request.airavata`` incur no cost and do not
-    require the SDK to be importable.
+    ``request.airavata`` is a lazy object: the client (and the ``airavata_sdk``
+    import) is built only when a view first accesses it, carrying the user's
+    Keycloak token from ``request.authz_token``. The channel is closed after 
the
+    response if it was used. Views that never touch ``request.airavata`` incur 
no
+    cost and do not require the SDK to be importable.
 
     Usage in a view::
 
diff --git a/airavata-django-portal/django_airavata/settings.py 
b/airavata-django-portal/django_airavata/settings.py
index c951a101b..4e1fcb37f 100644
--- a/airavata-django-portal/django_airavata/settings.py
+++ b/airavata-django-portal/django_airavata/settings.py
@@ -70,12 +70,10 @@ MIDDLEWARE = [
     'django.contrib.messages.middleware.MessageMiddleware',
     'django.middleware.clickjacking.XFrameOptionsMiddleware',
     'django_airavata.apps.auth.middleware.authz_token_middleware',
-    'django_airavata.middleware.AiravataClientMiddleware',
-    # Track D: new gRPC AiravataClient (request.airavata), additive alongside 
the
-    # Thrift request.airavata_client. Must come after authz_token_middleware 
(uses
-    # request.authz_token for the access token).
+    # gRPC AiravataClient (request.airavata). Must come after 
authz_token_middleware
+    # (uses request.authz_token for the access token).
     'django_airavata.middleware.airavata_grpc_client',
-    # Needs to come after authz_token_middleware and airavata_client.
+    # Needs to come after authz_token_middleware and airavata_grpc_client.
     'django_airavata.apps.auth.middleware.gateway_groups_middleware',
     'django_airavata.apps.auth.middleware.user_profile_completeness_check',
 ]
@@ -262,11 +260,6 @@ AUTHENTICATION_OPTIONS = {
 # for the access token parameter (defaults to 'access_token').
 ACCESS_TOKEN_REDIRECT_ALLOWED_URIS = []
 
-# Seconds each connection in the pool is able to stay alive. If open connection
-# has lived longer than this period, it will be closed.
-# (https://github.com/Thriftpy/thrift_connector)
-THRIFT_CLIENT_POOL_KEEPALIVE = 5
-
 # Webpack loader
 WEBPACK_LOADER = {
     'COMMON': {
diff --git a/airavata-django-portal/django_airavata/utils.py 
b/airavata-django-portal/django_airavata/utils.py
deleted file mode 100644
index e71b4e7d2..000000000
--- a/airavata-django-portal/django_airavata/utils.py
+++ /dev/null
@@ -1,231 +0,0 @@
-import logging
-import ssl
-import queue
-from django.conf import settings
-from contextlib import contextmanager
-
-import thrift_connector.connection_pool as connection_pool
-from airavata.api import Airavata
-from thrift.protocol import TBinaryProtocol
-from thrift.protocol.TMultiplexedProtocol import TMultiplexedProtocol
-from thrift.transport import TSocket, TSSLSocket, TTransport
-
-log = logging.getLogger(__name__)
-
-
-class ThriftConnectionException(Exception):
-    pass
-
-
-class ThriftClientException(Exception):
-    pass
-
-
-def get_unsecure_transport(hostname, port):
-    # Create a socket to the Airavata Server
-    transport = TSocket.TSocket(hostname, port)
-
-    # Use Buffered Protocol to speedup over raw sockets
-    transport = TTransport.TBufferedTransport(transport)
-    return transport
-
-
-def get_secure_transport(hostname, port):
-
-    # Create a socket to the Airavata Server
-    transport = TSSLSocket.TSSLSocket(
-        hostname,
-        port,
-        cert_reqs=ssl.CERT_REQUIRED,
-        ca_certs=settings.CA_CERTS_PATH,
-    )
-    return TTransport.TBufferedTransport(transport)
-
-
-def get_transport(hostname, port, secure=True):
-    if secure:
-        transport = get_secure_transport(hostname, port)
-    else:
-        transport = get_unsecure_transport(hostname, port)
-    return transport
-
-
-def create_airavata_client(transport):
-
-    # Airavata currently uses Binary Protocol
-    protocol = TBinaryProtocol.TBinaryProtocol(transport)
-
-    # Create a Airavata client to use the protocol encoder
-    client = Airavata.Client(protocol)
-    return client
-
-
-def get_binary_protocol(transport):
-    return TBinaryProtocol.TBinaryProtocol(transport)
-
-
-def get_airavata_client():
-    """Get Airavata API client as context manager (use in `with statement`)."""
-    return get_thrift_client(settings.AIRAVATA_API_HOST,
-                             settings.AIRAVATA_API_PORT,
-                             settings.AIRAVATA_API_SECURE,
-                             create_airavata_client)
-
-
-@contextmanager
-def get_thrift_client(host, port, is_secure, client_generator):
-    transport = get_transport(host, port, is_secure)
-    client = client_generator(transport)
-
-    try:
-        transport.open()
-        log.debug("Thrift connection opened to {}:{}, "
-                  "secure={}".format(host, port, is_secure))
-        try:
-            yield client
-        except Exception as e:
-            log.exception("Thrift client error occurred")
-            raise ThriftClientException(
-                "Thrift client error occurred: " + str(e)) from e
-        finally:
-            if transport.isOpen():
-                transport.close()
-                log.debug("Thrift connection closed to {}:{}, "
-                          "secure={}".format(host, port, is_secure))
-    except ThriftClientException as tce:
-        # Allow thrift client errors to bubble up
-        raise tce
-    except Exception as e:
-        msg = "Failed to open thrift connection to {}:{}, secure={}".format(
-            host, port, is_secure)
-        log.debug(msg)
-        raise ThriftConnectionException(msg) from e
-
-
-@contextmanager
-def simple_thrift_connection(pool):
-    """Context manager for borrowing a connection from the pool."""
-    conn = pool.get_connection()
-    try:
-        yield conn['client']
-    finally:
-        pool.return_connection(conn)
-
-
-class SimpleThriftPool:
-    """
-    A thread-safe Thrift connection pool that uses raw Thrift and the 
TBufferedTransport.
-    """
-
-    def __init__(self, service, host, port, size=5, secure=False, 
ca_certs=None):
-        self._service = service
-        self._host = host
-        self._port = port
-        self._size = size
-        self._secure = secure
-        self._ca_certs = ca_certs or settings.CA_CERTS_PATH
-        self._pool = queue.Queue(maxsize=size)
-        self._initialize_pool()
-
-    def _initialize_pool(self):
-        for _ in range(self._size):
-            self._pool.put(self._create_connection())
-
-    def _create_connection(self):
-        if self._secure:
-            socket = TSSLSocket.TSSLSocket(
-                host=self._host,
-                port=self._port,
-                cert_reqs=ssl.CERT_REQUIRED,
-                ca_certs=self._ca_certs,
-            )
-        else:
-            socket = TSocket.TSocket(host=self._host, port=self._port)
-        transport = TTransport.TBufferedTransport(socket)
-        protocol = TBinaryProtocol.TBinaryProtocol(transport)
-        client = self._service.Client(protocol)
-        transport.open()
-        return {'client': client, 'transport': transport}
-
-    def get_connection(self):
-        conn = self._pool.get()
-        if not self._is_alive(conn):
-            log.debug("Stale connection detected, creating new one")
-            try:
-                conn['transport'].close()
-            except Exception:
-                pass
-            conn = self._create_connection()
-        return conn
-
-    def _is_alive(self, conn):
-        try:
-            if not conn['transport'].isOpen():
-                return False
-            conn['client'].getAPIVersion()
-            return True
-        except Exception:
-            return False
-
-    def return_connection(self, conn):
-        try:
-            if conn['transport'].isOpen():
-                conn['transport'].close()
-        except Exception:
-            pass
-        self._pool.put(self._create_connection())
-
-    def connection(self):
-        return simple_thrift_connection(self)
-
-
-class CustomThriftClient(connection_pool.ThriftClient):
-    secure = False
-    validate = False
-
-    @classmethod
-    def get_socket_factory(cls):
-        if not cls.secure:
-            return super().get_socket_factory()
-        else:
-            def factory(host, port):
-                return TSSLSocket.TSSLSocket(
-                    host,
-                    port,
-                    cert_reqs=ssl.CERT_REQUIRED,
-                    ca_certs=settings.CA_CERTS_PATH,
-                )
-
-            return factory
-
-    def ping(self):
-        try:
-            self.client.getAPIVersion()
-        except Exception as e:
-            log.debug("getAPIVersion failed: {}".format(str(e)))
-            raise
-
-
-class MultiplexThriftClientMixin:
-    service_name = None
-
-    @classmethod
-    def get_protoco_factory(cls):
-        def factory(transport):
-            protocol = TBinaryProtocol.TBinaryProtocol(transport)
-            multiplex_prot = TMultiplexedProtocol(protocol, cls.service_name)
-            return multiplex_prot
-        return factory
-
-
-class AiravataAPIThriftClient(CustomThriftClient):
-    secure = settings.AIRAVATA_API_SECURE
-
-
-airavata_api_client_pool = SimpleThriftPool(
-    Airavata,
-    settings.AIRAVATA_API_HOST,
-    settings.AIRAVATA_API_PORT,
-    secure=settings.AIRAVATA_API_SECURE,
-    ca_certs=settings.CA_CERTS_PATH,
-)
diff --git a/airavata-django-portal/requirements.txt 
b/airavata-django-portal/requirements.txt
index 13442761d..49045bbff 100644
--- a/airavata-django-portal/requirements.txt
+++ b/airavata-django-portal/requirements.txt
@@ -3,7 +3,6 @@ Django==5.2.15
 requests==2.25.1
 requests-oauthlib==0.7.0
 thrift==0.22.0
-thrift_connector==0.24
 djangorestframework==3.16.1
 django-webpack-loader==0.6.0
 logging-formatter-anticrlf==1.2

Reply via email to