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 902e23ce7c08527590d38de52572666f12f40f85 Author: yasithdev <[email protected]> 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 edafd19681..14dabd6a59 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>
