You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by ya...@apache.org on 2026/03/26 22:10:24 UTC

(airavata) branch master updated: AIRAVATA-3981: Add integration health check — ServiceRegistry, monitoring endpoints, Tiltfile, test suite (#596)

This is an automated email from the ASF dual-hosted git repository.

yasith pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata.git


The following commit(s) were added to refs/heads/master by this push:
     new f1872d30d5 AIRAVATA-3981: Add integration health check — ServiceRegistry, monitoring endpoints, Tiltfile, test suite (#596)
f1872d30d5 is described below

commit f1872d30d5bc34e7e8a7998db200c9f46299b502
Author: Yasith Jayawardana <ya...@gatech.edu>
AuthorDate: Thu Mar 26 17:10:18 2026 -0500

    AIRAVATA-3981: Add integration health check — ServiceRegistry, monitoring endpoints, Tiltfile, test suite (#596)
    
    * feat: add ServiceRegistry and ServiceStatus for background service tracking
    
    * feat: add /health/services and /admin/restart endpoints to MonitoringServer
    
    * refactor: use ServiceRegistry in AiravataServer for background service lifecycle
    
    * feat: add Tiltfile for dev orchestration and integration testing
    
    * feat: add integration-tests Maven module scaffold
    
    * feat: add ThriftServiceHealthTest for all 9 multiplexed services
    
    * feat: add Database, RabbitMQ, and Kafka integration health tests
    
    * feat: add Keycloak and Prometheus integration health tests
    
    * feat: add ServiceHealthEndpoint and BackgroundServiceHealth integration tests
    
    * chore: final cleanup — gitignore docs/superpowers, fix health endpoint format
    
    - Wrap /health/services response in {"services": {...}} per spec
    - Update ServiceHealthEndpointTest and BackgroundServiceHealthTest to
      unwrap the "services" key before asserting on service entries
    - Add docs/superpowers/ to .gitignore and remove already-committed files
      from git tracking (local copies preserved)
    
    * style: apply spotless formatting to MonitoringServer and integration tests
    
    * fix: ServiceRegistry uses IServer.getStatus() for health, add actuator to Spring Boot services
    
    - ServiceRegistry now checks IServer.getStatus() == STARTED in addition to
      Thread.isAlive(), fixing false DOWN for services like PostWorkflowManager
      that spawn sub-threads and let their main thread exit
    - Add spring-boot-starter-actuator to all 4 Spring Boot modules with
      /actuator/health endpoint exposed
    - Update Tiltfile to use spring-boot:run for Spring Boot services
    - All 8/8 background services + 4/4 Spring Boot services report UP
    
    * docs: update README for thrift-server split, Tiltfile, health monitoring
    
    - Update architecture diagram to show airavata-thrift-server + airavata-api split
    - Replace scripts/start.sh quick start with tilt up
    - Add health monitoring endpoints table (/health/services, /admin/restart)
    - Add integration tests section
    - Update Spring Boot services table with ports and gRPC
    - Replace ide-integration IDE setup with Tilt-based workflow
    - Update workspace CLAUDE.md to include airavata project
---
 .gitignore                                         |    2 +
 README.md                                          |  102 +-
 Tiltfile                                           |  112 ++
 airavata-api/pom.xml                               |   18 +
 .../patform/monitoring/MonitoringServer.java       |  124 ++-
 .../patform/monitoring/ServiceRegistry.java        |  153 +++
 .../airavata/patform/monitoring/ServiceStatus.java |   62 ++
 airavata-thrift-server/pom.xml                     |   19 +-
 .../apache/airavata/api/server/AiravataServer.java |   40 +-
 .../api/server/mapper/WorkspaceMapper.java         |   40 +-
 ...2026-03-26-airavata-thrift-server-extraction.md | 1090 --------------------
 ...-26-airavata-thrift-server-extraction-design.md |  190 ----
 integration-tests/pom.xml                          |   99 ++
 .../integration/BackgroundServiceHealthTest.java   |  120 +++
 .../airavata/integration/DatabaseHealthTest.java   |   79 ++
 .../airavata/integration/KafkaHealthTest.java      |   57 +
 .../airavata/integration/KeycloakHealthTest.java   |  132 +++
 .../airavata/integration/PrometheusHealthTest.java |   84 ++
 .../airavata/integration/RabbitMQHealthTest.java   |   66 ++
 .../integration/ServiceHealthEndpointTest.java     |  122 +++
 .../integration/ThriftServiceHealthTest.java       |  115 +++
 modules/agent-framework/agent-service/pom.xml      |    4 +
 .../src/main/resources/application.yml             |    6 +
 modules/file-server/pom.xml                        |    4 +
 .../src/main/resources/application.properties      |    2 +
 modules/ide-integration/.gitignore                 |    2 -
 modules/ide-integration/README.md                  |  294 ------
 modules/ide-integration/pom.xml                    |   61 --
 .../airavata/ide/integration/APIServerStarter.java |   34 -
 .../airavata/ide/integration/JobEngineStarter.java |   72 --
 .../ide/integration/JobMonitorStarter.java         |   33 -
 .../research-framework/research-service/pom.xml    |    4 +
 .../src/main/resources/application.yml             |    6 +
 modules/restproxy/pom.xml                          |    4 +
 .../src/main/resources/application.properties      |    2 +
 pom.xml                                            |    7 +-
 36 files changed, 1498 insertions(+), 1863 deletions(-)

diff --git a/.gitignore b/.gitignore
index 6a9a2b896a..a9c37a7d2d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -66,3 +66,5 @@ keystores/airavata.p12
 keystores/aes.p12
 modules/*/distribution/
 modules/*/*/distribution/
+
+docs/superpowers/
diff --git a/README.md b/README.md
index 978e948b4b..f099284164 100644
--- a/README.md
+++ b/README.md
@@ -59,7 +59,7 @@ Airavata is composed of a consolidated JVM server plus three Spring Boot microse
 
 ```mermaid
 graph TB
-    subgraph "Docker Infrastructure"
+    subgraph "Docker Infrastructure (compose.yml)"
         DB[(MariaDB<br/>:13306)]
         RMQ[RabbitMQ<br/>:5672]
         ZK[ZooKeeper<br/>:2181]
@@ -67,34 +67,38 @@ graph TB
         KC[Keycloak<br/>:18080]
     end
 
-    subgraph "AiravataServer (single JVM, :8930)"
+    subgraph "AiravataServer (single JVM)"
         direction TB
-        MUX["TMultiplexedProcessor<br/>9 Thrift services"]
-        BG["Background Services<br/>12 IServer workers"]
+        MUX["TMultiplexedProcessor :8930<br/>9 Thrift services<br/>(airavata-thrift-server module)"]
+        SVC["Service Layer<br/>(airavata-api module)"]
+        BG["Background Services<br/>8 IServer workers<br/>(ServiceRegistry)"]
+        MON["MonitoringServer :9097<br/>/metrics  /health/services<br/>/admin/restart"]
     end
 
-    subgraph "Spring Boot Modules"
-        AS[Agent Service<br/>:18880]
+    subgraph "Spring Boot Services"
+        RS[Research Service<br/>:18889 / gRPC :19908]
+        AS[Agent Service<br/>:18880 / gRPC :19900]
         FS[File Server<br/>:8050]
-        RS[Research Service<br/>:18889]
         RP[REST Proxy<br/>:8082]
     end
 
     SDK[Python SDK] -->|TMultiplexedProtocol| MUX
     Portal[Web Portal] -->|REST| RP
-    MUX --> DB
-    MUX --> RMQ
+    MUX --> SVC
+    SVC --> DB
+    SVC --> RMQ
     BG --> ZK
     BG --> KFK
     BG --> RMQ
     AS --> DB
     RS --> DB
-    MUX -.->|auth| KC
+    SVC -.->|auth| KC
+    MON -.->|tracks| BG
 ```
 
-### 1. Airavata Server `(apache-airavata-api-server)`
-> Entry point: `org.apache.airavata.api.server.AiravataServer`
-> Started via: `./scripts/start.sh`
+### 1. Airavata Server `(airavata-thrift-server + airavata-api)`
+> Entry point: `org.apache.airavata.api.server.AiravataServer` (in `airavata-thrift-server` module)
+> Started via: `tilt up` or manually with `java -cp ...`
 
 `AiravataServer` is the single JVM that hosts all Thrift services and background workers. On startup it:
 
@@ -219,50 +223,76 @@ The Airavata Research Service is the backend for the **research catalog** in Air
 | **Apache Maven** | 3.8+ | `mvn -v` |
 | **Docker Engine** | 20.10+ | `docker -v` |
 | **Docker Compose** | 2.0+ | `docker compose version` |
+| **Tilt** | 0.33+ | `tilt version` |
 
-### Quick Start
+### Quick Start (Tilt)
 
-First, clone the repository and start the infrastructure (MariaDB, RabbitMQ, ZooKeeper, Kafka, Keycloak):
+Tilt orchestrates the full development stack — infrastructure, build, and all services:
 
 ```bash
 git clone https://github.com/apache/airavata.git
 cd airavata
-docker compose up -d
+tilt up
 ```
 
-Next, build Airavata and set up the database:
+This starts:
+- **Infrastructure**: MariaDB, RabbitMQ, ZooKeeper, Kafka, Keycloak (via `compose.yml`)
+- **Airavata Thrift Server**: All 9 services on port 8930, monitoring on 9097
+- **Spring Boot Services**: Research Service (18889), Agent Service (18880), File Server (8050), REST Proxy (8082)
 
-```bash
-./scripts/setup.sh
-```
+Open the Tilt UI at `http://localhost:10350` to monitor all resources. Integration tests can be triggered manually from the Tilt UI.
 
-Finally, start the server:
+### Manual Start
 
 ```bash
-./scripts/start.sh
+docker compose up -d                          # Infrastructure
+mvn clean package -DskipTests -T4             # Build
+java -cp "airavata-thrift-server/target/classes:airavata-api/target/classes:airavata-api/target/dependency/*" \
+  -Dairavata.config.dir=airavata-api/src/main/resources \
+  org.apache.airavata.api.server.AiravataServer   # Thrift server
+mvn spring-boot:run -pl modules/research-framework/research-service  # Research Service
+mvn spring-boot:run -pl modules/agent-framework/agent-service        # Agent Service
+mvn spring-boot:run -pl modules/file-server                          # File Server
+mvn spring-boot:run -pl modules/restproxy                            # REST Proxy
 ```
 
-The Airavata Server will be available on port **8930** (Thrift, multiplexed). All 9 Thrift services are served on this single port.
+### Health Monitoring
+
+The monitoring server on port **9097** exposes:
+
+| Endpoint | Method | Description |
+|----------|--------|-------------|
+| `/metrics` | GET | Prometheus metrics |
+| `/health/services` | GET | JSON status of all background services (UP/DOWN/DISABLED) |
+| `/admin/restart/{name}` | POST | Restart a background service by name |
+
+Spring Boot services expose `/actuator/health` on their respective ports.
 
 ### Configuration
 
 The main configuration file is `airavata-api/src/main/resources/airavata-server.properties`. Key settings:
 
 * `apiserver.port` — Thrift port (default: `8930`)
-* `spring.datasource.url` — JDBC URL for the unified `airavata` database
+* `airavata.jdbc.url` — JDBC URL for the unified `airavata` database
 * `email.based.monitoring.enabled` — Enable/disable email-based job monitor
 * `api.server.monitoring.enabled` — Enable/disable Prometheus metrics endpoint
 
-### Spring Boot Microservices
+### Integration Tests
+
+```bash
+mvn test -pl integration-tests -Dgroups=integration
+```
 
-The following services are separate Spring Boot applications, packaged independently:
+Tests verify: all 9 thrift services, database tables, RabbitMQ exchanges, Kafka broker, Keycloak realm, Prometheus metrics, and the `/health/services` endpoint.
 
-| Service | Entry point | Purpose |
-|---|---|---|
-| **Agent Service** | `AgentServiceApplication` | Backend for interactive jobs via gRPC |
-| **File Server** | `FileServerApplication` | SFTP wrapper for storage nodes |
-| **Research Service** | `ResearchServiceApplication` | Research catalog API |
-| **REST Proxy** | *(restproxy module)* | REST-to-Thrift proxy for the API |
+### Spring Boot Microservices
+
+| Service | Port | gRPC | Purpose |
+|---|---|---|---|
+| **Research Service** | 18889 | 19908 | Research catalog API |
+| **Agent Service** | 18880 | 19900 | Backend for interactive jobs |
+| **File Server** | 8050 | — | SFTP wrapper for storage nodes |
+| **REST Proxy** | 8082 | — | REST-to-Kafka bridge |
 
 
 ## 🤝 Contributing
@@ -282,13 +312,7 @@ We welcome contributions from the community! Here's how you can help:
 
 ### Setting up your IDE
 
-The easiest way to setup a development environment is to follow the instructions in the [ide-integration README](./modules/ide-integration/README.md).Those instructions will guide you on setting up a development environment with IntelliJ IDEA.
-
-### Additional Tools
-
-* `org.apache.airavata.sharing.registry.migrator.airavata.AiravataDataMigrator`
-* `modules/deployment-scripts`
-* `modules/load-client`
+Use `tilt up` to start the full stack, then attach your IDE debugger to the running services. For the Airavata Thrift Server, use remote JVM debug (add `-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005` to the `serve_cmd` in the Tiltfile). For Spring Boot services, use `mvn spring-boot:run -Dspring-boot.run.jvmArguments="-agentlib:jdwp=..."` directly.
 
 
 ## 💬 Community & Support
diff --git a/Tiltfile b/Tiltfile
new file mode 100644
index 0000000000..ab6859953f
--- /dev/null
+++ b/Tiltfile
@@ -0,0 +1,112 @@
+# Airavata Development Tiltfile
+# Usage: tilt up
+
+# --- Infrastructure (from compose.yml) ---
+docker_compose('./compose.yml')
+
+# --- Build ---
+local_resource(
+    'build',
+    cmd='mvn package -DskipTests -T4 -am -q',
+    deps=[
+        'airavata-api/src',
+        'airavata-api/pom.xml',
+        'airavata-thrift-server/src',
+        'airavata-thrift-server/pom.xml',
+        'modules/file-server/src',
+        'modules/file-server/pom.xml',
+        'modules/agent-framework/agent-service/src',
+        'modules/agent-framework/agent-service/pom.xml',
+        'modules/research-framework/research-service/src',
+        'modules/research-framework/research-service/pom.xml',
+        'modules/restproxy/src',
+        'modules/restproxy/pom.xml',
+    ],
+    labels=['build'],
+)
+
+# --- Airavata Thrift Server (port 8930 thrift, 9097 monitoring) ---
+local_resource(
+    'airavata-server',
+    serve_cmd=' '.join([
+        'java',
+        '-cp', 'airavata-thrift-server/target/classes:airavata-api/target/classes:airavata-api/target/dependency/*',
+        '-Dlog4j.configurationFile=airavata-api/src/main/resources/log4j2.xml',
+        '-Dairavata.config.dir=airavata-api/src/main/resources',
+        'org.apache.airavata.api.server.AiravataServer',
+    ]),
+    readiness_probe=probe(
+        http_get=http_get_action(port=9097, path='/health/services'),
+        period_secs=5,
+        timeout_secs=5,
+        initial_delay_secs=15,
+    ),
+    resource_deps=['build', 'db', 'rabbitmq', 'zookeeper', 'kafka', 'keycloak'],
+    labels=['airavata'],
+)
+
+# --- Research Service (port 18889 HTTP, 19908 gRPC) ---
+local_resource(
+    'research-service',
+    serve_cmd='mvn spring-boot:run -pl modules/research-framework/research-service -q',
+    readiness_probe=probe(
+        http_get=http_get_action(port=18889, path='/actuator/health'),
+        period_secs=5,
+        timeout_secs=5,
+        initial_delay_secs=20,
+    ),
+    resource_deps=['build', 'airavata-server', 'db', 'keycloak'],
+    labels=['airavata'],
+)
+
+# --- Agent Service (port 18880 HTTP, 19900 gRPC) ---
+local_resource(
+    'agent-service',
+    serve_cmd='mvn spring-boot:run -pl modules/agent-framework/agent-service -q',
+    readiness_probe=probe(
+        http_get=http_get_action(port=18880, path='/actuator/health'),
+        period_secs=5,
+        timeout_secs=5,
+        initial_delay_secs=20,
+    ),
+    resource_deps=['build', 'airavata-server', 'db'],
+    labels=['airavata'],
+)
+
+# --- File Server (port 8050) ---
+local_resource(
+    'file-server',
+    serve_cmd='mvn spring-boot:run -pl modules/file-server -q',
+    readiness_probe=probe(
+        http_get=http_get_action(port=8050, path='/actuator/health'),
+        period_secs=5,
+        timeout_secs=5,
+        initial_delay_secs=15,
+    ),
+    resource_deps=['build', 'airavata-server'],
+    labels=['airavata'],
+)
+
+# --- REST Proxy (port 8082) ---
+local_resource(
+    'rest-proxy',
+    serve_cmd='mvn spring-boot:run -pl modules/restproxy -q',
+    readiness_probe=probe(
+        http_get=http_get_action(port=8082, path='/actuator/health'),
+        period_secs=5,
+        timeout_secs=5,
+        initial_delay_secs=15,
+    ),
+    resource_deps=['build', 'airavata-server', 'kafka'],
+    labels=['airavata'],
+)
+
+# --- Integration Tests (manual trigger) ---
+local_resource(
+    'integration-tests',
+    cmd='mvn test -pl integration-tests -Dgroups=integration -am -q',
+    resource_deps=['airavata-server'],
+    auto_init=False,
+    trigger_mode=TRIGGER_MODE_MANUAL,
+    labels=['tests'],
+)
diff --git a/airavata-api/pom.xml b/airavata-api/pom.xml
index d9a8a88957..363c46a43a 100644
--- a/airavata-api/pom.xml
+++ b/airavata-api/pom.xml
@@ -570,6 +570,24 @@ under the License.
         </executions>
       </plugin>
 
+      <!-- Copy runtime dependencies for classpath mode (Tiltfile) -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>copy-dependencies</id>
+            <phase>package</phase>
+            <goals>
+              <goal>copy-dependencies</goal>
+            </goals>
+            <configuration>
+              <outputDirectory>${project.build.directory}/dependency</outputDirectory>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
       <!-- Package distribution (docker) preparation -->
       <plugin>
         <artifactId>maven-resources-plugin</artifactId>
diff --git a/airavata-api/src/main/java/org/apache/airavata/patform/monitoring/MonitoringServer.java b/airavata-api/src/main/java/org/apache/airavata/patform/monitoring/MonitoringServer.java
index 23d90111b7..ce92273fdd 100644
--- a/airavata-api/src/main/java/org/apache/airavata/patform/monitoring/MonitoringServer.java
+++ b/airavata-api/src/main/java/org/apache/airavata/patform/monitoring/MonitoringServer.java
@@ -19,8 +19,17 @@
 */
 package org.apache.airavata.patform.monitoring;
 
-import io.prometheus.client.exporter.HTTPServer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpServer;
+import io.prometheus.client.CollectorRegistry;
+import io.prometheus.client.exporter.common.TextFormat;
 import java.io.IOException;
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
 import org.apache.airavata.common.utils.IServer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -30,9 +39,11 @@ public class MonitoringServer implements IServer {
     private static final Logger logger = LoggerFactory.getLogger(MonitoringServer.class);
     private static final String SERVER_NAME = "Monitoring Server";
 
-    private String host;
-    private int port;
-    private HTTPServer httpServer;
+    private final String host;
+    private final int port;
+    private final ObjectMapper objectMapper = new ObjectMapper();
+    private HttpServer httpServer;
+    private ServiceRegistry serviceRegistry;
     private ServerStatus status = ServerStatus.STOPPED;
 
     public MonitoringServer(String host, int port) {
@@ -40,6 +51,10 @@ public class MonitoringServer implements IServer {
         this.port = port;
     }
 
+    public void setServiceRegistry(ServiceRegistry serviceRegistry) {
+        this.serviceRegistry = serviceRegistry;
+    }
+
     @Override
     public String getName() {
         return SERVER_NAME;
@@ -49,10 +64,18 @@ public class MonitoringServer implements IServer {
     public void run() {
         setStatus(ServerStatus.STARTING);
         try {
-            logger.info("Starting the monitoring server");
-            httpServer = new HTTPServer(host, port, true);
+            logger.info("Starting the monitoring server on {}:{}", host, port);
+            httpServer = HttpServer.create(new InetSocketAddress(host, port), 0);
+
+            httpServer.createContext("/metrics", this::handleMetrics);
+            httpServer.createContext("/health/services", this::handleHealthServices);
+            httpServer.createContext("/admin/restart/", this::handleAdminRestart);
+
+            httpServer.start();
             setStatus(ServerStatus.STARTED);
-            // HTTPServer is non-blocking; park this thread until interrupted
+            logger.info("Monitoring server started on {}:{}", host, port);
+
+            // Park thread until interrupted
             while (!Thread.currentThread().isInterrupted()) {
                 try {
                     Thread.sleep(Long.MAX_VALUE);
@@ -61,7 +84,7 @@ public class MonitoringServer implements IServer {
                 }
             }
         } catch (IOException e) {
-            logger.error("Failed to start the monitoring server on host {} na port {}", host, port, e);
+            logger.error("Failed to start the monitoring server on host {} and port {}", host, port, e);
             setStatus(ServerStatus.FAILED);
         }
     }
@@ -70,8 +93,8 @@ public class MonitoringServer implements IServer {
     public void stop() {
         setStatus(ServerStatus.STOPPING);
         if (httpServer != null) {
-            logger.info("Stopping the monitor server");
-            httpServer.stop();
+            logger.info("Stopping the monitoring server");
+            httpServer.stop(0);
         }
         setStatus(ServerStatus.STOPPED);
     }
@@ -85,4 +108,85 @@ public class MonitoringServer implements IServer {
         status = stat;
         status.updateTime();
     }
+
+    private void handleMetrics(HttpExchange exchange) throws IOException {
+        if (!"GET".equalsIgnoreCase(exchange.getRequestMethod())) {
+            sendResponse(exchange, 405, "Method Not Allowed");
+            return;
+        }
+        try {
+            StringWriter writer = new StringWriter();
+            TextFormat.write004(writer, CollectorRegistry.defaultRegistry.metricFamilySamples());
+            byte[] body = writer.toString().getBytes(StandardCharsets.UTF_8);
+            exchange.getResponseHeaders().set("Content-Type", TextFormat.CONTENT_TYPE_004);
+            exchange.sendResponseHeaders(200, body.length);
+            try (OutputStream os = exchange.getResponseBody()) {
+                os.write(body);
+            }
+        } catch (Exception e) {
+            logger.error("Error serving /metrics", e);
+            sendResponse(exchange, 500, "Internal Server Error");
+        }
+    }
+
+    private void handleHealthServices(HttpExchange exchange) throws IOException {
+        if (!"GET".equalsIgnoreCase(exchange.getRequestMethod())) {
+            sendResponse(exchange, 405, "Method Not Allowed");
+            return;
+        }
+        try {
+            Map<String, ServiceStatus> statuses = serviceRegistry != null ? serviceRegistry.getStatuses() : Map.of();
+            sendJson(exchange, 200, Map.of("services", statuses));
+        } catch (Exception e) {
+            logger.error("Error serving /health/services", e);
+            sendResponse(exchange, 500, "Internal Server Error");
+        }
+    }
+
+    private void handleAdminRestart(HttpExchange exchange) throws IOException {
+        if (!"POST".equalsIgnoreCase(exchange.getRequestMethod())) {
+            sendResponse(exchange, 405, "Method Not Allowed");
+            return;
+        }
+        String path = exchange.getRequestURI().getPath();
+        // path is /admin/restart/{name}
+        String prefix = "/admin/restart/";
+        if (!path.startsWith(prefix) || path.length() <= prefix.length()) {
+            sendJson(exchange, 400, Map.of("error", "Missing service name in path"));
+            return;
+        }
+        String name = path.substring(prefix.length());
+        if (serviceRegistry == null) {
+            sendJson(exchange, 500, Map.of("error", "ServiceRegistry not configured"));
+            return;
+        }
+        try {
+            serviceRegistry.restart(name);
+            sendJson(exchange, 200, Map.of("status", "restarted", "service", name));
+        } catch (IllegalArgumentException e) {
+            sendJson(exchange, 404, Map.of("error", e.getMessage()));
+        } catch (IllegalStateException e) {
+            sendJson(exchange, 400, Map.of("error", e.getMessage()));
+        } catch (Exception e) {
+            logger.error("Error restarting service '{}'", name, e);
+            sendJson(exchange, 500, Map.of("error", e.getMessage()));
+        }
+    }
+
+    private void sendJson(HttpExchange exchange, int statusCode, Object body) throws IOException {
+        byte[] bytes = objectMapper.writeValueAsBytes(body);
+        exchange.getResponseHeaders().set("Content-Type", "application/json");
+        exchange.sendResponseHeaders(statusCode, bytes.length);
+        try (OutputStream os = exchange.getResponseBody()) {
+            os.write(bytes);
+        }
+    }
+
+    private void sendResponse(HttpExchange exchange, int statusCode, String body) throws IOException {
+        byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
+        exchange.sendResponseHeaders(statusCode, bytes.length);
+        try (OutputStream os = exchange.getResponseBody()) {
+            os.write(bytes);
+        }
+    }
 }
diff --git a/airavata-api/src/main/java/org/apache/airavata/patform/monitoring/ServiceRegistry.java b/airavata-api/src/main/java/org/apache/airavata/patform/monitoring/ServiceRegistry.java
new file mode 100644
index 0000000000..61b6ac81ed
--- /dev/null
+++ b/airavata-api/src/main/java/org/apache/airavata/patform/monitoring/ServiceRegistry.java
@@ -0,0 +1,153 @@
+/**
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+package org.apache.airavata.patform.monitoring;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.function.Supplier;
+import org.apache.airavata.common.utils.IServer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Tracks background {@link IServer} instances and their threads, exposing
+ * lifecycle operations (status query, restart, stop-all) and error recording.
+ */
+public class ServiceRegistry {
+
+    private static final Logger logger = LoggerFactory.getLogger(ServiceRegistry.class);
+
+    private static class Entry {
+        final IServer server;
+        Thread thread;
+        final Supplier<IServer> factory;
+        final long startedAt;
+        String lastError;
+
+        Entry(IServer server, Thread thread, Supplier<IServer> factory) {
+            this.server = server;
+            this.thread = thread;
+            this.factory = factory;
+            this.startedAt = System.currentTimeMillis();
+        }
+    }
+
+    private final Map<String, Entry> entries = new LinkedHashMap<>();
+
+    /**
+     * Registers a service with an optional restart factory.
+     *
+     * @param label   human-readable service identifier
+     * @param service the IServer instance
+     * @param thread  the thread running the service
+     * @param factory optional supplier to create a fresh IServer for restart; may be null
+     */
+    public synchronized void register(String label, IServer service, Thread thread, Supplier<IServer> factory) {
+        entries.put(label, new Entry(service, thread, factory));
+    }
+
+    /**
+     * Registers a service without a restart factory.
+     */
+    public synchronized void register(String label, IServer service, Thread thread) {
+        register(label, service, thread, null);
+    }
+
+    /**
+     * Returns a snapshot of statuses keyed by label.
+     * A service is "UP" if its IServer status is STARTED (preferred) or its thread is alive.
+     * Some services (e.g. PostWorkflowManager) spawn sub-threads and let their main thread
+     * exit — IServer.getStatus() remains STARTED in that case.
+     */
+    public synchronized Map<String, ServiceStatus> getStatuses() {
+        Map<String, ServiceStatus> result = new LinkedHashMap<>();
+        long now = System.currentTimeMillis();
+        for (Map.Entry<String, Entry> e : entries.entrySet()) {
+            Entry entry = e.getValue();
+            boolean up = entry.server.getStatus() == IServer.ServerStatus.STARTED || entry.thread.isAlive();
+            String status = up ? "UP" : "DOWN";
+            long uptimeMs = up ? now - entry.startedAt : 0L;
+            result.put(e.getKey(), new ServiceStatus(status, uptimeMs, entry.lastError));
+        }
+        return result;
+    }
+
+    /**
+     * Attempts to restart the named service using its registered factory.
+     *
+     * @param name the service label
+     * @throws IllegalArgumentException if no service with that name exists
+     * @throws IllegalStateException    if the service has no restart factory
+     * @throws Exception                if the new service thread fails to start
+     */
+    public synchronized void restart(String name) throws Exception {
+        Entry entry = entries.get(name);
+        if (entry == null) {
+            throw new IllegalArgumentException("No service registered with name: " + name);
+        }
+        if (entry.factory == null) {
+            throw new IllegalStateException("Service '" + name + "' has no restart factory");
+        }
+        // Stop the old instance
+        try {
+            entry.server.stop();
+        } catch (Exception e) {
+            logger.warn("Error stopping '{}' before restart: {}", name, e.getMessage());
+        }
+        entry.thread.interrupt();
+
+        // Start a fresh instance
+        IServer newServer = entry.factory.get();
+        Thread newThread = new Thread(newServer, "airavata-" + name);
+        newThread.setDaemon(true);
+        newThread.start();
+
+        Entry newEntry = new Entry(newServer, newThread, entry.factory);
+        entries.put(name, newEntry);
+        logger.info("Service '{}' restarted", name);
+    }
+
+    /**
+     * Records an error message against the named service.
+     * Silently ignores unknown names.
+     */
+    public synchronized void recordError(String name, String errorMessage) {
+        Entry entry = entries.get(name);
+        if (entry != null) {
+            entry.lastError = errorMessage;
+        }
+    }
+
+    /**
+     * Stops all registered services and interrupts their threads.
+     */
+    public synchronized void stopAll() {
+        for (Map.Entry<String, Entry> e : entries.entrySet()) {
+            String label = e.getKey();
+            Entry entry = e.getValue();
+            try {
+                entry.server.stop();
+            } catch (Exception ex) {
+                logger.warn("Error stopping '{}': {}", label, ex.getMessage());
+            }
+            entry.thread.interrupt();
+        }
+    }
+}
diff --git a/airavata-api/src/main/java/org/apache/airavata/patform/monitoring/ServiceStatus.java b/airavata-api/src/main/java/org/apache/airavata/patform/monitoring/ServiceStatus.java
new file mode 100644
index 0000000000..9d1b48854a
--- /dev/null
+++ b/airavata-api/src/main/java/org/apache/airavata/patform/monitoring/ServiceStatus.java
@@ -0,0 +1,62 @@
+/**
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+package org.apache.airavata.patform.monitoring;
+
+/**
+ * DTO representing the runtime status of a single background service.
+ */
+public class ServiceStatus {
+
+    private String status;
+    private long uptimeMs;
+    private String lastError;
+
+    public ServiceStatus() {}
+
+    public ServiceStatus(String status, long uptimeMs, String lastError) {
+        this.status = status;
+        this.uptimeMs = uptimeMs;
+        this.lastError = lastError;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public long getUptimeMs() {
+        return uptimeMs;
+    }
+
+    public void setUptimeMs(long uptimeMs) {
+        this.uptimeMs = uptimeMs;
+    }
+
+    public String getLastError() {
+        return lastError;
+    }
+
+    public void setLastError(String lastError) {
+        this.lastError = lastError;
+    }
+}
diff --git a/airavata-thrift-server/pom.xml b/airavata-thrift-server/pom.xml
index a77569bb70..294e6ed356 100644
--- a/airavata-thrift-server/pom.xml
+++ b/airavata-thrift-server/pom.xml
@@ -1,4 +1,21 @@
-<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
 <project xmlns="http://maven.apache.org/POM/4.0.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
diff --git a/airavata-thrift-server/src/main/java/org/apache/airavata/api/server/AiravataServer.java b/airavata-thrift-server/src/main/java/org/apache/airavata/api/server/AiravataServer.java
index 4d4acf38cf..b3ad7a80f1 100644
--- a/airavata-thrift-server/src/main/java/org/apache/airavata/api/server/AiravataServer.java
+++ b/airavata-thrift-server/src/main/java/org/apache/airavata/api/server/AiravataServer.java
@@ -23,6 +23,7 @@ import java.net.InetSocketAddress;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.function.Supplier;
 import org.apache.airavata.api.Airavata;
 import org.apache.airavata.api.server.handler.AiravataServerHandler;
 import org.apache.airavata.api.server.util.Constants;
@@ -51,6 +52,7 @@ import org.apache.airavata.monitor.realtime.RealtimeMonitor;
 import org.apache.airavata.orchestrator.cpi.OrchestratorService;
 import org.apache.airavata.orchestrator.server.OrchestratorServerHandler;
 import org.apache.airavata.patform.monitoring.MonitoringServer;
+import org.apache.airavata.patform.monitoring.ServiceRegistry;
 import org.apache.airavata.registry.api.RegistryService;
 import org.apache.airavata.registry.api.service.handler.RegistryServerHandler;
 import org.apache.airavata.registry.core.utils.AppCatalogDBInitConfig;
@@ -101,8 +103,7 @@ public class AiravataServer {
     private static final Logger logger = LoggerFactory.getLogger(AiravataServer.class);
 
     private TServer server;
-    private final List<IServer> backgroundServices = new ArrayList<>();
-    private final List<Thread> serviceThreads = new ArrayList<>();
+    private final ServiceRegistry serviceRegistry = new ServiceRegistry();
 
     private final List<DBInitConfig> dbInitConfigs = Arrays.asList(
             new ExpCatalogDBInitConfig(),
@@ -239,10 +240,13 @@ public class AiravataServer {
             if (ServerSettings.getBooleanSetting("api.server.monitoring.enabled")) {
                 String monHost = ServerSettings.getSetting("api.server.monitoring.host", "localhost");
                 int monPort = Integer.parseInt(ServerSettings.getSetting("api.server.monitoring.port", "9097"));
-                registerAndStart(new MonitoringServer(monHost, monPort), "monitoring_server");
+                MonitoringServer monitoringServer = new MonitoringServer(monHost, monPort);
+                monitoringServer.setServiceRegistry(serviceRegistry);
+                registerAndStart(monitoringServer, "monitoring_server");
             }
         } catch (Exception e) {
             logger.warn("  monitoring_server: config error — {}", e.getMessage());
+            serviceRegistry.recordError("monitoring_server", e.getMessage());
         }
 
         // Cluster status monitoring — polls compute resource queue status
@@ -252,6 +256,7 @@ public class AiravataServer {
             }
         } catch (Exception e) {
             logger.warn("  cluster_status_monitor: config error — {}", e.getMessage());
+            serviceRegistry.recordError("cluster_status_monitor", e.getMessage());
         }
 
         // Data interpreter — metadata analysis for submitted jobs
@@ -261,6 +266,7 @@ public class AiravataServer {
             }
         } catch (Exception e) {
             logger.warn("  data_interpreter: config error — {}", e.getMessage());
+            serviceRegistry.recordError("data_interpreter", e.getMessage());
         }
 
         // Process rescheduler — retries/reschedules failed processes
@@ -270,6 +276,7 @@ public class AiravataServer {
             }
         } catch (Exception e) {
             logger.warn("  process_rescheduler: config error — {}", e.getMessage());
+            serviceRegistry.recordError("process_rescheduler", e.getMessage());
         }
 
         // Execution engine services — controller must initialize the cluster
@@ -279,6 +286,7 @@ public class AiravataServer {
             waitForHelixCluster();
         } catch (Exception e) {
             logger.warn("  helix_controller: config error — {}", e.getMessage());
+            serviceRegistry.recordError("helix_controller", e.getMessage());
         }
         try {
             ArrayList<Class<? extends AbstractTask>> taskClasses = new ArrayList<>();
@@ -288,21 +296,25 @@ public class AiravataServer {
             registerAndStart(new GlobalParticipant(taskClasses, null), "helix_participant");
         } catch (Exception e) {
             logger.warn("  helix_participant: config error — {}", e.getMessage());
+            serviceRegistry.recordError("helix_participant", e.getMessage());
         }
         try {
             registerAndStart(new PreWorkflowManager(), "pre_workflow_manager");
         } catch (Exception e) {
             logger.warn("  pre_workflow_manager: config error — {}", e.getMessage());
+            serviceRegistry.recordError("pre_workflow_manager", e.getMessage());
         }
         try {
             registerAndStart(new PostWorkflowManager(), "post_workflow_manager");
         } catch (Exception e) {
             logger.warn("  post_workflow_manager: config error — {}", e.getMessage());
+            serviceRegistry.recordError("post_workflow_manager", e.getMessage());
         }
         try {
             registerAndStart(new ParserWorkflowManager(), "parser_workflow_manager");
         } catch (Exception e) {
             logger.warn("  parser_workflow_manager: config error — {}", e.getMessage());
+            serviceRegistry.recordError("parser_workflow_manager", e.getMessage());
         }
 
         // Job monitors
@@ -312,14 +324,16 @@ public class AiravataServer {
             }
         } catch (Exception e) {
             logger.warn("  email_monitor: config error — {}", e.getMessage());
+            serviceRegistry.recordError("email_monitor", e.getMessage());
         }
         try {
             registerAndStart(new RealtimeMonitor(), "realtime_monitor");
         } catch (Exception e) {
             logger.warn("  realtime_monitor: config error — {}", e.getMessage());
+            serviceRegistry.recordError("realtime_monitor", e.getMessage());
         }
 
-        logger.info("Background services initialization complete ({} running)", backgroundServices.size());
+        logger.info("Background services initialization complete");
     }
 
     private void waitForHelixCluster() {
@@ -347,11 +361,14 @@ public class AiravataServer {
     }
 
     private void registerAndStart(IServer service, String label) {
+        registerAndStart(service, label, null);
+    }
+
+    private void registerAndStart(IServer service, String label, Supplier<IServer> factory) {
         Thread t = new Thread(service, "airavata-" + label);
         t.setDaemon(true);
         t.start();
-        backgroundServices.add(service);
-        serviceThreads.add(t);
+        serviceRegistry.register(label, service, t, factory);
         logger.info("  {}: started", label);
     }
 
@@ -359,16 +376,7 @@ public class AiravataServer {
         if (server != null && server.isServing()) {
             server.stop();
         }
-        for (IServer service : backgroundServices) {
-            try {
-                service.stop();
-            } catch (Exception e) {
-                logger.warn("Error stopping {}: {}", service.getName(), e.getMessage());
-            }
-        }
-        for (Thread t : serviceThreads) {
-            t.interrupt();
-        }
+        serviceRegistry.stopAll();
     }
 
     public static void main(String[] args) {
diff --git a/airavata-thrift-server/src/main/java/org/apache/airavata/api/server/mapper/WorkspaceMapper.java b/airavata-thrift-server/src/main/java/org/apache/airavata/api/server/mapper/WorkspaceMapper.java
index 44dad11244..aff29a9c30 100644
--- a/airavata-thrift-server/src/main/java/org/apache/airavata/api/server/mapper/WorkspaceMapper.java
+++ b/airavata-thrift-server/src/main/java/org/apache/airavata/api/server/mapper/WorkspaceMapper.java
@@ -1,21 +1,22 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
+/**
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
 package org.apache.airavata.api.server.mapper;
 
 import java.util.List;
@@ -78,8 +79,7 @@ public interface WorkspaceMapper {
      */
     @AfterMapping
     default void fillRepeatedFields(
-            org.apache.airavata.model.workspace.Project thrift,
-            @MappingTarget Project.Builder builder) {
+            org.apache.airavata.model.workspace.Project thrift, @MappingTarget Project.Builder builder) {
         List<String> users = thrift.getSharedUsers();
         if (users != null) {
             builder.addAllSharedUsers(users);
diff --git a/docs/superpowers/plans/2026-03-26-airavata-thrift-server-extraction.md b/docs/superpowers/plans/2026-03-26-airavata-thrift-server-extraction.md
deleted file mode 100644
index 837d304414..0000000000
--- a/docs/superpowers/plans/2026-03-26-airavata-thrift-server-extraction.md
+++ /dev/null
@@ -1,1090 +0,0 @@
-# Airavata Thrift Server Extraction Implementation Plan
-
-> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
-
-**Goal:** Extract all thrift server concerns from `airavata-api` into a new `airavata-thrift-server` Maven module, introduce proto DTOs in `airavata-api`, and set up MapStruct mapping at the thrift handler boundary.
-
-**Architecture:** `airavata-thrift-server` depends on `airavata-api`. Thrift IDL lives in `airavata-thrift-server/src/main/thrift/`. Server bootstrap (AiravataServer), the main API handler (AiravataServerHandler), profile handlers, OrchestratorServerHandler, ThriftAdapter, and Constants move to `airavata-thrift-server`. Data-access handlers (RegistryServerHandler, SharingRegistryServerHandler, CredentialStoreServerHandler) stay in `airavata-api` because services depend on them directly — [...]
-
-**Tech Stack:** Maven, Apache Thrift 0.22.0, Protocol Buffers 4.30.1, MapStruct 1.6.3, protobuf-maven-plugin 3.10.2
-
-**Spec:** `docs/superpowers/specs/2026-03-26-airavata-thrift-server-extraction-design.md`
-
----
-
-## File Structure
-
-### New files (airavata-thrift-server)
-
-| File | Responsibility |
-|------|---------------|
-| `airavata-thrift-server/pom.xml` | Module POM — depends on airavata-api, libthrift, MapStruct |
-| `airavata-thrift-server/src/main/thrift/**` | Moved from `thrift-interface-descriptions/` — all IDL files |
-| `airavata-thrift-server/src/main/java/org/apache/airavata/api/server/AiravataServer.java` | Moved — thrift server bootstrap |
-| `airavata-thrift-server/src/main/java/org/apache/airavata/api/server/util/Constants.java` | Moved — server config constants |
-| `airavata-thrift-server/src/main/java/org/apache/airavata/api/server/handler/AiravataServerHandler.java` | Moved — main API thrift handler |
-| `airavata-thrift-server/src/main/java/org/apache/airavata/api/server/handler/ThriftAdapter.java` | Moved — thrift error translation |
-| `airavata-thrift-server/src/main/java/org/apache/airavata/service/profile/handlers/UserProfileServiceHandler.java` | Moved |
-| `airavata-thrift-server/src/main/java/org/apache/airavata/service/profile/handlers/TenantProfileServiceHandler.java` | Moved |
-| `airavata-thrift-server/src/main/java/org/apache/airavata/service/profile/handlers/IamAdminServicesHandler.java` | Moved |
-| `airavata-thrift-server/src/main/java/org/apache/airavata/service/profile/handlers/GroupManagerServiceHandler.java` | Moved |
-| `airavata-thrift-server/src/main/java/org/apache/airavata/orchestrator/server/OrchestratorServerHandler.java` | Moved |
-
-### New files (airavata-api — proto)
-
-| File | Responsibility |
-|------|---------------|
-| `airavata-api/src/main/proto/org/apache/airavata/model/workspace/workspace.proto` | Proto mirror of workspace_model.thrift |
-| `airavata-api/src/main/proto/org/apache/airavata/model/experiment/experiment.proto` | Proto mirror of experiment_model.thrift |
-| `airavata-api/src/main/proto/org/apache/airavata/model/status/status.proto` | Proto mirror of status_models.thrift |
-| ... (one proto per thrift data-model file — 28 total) | Mechanical 1:1 translation |
-| `airavata-api/src/main/proto/org/apache/airavata/model/security/security.proto` | Proto mirror of security_model.thrift |
-| `airavata-api/src/main/proto/org/apache/airavata/model/error/errors.proto` | Proto mirror of airavata_errors.thrift |
-| `airavata-api/src/main/proto/org/apache/airavata/model/commons/commons.proto` | Proto mirror of airavata_commons.thrift |
-
-### Modified files
-
-| File | Change |
-|------|--------|
-| `pom.xml` (root) | Add `airavata-thrift-server` module before `airavata-api` in module order |
-| `airavata-api/pom.xml` | Update thrift compiler path, add protobuf-java + protobuf-maven-plugin, add MapStruct deps |
-
-### Deleted files (from airavata-api)
-
-| File | Reason |
-|------|--------|
-| `airavata-api/src/main/java/org/apache/airavata/api/server/AiravataServer.java` | Moved |
-| `airavata-api/src/main/java/org/apache/airavata/api/server/util/Constants.java` | Moved |
-| `airavata-api/src/main/java/org/apache/airavata/api/server/handler/AiravataServerHandler.java` | Moved |
-| `airavata-api/src/main/java/org/apache/airavata/api/server/handler/ThriftAdapter.java` | Moved |
-| `airavata-api/src/main/java/org/apache/airavata/service/profile/handlers/UserProfileServiceHandler.java` | Moved |
-| `airavata-api/src/main/java/org/apache/airavata/service/profile/handlers/TenantProfileServiceHandler.java` | Moved |
-| `airavata-api/src/main/java/org/apache/airavata/service/profile/handlers/IamAdminServicesHandler.java` | Moved |
-| `airavata-api/src/main/java/org/apache/airavata/service/profile/handlers/GroupManagerServiceHandler.java` | Moved |
-| `airavata-api/src/main/java/org/apache/airavata/orchestrator/server/OrchestratorServerHandler.java` | Moved |
-| `thrift-interface-descriptions/` | Moved to airavata-thrift-server/src/main/thrift/ |
-
----
-
-### Task 1: Create git worktree
-
-**Files:**
-- None (git operation)
-
-- [ ] **Step 1: Create worktree branch**
-
-```bash
-cd /Users/yasith/code/artisan/airavata
-git worktree add ../airavata-thrift-extraction -b feat/thrift-server-extraction
-```
-
-- [ ] **Step 2: Verify worktree**
-
-```bash
-cd /Users/yasith/code/artisan/airavata-thrift-extraction
-git branch --show-current
-```
-
-Expected: `feat/thrift-server-extraction`
-
-All subsequent tasks run inside `/Users/yasith/code/artisan/airavata-thrift-extraction/`.
-
----
-
-### Task 2: Create airavata-thrift-server Maven module
-
-**Files:**
-- Create: `airavata-thrift-server/pom.xml`
-- Modify: `pom.xml` (root)
-
-- [ ] **Step 1: Create module directory**
-
-```bash
-mkdir -p airavata-thrift-server/src/main/java
-```
-
-- [ ] **Step 2: Write airavata-thrift-server/pom.xml**
-
-```xml
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>org.apache.airavata</groupId>
-    <artifactId>airavata</artifactId>
-    <version>0.21-SNAPSHOT</version>
-    <relativePath>../pom.xml</relativePath>
-  </parent>
-
-  <artifactId>airavata-thrift-server</artifactId>
-  <packaging>jar</packaging>
-  <name>Airavata Thrift Server</name>
-
-  <dependencies>
-    <dependency>
-      <groupId>org.apache.airavata</groupId>
-      <artifactId>airavata-api</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.thrift</groupId>
-      <artifactId>libthrift</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.slf4j</groupId>
-      <artifactId>slf4j-api</artifactId>
-    </dependency>
-  </dependencies>
-</project>
-```
-
-- [ ] **Step 3: Add module to root pom.xml**
-
-In `pom.xml`, add `airavata-thrift-server` AFTER `airavata-api` in the `<modules>` block (it depends on airavata-api):
-
-```xml
-    <modules>
-        <module>airavata-api</module>
-        <module>airavata-thrift-server</module>
-        <module>modules/file-server</module>
-        <module>modules/agent-framework/agent-service</module>
-        <module>modules/research-framework/research-service</module>
-        <module>modules/restproxy</module>
-        <module>modules/ide-integration</module>
-    </modules>
-```
-
-- [ ] **Step 4: Verify module resolves**
-
-```bash
-mvn validate -pl airavata-thrift-server -am --quiet
-```
-
-Expected: BUILD SUCCESS (no compilation yet, just POM validation)
-
-- [ ] **Step 5: Commit**
-
-```bash
-git add airavata-thrift-server/pom.xml pom.xml
-git commit -m "feat: add airavata-thrift-server Maven module scaffold"
-```
-
----
-
-### Task 3: Move thrift IDL files
-
-**Files:**
-- Move: `thrift-interface-descriptions/` → `airavata-thrift-server/src/main/thrift/`
-- Modify: `airavata-api/pom.xml` (update thrift compiler path)
-
-- [ ] **Step 1: Move the directory**
-
-```bash
-git mv thrift-interface-descriptions airavata-thrift-server/src/main/thrift
-```
-
-- [ ] **Step 2: Update thrift compiler path in airavata-api/pom.xml**
-
-In `airavata-api/pom.xml`, find the `maven-antrun-plugin` execution `generate-thrift-sources` and change the thrift input path from:
-
-```xml
-<arg value="${project.basedir}/../thrift-interface-descriptions/stubs_java.thrift" />
-```
-
-to:
-
-```xml
-<arg value="${project.basedir}/../airavata-thrift-server/src/main/thrift/stubs_java.thrift" />
-```
-
-- [ ] **Step 3: Verify thrift compilation still works**
-
-```bash
-mvn generate-sources -pl airavata-api --quiet
-ls airavata-api/target/generated-sources/thrift/org/apache/airavata/api/Airavata.java
-```
-
-Expected: file exists (thrift compiler found the moved IDL and generated code)
-
-- [ ] **Step 4: Commit**
-
-```bash
-git add -A
-git commit -m "refactor: move thrift IDL to airavata-thrift-server/src/main/thrift"
-```
-
----
-
-### Task 4: Move server bootstrap to airavata-thrift-server
-
-**Files:**
-- Move: `airavata-api/src/main/java/org/apache/airavata/api/server/AiravataServer.java` → `airavata-thrift-server/src/main/java/org/apache/airavata/api/server/AiravataServer.java`
-- Move: `airavata-api/src/main/java/org/apache/airavata/api/server/util/Constants.java` → `airavata-thrift-server/src/main/java/org/apache/airavata/api/server/util/Constants.java`
-
-- [ ] **Step 1: Create target directories**
-
-```bash
-mkdir -p airavata-thrift-server/src/main/java/org/apache/airavata/api/server/util
-```
-
-- [ ] **Step 2: Move AiravataServer.java**
-
-```bash
-git mv airavata-api/src/main/java/org/apache/airavata/api/server/AiravataServer.java \
-      airavata-thrift-server/src/main/java/org/apache/airavata/api/server/AiravataServer.java
-```
-
-- [ ] **Step 3: Move Constants.java**
-
-```bash
-git mv airavata-api/src/main/java/org/apache/airavata/api/server/util/Constants.java \
-      airavata-thrift-server/src/main/java/org/apache/airavata/api/server/util/Constants.java
-```
-
-Note: `Constants.java` contains `API_SERVER_PORT`, `API_SERVER_HOST`, `API_SERVER_MIN_THREADS` — used only by `AiravataServer.java`. The `ThriftAdapter.java` imports `org.apache.airavata.common.utils.Constants` (a different class in commons), NOT this one.
-
-- [ ] **Step 4: Commit**
-
-```bash
-git add -A
-git commit -m "refactor: move AiravataServer and Constants to airavata-thrift-server"
-```
-
----
-
-### Task 5: Move AiravataServerHandler and ThriftAdapter
-
-**Files:**
-- Move: `airavata-api/src/main/java/org/apache/airavata/api/server/handler/AiravataServerHandler.java` → `airavata-thrift-server/...`
-- Move: `airavata-api/src/main/java/org/apache/airavata/api/server/handler/ThriftAdapter.java` → `airavata-thrift-server/...`
-
-- [ ] **Step 1: Create target directory**
-
-```bash
-mkdir -p airavata-thrift-server/src/main/java/org/apache/airavata/api/server/handler
-```
-
-- [ ] **Step 2: Move both files**
-
-```bash
-git mv airavata-api/src/main/java/org/apache/airavata/api/server/handler/AiravataServerHandler.java \
-      airavata-thrift-server/src/main/java/org/apache/airavata/api/server/handler/AiravataServerHandler.java
-
-git mv airavata-api/src/main/java/org/apache/airavata/api/server/handler/ThriftAdapter.java \
-      airavata-thrift-server/src/main/java/org/apache/airavata/api/server/handler/ThriftAdapter.java
-```
-
-No import changes needed — packages stay the same, and `airavata-thrift-server` depends on `airavata-api` which provides all service classes and handler classes they reference.
-
-- [ ] **Step 3: Commit**
-
-```bash
-git add -A
-git commit -m "refactor: move AiravataServerHandler and ThriftAdapter to airavata-thrift-server"
-```
-
----
-
-### Task 6: Move profile handlers and OrchestratorServerHandler
-
-**Files:**
-- Move: `airavata-api/src/main/java/org/apache/airavata/service/profile/handlers/UserProfileServiceHandler.java`
-- Move: `airavata-api/src/main/java/org/apache/airavata/service/profile/handlers/TenantProfileServiceHandler.java`
-- Move: `airavata-api/src/main/java/org/apache/airavata/service/profile/handlers/IamAdminServicesHandler.java`
-- Move: `airavata-api/src/main/java/org/apache/airavata/service/profile/handlers/GroupManagerServiceHandler.java`
-- Move: `airavata-api/src/main/java/org/apache/airavata/orchestrator/server/OrchestratorServerHandler.java`
-
-- [ ] **Step 1: Create target directories**
-
-```bash
-mkdir -p airavata-thrift-server/src/main/java/org/apache/airavata/service/profile/handlers
-mkdir -p airavata-thrift-server/src/main/java/org/apache/airavata/orchestrator/server
-```
-
-- [ ] **Step 2: Move profile handlers**
-
-```bash
-git mv airavata-api/src/main/java/org/apache/airavata/service/profile/handlers/UserProfileServiceHandler.java \
-      airavata-thrift-server/src/main/java/org/apache/airavata/service/profile/handlers/UserProfileServiceHandler.java
-
-git mv airavata-api/src/main/java/org/apache/airavata/service/profile/handlers/TenantProfileServiceHandler.java \
-      airavata-thrift-server/src/main/java/org/apache/airavata/service/profile/handlers/TenantProfileServiceHandler.java
-
-git mv airavata-api/src/main/java/org/apache/airavata/service/profile/handlers/IamAdminServicesHandler.java \
-      airavata-thrift-server/src/main/java/org/apache/airavata/service/profile/handlers/IamAdminServicesHandler.java
-
-git mv airavata-api/src/main/java/org/apache/airavata/service/profile/handlers/GroupManagerServiceHandler.java \
-      airavata-thrift-server/src/main/java/org/apache/airavata/service/profile/handlers/GroupManagerServiceHandler.java
-```
-
-- [ ] **Step 3: Move OrchestratorServerHandler**
-
-```bash
-git mv airavata-api/src/main/java/org/apache/airavata/orchestrator/server/OrchestratorServerHandler.java \
-      airavata-thrift-server/src/main/java/org/apache/airavata/orchestrator/server/OrchestratorServerHandler.java
-```
-
-- [ ] **Step 4: Commit**
-
-```bash
-git add -A
-git commit -m "refactor: move profile handlers and OrchestratorServerHandler to airavata-thrift-server"
-```
-
----
-
-### Task 7: Verify full build compiles
-
-**Files:**
-- Possibly modify: `airavata-thrift-server/pom.xml` (if missing deps surface)
-
-- [ ] **Step 1: Build airavata-api (should still compile — only lost handler classes it no longer owns)**
-
-```bash
-mvn compile -pl airavata-api --quiet 2>&1 | tail -5
-```
-
-Expected: BUILD SUCCESS. If any code in `airavata-api` still imports the moved handler classes, identify the files and update imports or remove dead references.
-
-- [ ] **Step 2: Build airavata-thrift-server**
-
-```bash
-mvn compile -pl airavata-thrift-server -am 2>&1 | tail -20
-```
-
-Expected: BUILD SUCCESS. If compilation fails due to missing dependencies, add them to `airavata-thrift-server/pom.xml`. Likely candidates:
-- Profile service CPI constants (thrift generated — already available via airavata-api dependency)
-- Helix/ZooKeeper classes used by OrchestratorServerHandler
-- Messaging classes used by AiravataServerHandler
-- Monitoring/background service classes used by AiravataServer
-
-For each missing dependency, add it to `airavata-thrift-server/pom.xml`:
-
-```xml
-<!-- Add as needed based on compilation errors -->
-<dependency>
-  <groupId>org.apache.helix</groupId>
-  <artifactId>helix-core</artifactId>
-</dependency>
-<dependency>
-  <groupId>io.prometheus</groupId>
-  <artifactId>simpleclient</artifactId>
-</dependency>
-<!-- etc. -->
-```
-
-- [ ] **Step 3: Fix any compilation errors**
-
-Iterate: read error → add dependency or fix import → rebuild. Continue until BUILD SUCCESS for both modules.
-
-- [ ] **Step 4: Run tests**
-
-```bash
-mvn test -pl airavata-api --quiet 2>&1 | tail -5
-```
-
-Expected: Tests pass (no behavioral changes, only file moves).
-
-- [ ] **Step 5: Commit**
-
-```bash
-git add -A
-git commit -m "fix: resolve airavata-thrift-server compilation dependencies"
-```
-
----
-
-### Task 8: Create proto files — workspace model (representative example)
-
-**Files:**
-- Create: `airavata-api/src/main/proto/org/apache/airavata/model/workspace/workspace.proto`
-
-This task demonstrates the proto translation pattern. All subsequent proto files follow the same rules.
-
-**Translation rules (thrift → proto):**
-- `namespace java X` → `option java_package = "X"; option java_outer_classname = "XProtos";`
-- `struct` → `message`
-- `enum` → `enum` (add `UNKNOWN = 0` sentinel as proto3 requires 0-value)
-- `required/optional` qualifiers are dropped (proto3 has no required/optional)
-- Field numbers match thrift field IDs
-- `string` → `string`, `i32` → `int32`, `i64` → `int64`, `bool` → `bool`, `double` → `double`, `binary` → `bytes`
-- `list<T>` → `repeated T`
-- `map<K,V>` → `map<K,V>`
-- `set<T>` → `repeated T` (proto has no set; enforce uniqueness in application code)
-- Cross-file references: use `import` and fully qualified message names
-
-- [ ] **Step 1: Create directory**
-
-```bash
-mkdir -p airavata-api/src/main/proto/org/apache/airavata/model/workspace
-```
-
-- [ ] **Step 2: Write workspace.proto**
-
-```protobuf
-syntax = "proto3";
-
-package org.apache.airavata.model.workspace;
-
-option java_package = "org.apache.airavata.model.workspace";
-option java_outer_classname = "WorkspaceProtos";
-option java_multiple_files = true;
-
-import "org/apache/airavata/model/commons/commons.proto";
-
-message Group {
-    string group_name = 1;
-    string description = 2;
-}
-
-message Project {
-    string project_id = 1;
-    string owner = 2;
-    string gateway_id = 3;
-    string name = 4;
-    string description = 5;
-    int64 creation_time = 6;
-    repeated string shared_users = 7;
-    repeated string shared_groups = 8;
-}
-
-message User {
-    string airavata_internal_user_id = 1;
-    string user_name = 2;
-    string gateway_id = 3;
-    string first_name = 4;
-    string last_name = 5;
-    string email = 6;
-}
-
-enum GatewayApprovalStatus {
-    GATEWAY_APPROVAL_STATUS_UNKNOWN = 0;
-    REQUESTED = 1;
-    APPROVED = 2;
-    ACTIVE = 3;
-    DEACTIVATED = 4;
-    CANCELLED = 5;
-    DENIED = 6;
-    CREATED = 7;
-    DEPLOYED = 8;
-}
-
-message Gateway {
-    string airavata_internal_gateway_id = 1;
-    string gateway_id = 2;
-    GatewayApprovalStatus gateway_approval_status = 3;
-    string gateway_name = 4;
-    string domain = 5;
-    string email_address = 6;
-    string gateway_acronym = 7;
-    string gateway_url = 8;
-    string gateway_public_abstract = 9;
-    string review_proposal_description = 10;
-    string gateway_admin_first_name = 11;
-    string gateway_admin_last_name = 12;
-    string gateway_admin_email = 13;
-    string identity_server_user_name = 14;
-    string identity_server_password_token = 15;
-    string declined_reason = 16;
-    string oauth_client_id = 17;
-    string oauth_client_secret = 18;
-    int64 request_creation_time = 19;
-    string requester_username = 20;
-}
-
-message GatewayUsageReportingCommand {
-    string gateway_id = 1;
-    string compute_resource_id = 2;
-    string command = 3;
-}
-
-enum NotificationPriority {
-    NOTIFICATION_PRIORITY_UNKNOWN = 0;
-    LOW = 1;
-    NORMAL = 2;
-    HIGH = 3;
-}
-
-message Notification {
-    string notification_id = 1;
-    string gateway_id = 2;
-    string title = 3;
-    string notification_message = 4;
-    int64 creation_time = 5;
-    int64 published_time = 6;
-    int64 expiration_time = 7;
-    NotificationPriority priority = 8;
-}
-```
-
-- [ ] **Step 3: Commit**
-
-```bash
-git add airavata-api/src/main/proto/
-git commit -m "feat: add workspace proto definitions"
-```
-
----
-
-### Task 9: Create proto files — commons and security
-
-**Files:**
-- Create: `airavata-api/src/main/proto/org/apache/airavata/model/commons/commons.proto`
-- Create: `airavata-api/src/main/proto/org/apache/airavata/model/security/security.proto`
-
-These are foundational protos referenced by many others.
-
-- [ ] **Step 1: Create directories**
-
-```bash
-mkdir -p airavata-api/src/main/proto/org/apache/airavata/model/commons
-mkdir -p airavata-api/src/main/proto/org/apache/airavata/model/security
-```
-
-- [ ] **Step 2: Write commons.proto**
-
-Read `airavata-thrift-server/src/main/thrift/airavata-apis/airavata_commons.thrift` to get the exact constants and types. Translate to:
-
-```protobuf
-syntax = "proto3";
-
-package org.apache.airavata.model.commons;
-
-option java_package = "org.apache.airavata.model.commons";
-option java_outer_classname = "CommonsProtos";
-option java_multiple_files = true;
-
-// DEFAULT_ID is a compile-time constant in thrift; in proto it becomes a convention.
-// Consumers should use "DO_NOT_SET_AT_CLIENTS" as the default for ID fields.
-
-message ValidationResults {
-    bool validation_state = 1;
-    repeated ValidationResult validation_result_list = 2;
-}
-
-message ValidationResult {
-    bool result = 1;
-    string error_details = 2;
-}
-```
-
-- [ ] **Step 3: Write security.proto**
-
-Read `airavata-thrift-server/src/main/thrift/airavata-apis/security_model.thrift` and translate:
-
-```protobuf
-syntax = "proto3";
-
-package org.apache.airavata.model.security;
-
-option java_package = "org.apache.airavata.model.security";
-option java_outer_classname = "SecurityProtos";
-option java_multiple_files = true;
-
-message AuthzToken {
-    string access_token = 1;
-    map<string, string> claims_map = 2;
-}
-```
-
-- [ ] **Step 4: Commit**
-
-```bash
-git add airavata-api/src/main/proto/
-git commit -m "feat: add commons and security proto definitions"
-```
-
----
-
-### Task 10: Create proto files — status and experiment models
-
-**Files:**
-- Create: `airavata-api/src/main/proto/org/apache/airavata/model/status/status.proto`
-- Create: `airavata-api/src/main/proto/org/apache/airavata/model/experiment/experiment.proto`
-
-- [ ] **Step 1: Create directories**
-
-```bash
-mkdir -p airavata-api/src/main/proto/org/apache/airavata/model/status
-mkdir -p airavata-api/src/main/proto/org/apache/airavata/model/experiment
-```
-
-- [ ] **Step 2: Write status.proto**
-
-Read `airavata-thrift-server/src/main/thrift/data-models/status_models.thrift` and translate all enums/structs. Example structure:
-
-```protobuf
-syntax = "proto3";
-
-package org.apache.airavata.model.status;
-
-option java_package = "org.apache.airavata.model.status";
-option java_outer_classname = "StatusProtos";
-option java_multiple_files = true;
-
-enum ExperimentState {
-    EXPERIMENT_STATE_UNKNOWN = 0;
-    CREATED = 1;
-    VALIDATED = 2;
-    SCHEDULED = 3;
-    LAUNCHED = 4;
-    EXECUTING = 5;
-    CANCELING = 6;
-    CANCELED = 7;
-    COMPLETED = 8;
-    FAILED = 9;
-}
-
-message ExperimentStatus {
-    ExperimentState state = 1;
-    int64 time_of_state_change = 2;
-    string reason = 3;
-    int64 status_id = 4;
-}
-
-enum JobState {
-    JOB_STATE_UNKNOWN = 0;
-    SUBMITTED = 1;
-    QUEUED = 2;
-    ACTIVE = 3;
-    COMPLETE = 4;
-    CANCELED = 5;
-    FAILED = 6;
-    SUSPENDED = 7;
-    NON_CRITICAL_FAIL = 8;
-}
-
-message JobStatus {
-    JobState job_state = 1;
-    int64 time_of_state_change = 2;
-    string reason = 3;
-    int64 status_id = 4;
-}
-
-enum ProcessState {
-    PROCESS_STATE_UNKNOWN = 0;
-    CREATED = 1;
-    VALIDATED = 2;
-    STARTED = 3;
-    PRE_PROCESSING = 4;
-    CONFIGURING_WORKSPACE = 5;
-    INPUT_DATA_STAGING = 6;
-    EXECUTING = 7;
-    MONITORING = 8;
-    OUTPUT_DATA_STAGING = 9;
-    POST_PROCESSING = 10;
-    COMPLETED = 11;
-    FAILED = 12;
-    CANCELLING = 13;
-    CANCELED = 14;
-}
-
-message ProcessStatus {
-    ProcessState state = 1;
-    int64 time_of_state_change = 2;
-    string reason = 3;
-    int64 status_id = 4;
-}
-
-enum TaskState {
-    TASK_STATE_UNKNOWN = 0;
-    CREATED = 1;
-    EXECUTING = 2;
-    COMPLETED = 3;
-    FAILED = 4;
-    CANCELED = 5;
-}
-
-message TaskStatus {
-    TaskState state = 1;
-    int64 time_of_state_change = 2;
-    string reason = 3;
-    int64 status_id = 4;
-}
-
-message QueueStatusModel {
-    string host_name = 1;
-    string queue_name = 2;
-    bool queue_up = 3;
-    int32 running_jobs = 4;
-    int32 queued_jobs = 5;
-    int64 time = 6;
-}
-```
-
-- [ ] **Step 3: Write experiment.proto**
-
-Read `airavata-thrift-server/src/main/thrift/data-models/experiment_model.thrift` and translate. Use imports for cross-references:
-
-```protobuf
-syntax = "proto3";
-
-package org.apache.airavata.model.experiment;
-
-option java_package = "org.apache.airavata.model.experiment";
-option java_outer_classname = "ExperimentProtos";
-option java_multiple_files = true;
-
-import "org/apache/airavata/model/commons/commons.proto";
-import "org/apache/airavata/model/status/status.proto";
-// import other model protos as needed for cross-references
-
-enum ExperimentType {
-    EXPERIMENT_TYPE_UNKNOWN = 0;
-    SINGLE_APPLICATION = 1;
-    WORKFLOW = 2;
-}
-
-enum ExperimentSearchFields {
-    EXPERIMENT_SEARCH_FIELDS_UNKNOWN = 0;
-    EXPERIMENT_NAME = 1;
-    EXPERIMENT_DESC = 2;
-    APPLICATION_ID = 3;
-    FROM_DATE = 4;
-    TO_DATE = 5;
-    STATUS = 6;
-    PROJECT_ID = 7;
-    USER_NAME = 8;
-    JOB_ID = 9;
-}
-
-enum ProjectSearchFields {
-    PROJECT_SEARCH_FIELDS_UNKNOWN = 0;
-    PROJECT_NAME = 1;
-    PROJECT_DESCRIPTION = 2;
-}
-
-// Continue translating all structs from experiment_model.thrift:
-// UserConfigurationDataModel, ExperimentModel, ExperimentSummaryModel, ExperimentStatistics
-// Follow the same field-number and naming conventions as workspace.proto
-```
-
-- [ ] **Step 4: Commit**
-
-```bash
-git add airavata-api/src/main/proto/
-git commit -m "feat: add status and experiment proto definitions"
-```
-
----
-
-### Task 11: Create remaining proto files
-
-**Files:**
-- Create: one `.proto` file per thrift data-model file (approximately 25 more)
-
-This is mechanical translation work. For each thrift file in `airavata-thrift-server/src/main/thrift/data-models/`, create a corresponding proto file following the exact pattern from Tasks 8-10.
-
-- [ ] **Step 1: Create all remaining proto files**
-
-Complete checklist of thrift files to translate (check off each):
-
-| Thrift File | Proto Output Path |
-|-------------|------------------|
-| `account_provisioning_model.thrift` | `org/apache/airavata/model/appcatalog/accountprovisioning/account_provisioning.proto` |
-| `application_deployment_model.thrift` | `org/apache/airavata/model/appcatalog/appdeployment/app_deployment.proto` |
-| `application_interface_model.thrift` | `org/apache/airavata/model/appcatalog/appinterface/app_interface.proto` |
-| `application_io_models.thrift` | `org/apache/airavata/model/application/io/application_io.proto` |
-| `compute_resource_model.thrift` | `org/apache/airavata/model/appcatalog/computeresource/compute_resource.proto` |
-| `credential_store_models.thrift` | `org/apache/airavata/model/credential/store/credential_store.proto` |
-| `data_movement_models.thrift` | `org/apache/airavata/model/data/movement/data_movement.proto` |
-| `gateway_groups_model.thrift` | `org/apache/airavata/model/appcatalog/gatewaygroups/gateway_groups.proto` |
-| `gateway_resource_profile_model.thrift` | `org/apache/airavata/model/appcatalog/gatewayprofile/gateway_profile.proto` |
-| `group_manager_model.thrift` | `org/apache/airavata/model/group/group_manager.proto` |
-| `group_resource_profile_model.thrift` | `org/apache/airavata/model/appcatalog/groupresourceprofile/group_resource_profile.proto` |
-| `job_model.thrift` | `org/apache/airavata/model/job/job.proto` |
-| `parallelism_model.thrift` | `org/apache/airavata/model/parallelism/parallelism.proto` |
-| `parser_model.thrift` | `org/apache/airavata/model/appcatalog/parser/parser.proto` |
-| `process_model.thrift` | `org/apache/airavata/model/process/process.proto` |
-| `replica_catalog_models.thrift` | `org/apache/airavata/model/data/replica/replica_catalog.proto` |
-| `scheduling_model.thrift` | `org/apache/airavata/model/scheduling/scheduling.proto` |
-| `sharing_models.thrift` | `org/apache/airavata/model/sharing/sharing.proto` |
-| `storage_resource_model.thrift` | `org/apache/airavata/model/appcatalog/storageresource/storage_resource.proto` |
-| `task_model.thrift` | `org/apache/airavata/model/task/task.proto` |
-| `tenant_profile_model.thrift` | `org/apache/airavata/model/tenant/tenant_profile.proto` |
-| `user_profile_model.thrift` | `org/apache/airavata/model/user/user_profile.proto` |
-| `user_resource_profile_model.thrift` | `org/apache/airavata/model/appcatalog/userresourceprofile/user_resource_profile.proto` |
-| `workflow_data_model.thrift` | `org/apache/airavata/model/workflow/data/workflow_data.proto` |
-| `workflow_model.thrift` | `org/apache/airavata/model/workflow/workflow.proto` |
-
-Also translate API-level thrift files:
-
-| Thrift File | Proto Output Path |
-|-------------|------------------|
-| `airavata_errors.thrift` | `org/apache/airavata/model/error/errors.proto` |
-| `db_event_model.thrift` | `org/apache/airavata/model/dbevent/db_event.proto` |
-| `messaging_events.thrift` | `org/apache/airavata/model/messaging/messaging_events.proto` |
-
-For each file:
-1. Read the thrift file
-2. Create the proto file following translation rules from Task 8
-3. Match field numbers to thrift field IDs
-4. Add `UNKNOWN = 0` sentinel to every enum
-5. Use `import` for cross-file references
-6. Set `java_package` matching the thrift `namespace java`
-
-- [ ] **Step 2: Commit**
-
-```bash
-git add airavata-api/src/main/proto/
-git commit -m "feat: add all remaining proto definitions mirroring thrift IDL"
-```
-
----
-
-### Task 12: Set up protoc compilation in airavata-api
-
-**Files:**
-- Modify: `airavata-api/pom.xml`
-
-- [ ] **Step 1: Add protobuf-java dependency**
-
-In `airavata-api/pom.xml` `<dependencies>` section, add:
-
-```xml
-    <dependency>
-      <groupId>com.google.protobuf</groupId>
-      <artifactId>protobuf-java</artifactId>
-    </dependency>
-```
-
-- [ ] **Step 2: Add protobuf-maven-plugin**
-
-In `airavata-api/pom.xml` `<build><plugins>` section, add the protobuf-maven-plugin (already in root POM's pluginManagement):
-
-```xml
-      <plugin>
-        <groupId>io.github.ascopes</groupId>
-        <artifactId>protobuf-maven-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>generate-proto-sources</id>
-            <goals>
-              <goal>generate</goal>
-            </goals>
-            <configuration>
-              <sourceDirectories>
-                <sourceDirectory>${project.basedir}/src/main/proto</sourceDirectory>
-              </sourceDirectories>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-```
-
-- [ ] **Step 3: Verify proto compilation**
-
-```bash
-mvn generate-sources -pl airavata-api --quiet 2>&1 | tail -10
-ls airavata-api/target/generated-sources/protobuf/java/org/apache/airavata/model/workspace/
-```
-
-Expected: generated Java files for the proto messages (Project.java, Gateway.java, etc.)
-
-- [ ] **Step 4: Full compile**
-
-```bash
-mvn compile -pl airavata-api --quiet 2>&1 | tail -5
-```
-
-Expected: BUILD SUCCESS — both thrift generated code and proto generated code compile.
-
-- [ ] **Step 5: Commit**
-
-```bash
-git add airavata-api/pom.xml
-git commit -m "build: add protobuf-java dependency and protoc compilation to airavata-api"
-```
-
----
-
-### Task 13: Add MapStruct to airavata-thrift-server
-
-**Files:**
-- Modify: `airavata-thrift-server/pom.xml`
-- Modify: `pom.xml` (root — add MapStruct to dependency management)
-
-- [ ] **Step 1: Add MapStruct to root pom.xml dependency management**
-
-In `pom.xml` root, add to `<dependencyManagement><dependencies>`:
-
-```xml
-            <dependency>
-                <groupId>org.mapstruct</groupId>
-                <artifactId>mapstruct</artifactId>
-                <version>1.6.3</version>
-            </dependency>
-            <dependency>
-                <groupId>org.mapstruct</groupId>
-                <artifactId>mapstruct-processor</artifactId>
-                <version>1.6.3</version>
-            </dependency>
-```
-
-- [ ] **Step 2: Add MapStruct dependency to airavata-thrift-server/pom.xml**
-
-Add to `<dependencies>`:
-
-```xml
-    <dependency>
-      <groupId>org.mapstruct</groupId>
-      <artifactId>mapstruct</artifactId>
-    </dependency>
-```
-
-Add to `<build><plugins>`:
-
-```xml
-    <build>
-      <plugins>
-        <plugin>
-          <groupId>org.apache.maven.plugins</groupId>
-          <artifactId>maven-compiler-plugin</artifactId>
-          <configuration>
-            <annotationProcessorPaths>
-              <path>
-                <groupId>org.mapstruct</groupId>
-                <artifactId>mapstruct-processor</artifactId>
-                <version>1.6.3</version>
-              </path>
-            </annotationProcessorPaths>
-          </configuration>
-        </plugin>
-      </plugins>
-    </build>
-```
-
-- [ ] **Step 3: Verify build**
-
-```bash
-mvn compile -pl airavata-thrift-server -am --quiet 2>&1 | tail -5
-```
-
-Expected: BUILD SUCCESS
-
-- [ ] **Step 4: Commit**
-
-```bash
-git add pom.xml airavata-thrift-server/pom.xml
-git commit -m "build: add MapStruct dependency to airavata-thrift-server"
-```
-
----
-
-### Task 14: Create representative MapStruct mapper
-
-**Files:**
-- Create: `airavata-thrift-server/src/main/java/org/apache/airavata/api/server/mapper/WorkspaceMapper.java`
-
-This establishes the mapper pattern. Full mapper coverage is built incrementally as services are converted to proto types (follow-up work).
-
-- [ ] **Step 1: Create mapper directory**
-
-```bash
-mkdir -p airavata-thrift-server/src/main/java/org/apache/airavata/api/server/mapper
-```
-
-- [ ] **Step 2: Write WorkspaceMapper.java**
-
-```java
-package org.apache.airavata.api.server.mapper;
-
-import org.apache.airavata.model.workspace.Gateway;
-import org.apache.airavata.model.workspace.Notification;
-import org.apache.airavata.model.workspace.Project;
-import org.mapstruct.Mapper;
-import org.mapstruct.Mapping;
-import org.mapstruct.factory.Mappers;
-
-/**
- * Maps between thrift-generated workspace types and proto-generated workspace types.
- *
- * <p>Thrift types use camelCase field accessors (getGatewayId()).
- * Proto types use snake_case with standard proto getters (getGatewayId()).
- * MapStruct handles the mapping automatically when accessor names match.
- */
-@Mapper
-public interface WorkspaceMapper {
-    WorkspaceMapper INSTANCE = Mappers.getMapper(WorkspaceMapper.class);
-
-    // --- Project ---
-    org.apache.airavata.model.workspace.ProjectProtos.Project toProto(Project thrift);
-    Project toThrift(org.apache.airavata.model.workspace.ProjectProtos.Project proto);
-
-    // --- Gateway ---
-    org.apache.airavata.model.workspace.WorkspaceProtos.Gateway toProtoGateway(Gateway thrift);
-    Gateway toThriftGateway(org.apache.airavata.model.workspace.WorkspaceProtos.Gateway proto);
-}
-```
-
-Note: The exact proto class names depend on how protoc generates them (based on `java_multiple_files`, `java_outer_classname`). Adjust class references after verifying proto compilation output from Task 12.
-
-- [ ] **Step 3: Verify compilation**
-
-```bash
-mvn compile -pl airavata-thrift-server -am --quiet 2>&1 | tail -10
-```
-
-If proto class names don't match, check `airavata-api/target/generated-sources/` for exact class names and update mapper imports.
-
-- [ ] **Step 4: Commit**
-
-```bash
-git add airavata-thrift-server/src/main/java/org/apache/airavata/api/server/mapper/
-git commit -m "feat: add representative MapStruct mapper for workspace types"
-```
-
----
-
-### Task 15: Final build verification and cleanup
-
-**Files:**
-- None (verification only)
-
-- [ ] **Step 1: Full project build**
-
-```bash
-mvn clean compile -T4 2>&1 | tail -20
-```
-
-Expected: BUILD SUCCESS for all modules
-
-- [ ] **Step 2: Run tests**
-
-```bash
-mvn test -pl airavata-api,airavata-thrift-server 2>&1 | tail -20
-```
-
-Expected: All tests pass
-
-- [ ] **Step 3: Verify no remaining handler references in airavata-api**
-
-```bash
-grep -r "AiravataServer\b" airavata-api/src/main/java/ --include="*.java" | grep -v "import"
-grep -r "AiravataServerHandler\b" airavata-api/src/main/java/ --include="*.java" | grep -v "import"
-```
-
-Expected: No matches (these classes have moved). If there are stale references, remove them.
-
-- [ ] **Step 4: Commit**
-
-```bash
-git add -A
-git commit -m "chore: final cleanup after thrift server extraction"
-```
-
----
-
-## Deferred Work (Future Plans)
-
-These items are explicitly out of scope for this plan but are required for full thrift removal from `airavata-api`:
-
-1. **Extract data access from Registry/Sharing/Credential handlers** — Create non-thrift interfaces in `airavata-api`, refactor services to use them, move handler implementations to `airavata-thrift-server`
-2. **Convert service signatures to proto types** — Change all 14 service classes to accept/return proto types instead of thrift types
-3. **Move thrift code generation to airavata-thrift-server** — Once `airavata-api` no longer imports any thrift types
-4. **Remove libthrift dependency from airavata-api** — Final step after all thrift references are eliminated
-5. **Full MapStruct mapper coverage** — One mapper per domain (experiment, credential, sharing, etc.)
diff --git a/docs/superpowers/specs/2026-03-26-airavata-thrift-server-extraction-design.md b/docs/superpowers/specs/2026-03-26-airavata-thrift-server-extraction-design.md
deleted file mode 100644
index c792b0596e..0000000000
--- a/docs/superpowers/specs/2026-03-26-airavata-thrift-server-extraction-design.md
+++ /dev/null
@@ -1,190 +0,0 @@
-# Airavata Thrift Server Extraction Design
-
-**Date:** 2026-03-26
-**Status:** Draft
-
-## Goal
-
-Extract all thrift server concerns from `airavata-api` into a new `airavata-thrift-server` Maven module. Introduce protobuf DTOs as the canonical data types in the service layer, making `airavata-api` completely thrift-free. MapStruct mappers in `airavata-thrift-server` bridge proto and thrift types at the handler boundary.
-
-## Repository Layout (Post-Extraction)
-
-```
-airavata/
-├── airavata-thrift-server/             # NEW — all thrift concerns
-│   ├── pom.xml
-│   └── src/main/
-│       ├── java/
-│       │   └── org/apache/airavata/
-│       │       ├── server/             # AiravataServer.java (TMultiplexedProcessor bootstrap)
-│       │       ├── handler/            # All 9 thrift handlers
-│       │       └── mapper/             # MapStruct mappers (proto <-> thrift)
-│       └── thrift/                     # Moved from thrift-interface-descriptions/
-│           ├── airavata-apis/
-│           ├── base-api/
-│           ├── data-models/
-│           ├── service-cpis/
-│           └── stubs_java.thrift (etc.)
-│
-├── airavata-api/                       # SLIMMED — services + background processes
-│   ├── pom.xml
-│   └── src/main/
-│       ├── java/
-│       │   └── org/apache/airavata/
-│       │       ├── service/            # Business logic services
-│       │       ├── repository/         # JPA data access
-│       │       └── background/         # Helix, monitors, event managers, etc.
-│       └── proto/                      # NEW — .proto files mirroring thrift IDL
-│           └── org/apache/airavata/
-│               ├── model/
-│               ├── api/
-│               ├── registry/
-│               ├── credential/
-│               ├── sharing/
-│               └── ...
-│
-├── modules/                            # Unchanged
-└── pom.xml                             # Root — adds airavata-thrift-server module
-```
-
-## Dependency Graph
-
-```
-airavata-thrift-server
-├── depends on: airavata-api (service layer + proto generated classes)
-├── depends on: libthrift (Apache Thrift runtime)
-├── owns: thrift IDL compilation -> generated thrift Java classes
-├── owns: MapStruct mappers (proto <-> thrift)
-└── owns: TMultiplexedProcessor, TThreadPoolServer bootstrap
-
-airavata-api
-├── depends on: protobuf-java (for generated proto classes)
-├── owns: service layer (o.a.a.service.*)
-├── owns: repository / data access layer
-├── owns: background services (Helix, monitors, event managers)
-├── owns: proto definitions + protoc generation
-└── NO thrift dependency
-```
-
-## Thrift Services Moved (All 9)
-
-| Service Name        | Handler Class                      |
-|---------------------|------------------------------------|
-| Airavata            | AiravataServerHandler              |
-| RegistryService     | RegistryServerHandler              |
-| SharingRegistry     | SharingRegistryServerHandler       |
-| CredentialStore     | CredentialStoreServerHandler       |
-| UserProfile         | UserProfileServiceHandler          |
-| TenantProfile       | TenantProfileServiceHandler        |
-| IamAdminServices    | IamAdminServicesHandler            |
-| GroupManager        | GroupManagerServiceHandler          |
-| Orchestrator        | OrchestratorServerHandler          |
-
-## What Stays in `airavata-api`
-
-### Services (refactored to proto types)
-All 14 business logic services: ExperimentService, ApplicationCatalogService, CredentialService, GatewayService, GatewayResourceProfileService, GroupResourceProfileService, NotificationService, ParserService, ProjectService, ResourceService, UserResourceProfileService, DataProductService, ResourceSharingService, SSHAccountService.
-
-### Data Access
-JPA entities, repositories, Hibernate config, database initialization (ExpCatalogDB, AppCatalogDB, ReplicaCatalogDB, WorkflowCatalogDB, CredentialStoreDB, UserProfileCatalogDB).
-
-### Background Services (retains its own main entry point)
-- DB Event Manager
-- Monitoring Server (Prometheus on port 9097)
-- Cluster Status Monitor
-- Data Interpreter
-- Process Rescheduler
-- Helix Controller/Participant
-- Workflow Managers (Pre/Post/Parser)
-- Job Monitors (Email + Real-time)
-
-### Removed from `airavata-api`
-- `AiravataServer.java`
-- All 9 thrift handler classes
-- Thrift IDL files
-- `libthrift` dependency
-- Thrift code generation Maven plugin config
-
-## Proto File Strategy
-
-Proto files mirror thrift IDL 1:1 — same fields, same nesting, same enums. Mechanical translation, not a redesign.
-
-### Conventions
-- Field numbers match thrift field IDs where possible
-- Proto uses `snake_case` per protobuf convention; MapStruct handles naming differences
-- No proto service definitions — only message types (no gRPC yet)
-- Package structure mirrors thrift: `org.apache.airavata.model.*`, `org.apache.airavata.api.*`, etc.
-- Generated via protoc Maven plugin in `airavata-api`
-
-### Example
-
-Thrift:
-```thrift
-struct ExperimentModel {
-    1: string experimentId,
-    2: string projectId,
-    3: string gatewayId,
-    4: ExperimentType experimentType,
-    5: string userName,
-    6: string experimentName,
-    7: i64 creationTime
-}
-```
-
-Proto:
-```protobuf
-message ExperimentModel {
-    string experiment_id = 1;
-    string project_id = 2;
-    string gateway_id = 3;
-    ExperimentType experiment_type = 4;
-    string user_name = 5;
-    string experiment_name = 6;
-    int64 creation_time = 7;
-}
-```
-
-## Handler Conversion Pattern
-
-```
-Thrift Client
-  -> TMultiplexedProcessor (airavata-thrift-server)
-    -> ThriftHandler.method(ThriftRequest)
-      -> MapStruct: ThriftRequest -> ProtoDTO
-        -> Service.method(ProtoDTO)          (airavata-api)
-        -> returns ProtoResult
-      -> MapStruct: ProtoResult -> ThriftResponse
-    -> returns ThriftResponse
-```
-
-### Example — createExperiment
-
-```java
-// In airavata-thrift-server: AiravataServerHandler
-public String createExperiment(AuthzToken authzToken, String gatewayId, ExperimentModel experiment) {
-    var protoExperiment = ExperimentMapper.INSTANCE.toProto(experiment);
-    var protoAuthz = AuthzTokenMapper.INSTANCE.toProto(authzToken);
-    var result = experimentService.createExperiment(protoAuthz, gatewayId, protoExperiment);
-    return result;
-}
-```
-
-### MapStruct Mapper
-
-```java
-@Mapper
-public interface ExperimentMapper {
-    ExperimentMapper INSTANCE = Mappers.getMapper(ExperimentMapper.class);
-    ExperimentProto toProto(ExperimentModel thriftModel);
-    ExperimentModel toThrift(ExperimentProto proto);
-}
-```
-
-## Build Order
-
-1. `airavata-api` builds first — compiles proto files, produces service classes + proto generated code
-2. `airavata-thrift-server` builds second — compiles thrift IDL, compiles handlers + mappers against airavata-api
-
-## Development
-
-Work is done in a git worktree off the main `airavata` repo for isolation. Thrift IDL content is unchanged (no SDK breakage). Thrift spec changes are a separate future effort.
diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml
new file mode 100644
index 0000000000..df6504700d
--- /dev/null
+++ b/integration-tests/pom.xml
@@ -0,0 +1,99 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.airavata</groupId>
+    <artifactId>airavata</artifactId>
+    <version>0.21-SNAPSHOT</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+
+  <artifactId>airavata-integration-tests</artifactId>
+  <packaging>jar</packaging>
+  <name>Airavata Integration Tests</name>
+
+  <dependencies>
+    <!-- Airavata API (thrift clients, model classes) -->
+    <dependency>
+      <groupId>org.apache.airavata</groupId>
+      <artifactId>airavata-api</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+
+    <!-- Thrift runtime -->
+    <dependency>
+      <groupId>org.apache.thrift</groupId>
+      <artifactId>libthrift</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+    <!-- MariaDB JDBC driver -->
+    <dependency>
+      <groupId>org.mariadb.jdbc</groupId>
+      <artifactId>mariadb-java-client</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+    <!-- RabbitMQ AMQP client -->
+    <dependency>
+      <groupId>com.rabbitmq</groupId>
+      <artifactId>amqp-client</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+    <!-- Kafka admin client -->
+    <dependency>
+      <groupId>org.apache.kafka</groupId>
+      <artifactId>kafka-clients</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+    <!-- JSON parsing for health endpoint responses -->
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-databind</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+    <!-- JUnit 5 -->
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <excludedGroups>integration</excludedGroups>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/integration-tests/src/test/java/org/apache/airavata/integration/BackgroundServiceHealthTest.java b/integration-tests/src/test/java/org/apache/airavata/integration/BackgroundServiceHealthTest.java
new file mode 100644
index 0000000000..e6ad95a15a
--- /dev/null
+++ b/integration-tests/src/test/java/org/apache/airavata/integration/BackgroundServiceHealthTest.java
@@ -0,0 +1,120 @@
+/**
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+package org.apache.airavata.integration;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Verifies that each expected background service is registered and reported
+ * by the {@code /health/services} endpoint.
+ *
+ * <p>The parameterized test asserts that every listed service name appears in
+ * the health response. Services are expected to match the labels used in
+ * {@code AiravataServer#startBackgroundServices()}.
+ */
+@Tag("integration")
+class BackgroundServiceHealthTest {
+
+    private static final String HOST = System.getProperty("airavata.monitoring.host", "localhost");
+    private static final int PORT = Integer.parseInt(System.getProperty("airavata.monitoring.port", "9097"));
+
+    private static final String HEALTH_URL = "http://" + HOST + ":" + PORT + "/health/services";
+
+    private static HttpClient httpClient;
+    private static ObjectMapper objectMapper;
+    private static JsonNode cachedRoot;
+
+    @BeforeAll
+    static void fetchHealthResponse() throws Exception {
+        httpClient =
+                HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
+        objectMapper = new ObjectMapper();
+
+        HttpRequest request = HttpRequest.newBuilder()
+                .uri(URI.create(HEALTH_URL))
+                .timeout(Duration.ofSeconds(10))
+                .GET()
+                .build();
+        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+        assertEquals(
+                200, response.statusCode(), "Could not reach /health/services — got HTTP " + response.statusCode());
+        JsonNode body = objectMapper.readTree(response.body());
+        cachedRoot = body.get("services");
+        assertNotNull(cachedRoot, "Expected 'services' key in /health/services response");
+    }
+
+    static Stream<String> expectedServices() {
+        return Stream.of(
+                "db_event_manager",
+                "monitoring_server",
+                "helix_controller",
+                "helix_participant",
+                "pre_workflow_manager",
+                "post_workflow_manager",
+                "parser_workflow_manager",
+                "realtime_monitor");
+    }
+
+    @ParameterizedTest(name = "{0} is registered")
+    @MethodSource("expectedServices")
+    void serviceShouldBeRegistered(String serviceName) {
+        assertNotNull(cachedRoot, "/health/services response was not fetched");
+        assertTrue(
+                cachedRoot.has(serviceName),
+                "Expected service '" + serviceName + "' to be registered, "
+                        + "but it was not found in the health response. "
+                        + "Registered services: " + cachedRoot.fieldNames());
+    }
+
+    @ParameterizedTest(name = "{0} is UP with positive uptime")
+    @MethodSource("expectedServices")
+    void registeredServiceShouldBeUp(String serviceName) {
+        assertNotNull(cachedRoot, "/health/services response was not fetched");
+
+        JsonNode serviceNode = cachedRoot.get(serviceName);
+        assertNotNull(serviceNode, "Service '" + serviceName + "' is not in the health response");
+
+        JsonNode statusNode = serviceNode.get("status");
+        assertNotNull(statusNode, "Service '" + serviceName + "' has no 'status' field");
+        assertEquals(
+                "UP", statusNode.asText(), "Service '" + serviceName + "' is not UP — status: " + statusNode.asText());
+
+        JsonNode uptimeNode = serviceNode.get("uptimeMs");
+        assertNotNull(uptimeNode, "Service '" + serviceName + "' has no 'uptimeMs' field");
+        assertTrue(
+                uptimeNode.asLong() > 0,
+                "Service '" + serviceName + "' is UP but uptimeMs is not positive: " + uptimeNode.asLong());
+    }
+}
diff --git a/integration-tests/src/test/java/org/apache/airavata/integration/DatabaseHealthTest.java b/integration-tests/src/test/java/org/apache/airavata/integration/DatabaseHealthTest.java
new file mode 100644
index 0000000000..23f80a290c
--- /dev/null
+++ b/integration-tests/src/test/java/org/apache/airavata/integration/DatabaseHealthTest.java
@@ -0,0 +1,79 @@
+/**
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+package org.apache.airavata.integration;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Verifies JDBC connectivity to the Airavata database and the presence of
+ * core tables required for experiment and resource management.
+ */
+@Tag("integration")
+class DatabaseHealthTest {
+
+    private static final String HOST = System.getProperty("airavata.db.host", "localhost");
+    private static final int PORT = Integer.parseInt(System.getProperty("airavata.db.port", "13306"));
+    private static final String DATABASE = System.getProperty("airavata.db.name", "airavata");
+    private static final String USER = System.getProperty("airavata.db.user", "airavata");
+    private static final String PASSWORD = System.getProperty("airavata.db.password", "123456");
+
+    private static final String JDBC_URL = "jdbc:mariadb://" + HOST + ":" + PORT + "/" + DATABASE;
+
+    static Stream<String> requiredTables() {
+        return Stream.of(
+                "EXPERIMENT",
+                "APPLICATION_DEPLOYMENT",
+                "COMPUTE_RESOURCE",
+                "GATEWAY",
+                "USER_PROFILE",
+                "SHARING_ENTITY",
+                "CREDENTIAL");
+    }
+
+    @ParameterizedTest(name = "table {0} exists")
+    @MethodSource("requiredTables")
+    void requiredTableShouldExist(String tableName) throws Exception {
+        try (Connection connection = DriverManager.getConnection(JDBC_URL, USER, PASSWORD)) {
+            assertTrue(connection.isValid(5), "Database connection is not valid");
+
+            DatabaseMetaData meta = connection.getMetaData();
+            List<String> found = new ArrayList<>();
+            try (ResultSet rs = meta.getTables(DATABASE, null, tableName, new String[] {"TABLE"})) {
+                while (rs.next()) {
+                    found.add(rs.getString("TABLE_NAME"));
+                }
+            }
+            assertTrue(
+                    found.stream().anyMatch(t -> t.equalsIgnoreCase(tableName)),
+                    "Expected table '" + tableName + "' was not found in database '" + DATABASE + "'");
+        }
+    }
+}
diff --git a/integration-tests/src/test/java/org/apache/airavata/integration/KafkaHealthTest.java b/integration-tests/src/test/java/org/apache/airavata/integration/KafkaHealthTest.java
new file mode 100644
index 0000000000..a63d4b73b2
--- /dev/null
+++ b/integration-tests/src/test/java/org/apache/airavata/integration/KafkaHealthTest.java
@@ -0,0 +1,57 @@
+/**
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+package org.apache.airavata.integration;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import java.util.Collection;
+import java.util.Properties;
+import java.util.concurrent.ExecutionException;
+import org.apache.kafka.clients.admin.AdminClient;
+import org.apache.kafka.clients.admin.AdminClientConfig;
+import org.apache.kafka.clients.admin.DescribeClusterResult;
+import org.apache.kafka.common.Node;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Verifies that the Kafka cluster is reachable and has at least one broker node.
+ */
+@Tag("integration")
+class KafkaHealthTest {
+
+    private static final String BOOTSTRAP_SERVERS =
+            System.getProperty("airavata.kafka.bootstrap.servers", "localhost:9092");
+
+    @Test
+    void kafkaClusterShouldHaveAtLeastOneBroker() throws ExecutionException, InterruptedException {
+        Properties config = new Properties();
+        config.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, BOOTSTRAP_SERVERS);
+        config.put(AdminClientConfig.REQUEST_TIMEOUT_MS_CONFIG, "10000");
+        config.put(AdminClientConfig.DEFAULT_API_TIMEOUT_MS_CONFIG, "10000");
+
+        try (AdminClient adminClient = AdminClient.create(config)) {
+            DescribeClusterResult result = adminClient.describeCluster();
+            Collection<Node> nodes = result.nodes().get();
+
+            assertFalse(nodes.isEmpty(), "Kafka cluster reported no broker nodes");
+        }
+    }
+}
diff --git a/integration-tests/src/test/java/org/apache/airavata/integration/KeycloakHealthTest.java b/integration-tests/src/test/java/org/apache/airavata/integration/KeycloakHealthTest.java
new file mode 100644
index 0000000000..5e99bb7083
--- /dev/null
+++ b/integration-tests/src/test/java/org/apache/airavata/integration/KeycloakHealthTest.java
@@ -0,0 +1,132 @@
+/**
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+package org.apache.airavata.integration;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Verifies that Keycloak is reachable, the OIDC discovery document is valid,
+ * and that a client-credentials token can be obtained for the test client.
+ */
+@Tag("integration")
+class KeycloakHealthTest {
+
+    private static final String BASE_URL = System.getProperty("airavata.keycloak.url", "http://localhost:18080");
+    private static final String REALM = System.getProperty("airavata.keycloak.realm", "default");
+    private static final String CLIENT_ID = System.getProperty("airavata.keycloak.client.id", "cs-jupyterlab");
+    private static final String CLIENT_SECRET =
+            System.getProperty("airavata.keycloak.client.secret", "DxeMtfiWU1qkDEmaGHf13RDahCujzhy1");
+
+    private static final String OIDC_URL = BASE_URL + "/realms/" + REALM + "/.well-known/openid-configuration";
+
+    private static HttpClient httpClient;
+    private static ObjectMapper objectMapper;
+
+    @BeforeAll
+    static void setUp() {
+        httpClient =
+                HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
+        objectMapper = new ObjectMapper();
+    }
+
+    @Test
+    void oidcDiscoveryShouldReturn200WithTokenEndpoint() throws Exception {
+        HttpRequest request = HttpRequest.newBuilder()
+                .uri(URI.create(OIDC_URL))
+                .timeout(Duration.ofSeconds(10))
+                .GET()
+                .build();
+
+        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+
+        assertEquals(
+                200,
+                response.statusCode(),
+                "Expected HTTP 200 from OIDC discovery endpoint, got " + response.statusCode());
+
+        JsonNode doc = objectMapper.readTree(response.body());
+        JsonNode tokenEndpoint = doc.get("token_endpoint");
+        assertNotNull(tokenEndpoint, "OIDC discovery document is missing 'token_endpoint'");
+        assertTrue(
+                tokenEndpoint.asText().startsWith("http"),
+                "token_endpoint is not a valid URL: " + tokenEndpoint.asText());
+    }
+
+    @Test
+    void clientCredentialsGrantShouldReturnAccessToken() throws Exception {
+        // First retrieve the token endpoint from the discovery document
+        HttpRequest discoveryRequest = HttpRequest.newBuilder()
+                .uri(URI.create(OIDC_URL))
+                .timeout(Duration.ofSeconds(10))
+                .GET()
+                .build();
+        HttpResponse<String> discoveryResponse =
+                httpClient.send(discoveryRequest, HttpResponse.BodyHandlers.ofString());
+        assertEquals(200, discoveryResponse.statusCode(), "Could not reach Keycloak OIDC discovery");
+
+        String tokenEndpoint = objectMapper
+                .readTree(discoveryResponse.body())
+                .get("token_endpoint")
+                .asText();
+
+        String form = "grant_type=" + encode("client_credentials")
+                + "&client_id=" + encode(CLIENT_ID)
+                + "&client_secret=" + encode(CLIENT_SECRET);
+
+        HttpRequest tokenRequest = HttpRequest.newBuilder()
+                .uri(URI.create(tokenEndpoint))
+                .timeout(Duration.ofSeconds(10))
+                .header("Content-Type", "application/x-www-form-urlencoded")
+                .POST(HttpRequest.BodyPublishers.ofString(form))
+                .build();
+
+        HttpResponse<String> tokenResponse = httpClient.send(tokenRequest, HttpResponse.BodyHandlers.ofString());
+
+        assertEquals(
+                200,
+                tokenResponse.statusCode(),
+                "client_credentials grant failed with status " + tokenResponse.statusCode() + ": "
+                        + tokenResponse.body());
+
+        JsonNode tokenDoc = objectMapper.readTree(tokenResponse.body());
+        JsonNode accessToken = tokenDoc.get("access_token");
+        assertNotNull(accessToken, "Token response is missing 'access_token'");
+        assertTrue(accessToken.asText().length() > 0, "access_token is empty");
+    }
+
+    private static String encode(String value) {
+        return URLEncoder.encode(value, StandardCharsets.UTF_8);
+    }
+}
diff --git a/integration-tests/src/test/java/org/apache/airavata/integration/PrometheusHealthTest.java b/integration-tests/src/test/java/org/apache/airavata/integration/PrometheusHealthTest.java
new file mode 100644
index 0000000000..2051b81439
--- /dev/null
+++ b/integration-tests/src/test/java/org/apache/airavata/integration/PrometheusHealthTest.java
@@ -0,0 +1,84 @@
+/**
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+package org.apache.airavata.integration;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Verifies that the Prometheus metrics endpoint served by {@code MonitoringServer}
+ * is reachable and returns valid Prometheus text format output.
+ */
+@Tag("integration")
+class PrometheusHealthTest {
+
+    private static final String HOST = System.getProperty("airavata.monitoring.host", "localhost");
+    private static final int PORT = Integer.parseInt(System.getProperty("airavata.monitoring.port", "9097"));
+
+    private static final String METRICS_URL = "http://" + HOST + ":" + PORT + "/metrics";
+
+    private static HttpClient httpClient;
+
+    @BeforeAll
+    static void setUp() {
+        httpClient =
+                HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
+    }
+
+    @Test
+    void metricsEndpointShouldReturn200() throws Exception {
+        HttpRequest request = HttpRequest.newBuilder()
+                .uri(URI.create(METRICS_URL))
+                .timeout(Duration.ofSeconds(10))
+                .GET()
+                .build();
+
+        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+
+        assertEquals(
+                200, response.statusCode(), "Expected HTTP 200 from /metrics endpoint, got " + response.statusCode());
+    }
+
+    @Test
+    void metricsEndpointShouldReturnPrometheusTextFormat() throws Exception {
+        HttpRequest request = HttpRequest.newBuilder()
+                .uri(URI.create(METRICS_URL))
+                .timeout(Duration.ofSeconds(10))
+                .GET()
+                .build();
+
+        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+        String body = response.body();
+
+        assertTrue(
+                body.contains("# HELP") || body.contains("# TYPE"),
+                "Expected Prometheus text format with '# HELP' or '# TYPE' markers, but body was: "
+                        + body.substring(0, Math.min(body.length(), 500)));
+    }
+}
diff --git a/integration-tests/src/test/java/org/apache/airavata/integration/RabbitMQHealthTest.java b/integration-tests/src/test/java/org/apache/airavata/integration/RabbitMQHealthTest.java
new file mode 100644
index 0000000000..0cdbc56b54
--- /dev/null
+++ b/integration-tests/src/test/java/org/apache/airavata/integration/RabbitMQHealthTest.java
@@ -0,0 +1,66 @@
+/**
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+package org.apache.airavata.integration;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Verifies AMQP connectivity to RabbitMQ and that the core Airavata exchanges
+ * are already declared (passive declare — does not create them).
+ */
+@Tag("integration")
+class RabbitMQHealthTest {
+
+    private static final String HOST = System.getProperty("airavata.rabbitmq.host", "localhost");
+    private static final int PORT = Integer.parseInt(System.getProperty("airavata.rabbitmq.port", "5672"));
+    private static final String USERNAME = System.getProperty("airavata.rabbitmq.username", "airavata");
+    private static final String PASSWORD = System.getProperty("airavata.rabbitmq.password", "airavata");
+    private static final String VHOST = System.getProperty("airavata.rabbitmq.vhost", "/");
+
+    static Stream<String> exchanges() {
+        return Stream.of("experiment_exchange", "process_exchange", "status_exchange");
+    }
+
+    @ParameterizedTest(name = "exchange {0} is declared")
+    @MethodSource("exchanges")
+    void exchangeShouldBeDeclared(String exchangeName) throws Exception {
+        ConnectionFactory factory = new ConnectionFactory();
+        factory.setHost(HOST);
+        factory.setPort(PORT);
+        factory.setUsername(USERNAME);
+        factory.setPassword(PASSWORD);
+        factory.setVirtualHost(VHOST);
+
+        try (Connection connection = factory.newConnection();
+                Channel channel = connection.createChannel()) {
+            assertDoesNotThrow(
+                    () -> channel.exchangeDeclarePassive(exchangeName),
+                    "Exchange '" + exchangeName + "' is not declared in RabbitMQ");
+        }
+    }
+}
diff --git a/integration-tests/src/test/java/org/apache/airavata/integration/ServiceHealthEndpointTest.java b/integration-tests/src/test/java/org/apache/airavata/integration/ServiceHealthEndpointTest.java
new file mode 100644
index 0000000000..886627a6c5
--- /dev/null
+++ b/integration-tests/src/test/java/org/apache/airavata/integration/ServiceHealthEndpointTest.java
@@ -0,0 +1,122 @@
+/**
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+package org.apache.airavata.integration;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import java.util.Iterator;
+import java.util.Map;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Verifies the {@code /health/services} endpoint on the MonitoringServer.
+ *
+ * <p>The endpoint returns a JSON object whose keys are service names and values are
+ * {@code ServiceStatus} objects with fields: {@code status} (UP/DOWN),
+ * {@code uptimeMs} (long), and {@code lastError} (string or null).
+ *
+ * <p>Every service that is UP must report a positive uptimeMs.
+ */
+@Tag("integration")
+class ServiceHealthEndpointTest {
+
+    private static final String HOST = System.getProperty("airavata.monitoring.host", "localhost");
+    private static final int PORT = Integer.parseInt(System.getProperty("airavata.monitoring.port", "9097"));
+
+    private static final String HEALTH_URL = "http://" + HOST + ":" + PORT + "/health/services";
+
+    private static HttpClient httpClient;
+    private static ObjectMapper objectMapper;
+
+    @BeforeAll
+    static void setUp() {
+        httpClient =
+                HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
+        objectMapper = new ObjectMapper();
+    }
+
+    @Test
+    void healthEndpointShouldReturn200() throws Exception {
+        HttpResponse<String> response = getServicesResponse();
+        assertEquals(
+                200, response.statusCode(), "Expected HTTP 200 from /health/services, got " + response.statusCode());
+    }
+
+    @Test
+    void healthResponseShouldBeNonEmptyJsonObject() throws Exception {
+        HttpResponse<String> response = getServicesResponse();
+        assertEquals(200, response.statusCode());
+
+        JsonNode root = objectMapper.readTree(response.body());
+        assertNotNull(root, "Response body could not be parsed as JSON");
+        assertTrue(root.isObject(), "Expected JSON object at root, got: " + root.getNodeType());
+        JsonNode services = root.get("services");
+        assertNotNull(services, "Expected 'services' key in response, got: " + root);
+        assertFalse(services.isEmpty(), "Expected at least one service entry in the response");
+    }
+
+    @Test
+    void allUpServicesShouldHavePositiveUptime() throws Exception {
+        HttpResponse<String> response = getServicesResponse();
+        assertEquals(200, response.statusCode());
+
+        JsonNode root = objectMapper.readTree(response.body());
+        JsonNode services = root.get("services");
+        assertNotNull(services, "Expected 'services' key in response");
+        Iterator<Map.Entry<String, JsonNode>> fields = services.fields();
+        while (fields.hasNext()) {
+            Map.Entry<String, JsonNode> entry = fields.next();
+            String serviceName = entry.getKey();
+            JsonNode serviceNode = entry.getValue();
+
+            JsonNode statusNode = serviceNode.get("status");
+            assertNotNull(statusNode, "Service '" + serviceName + "' is missing 'status' field");
+
+            if ("UP".equals(statusNode.asText())) {
+                JsonNode uptimeNode = serviceNode.get("uptimeMs");
+                assertNotNull(uptimeNode, "UP service '" + serviceName + "' is missing 'uptimeMs' field");
+                assertTrue(
+                        uptimeNode.asLong() > 0,
+                        "UP service '" + serviceName + "' has non-positive uptimeMs: " + uptimeNode.asLong());
+            }
+        }
+    }
+
+    private HttpResponse<String> getServicesResponse() throws Exception {
+        HttpRequest request = HttpRequest.newBuilder()
+                .uri(URI.create(HEALTH_URL))
+                .timeout(Duration.ofSeconds(10))
+                .GET()
+                .build();
+        return httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+    }
+}
diff --git a/integration-tests/src/test/java/org/apache/airavata/integration/ThriftServiceHealthTest.java b/integration-tests/src/test/java/org/apache/airavata/integration/ThriftServiceHealthTest.java
new file mode 100644
index 0000000000..e76d45e266
--- /dev/null
+++ b/integration-tests/src/test/java/org/apache/airavata/integration/ThriftServiceHealthTest.java
@@ -0,0 +1,115 @@
+/**
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+package org.apache.airavata.integration;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.util.stream.Stream;
+import org.apache.airavata.api.Airavata;
+import org.apache.airavata.credential.store.cpi.CredentialStoreService;
+import org.apache.airavata.orchestrator.cpi.OrchestratorService;
+import org.apache.airavata.registry.api.RegistryService;
+import org.apache.airavata.service.profile.groupmanager.cpi.GroupManagerService;
+import org.apache.airavata.service.profile.groupmanager.cpi.group_manager_cpiConstants;
+import org.apache.airavata.service.profile.iam.admin.services.cpi.IamAdminServices;
+import org.apache.airavata.service.profile.iam.admin.services.cpi.iam_admin_services_cpiConstants;
+import org.apache.airavata.service.profile.tenant.cpi.TenantProfileService;
+import org.apache.airavata.service.profile.tenant.cpi.profile_tenant_cpiConstants;
+import org.apache.airavata.service.profile.user.cpi.UserProfileService;
+import org.apache.airavata.service.profile.user.cpi.profile_user_cpiConstants;
+import org.apache.airavata.sharing.registry.service.cpi.SharingRegistryService;
+import org.apache.thrift.protocol.TBinaryProtocol;
+import org.apache.thrift.protocol.TMultiplexedProtocol;
+import org.apache.thrift.protocol.TProtocol;
+import org.apache.thrift.transport.TSocket;
+import org.apache.thrift.transport.TTransport;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Verifies that all 9 multiplexed Thrift services on port 8930 respond to
+ * getAPIVersion(). Each service is tested independently; a failure in one
+ * does not prevent the others from running.
+ */
+@Tag("integration")
+class ThriftServiceHealthTest {
+
+    private static final String HOST = System.getProperty("airavata.thrift.host", "localhost");
+    private static final int PORT = Integer.parseInt(System.getProperty("airavata.thrift.port", "8930"));
+
+    static Stream<String> serviceNames() {
+        return Stream.of(
+                "Airavata",
+                "RegistryService",
+                "SharingRegistry",
+                "CredentialStore",
+                profile_user_cpiConstants.USER_PROFILE_CPI_NAME,
+                profile_tenant_cpiConstants.TENANT_PROFILE_CPI_NAME,
+                iam_admin_services_cpiConstants.IAM_ADMIN_SERVICES_CPI_NAME,
+                group_manager_cpiConstants.GROUP_MANAGER_CPI_NAME,
+                "Orchestrator");
+    }
+
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("serviceNames")
+    void serviceShouldRespondToGetAPIVersion(String serviceName) throws Exception {
+        try (TTransport transport = new TSocket(HOST, PORT)) {
+            transport.open();
+            TProtocol base = new TBinaryProtocol(transport);
+            TMultiplexedProtocol multiplexed = new TMultiplexedProtocol(base, serviceName);
+
+            String version = callGetAPIVersion(serviceName, multiplexed);
+
+            assertNotNull(version, "getAPIVersion() returned null for service: " + serviceName);
+        }
+    }
+
+    /**
+     * Instantiates the appropriate generated client and calls getAPIVersion().
+     * Each generated client extends BaseAPI.Client which provides the method.
+     */
+    private String callGetAPIVersion(String serviceName, TProtocol protocol) throws Exception {
+        return assertDoesNotThrow(
+                () -> switch (serviceName) {
+                    case "Airavata" -> new Airavata.Client(protocol).getAPIVersion();
+                    case "RegistryService" -> new RegistryService.Client(protocol).getAPIVersion();
+                    case "SharingRegistry" -> new SharingRegistryService.Client(protocol).getAPIVersion();
+                    case "CredentialStore" -> new CredentialStoreService.Client(protocol).getAPIVersion();
+                    case "Orchestrator" -> new OrchestratorService.Client(protocol).getAPIVersion();
+                    default -> getProfileServiceVersion(serviceName, protocol);
+                },
+                "getAPIVersion() threw for service: " + serviceName);
+    }
+
+    private String getProfileServiceVersion(String serviceName, TProtocol protocol) throws Exception {
+        if (profile_user_cpiConstants.USER_PROFILE_CPI_NAME.equals(serviceName)) {
+            return new UserProfileService.Client(protocol).getAPIVersion();
+        } else if (profile_tenant_cpiConstants.TENANT_PROFILE_CPI_NAME.equals(serviceName)) {
+            return new TenantProfileService.Client(protocol).getAPIVersion();
+        } else if (iam_admin_services_cpiConstants.IAM_ADMIN_SERVICES_CPI_NAME.equals(serviceName)) {
+            return new IamAdminServices.Client(protocol).getAPIVersion();
+        } else if (group_manager_cpiConstants.GROUP_MANAGER_CPI_NAME.equals(serviceName)) {
+            return new GroupManagerService.Client(protocol).getAPIVersion();
+        }
+        throw new IllegalArgumentException("Unknown service: " + serviceName);
+    }
+}
diff --git a/modules/agent-framework/agent-service/pom.xml b/modules/agent-framework/agent-service/pom.xml
index 6d383f732d..53db664256 100644
--- a/modules/agent-framework/agent-service/pom.xml
+++ b/modules/agent-framework/agent-service/pom.xml
@@ -165,6 +165,10 @@ under the License.
         </exclusion>
       </exclusions>
     </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-actuator</artifactId>
+    </dependency>
   </dependencies>
 
   <build>
diff --git a/modules/agent-framework/agent-service/src/main/resources/application.yml b/modules/agent-framework/agent-service/src/main/resources/application.yml
index fab385e8ea..45de80b923 100644
--- a/modules/agent-framework/agent-service/src/main/resources/application.yml
+++ b/modules/agent-framework/agent-service/src/main/resources/application.yml
@@ -56,3 +56,9 @@ airavata:
     serverPort: 17000
     serverToken: airavata
     serverApiUrl: http://localhost:8000
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: health
diff --git a/modules/file-server/pom.xml b/modules/file-server/pom.xml
index 9e2d80ec31..0276417a25 100644
--- a/modules/file-server/pom.xml
+++ b/modules/file-server/pom.xml
@@ -54,6 +54,10 @@ under the License.
                 </exclusion>
             </exclusions>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/modules/file-server/src/main/resources/application.properties b/modules/file-server/src/main/resources/application.properties
index 9edf9c74e5..44675bb7ba 100644
--- a/modules/file-server/src/main/resources/application.properties
+++ b/modules/file-server/src/main/resources/application.properties
@@ -6,3 +6,5 @@ airavata.server.host=localhost
 airavata.server.port=8930
 
 server.port=8050
+
+management.endpoints.web.exposure.include=health
diff --git a/modules/ide-integration/.gitignore b/modules/ide-integration/.gitignore
deleted file mode 100644
index 5bd7a3df36..0000000000
--- a/modules/ide-integration/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-database_data/
-src/main/containers/pga/airavata-php-gateway/vendor
diff --git a/modules/ide-integration/README.md b/modules/ide-integration/README.md
deleted file mode 100644
index 40ab393d7d..0000000000
--- a/modules/ide-integration/README.md
+++ /dev/null
@@ -1,294 +0,0 @@
-# Apache Airavata - IDE Integration Setup
-
-<div align="center">
-  <h3>🚀 Complete Development Environment Setup for IntelliJ IDEA</h3>
-  <p>Set up a full Airavata installation inside IntelliJ IDEA for seamless development</p>
-</div>
-
----
-
-## 📋 Prerequisites
-
-Before starting, ensure you have the following installed on your system:
-
-| Tool | Version | Purpose | Installation Link |
-|------|---------|---------|-------------------|
-| 🐳 **Docker & Docker Compose** | Latest | Container orchestration | [Get Docker](https://docs.docker.com/compose/) |
-| 💡 **IntelliJ IDEA** | Latest | IDE with Java 17+ | [Download IDEA](https://www.jetbrains.com/idea/download/) |
-| ☕ **Java JDK** | 17+ | Runtime environment | [OpenJDK 17](https://openjdk.org/projects/jdk/17/) |
-| 🔧 **Apache Maven** | 3.8+ | Build tool | [Install Maven](https://maven.apache.org/install.html) |
-| 📝 **Git** | Latest | Version control | [Install Git](https://git-scm.com/downloads) |
-| 🐍 **Python** | 3.8+ | Django portal | [Python.org](https://www.python.org/downloads/) |
-| 📦 **Node.js & npm** | Latest LTS | Frontend build tools | [Node.js](https://nodejs.org/) |
-
-## 🏗️ Development Environment Setup
-
-### 1️⃣ Clone and Prepare Repository
-
-```bash
-# Clone the main repository
-git clone https://github.com/apache/airavata.git
-cd airavata
-
-# Build the project (this may take a few minutes)
-mvn clean install -DskipTests
-```
-
-### 2️⃣ Open in IntelliJ IDEA
-
-1. **Launch IntelliJ IDEA**
-2. **Open Project** → Navigate to your cloned `airavata` directory
-3. **Navigate to:** `modules` → `ide-integration` module
-
-## 🐳 Backend Services Setup
-
-### 3️⃣ Configure Host Resolution
-
-Add the following entry to your system's hosts file:
-
-**Linux/macOS:** `/etc/hosts`
-**Windows:** `C:\Windows\System32\drivers\etc\hosts`
-
-```bash
-127.0.0.1    airavata.host
-```
-
-### 4️⃣ Start Backend Services
-
-Navigate to the containers directory and start all required services:
-
-```bash
-cd .devcontainer
-docker-compose up -d
-```
-
-**Services Started:**
-- 🗄️ **MySQL Database**
-- 🔐 **Keycloak** (Authentication)
-- 📨 **Apache Kafka** (Messaging)
-- 🐰 **RabbitMQ** (Message Queue)
-- 🔒 **SSHD Server** (Secure connections)
-
-### 5️⃣ Initialize Database
-
-Apply database migrations:
-
-```bash
-cd .devcontainer
-cat ./database_scripts/init/*-migrations.sql | docker exec -i containers-db-1 mysql -p123456
-```
-
-## 🖥️ Starting Airavata Components
-
-### 6️⃣ Start API Server
-
-1. **Navigate to:** `org.apache.airavata.ide.integration.APIServerStarter`
-2. **Right-click** in the editor
-3. **Select:** `Run 'APIServerStarter.main()'`
-
-> 💡 **JDK 17+ Note:** Add this JVM argument in your run configuration:
-> ```
-> --add-opens java.base/java.lang=ALL-UNNAMED
-> ```
-
-### 7️⃣ Start Job Execution Engine
-
-1. **Navigate to:** `org.apache.airavata.ide.integration.JobEngineStarter`
-2. **Right-click** and select **Run**
-
-**Components Started:**
-- 🔄 Helix Controller
-- 👥 Helix Participant  
-- ⚙️ Pre Workflow Manager
-- 📋 Post Workflow Manager
-
-### 8️⃣ Start Job Monitoring
-
-#### **Setup Email Monitor (One-Time Setup)**
-
-1. **Create a Gmail Account**  [https://accounts.google.com/signup](https://accounts.google.com/signup)
-
-2. **Enable 2-Step Verification**  [https://myaccount.google.com/security](https://myaccount.google.com/security)
-
-3. **Go to App Passwords**  [https://myaccount.google.com/apppasswords](https://myaccount.google.com/apppasswords)  
-   *(Make sure you're logged in and have already enabled 2-Step Verification.)*
-
-4. **Generate App Password:**
-   - Enter the name **"Airavata"** and click **"Generate"**.
-
-5. **Copy the Generated App Password**  
-   - A 16 character password will appear **copy and save it immediately**, as it will not be shown again.
-
-5. **Update Configuration:**
-   Edit `src/main/resources/airavata-server.properties`:
-   ```properties
-   email.based.monitor.address=your-email@gmail.com
-   email.based.monitor.password=your-app-password
-   ```
-
-6. **Start Monitor:**
-   - Navigate to: `org.apache.airavata.ide.integration.JobMonitorStarter`
-   - Right-click and **Run**
-
-## 🌐 User Portal Setup (Django)
-
-### 9️⃣ Django Portal Installation
-
-**You can create and launch experiments and manage credentials using this portal.**
-
-```bash
-# Navigate outside the Airavata directory
-cd ..
-
-# Clone the Django portal repository
-git clone https://github.com/apache/airavata-portals.git
-cd airavata-portals/airavata-django-portal
-
-# Create a virtual environment
-python3 -m venv venv
-
-# Activate the virtual environment
-source venv/bin/activate  # For Windows: venv\Scripts\activate
-
-# Install Python dependencies
-pip install -r requirements.txt
-
-### 🔟 Configure Django Portal
-
-```bash
-# Create local settings
-cp django_airavata/settings_local.py.ide django_airavata/settings_local.py
-
-# Run database migrations
-python3 manage.py migrate
-
-# Build JavaScript components
-./build_js.sh
-
-# Load default CMS pages
-python3 manage.py load_default_gateway
-
-# Start development server
-python3 manage.py runserver
-```
-
-### 🌍 Access User Portal
-
-- **URL:** [http://localhost:8000/auth/login](http://localhost:8000/auth/login)
-- **Username:** `default-admin`
-- **Password:** `123456`
-
-## 🛠️ Admin Portal Setup (Optional)
-
-For registering compute resources and storage resources:
-
-### 1️⃣ Starting Super Admin Portal (PGA)
-
-**This portal is required when registering new compute or storage resources into the gateway.**
-
-```bash
-cd .devcontainer/pga
-docker-compose up -d
-```
-
-### 2️⃣ Configure Host Resolution
-
-**Get host machine IP:**
-
-**macOS:**
-```bash
-docker-compose exec pga getent hosts docker.for.mac.host.internal | awk '{ print $1 }'
-```
-
-**Windows:**
-```bash
-docker-compose exec pga getent hosts host.docker.internal
-```
-
-**Update container hosts:**
-*Replace <host-machine-ip> with the actual IP*
-```bash
-docker-compose exec pga /bin/sh -c "echo '<host-machine-ip> airavata.host' >> /etc/hosts"
-```
-
-### 3️⃣ Access Admin Portal
-
-- **URL:** [http://airavata.host:8008](http://airavata.host:8008)
-- **Username:** `default-admin`
-- **Password:** `123456`
-
-## 🛑 Cleanup & Troubleshooting
-
-### Stop All Services
-
-```bash
-# In each docker-compose directory, run:
-docker-compose down
-docker-compose rm -f
-
-# Remove unused containers and networks
-docker system prune
-```
-
-### 🔐 Certificate Renewal (If Expired)
-
-Only needed when Keycloak certificates expire:
-
-```bash
-cd modules/ide-integration/src/main/resources/keystores
-
-# Remove old keystore
-rm airavata.p12
-
-# Generate new keystore (airavata.p12)
-keytool -genkey -keyalg RSA -alias selfsigned -keystore airavata.p12 \
-        -storetype pkcs12 -storepass airavata -validity 360 -keysize 2048 \
-        -dname "CN=airavata.host,OU=airavata.host,O=airavata.host,L=airavata.host,ST=airavata.host,C=airavata.host"
-
-# Generate self-signed key-pair (for TLS)
-openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout server.key -out server.crt \
-  -subj "/CN=airavata.host/OU=airavata.host/O=airavata.host/L=airavata.host/ST=airavata.host/C=airavata.host" \
-  -addext "subjectAltName=DNS:airavata.host"
-```
-
-## 📊 Service Status Overview
-
-| Service | Port | Status Check | Purpose |
-|---------|------|-------------|---------|
-| 🗄️ **MySQL** | 3306 | `docker ps` | Database |
-| 🔐 **Keycloak** | 8443 | [airavata.host:8443](http://airavata.host:8443) | Authentication |
-| 📨 **Kafka** | 9092 | Internal | Messaging |
-| 🐰 **RabbitMQ** | 5672 | Internal | Message Queue |
-| 🌐 **Django Portal** | 8000 | [localhost:8000](http://localhost:8000) | User Interface |
-| 🛠️ **PGA Admin** | 8008 | [airavata.host:8008](http://airavata.host:8008) | Admin Portal |
-
-## 🆘 Common Issues
-
-**Port Conflicts:**
-```bash
-# Check what's using a port
-lsof -i :8000
-netstat -tulpn | grep :8000
-```
-
-**Docker Issues:**
-```bash
-# Reset Docker
-docker system prune -a
-docker-compose down --volumes
-```
-
-**Build Failures:**
-```bash
-# Clean Maven cache
-mvn clean
-rm -rf ~/.m2/repository/org/apache/airavata
-```
-
----
-
-<div align="center">
-  <strong>🎉 Happy Developing with Apache Airavata!</strong>
-  <br>
-  <em>Need help? Check our <a href="https://airavata.apache.org/mailing-list.html">mailing lists</a></em>
-</div>
\ No newline at end of file
diff --git a/modules/ide-integration/pom.xml b/modules/ide-integration/pom.xml
deleted file mode 100644
index 4da3035247..0000000000
--- a/modules/ide-integration/pom.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-<!--
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements. See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership. The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License. You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied. See the License for the
-specific language governing permissions and limitations
-under the License.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <parent>
-        <artifactId>airavata</artifactId>
-        <groupId>org.apache.airavata</groupId>
-        <version>0.21-SNAPSHOT</version>
-        <relativePath>../../pom.xml</relativePath>
-    </parent>
-    <modelVersion>4.0.0</modelVersion>
-
-    <artifactId>ide-integration</artifactId>
-    <name>Airavata IDE Integration</name>
-    <packaging>jar</packaging>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.apache.airavata</groupId>
-            <artifactId>airavata-api</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.airavata</groupId>
-            <artifactId>airavata-thrift-server</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.mariadb.jdbc</groupId>
-            <artifactId>mariadb-java-client</artifactId>
-        </dependency>
-    </dependencies>
-
-    <build>
-        <resources>
-            <resource>
-              <directory>../../keystores</directory>
-              <targetPath>keystores</targetPath>
-              <includes>
-                <include>*.jks</include>
-              </includes>
-            </resource>
-        </resources>
-    </build>
-
-</project>
diff --git a/modules/ide-integration/src/main/java/org/apache/airavata/ide/integration/APIServerStarter.java b/modules/ide-integration/src/main/java/org/apache/airavata/ide/integration/APIServerStarter.java
deleted file mode 100644
index b27f84141f..0000000000
--- a/modules/ide-integration/src/main/java/org/apache/airavata/ide/integration/APIServerStarter.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
-*
-* Licensed to the Apache Software Foundation (ASF) under one
-* or more contributor license agreements. See the NOTICE file
-* distributed with this work for additional information
-* regarding copyright ownership. The ASF licenses this file
-* to you under the Apache License, Version 2.0 (the
-* "License"); you may not use this file except in compliance
-* with the License. You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing,
-* software distributed under the License is distributed on an
-* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-* KIND, either express or implied. See the License for the
-* specific language governing permissions and limitations
-* under the License.
-*/
-package org.apache.airavata.ide.integration;
-
-import org.apache.airavata.api.server.AiravataServer;
-
-/**
- * IDE convenience launcher that starts the consolidated Airavata Thrift server.
- * The AiravataServer hosts all services (API, Registry, Orchestrator, Profile, etc.)
- * on a single multiplexed port and manages all background services internally.
- */
-public class APIServerStarter {
-
-    public static void main(String[] args) throws Exception {
-        new AiravataServer().start();
-    }
-}
diff --git a/modules/ide-integration/src/main/java/org/apache/airavata/ide/integration/JobEngineStarter.java b/modules/ide-integration/src/main/java/org/apache/airavata/ide/integration/JobEngineStarter.java
deleted file mode 100644
index c9945f18c0..0000000000
--- a/modules/ide-integration/src/main/java/org/apache/airavata/ide/integration/JobEngineStarter.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/**
-*
-* Licensed to the Apache Software Foundation (ASF) under one
-* or more contributor license agreements. See the NOTICE file
-* distributed with this work for additional information
-* regarding copyright ownership. The ASF licenses this file
-* to you under the Apache License, Version 2.0 (the
-* "License"); you may not use this file except in compliance
-* with the License. You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing,
-* software distributed under the License is distributed on an
-* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-* KIND, either express or implied. See the License for the
-* specific language governing permissions and limitations
-* under the License.
-*/
-package org.apache.airavata.ide.integration;
-
-import java.util.ArrayList;
-import org.apache.airavata.common.utils.ServerSettings;
-import org.apache.airavata.helix.core.AbstractTask;
-import org.apache.airavata.helix.impl.controller.HelixController;
-import org.apache.airavata.helix.impl.participant.GlobalParticipant;
-import org.apache.airavata.helix.impl.workflow.PostWorkflowManager;
-import org.apache.airavata.helix.impl.workflow.PreWorkflowManager;
-import org.apache.helix.manager.zk.ZKHelixAdmin;
-import org.apache.helix.manager.zk.ZNRecordSerializer;
-import org.apache.helix.manager.zk.ZkClient;
-
-/**
- * IDE convenience launcher for the Airavata job execution engine components.
- * IServer implementations run via Runnable.run() — each is started in its own thread.
- */
-public class JobEngineStarter {
-
-    public static void main(String args[]) throws Exception {
-
-        ZkClient zkClient = new ZkClient(
-                ServerSettings.getZookeeperConnection(),
-                ZkClient.DEFAULT_SESSION_TIMEOUT,
-                ZkClient.DEFAULT_CONNECTION_TIMEOUT,
-                new ZNRecordSerializer());
-        ZKHelixAdmin zkHelixAdmin = new ZKHelixAdmin(zkClient);
-
-        zkHelixAdmin.addCluster(ServerSettings.getSetting("helix.cluster.name"), true);
-
-        System.out.println("Starting Helix Controller .......");
-        HelixController controller = new HelixController();
-        new Thread(controller, "helix-controller").start();
-
-        ArrayList<Class<? extends AbstractTask>> taskClasses = new ArrayList<>();
-
-        for (String taskClassName : GlobalParticipant.TASK_CLASS_NAMES) {
-            taskClasses.add(Class.forName(taskClassName).asSubclass(AbstractTask.class));
-        }
-
-        System.out.println("Starting Helix Participant .......");
-        GlobalParticipant participant = new GlobalParticipant(taskClasses, null);
-        new Thread(participant, "helix-participant").start();
-
-        System.out.println("Starting Pre Workflow Manager .......");
-        PreWorkflowManager preWorkflowManager = new PreWorkflowManager();
-        new Thread(preWorkflowManager, "pre-workflow-manager").start();
-
-        System.out.println("Starting Post Workflow Manager .......");
-        PostWorkflowManager postWorkflowManager = new PostWorkflowManager();
-        new Thread(postWorkflowManager, "post-workflow-manager").start();
-    }
-}
diff --git a/modules/ide-integration/src/main/java/org/apache/airavata/ide/integration/JobMonitorStarter.java b/modules/ide-integration/src/main/java/org/apache/airavata/ide/integration/JobMonitorStarter.java
deleted file mode 100644
index a863b18440..0000000000
--- a/modules/ide-integration/src/main/java/org/apache/airavata/ide/integration/JobMonitorStarter.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
-*
-* Licensed to the Apache Software Foundation (ASF) under one
-* or more contributor license agreements. See the NOTICE file
-* distributed with this work for additional information
-* regarding copyright ownership. The ASF licenses this file
-* to you under the Apache License, Version 2.0 (the
-* "License"); you may not use this file except in compliance
-* with the License. You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing,
-* software distributed under the License is distributed on an
-* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-* KIND, either express or implied. See the License for the
-* specific language governing permissions and limitations
-* under the License.
-*/
-package org.apache.airavata.ide.integration;
-
-import org.apache.airavata.monitor.email.EmailBasedMonitor;
-
-/**
- * IDE convenience launcher for the email-based job monitor.
- * EmailBasedMonitor implements IServer (Runnable) — started in its own thread.
- */
-public class JobMonitorStarter {
-    public static void main(String args[]) throws Exception {
-        EmailBasedMonitor emailBasedMonitor = new EmailBasedMonitor();
-        new Thread(emailBasedMonitor, "email-monitor").start();
-    }
-}
diff --git a/modules/research-framework/research-service/pom.xml b/modules/research-framework/research-service/pom.xml
index 79deaf1ee8..1c799544a1 100644
--- a/modules/research-framework/research-service/pom.xml
+++ b/modules/research-framework/research-service/pom.xml
@@ -173,6 +173,10 @@ under the License.
         </exclusion>
       </exclusions>
     </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-actuator</artifactId>
+    </dependency>
   </dependencies>
 
   <build>
diff --git a/modules/research-framework/research-service/src/main/resources/application.yml b/modules/research-framework/research-service/src/main/resources/application.yml
index 914a0e29f0..64657d99e0 100644
--- a/modules/research-framework/research-service/src/main/resources/application.yml
+++ b/modules/research-framework/research-service/src/main/resources/application.yml
@@ -68,3 +68,9 @@ springdoc:
       use-pkce-with-authorization-code-grant: true
       client-id: data-catalog-portal
 
+management:
+  endpoints:
+    web:
+      exposure:
+        include: health
+
diff --git a/modules/restproxy/pom.xml b/modules/restproxy/pom.xml
index e941cb7afc..7a955a62de 100644
--- a/modules/restproxy/pom.xml
+++ b/modules/restproxy/pom.xml
@@ -51,6 +51,10 @@ under the License.
             <groupId>jakarta.annotation</groupId>
             <artifactId>jakarta.annotation-api</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/modules/restproxy/src/main/resources/application.properties b/modules/restproxy/src/main/resources/application.properties
index 0ce150e047..4c3005b8b6 100644
--- a/modules/restproxy/src/main/resources/application.properties
+++ b/modules/restproxy/src/main/resources/application.properties
@@ -1,2 +1,4 @@
 restproxy.broker.url=localhost:9092
 server.port=8082
+
+management.endpoints.web.exposure.include=health
diff --git a/pom.xml b/pom.xml
index 957f82c16a..0a13f3a6a5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -70,11 +70,11 @@ under the License.
     <modules>
         <module>airavata-api</module>
         <module>airavata-thrift-server</module>
+        <module>integration-tests</module>
         <module>modules/file-server</module>
         <module>modules/agent-framework/agent-service</module>
         <module>modules/research-framework/research-service</module>
         <module>modules/restproxy</module>
-        <module>modules/ide-integration</module>
     </modules>
 
     <properties>
@@ -480,6 +480,11 @@ under the License.
                 <artifactId>spring-boot-starter-data-jpa</artifactId>
                 <version>3.5.3</version>
             </dependency>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter-actuator</artifactId>
+                <version>3.5.3</version>
+            </dependency>
             <dependency>
                 <groupId>org.springframework.boot</groupId>
                 <artifactId>spring-boot-maven-plugin</artifactId>