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 2025/11/10 23:45:56 UTC
(airavata) 17/28: initial plumbing
This is an automated email from the ASF dual-hosted git repository.
yasith pushed a commit to branch resource-mgmt-rest-api
in repository https://gitbox.apache.org/repos/asf/airavata.git
commit bf7a091b719d2b2937c8e65ed00c81c6cce45933
Author: yasithdev <ya...@icloud.com>
AuthorDate: Thu Nov 6 13:48:00 2025 -0500
initial plumbing
---
modules/airavata-rest-api/README.md | 416 +++++++++++++++++++++
modules/airavata-rest-api/pom.xml | 124 ++++++
.../rest/api/AiravataRestApiApplication.java | 31 ++
.../airavata/rest/api/config/AuthzTokenFilter.java | 143 +++++++
.../airavata/rest/api/config/OpenApiConfig.java | 85 +++++
.../api/controller/ComputeResourceController.java | 215 +++++++++++
.../api/controller/StorageResourceController.java | 124 ++++++
.../rest/api/exception/GlobalExceptionHandler.java | 140 +++++++
.../rest/api/service/ComputeResourceService.java | 98 +++++
.../rest/api/service/StorageResourceService.java | 73 ++++
.../src/main/resources/application.properties | 21 ++
.../src/main/resources/log4j2.xml | 37 ++
pom.xml | 1 +
13 files changed, 1508 insertions(+)
diff --git a/modules/airavata-rest-api/README.md b/modules/airavata-rest-api/README.md
new file mode 100644
index 0000000000..b0af20cba4
--- /dev/null
+++ b/modules/airavata-rest-api/README.md
@@ -0,0 +1,416 @@
+# Airavata REST API
+
+REST API module for managing compute and storage resources in Airavata. This module provides REST endpoints that replace the Thrift API for resource management operations.
+
+## Overview
+
+The Airavata REST API provides HTTP/REST endpoints for:
+- **Compute Resources**: CRUD operations for compute resources, resource job managers (RJM), and batch queues
+- **Storage Resources**: CRUD operations for storage resources
+
+All endpoints use the same authorization logic as the Thrift servers and call handlers directly (no network calls).
+
+## Prerequisites
+
+- Java 17 or higher
+- Maven 3.8 or higher
+- Airavata API module built and available
+- Database configured (reuses OpenJPA from airavata-api)
+
+## Building
+
+Build the module from the root directory:
+
+```bash
+cd /path/to/airavata
+mvn clean install -pl modules/airavata-rest-api -am -DskipTests
+```
+
+Or build from the module directory:
+
+```bash
+cd modules/airavata-rest-api
+mvn clean install -DskipTests
+```
+
+## Running
+
+### Using Maven
+
+```bash
+cd modules/airavata-rest-api
+mvn spring-boot:run
+```
+
+The service will start on port 8080 by default.
+
+### Using JAR
+
+After building, run the generated JAR:
+
+```bash
+java -jar target/airavata-rest-api-0.21-SNAPSHOT.jar
+```
+
+### Configuration
+
+Server port and other settings can be configured in `src/main/resources/application.properties`:
+
+```properties
+server.port=8080
+spring.application.name=airavata-rest-api
+```
+
+## API Endpoints
+
+### Base URL
+
+All endpoints are prefixed with `/api/v1`
+
+### Compute Resources
+
+#### List all compute resources
+```http
+GET /api/v1/compute
+```
+
+Returns a map of compute resource IDs and hostnames.
+
+#### Get compute resource by ID
+```http
+GET /api/v1/compute/{id}
+```
+
+Returns detailed compute resource information.
+
+#### Register compute resource
+```http
+POST /api/v1/compute
+Content-Type: application/json
+
+{
+ "hostName": "example.host.edu",
+ "hostAliases": ["alias1.example.edu", "alias2.example.edu"],
+ "ipAddresses": ["192.168.1.1"],
+ "resourceDescription": "Example compute resource",
+ "enabled": true,
+ "cpusPerNode": 24,
+ "defaultNodeCount": 1,
+ "defaultCPUCount": 24,
+ "defaultWalltime": 3600,
+ "maxMemoryPerNode": 128
+}
+```
+
+Returns the created compute resource ID.
+
+#### Update compute resource
+```http
+PUT /api/v1/compute/{id}
+Content-Type: application/json
+
+{
+ "computeResourceId": "existing-resource-id",
+ "hostName": "example.host.edu",
+ "hostAliases": ["alias1.example.edu"],
+ "ipAddresses": ["192.168.1.1"],
+ "resourceDescription": "Updated description",
+ "enabled": true,
+ "cpusPerNode": 24,
+ "defaultNodeCount": 1,
+ "defaultCPUCount": 24,
+ "defaultWalltime": 3600
+}
+```
+
+#### Delete compute resource
+```http
+DELETE /api/v1/compute/{id}
+```
+
+### Resource Job Managers (RJM)
+
+#### Register resource job manager
+```http
+POST /api/v1/compute/{id}/rjm
+Content-Type: application/json
+
+{
+ "resourceJobManagerId": "DO_NOT_SET_AT_CLIENTS",
+ "resourceJobManagerType": "SLURM",
+ "jobManagerBinPath": "/usr/bin",
+ "jobManagerCommands": {
+ "SUBMISSION": "sbatch",
+ "JOB_MONITORING": "squeue",
+ "DELETION": "scancel"
+ },
+ "pushMonitoringEndpoint": null
+}
+```
+
+#### Get resource job manager
+```http
+GET /api/v1/compute/{id}/rjm/{rjmId}
+```
+
+#### Update resource job manager
+```http
+PUT /api/v1/compute/{id}/rjm/{rjmId}
+Content-Type: application/json
+
+{
+ "resourceJobManagerId": "existing-rjm-id",
+ "resourceJobManagerType": "SLURM",
+ "jobManagerBinPath": "/usr/bin",
+ "jobManagerCommands": {
+ "SUBMISSION": "sbatch",
+ "JOB_MONITORING": "squeue",
+ "DELETION": "scancel",
+ "CHECK_JOB": "scontrol show job"
+ },
+ "pushMonitoringEndpoint": null
+}
+```
+
+#### Delete resource job manager
+```http
+DELETE /api/v1/compute/{id}/rjm/{rjmId}
+```
+
+### Batch Queues
+
+#### Delete batch queue
+```http
+DELETE /api/v1/compute/{id}/queue/{queueName}
+```
+
+### Storage Resources
+
+#### List all storage resources
+```http
+GET /api/v1/storage
+```
+
+Returns a map of storage resource IDs and hostnames.
+
+#### Get storage resource by ID
+```http
+GET /api/v1/storage/{id}
+```
+
+Returns detailed storage resource information.
+
+#### Register storage resource
+```http
+POST /api/v1/storage
+Content-Type: application/json
+
+{
+ "storageResourceId": "DO_NOT_SET_AT_CLIENTS",
+ "hostName": "storage.host.edu",
+ "storageResourceDescription": "Example storage resource",
+ "enabled": true,
+ "dataMovementInterfaces": []
+}
+```
+
+Returns the created storage resource ID.
+
+#### Update storage resource
+```http
+PUT /api/v1/storage/{id}
+Content-Type: application/json
+
+{
+ "storageResourceId": "existing-storage-resource-id",
+ "hostName": "storage.host.edu",
+ "storageResourceDescription": "Updated description",
+ "enabled": true,
+ "dataMovementInterfaces": []
+}
+```
+
+#### Delete storage resource
+```http
+DELETE /api/v1/storage/{id}
+```
+
+## Authentication
+
+The API uses Bearer token authentication with claims. Include the following headers:
+
+```http
+Authorization: Bearer <access_token>
+X-Claims: {"userName": "username", "gatewayID": "gateway-id"}
+```
+
+The `X-Claims` header should be a JSON object containing:
+- `userName`: The username
+- `gatewayID`: The gateway identifier
+
+### Example with curl
+
+```bash
+curl -X GET http://localhost:8080/api/v1/compute \
+ -H "Authorization: Bearer your-token-here" \
+ -H "X-Claims: {\"userName\": \"user\", \"gatewayID\": \"gateway\"}"
+```
+
+**Note**: If TLS is not enabled in Airavata configuration, authentication may be optional. When TLS is enabled, authentication is required for all endpoints.
+
+## Swagger UI
+
+Interactive API documentation is available via Swagger UI:
+
+- **Swagger UI**: http://localhost:8080/swagger-ui/index.html
+- **OpenAPI Docs**: http://localhost:8080/v3/api-docs/public
+
+Swagger UI provides:
+- Interactive API testing
+- Complete endpoint documentation
+- Request/response schema definitions
+- Authentication testing
+
+## Error Handling
+
+The API returns standard HTTP status codes:
+
+- `200 OK` - Successful GET request
+- `201 Created` - Successful POST request
+- `204 No Content` - Successful PUT/DELETE request
+- `400 Bad Request` - Invalid request
+- `401 Unauthorized` - Authentication required or failed
+- `404 Not Found` - Resource not found
+- `500 Internal Server Error` - Server error
+
+Error responses include a JSON body with error details:
+
+```json
+{
+ "status": 400,
+ "error": "Invalid request",
+ "message": "Error details here",
+ "timestamp": 1234567890
+}
+```
+
+## Example Usage
+
+### Create a compute resource
+
+```bash
+curl -X POST http://localhost:8080/api/v1/compute \
+ -H "Content-Type: application/json" \
+ -H "Authorization: Bearer token" \
+ -H "X-Claims: {\"userName\": \"user\", \"gatewayID\": \"gateway\"}" \
+ -d '{
+ "hostName": "compute.example.edu",
+ "hostAliases": ["compute-alias.example.edu"],
+ "ipAddresses": ["192.168.1.100"],
+ "resourceDescription": "Example HPC cluster",
+ "enabled": true,
+ "cpusPerNode": 24,
+ "defaultNodeCount": 1,
+ "defaultCPUCount": 24,
+ "defaultWalltime": 3600,
+ "maxMemoryPerNode": 128
+ }'
+```
+
+### Get all compute resources
+
+```bash
+curl -X GET http://localhost:8080/api/v1/compute \
+ -H "Authorization: Bearer token" \
+ -H "X-Claims: {\"userName\": \"user\", \"gatewayID\": \"gateway\"}"
+```
+
+### Create a resource job manager
+
+```bash
+curl -X POST http://localhost:8080/api/v1/compute/{computeResourceId}/rjm \
+ -H "Content-Type: application/json" \
+ -H "Authorization: Bearer token" \
+ -H "X-Claims: {\"userName\": \"user\", \"gatewayID\": \"gateway\"}" \
+ -d '{
+ "resourceJobManagerId": "DO_NOT_SET_AT_CLIENTS",
+ "resourceJobManagerType": "SLURM",
+ "jobManagerBinPath": "/usr/bin",
+ "jobManagerCommands": {
+ "SUBMISSION": "sbatch",
+ "JOB_MONITORING": "squeue",
+ "DELETION": "scancel",
+ "CHECK_JOB": "scontrol show job"
+ }
+ }'
+```
+
+## Development
+
+### Project Structure
+
+```
+modules/airavata-rest-api/
+├── src/
+│ ├── main/
+│ │ ├── java/
+│ │ │ └── org/apache/airavata/rest/api/
+│ │ │ ├── AiravataRestApiApplication.java
+│ │ │ ├── config/
+│ │ │ │ ├── AuthzTokenFilter.java
+│ │ │ │ └── OpenApiConfig.java
+│ │ │ ├── controller/
+│ │ │ │ ├── ComputeResourceController.java
+│ │ │ │ └── StorageResourceController.java
+│ │ │ ├── service/
+│ │ │ │ ├── ComputeResourceService.java
+│ │ │ │ └── StorageResourceService.java
+│ │ │ └── exception/
+│ │ │ └── GlobalExceptionHandler.java
+│ │ └── resources/
+│ │ ├── application.properties
+│ │ └── log4j2.xml
+│ └── test/
+└── pom.xml
+```
+
+### Key Components
+
+- **Controllers**: REST endpoints for compute and storage resources
+- **Services**: Business logic that calls RegistryServerHandler directly
+- **AuthzTokenFilter**: Authentication and authorization filter
+- **OpenApiConfig**: Swagger/OpenAPI configuration
+- **GlobalExceptionHandler**: Exception to HTTP status code mapping
+
+## Compatibility
+
+This REST API is designed to be fully compatible with the existing Thrift API:
+- Same request/response models
+- Same authorization logic
+- Same database layer (reuses OpenJPA from airavata-api)
+- Direct handler calls (no network overhead)
+
+## Troubleshooting
+
+### Service won't start
+
+- Check that port 8080 is available
+- Verify airavata-api module is built
+- Check database configuration in airavata-api
+
+### Authentication errors
+
+- Verify TLS settings in Airavata configuration
+- Check that Authorization and X-Claims headers are properly formatted
+- Ensure the access token is valid
+
+### Endpoints not found
+
+- Verify the service is running on the correct port
+- Check that the path starts with `/api/v1`
+- Review Swagger UI to see available endpoints
+
+## License
+
+Licensed to the Apache Software Foundation (ASF) under the Apache License, Version 2.0.
+
diff --git a/modules/airavata-rest-api/pom.xml b/modules/airavata-rest-api/pom.xml
new file mode 100644
index 0000000000..b11c191aec
--- /dev/null
+++ b/modules/airavata-rest-api/pom.xml
@@ -0,0 +1,124 @@
+<!--
+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">
+ <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-rest-api</artifactId>
+ <name>Airavata REST API</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-to-slf4j</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-core</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-log4j2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-validation</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-to-slf4j</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-core</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.springdoc</groupId>
+ <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.airavata</groupId>
+ <artifactId>airavata-api</artifactId>
+ <version>${project.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-core</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <release>17</release>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <systemPropertyVariables>
+ <log4j2.configurationFile>src/main/resources/log4j2.xml</log4j2.configurationFile>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/modules/airavata-rest-api/src/main/java/org/apache/airavata/rest/api/AiravataRestApiApplication.java b/modules/airavata-rest-api/src/main/java/org/apache/airavata/rest/api/AiravataRestApiApplication.java
new file mode 100644
index 0000000000..ef651a4e04
--- /dev/null
+++ b/modules/airavata-rest-api/src/main/java/org/apache/airavata/rest/api/AiravataRestApiApplication.java
@@ -0,0 +1,31 @@
+/**
+ * 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.rest.api;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication(scanBasePackages = {"org.apache.airavata.rest.api"})
+public class AiravataRestApiApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(AiravataRestApiApplication.class, args);
+ }
+}
+
diff --git a/modules/airavata-rest-api/src/main/java/org/apache/airavata/rest/api/config/AuthzTokenFilter.java b/modules/airavata-rest-api/src/main/java/org/apache/airavata/rest/api/config/AuthzTokenFilter.java
new file mode 100644
index 0000000000..766a9c0256
--- /dev/null
+++ b/modules/airavata-rest-api/src/main/java/org/apache/airavata/rest/api/config/AuthzTokenFilter.java
@@ -0,0 +1,143 @@
+/**
+ * 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.rest.api.config;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.airavata.common.exception.ApplicationSettingsException;
+import org.apache.airavata.common.utils.Constants;
+import org.apache.airavata.common.utils.ServerSettings;
+import org.apache.airavata.model.error.AuthorizationException;
+import org.apache.airavata.model.security.AuthzToken;
+import org.apache.airavata.security.AiravataSecurityException;
+import org.apache.airavata.service.security.AiravataSecurityManager;
+import org.apache.airavata.service.security.IdentityContext;
+import org.apache.airavata.service.security.SecurityManagerFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+@Component
+public class AuthzTokenFilter extends OncePerRequestFilter {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(AuthzTokenFilter.class);
+
+ @Override
+ protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
+ String path = request.getRequestURI();
+ return path.startsWith("/swagger")
+ || path.startsWith("/v2/api-docs")
+ || path.startsWith("/v3/api-docs")
+ || path.startsWith("/swagger-ui")
+ || path.startsWith("/swagger-ui.html")
+ || path.startsWith("/swagger-resources")
+ || path.startsWith("/webjars/")
+ || path.equals("/actuator/health")
+ || path.equals("/");
+ }
+
+ @Override
+ protected void doFilterInternal(
+ HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+ throws ServletException, IOException {
+ try {
+ String authorizationHeader = request.getHeader("Authorization");
+ String xClaimsHeader = request.getHeader("X-Claims");
+
+ if (request.getMethod().equals("OPTIONS")) {
+ filterChain.doFilter(request, response);
+ return;
+ }
+
+ try {
+ boolean isAPISecured = ServerSettings.isTLSEnabled();
+
+ if (isAPISecured) {
+ // If API is secured, require authentication
+ if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ") || xClaimsHeader == null) {
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication required");
+ return;
+ }
+ }
+
+ if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ") && xClaimsHeader != null) {
+ String accessToken = authorizationHeader.substring(7); // Remove "Bearer " prefix
+ ObjectMapper objectMapper = new ObjectMapper();
+ Map<String, String> claimsMap = objectMapper.readValue(xClaimsHeader, new TypeReference<>() {});
+
+ AuthzToken authzToken = new AuthzToken();
+ authzToken.setAccessToken(accessToken);
+ authzToken.setClaimsMap(claimsMap);
+
+ // Validate authorization using SecurityInterceptor logic
+ authorize(authzToken, request.getMethod() + " " + request.getRequestURI());
+
+ // Set the user identity info in thread local for downstream execution
+ IdentityContext.set(authzToken);
+ }
+ } catch (AuthorizationException e) {
+ LOGGER.error("Authorization failed", e);
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "User is not authenticated or authorized.");
+ return;
+ } catch (ApplicationSettingsException e) {
+ LOGGER.error("Error checking security settings", e);
+ response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Internal server error");
+ return;
+ } catch (Exception e) {
+ LOGGER.error("Invalid authorization data", e);
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid authorization data");
+ return;
+ }
+
+ filterChain.doFilter(request, response);
+ } finally {
+ IdentityContext.unset();
+ }
+ }
+
+ private void authorize(AuthzToken authzToken, String action) throws AuthorizationException {
+ try {
+ boolean isAPISecured = ServerSettings.isTLSEnabled();
+ if (isAPISecured) {
+ AiravataSecurityManager securityManager = SecurityManagerFactory.getSecurityManager();
+ HashMap<String, String> metaDataMap = new HashMap<>();
+ metaDataMap.put(Constants.API_METHOD_NAME, action);
+ boolean isAuthz = securityManager.isUserAuthorized(authzToken, metaDataMap);
+ if (!isAuthz) {
+ throw new AuthorizationException("User is not authenticated or authorized.");
+ }
+ }
+ } catch (AiravataSecurityException e) {
+ LOGGER.error(e.getMessage(), e);
+ throw new AuthorizationException("Error in authenticating or authorizing user.");
+ } catch (ApplicationSettingsException e) {
+ LOGGER.error(e.getMessage(), e);
+ throw new AuthorizationException("Internal error in authenticating or authorizing user.");
+ }
+ }
+}
+
diff --git a/modules/airavata-rest-api/src/main/java/org/apache/airavata/rest/api/config/OpenApiConfig.java b/modules/airavata-rest-api/src/main/java/org/apache/airavata/rest/api/config/OpenApiConfig.java
new file mode 100644
index 0000000000..eac864e371
--- /dev/null
+++ b/modules/airavata-rest-api/src/main/java/org/apache/airavata/rest/api/config/OpenApiConfig.java
@@ -0,0 +1,85 @@
+/**
+ * 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.rest.api.config;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.media.StringSchema;
+import io.swagger.v3.oas.models.parameters.Parameter;
+import io.swagger.v3.oas.models.security.SecurityRequirement;
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import org.springdoc.core.customizers.OpenApiCustomizer;
+import org.springdoc.core.models.GroupedOpenApi;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class OpenApiConfig {
+
+ @Bean
+ public GroupedOpenApi publicApi() {
+ return GroupedOpenApi.builder()
+ .group("public")
+ .pathsToMatch("/api/**")
+ .addOpenApiCustomizer(globalHeaderCustomizer())
+ .build();
+ }
+
+ @Bean
+ public OpenAPI airavataRestApiOpenAPI() {
+ return new OpenAPI()
+ .info(new Info()
+ .title("Airavata REST API")
+ .description("REST API for managing compute and storage resources in Airavata")
+ .version("1.0.0")
+ .contact(new Contact()
+ .name("Apache Airavata")
+ .url("https://airavata.apache.org")))
+ .addSecurityItem(new SecurityRequirement().addList("Bearer"))
+ .components(new io.swagger.v3.oas.models.Components()
+ .addSecuritySchemes(
+ "Bearer",
+ new SecurityScheme()
+ .type(SecurityScheme.Type.HTTP)
+ .scheme("bearer")
+ .bearerFormat("JWT")
+ .description("Bearer token authentication. Include 'Bearer ' prefix.")));
+ }
+
+ @Bean
+ public OpenApiCustomizer globalHeaderCustomizer() {
+ return openApi -> {
+ Parameter claimsHeader = new Parameter()
+ .in("header")
+ .schema(new StringSchema())
+ .name("X-Claims")
+ .description("JSON object containing user claims: {\"userName\": \"...\", \"gatewayID\": \"...\"}")
+ .required(false);
+
+ openApi.getPaths().values().forEach(pathItem -> {
+ pathItem.readOperations().forEach(operation -> {
+ operation.addParametersItem(claimsHeader);
+ });
+ });
+ };
+ }
+}
+
+
diff --git a/modules/airavata-rest-api/src/main/java/org/apache/airavata/rest/api/controller/ComputeResourceController.java b/modules/airavata-rest-api/src/main/java/org/apache/airavata/rest/api/controller/ComputeResourceController.java
new file mode 100644
index 0000000000..e1ba90d229
--- /dev/null
+++ b/modules/airavata-rest-api/src/main/java/org/apache/airavata/rest/api/controller/ComputeResourceController.java
@@ -0,0 +1,215 @@
+/**
+ * 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.rest.api.controller;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import java.util.Map;
+import org.apache.airavata.model.appcatalog.computeresource.ComputeResourceDescription;
+import org.apache.airavata.model.appcatalog.computeresource.ResourceJobManager;
+import org.apache.airavata.rest.api.service.ComputeResourceService;
+import org.apache.thrift.TException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.apache.airavata.registry.api.exception.RegistryServiceException;
+
+@RestController
+@RequestMapping("/api/v1/compute")
+@Tag(name = "Compute Resources", description = "API for managing compute resources, resource job managers, and batch queues")
+public class ComputeResourceController {
+
+ private static final Logger logger = LoggerFactory.getLogger(ComputeResourceController.class);
+ private final ComputeResourceService computeResourceService;
+
+ public ComputeResourceController(ComputeResourceService computeResourceService) {
+ this.computeResourceService = computeResourceService;
+ }
+
+ @PostMapping
+ @Operation(summary = "Register a compute resource", description = "Creates a new compute resource and returns its ID")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "201", description = "Compute resource created successfully"),
+ @ApiResponse(responseCode = "400", description = "Invalid request"),
+ @ApiResponse(responseCode = "500", description = "Internal server error")
+ })
+ public ResponseEntity<String> registerComputeResource(
+ @Parameter(description = "Compute resource description", required = true)
+ @RequestBody ComputeResourceDescription computeResourceDescription)
+ throws RegistryServiceException, TException {
+ String computeResourceId = computeResourceService.registerComputeResource(computeResourceDescription);
+ return ResponseEntity.status(HttpStatus.CREATED).body(computeResourceId);
+ }
+
+ @GetMapping
+ @Operation(summary = "Get all compute resource names", description = "Returns a map of all compute resource IDs and their hostnames")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Successfully retrieved compute resources"),
+ @ApiResponse(responseCode = "500", description = "Internal server error")
+ })
+ public ResponseEntity<Map<String, String>> getAllComputeResourceNames()
+ throws RegistryServiceException, TException {
+ Map<String, String> computeResources = computeResourceService.getAllComputeResourceNames();
+ return ResponseEntity.ok(computeResources);
+ }
+
+ @GetMapping("/{id}")
+ @Operation(summary = "Get compute resource by ID", description = "Retrieves detailed information about a specific compute resource")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Successfully retrieved compute resource"),
+ @ApiResponse(responseCode = "404", description = "Compute resource not found"),
+ @ApiResponse(responseCode = "500", description = "Internal server error")
+ })
+ public ResponseEntity<ComputeResourceDescription> getComputeResource(
+ @Parameter(description = "Compute resource ID", required = true)
+ @PathVariable("id") String computeResourceId)
+ throws RegistryServiceException, TException {
+ ComputeResourceDescription computeResource = computeResourceService.getComputeResource(computeResourceId);
+ return ResponseEntity.ok(computeResource);
+ }
+
+ @PutMapping("/{id}")
+ @Operation(summary = "Update compute resource", description = "Updates an existing compute resource")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "204", description = "Compute resource updated successfully"),
+ @ApiResponse(responseCode = "400", description = "Invalid request"),
+ @ApiResponse(responseCode = "404", description = "Compute resource not found"),
+ @ApiResponse(responseCode = "500", description = "Internal server error")
+ })
+ public ResponseEntity<Void> updateComputeResource(
+ @Parameter(description = "Compute resource ID", required = true)
+ @PathVariable("id") String computeResourceId,
+ @Parameter(description = "Updated compute resource description", required = true)
+ @RequestBody ComputeResourceDescription computeResourceDescription)
+ throws RegistryServiceException, TException {
+ computeResourceService.updateComputeResource(computeResourceId, computeResourceDescription);
+ return ResponseEntity.noContent().build();
+ }
+
+ @DeleteMapping("/{id}")
+ @Operation(summary = "Delete compute resource", description = "Deletes a compute resource by ID")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "204", description = "Compute resource deleted successfully"),
+ @ApiResponse(responseCode = "404", description = "Compute resource not found"),
+ @ApiResponse(responseCode = "500", description = "Internal server error")
+ })
+ public ResponseEntity<Void> deleteComputeResource(
+ @Parameter(description = "Compute resource ID", required = true)
+ @PathVariable("id") String computeResourceId)
+ throws RegistryServiceException, TException {
+ computeResourceService.deleteComputeResource(computeResourceId);
+ return ResponseEntity.noContent().build();
+ }
+
+ @PostMapping("/{id}/rjm")
+ @Operation(summary = "Register resource job manager", description = "Creates a new resource job manager for a compute resource")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "201", description = "Resource job manager created successfully"),
+ @ApiResponse(responseCode = "400", description = "Invalid request"),
+ @ApiResponse(responseCode = "500", description = "Internal server error")
+ })
+ public ResponseEntity<String> registerResourceJobManager(
+ @Parameter(description = "Compute resource ID", required = true)
+ @PathVariable("id") String computeResourceId,
+ @Parameter(description = "Resource job manager description", required = true)
+ @RequestBody ResourceJobManager resourceJobManager)
+ throws RegistryServiceException, TException {
+ String rjmId = computeResourceService.registerResourceJobManager(resourceJobManager);
+ return ResponseEntity.status(HttpStatus.CREATED).body(rjmId);
+ }
+
+ @GetMapping("/{id}/rjm/{rjmId}")
+ @Operation(summary = "Get resource job manager", description = "Retrieves a resource job manager by ID")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Successfully retrieved resource job manager"),
+ @ApiResponse(responseCode = "404", description = "Resource job manager not found"),
+ @ApiResponse(responseCode = "500", description = "Internal server error")
+ })
+ public ResponseEntity<ResourceJobManager> getResourceJobManager(
+ @Parameter(description = "Compute resource ID", required = true)
+ @PathVariable("id") String computeResourceId,
+ @Parameter(description = "Resource job manager ID", required = true)
+ @PathVariable("rjmId") String resourceJobManagerId)
+ throws RegistryServiceException, TException {
+ ResourceJobManager rjm = computeResourceService.getResourceJobManager(resourceJobManagerId);
+ return ResponseEntity.ok(rjm);
+ }
+
+ @PutMapping("/{id}/rjm/{rjmId}")
+ @Operation(summary = "Update resource job manager", description = "Updates an existing resource job manager")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "204", description = "Resource job manager updated successfully"),
+ @ApiResponse(responseCode = "400", description = "Invalid request"),
+ @ApiResponse(responseCode = "404", description = "Resource job manager not found"),
+ @ApiResponse(responseCode = "500", description = "Internal server error")
+ })
+ public ResponseEntity<Void> updateResourceJobManager(
+ @Parameter(description = "Compute resource ID", required = true)
+ @PathVariable("id") String computeResourceId,
+ @Parameter(description = "Resource job manager ID", required = true)
+ @PathVariable("rjmId") String resourceJobManagerId,
+ @Parameter(description = "Updated resource job manager description", required = true)
+ @RequestBody ResourceJobManager updatedResourceJobManager)
+ throws RegistryServiceException, TException {
+ computeResourceService.updateResourceJobManager(resourceJobManagerId, updatedResourceJobManager);
+ return ResponseEntity.noContent().build();
+ }
+
+ @DeleteMapping("/{id}/rjm/{rjmId}")
+ @Operation(summary = "Delete resource job manager", description = "Deletes a resource job manager by ID")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "204", description = "Resource job manager deleted successfully"),
+ @ApiResponse(responseCode = "404", description = "Resource job manager not found"),
+ @ApiResponse(responseCode = "500", description = "Internal server error")
+ })
+ public ResponseEntity<Void> deleteResourceJobManager(
+ @Parameter(description = "Compute resource ID", required = true)
+ @PathVariable("id") String computeResourceId,
+ @Parameter(description = "Resource job manager ID", required = true)
+ @PathVariable("rjmId") String resourceJobManagerId)
+ throws RegistryServiceException, TException {
+ computeResourceService.deleteResourceJobManager(resourceJobManagerId);
+ return ResponseEntity.noContent().build();
+ }
+
+ @DeleteMapping("/{id}/queue/{queueName}")
+ @Operation(summary = "Delete batch queue", description = "Deletes a batch queue from a compute resource")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "204", description = "Batch queue deleted successfully"),
+ @ApiResponse(responseCode = "404", description = "Batch queue not found"),
+ @ApiResponse(responseCode = "500", description = "Internal server error")
+ })
+ public ResponseEntity<Void> deleteBatchQueue(
+ @Parameter(description = "Compute resource ID", required = true)
+ @PathVariable("id") String computeResourceId,
+ @Parameter(description = "Queue name", required = true)
+ @PathVariable("queueName") String queueName)
+ throws RegistryServiceException, TException {
+ computeResourceService.deleteBatchQueue(computeResourceId, queueName);
+ return ResponseEntity.noContent().build();
+ }
+}
+
diff --git a/modules/airavata-rest-api/src/main/java/org/apache/airavata/rest/api/controller/StorageResourceController.java b/modules/airavata-rest-api/src/main/java/org/apache/airavata/rest/api/controller/StorageResourceController.java
new file mode 100644
index 0000000000..608d09d972
--- /dev/null
+++ b/modules/airavata-rest-api/src/main/java/org/apache/airavata/rest/api/controller/StorageResourceController.java
@@ -0,0 +1,124 @@
+/**
+ * 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.rest.api.controller;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import java.util.Map;
+import org.apache.airavata.model.appcatalog.storageresource.StorageResourceDescription;
+import org.apache.airavata.rest.api.service.StorageResourceService;
+import org.apache.thrift.TException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.apache.airavata.registry.api.exception.RegistryServiceException;
+
+@RestController
+@RequestMapping("/api/v1/storage")
+@Tag(name = "Storage Resources", description = "API for managing storage resources")
+public class StorageResourceController {
+
+ private static final Logger logger = LoggerFactory.getLogger(StorageResourceController.class);
+ private final StorageResourceService storageResourceService;
+
+ public StorageResourceController(StorageResourceService storageResourceService) {
+ this.storageResourceService = storageResourceService;
+ }
+
+ @PostMapping
+ @Operation(summary = "Register a storage resource", description = "Creates a new storage resource and returns its ID")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "201", description = "Storage resource created successfully"),
+ @ApiResponse(responseCode = "400", description = "Invalid request"),
+ @ApiResponse(responseCode = "500", description = "Internal server error")
+ })
+ public ResponseEntity<String> registerStorageResource(
+ @Parameter(description = "Storage resource description", required = true)
+ @RequestBody StorageResourceDescription storageResourceDescription)
+ throws RegistryServiceException, TException {
+ String storageResourceId = storageResourceService.registerStorageResource(storageResourceDescription);
+ return ResponseEntity.status(HttpStatus.CREATED).body(storageResourceId);
+ }
+
+ @GetMapping
+ @Operation(summary = "Get all storage resource names", description = "Returns a map of all storage resource IDs and their hostnames")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Successfully retrieved storage resources"),
+ @ApiResponse(responseCode = "500", description = "Internal server error")
+ })
+ public ResponseEntity<Map<String, String>> getAllStorageResourceNames()
+ throws RegistryServiceException, TException {
+ Map<String, String> storageResources = storageResourceService.getAllStorageResourceNames();
+ return ResponseEntity.ok(storageResources);
+ }
+
+ @GetMapping("/{id}")
+ @Operation(summary = "Get storage resource by ID", description = "Retrieves detailed information about a specific storage resource")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Successfully retrieved storage resource"),
+ @ApiResponse(responseCode = "404", description = "Storage resource not found"),
+ @ApiResponse(responseCode = "500", description = "Internal server error")
+ })
+ public ResponseEntity<StorageResourceDescription> getStorageResource(
+ @Parameter(description = "Storage resource ID", required = true)
+ @PathVariable("id") String storageResourceId)
+ throws RegistryServiceException, TException {
+ StorageResourceDescription storageResource = storageResourceService.getStorageResource(storageResourceId);
+ return ResponseEntity.ok(storageResource);
+ }
+
+ @PutMapping("/{id}")
+ @Operation(summary = "Update storage resource", description = "Updates an existing storage resource")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "204", description = "Storage resource updated successfully"),
+ @ApiResponse(responseCode = "400", description = "Invalid request"),
+ @ApiResponse(responseCode = "404", description = "Storage resource not found"),
+ @ApiResponse(responseCode = "500", description = "Internal server error")
+ })
+ public ResponseEntity<Void> updateStorageResource(
+ @Parameter(description = "Storage resource ID", required = true)
+ @PathVariable("id") String storageResourceId,
+ @Parameter(description = "Updated storage resource description", required = true)
+ @RequestBody StorageResourceDescription storageResourceDescription)
+ throws RegistryServiceException, TException {
+ storageResourceService.updateStorageResource(storageResourceId, storageResourceDescription);
+ return ResponseEntity.noContent().build();
+ }
+
+ @DeleteMapping("/{id}")
+ @Operation(summary = "Delete storage resource", description = "Deletes a storage resource by ID")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "204", description = "Storage resource deleted successfully"),
+ @ApiResponse(responseCode = "404", description = "Storage resource not found"),
+ @ApiResponse(responseCode = "500", description = "Internal server error")
+ })
+ public ResponseEntity<Void> deleteStorageResource(
+ @Parameter(description = "Storage resource ID", required = true)
+ @PathVariable("id") String storageResourceId)
+ throws RegistryServiceException, TException {
+ storageResourceService.deleteStorageResource(storageResourceId);
+ return ResponseEntity.noContent().build();
+ }
+}
+
diff --git a/modules/airavata-rest-api/src/main/java/org/apache/airavata/rest/api/exception/GlobalExceptionHandler.java b/modules/airavata-rest-api/src/main/java/org/apache/airavata/rest/api/exception/GlobalExceptionHandler.java
new file mode 100644
index 0000000000..5d28c6735e
--- /dev/null
+++ b/modules/airavata-rest-api/src/main/java/org/apache/airavata/rest/api/exception/GlobalExceptionHandler.java
@@ -0,0 +1,140 @@
+/**
+ * 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.rest.api.exception;
+
+import org.apache.airavata.model.error.AuthorizationException;
+import org.apache.airavata.model.error.InvalidRequestException;
+import org.apache.airavata.registry.api.exception.RegistryServiceException;
+import org.apache.thrift.TException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+@ControllerAdvice
+public class GlobalExceptionHandler {
+
+ private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
+
+ @ExceptionHandler(AuthorizationException.class)
+ public ResponseEntity<ErrorResponse> handleAuthorizationException(AuthorizationException ex) {
+ logger.error("Authorization error", ex);
+ ErrorResponse errorResponse = new ErrorResponse(
+ HttpStatus.UNAUTHORIZED.value(), "Authorization failed", ex.getMessage(), System.currentTimeMillis());
+ return new ResponseEntity<>(errorResponse, HttpStatus.UNAUTHORIZED);
+ }
+
+ @ExceptionHandler(InvalidRequestException.class)
+ public ResponseEntity<ErrorResponse> handleInvalidRequestException(InvalidRequestException ex) {
+ logger.error("Invalid request", ex);
+ ErrorResponse errorResponse = new ErrorResponse(
+ HttpStatus.BAD_REQUEST.value(), "Invalid request", ex.getMessage(), System.currentTimeMillis());
+ return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
+ }
+
+ @ExceptionHandler(RegistryServiceException.class)
+ public ResponseEntity<ErrorResponse> handleRegistryServiceException(RegistryServiceException ex) {
+ logger.error("Registry service error", ex);
+ ErrorResponse errorResponse = new ErrorResponse(
+ HttpStatus.INTERNAL_SERVER_ERROR.value(),
+ "Registry service error",
+ ex.getMessage(),
+ System.currentTimeMillis());
+ return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+
+ @ExceptionHandler(TException.class)
+ public ResponseEntity<ErrorResponse> handleTException(TException ex) {
+ logger.error("Thrift exception", ex);
+ ErrorResponse errorResponse = new ErrorResponse(
+ HttpStatus.INTERNAL_SERVER_ERROR.value(),
+ "Internal server error",
+ ex.getMessage(),
+ System.currentTimeMillis());
+ return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+
+ @ExceptionHandler(IllegalStateException.class)
+ public ResponseEntity<ErrorResponse> handleIllegalStateException(IllegalStateException ex) {
+ logger.error("Illegal state", ex);
+ ErrorResponse errorResponse = new ErrorResponse(
+ HttpStatus.UNAUTHORIZED.value(), "Authentication required", ex.getMessage(), System.currentTimeMillis());
+ return new ResponseEntity<>(errorResponse, HttpStatus.UNAUTHORIZED);
+ }
+
+ @ExceptionHandler(Exception.class)
+ public ResponseEntity<ErrorResponse> handleGenericException(Exception ex) {
+ logger.error("Unexpected error", ex);
+ ErrorResponse errorResponse = new ErrorResponse(
+ HttpStatus.INTERNAL_SERVER_ERROR.value(),
+ "Internal server error",
+ ex.getMessage(),
+ System.currentTimeMillis());
+ return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+
+ public static class ErrorResponse {
+ private int status;
+ private String error;
+ private String message;
+ private long timestamp;
+
+ public ErrorResponse(int status, String error, String message, long timestamp) {
+ this.status = status;
+ this.error = error;
+ this.message = message;
+ this.timestamp = timestamp;
+ }
+
+ public int getStatus() {
+ return status;
+ }
+
+ public void setStatus(int status) {
+ this.status = status;
+ }
+
+ public String getError() {
+ return error;
+ }
+
+ public void setError(String error) {
+ this.error = error;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(long timestamp) {
+ this.timestamp = timestamp;
+ }
+ }
+}
+
diff --git a/modules/airavata-rest-api/src/main/java/org/apache/airavata/rest/api/service/ComputeResourceService.java b/modules/airavata-rest-api/src/main/java/org/apache/airavata/rest/api/service/ComputeResourceService.java
new file mode 100644
index 0000000000..8dc1724b7f
--- /dev/null
+++ b/modules/airavata-rest-api/src/main/java/org/apache/airavata/rest/api/service/ComputeResourceService.java
@@ -0,0 +1,98 @@
+/**
+ * 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.rest.api.service;
+
+import java.util.Map;
+import org.apache.airavata.model.appcatalog.computeresource.ComputeResourceDescription;
+import org.apache.airavata.model.appcatalog.computeresource.ResourceJobManager;
+import org.apache.airavata.model.security.AuthzToken;
+import org.apache.airavata.registry.api.exception.RegistryServiceException;
+import org.apache.airavata.registry.api.service.handler.RegistryServerHandler;
+import org.apache.airavata.service.security.IdentityContext;
+import org.apache.thrift.TException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ComputeResourceService {
+
+ private static final Logger logger = LoggerFactory.getLogger(ComputeResourceService.class);
+ private final RegistryServerHandler registryServerHandler;
+
+ public ComputeResourceService() {
+ this.registryServerHandler = new RegistryServerHandler();
+ }
+
+ public String registerComputeResource(ComputeResourceDescription computeResourceDescription)
+ throws RegistryServiceException, TException {
+ return registryServerHandler.registerComputeResource(computeResourceDescription);
+ }
+
+ public ComputeResourceDescription getComputeResource(String computeResourceId)
+ throws RegistryServiceException, TException {
+ return registryServerHandler.getComputeResource(computeResourceId);
+ }
+
+ public Map<String, String> getAllComputeResourceNames() throws RegistryServiceException, TException {
+ return registryServerHandler.getAllComputeResourceNames();
+ }
+
+ public boolean updateComputeResource(String computeResourceId, ComputeResourceDescription computeResourceDescription)
+ throws RegistryServiceException, TException {
+ return registryServerHandler.updateComputeResource(computeResourceId, computeResourceDescription);
+ }
+
+ public boolean deleteComputeResource(String computeResourceId) throws RegistryServiceException, TException {
+ return registryServerHandler.deleteComputeResource(computeResourceId);
+ }
+
+ public String registerResourceJobManager(ResourceJobManager resourceJobManager)
+ throws RegistryServiceException, TException {
+ return registryServerHandler.registerResourceJobManager(resourceJobManager);
+ }
+
+ public ResourceJobManager getResourceJobManager(String resourceJobManagerId)
+ throws RegistryServiceException, TException {
+ return registryServerHandler.getResourceJobManager(resourceJobManagerId);
+ }
+
+ public boolean updateResourceJobManager(String resourceJobManagerId, ResourceJobManager updatedResourceJobManager)
+ throws RegistryServiceException, TException {
+ return registryServerHandler.updateResourceJobManager(resourceJobManagerId, updatedResourceJobManager);
+ }
+
+ public boolean deleteResourceJobManager(String resourceJobManagerId) throws RegistryServiceException, TException {
+ return registryServerHandler.deleteResourceJobManager(resourceJobManagerId);
+ }
+
+ public boolean deleteBatchQueue(String computeResourceId, String queueName)
+ throws RegistryServiceException, TException {
+ return registryServerHandler.deleteBatchQueue(computeResourceId, queueName);
+ }
+
+ private AuthzToken getAuthzToken() {
+ AuthzToken authzToken = IdentityContext.get();
+ if (authzToken == null) {
+ throw new IllegalStateException("AuthzToken not found in IdentityContext");
+ }
+ return authzToken;
+ }
+}
+
diff --git a/modules/airavata-rest-api/src/main/java/org/apache/airavata/rest/api/service/StorageResourceService.java b/modules/airavata-rest-api/src/main/java/org/apache/airavata/rest/api/service/StorageResourceService.java
new file mode 100644
index 0000000000..34f13343b5
--- /dev/null
+++ b/modules/airavata-rest-api/src/main/java/org/apache/airavata/rest/api/service/StorageResourceService.java
@@ -0,0 +1,73 @@
+/**
+ * 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.rest.api.service;
+
+import java.util.Map;
+import org.apache.airavata.model.appcatalog.storageresource.StorageResourceDescription;
+import org.apache.airavata.model.security.AuthzToken;
+import org.apache.airavata.registry.api.exception.RegistryServiceException;
+import org.apache.airavata.registry.api.service.handler.RegistryServerHandler;
+import org.apache.airavata.service.security.IdentityContext;
+import org.apache.thrift.TException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+@Service
+public class StorageResourceService {
+
+ private static final Logger logger = LoggerFactory.getLogger(StorageResourceService.class);
+ private final RegistryServerHandler registryServerHandler;
+
+ public StorageResourceService() {
+ this.registryServerHandler = new RegistryServerHandler();
+ }
+
+ public String registerStorageResource(StorageResourceDescription storageResourceDescription)
+ throws RegistryServiceException, TException {
+ return registryServerHandler.registerStorageResource(storageResourceDescription);
+ }
+
+ public StorageResourceDescription getStorageResource(String storageResourceId)
+ throws RegistryServiceException, TException {
+ return registryServerHandler.getStorageResource(storageResourceId);
+ }
+
+ public Map<String, String> getAllStorageResourceNames() throws RegistryServiceException, TException {
+ return registryServerHandler.getAllStorageResourceNames();
+ }
+
+ public boolean updateStorageResource(String storageResourceId, StorageResourceDescription storageResourceDescription)
+ throws RegistryServiceException, TException {
+ return registryServerHandler.updateStorageResource(storageResourceId, storageResourceDescription);
+ }
+
+ public boolean deleteStorageResource(String storageResourceId) throws RegistryServiceException, TException {
+ return registryServerHandler.deleteStorageResource(storageResourceId);
+ }
+
+ private AuthzToken getAuthzToken() {
+ AuthzToken authzToken = IdentityContext.get();
+ if (authzToken == null) {
+ throw new IllegalStateException("AuthzToken not found in IdentityContext");
+ }
+ return authzToken;
+ }
+}
+
diff --git a/modules/airavata-rest-api/src/main/resources/application.properties b/modules/airavata-rest-api/src/main/resources/application.properties
new file mode 100644
index 0000000000..53c573ec0e
--- /dev/null
+++ b/modules/airavata-rest-api/src/main/resources/application.properties
@@ -0,0 +1,21 @@
+# Airavata REST API Configuration
+
+# Server Configuration
+server.port=8080
+
+# Application Configuration
+spring.application.name=airavata-rest-api
+
+# Logging Configuration
+logging.level.org.apache.airavata=DEBUG
+logging.level.org.springframework=INFO
+
+# SpringDoc OpenAPI Configuration
+springdoc.api-docs.enabled=true
+springdoc.api-docs.path=/v3/api-docs
+springdoc.swagger-ui.enabled=true
+springdoc.swagger-ui.path=/swagger-ui.html
+springdoc.swagger-ui.operationsSorter=alpha
+springdoc.swagger-ui.tagsSorter=alpha
+springdoc.swagger-ui.doc-expansion=none
+
diff --git a/modules/airavata-rest-api/src/main/resources/log4j2.xml b/modules/airavata-rest-api/src/main/resources/log4j2.xml
new file mode 100644
index 0000000000..d28e859bc9
--- /dev/null
+++ b/modules/airavata-rest-api/src/main/resources/log4j2.xml
@@ -0,0 +1,37 @@
+<?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.
+
+-->
+<Configuration status="WARN">
+ <Appenders>
+ <Console name="Console" target="SYSTEM_OUT">
+ <PatternLayout pattern="%d [%t] %-5p %c{30} %X - %m%n" />
+ </Console>
+ </Appenders>
+ <Loggers>
+ <logger name="ch.qos.logback" level="WARN" />
+ <logger name="org.apache.helix" level="WARN" />
+ <logger name="org.apache.zookeeper" level="ERROR" />
+ <logger name="org.apache.airavata" level="INFO" />
+ <logger name="org.hibernate" level="ERROR" />
+ <Root level="INFO">
+ <AppenderRef ref="Console" />
+ </Root>
+ </Loggers>
+</Configuration>
diff --git a/pom.xml b/pom.xml
index 2d860dc471..c778b2632c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -76,6 +76,7 @@ under the License.
<module>modules/registry-db-migrator</module>
<module>modules/registry-jpa-generator</module>
<module>modules/ide-integration</module>
+ <module>modules/airavata-rest-api</module>
</modules>
<properties>