From a4ddf4f1744f89509ab259d50bf0da6e5c50b06f Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Tue, 2 Apr 2024 09:40:45 -0400 Subject: [PATCH] [ALS-6222] Status endpoints now filters resources (#189) The database contains both open and auth hpds even if it wasn't deployed. This means we attempt to check for a resource that doesn't exist. This results in our service always showing degraded. --- .../harvard/dbmi/avillach/PicSureWarInit.java | 21 +- .../dbmi/avillach/service/SystemService.java | 296 ++++++++++-------- 2 files changed, 173 insertions(+), 144 deletions(-) diff --git a/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/PicSureWarInit.java b/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/PicSureWarInit.java index 95c07223..43356e1b 100644 --- a/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/PicSureWarInit.java +++ b/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/PicSureWarInit.java @@ -25,7 +25,10 @@ public class PicSureWarInit { @Resource(mappedName = "java:global/token_introspection_token") private String token_introspection_token; - //to be able to pre modified + @Resource(mappedName = "java:global/defaultApplicationUUID") + private String default_application_uuid; + + // to be able to pre modified public static final ObjectMapper objectMapper = new ObjectMapper(); // check the example from Apache HttpClient official website: @@ -39,14 +42,9 @@ public class PicSureWarInit { static { HTTP_CLIENT_CONNECTION_MANAGER = new PoolingHttpClientConnectionManager(); HTTP_CLIENT_CONNECTION_MANAGER.setMaxTotal(100); - CLOSEABLE_HTTP_CLIENT = HttpClients - .custom() - .setConnectionManager(HTTP_CLIENT_CONNECTION_MANAGER) - .useSystemProperties() - .build(); + CLOSEABLE_HTTP_CLIENT = HttpClients.custom().setConnectionManager(HTTP_CLIENT_CONNECTION_MANAGER).useSystemProperties().build(); } - public String getToken_introspection_url() { return token_introspection_url; } @@ -54,4 +52,13 @@ public String getToken_introspection_url() { public String getToken_introspection_token() { return token_introspection_token; } + + /** + * This method is used to get the default application UUID. This value is either the open or auth hpds resource UUID. + * + * @return the default application UUID + */ + public String getDefaultApplicationUUID() { + return this.default_application_uuid; + } } diff --git a/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/SystemService.java b/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/SystemService.java index 2d6328d9..a9c8fcb2 100755 --- a/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/SystemService.java +++ b/pic-sure-api-war/src/main/java/edu/harvard/dbmi/avillach/service/SystemService.java @@ -1,137 +1,159 @@ -package edu.harvard.dbmi.avillach.service; -import static edu.harvard.dbmi.avillach.util.Utilities.buildHttpClientContext; - -import java.io.IOException; -import java.util.List; - -import javax.annotation.PostConstruct; -import javax.inject.Inject; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; - -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.util.EntityUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -import edu.harvard.dbmi.avillach.PicSureWarInit; -import edu.harvard.dbmi.avillach.data.entity.Resource; -import edu.harvard.dbmi.avillach.data.repository.ResourceRepository; -import edu.harvard.dbmi.avillach.domain.GeneralQueryRequest; -import edu.harvard.dbmi.avillach.domain.ResourceInfo; -import edu.harvard.dbmi.avillach.util.exception.ApplicationException; - -@Path("/system") -public class SystemService { - static int max_test_frequency = 60000; - - static final String RUNNING = "RUNNING"; - - static final String ONE_OR_MORE_COMPONENTS_DEGRADED = "ONE OR MORE COMPONENTS DEGRADED"; - - Logger logger = LoggerFactory.getLogger(SystemService.class); - - @Inject - PicSureWarInit picSureWarInit; - - String lastStatus = "UNTESTED"; - long lastStatusCheck = 0l; - - @Inject - ResourceRepository resourceRepo; - - String token_introspection_url; - String token_introspection_token; - - @PostConstruct - public void init() { - token_introspection_url = picSureWarInit.getToken_introspection_url(); - token_introspection_token = picSureWarInit.getToken_introspection_token(); - if(token_introspection_url == null || token_introspection_token == null) { - throw new RuntimeException( - "token_introspection_url and token_introspection_token not configured"); - } - } - - @GET - @Path("/status") - @Produces("text/plain") - public String status() { - // Because there is no auth on this service we limit actually performing the checking to 1 per minute to avoid DOS scenarios. - long timeOfRequest = System.currentTimeMillis(); - if(timeOfRequest-lastStatusCheck < max_test_frequency) { - return lastStatus; - }else { - lastStatusCheck = timeOfRequest; - try{ - List resourcesToTest = resourceRepo.list(); - if( resourcesToTest != null && // This proves the MySQL database is serving queries - !resourcesToTest.isEmpty() && // This proves at least one resources is configured - testPSAMAResponds() && // This proves we can perform token introspection - testResourcesRespond(resourcesToTest) ){ // This proves all resources are at least serving info requests. - lastStatus = RUNNING; - return lastStatus; - }else { - lastStatus = ONE_OR_MORE_COMPONENTS_DEGRADED; - } - }catch(Exception e) { - e.printStackTrace(); - lastStatus = ONE_OR_MORE_COMPONENTS_DEGRADED; - } - return lastStatus; - } - } - - private boolean testPSAMAResponds() throws UnsupportedOperationException, IOException { - CloseableHttpClient client = PicSureWarInit.CLOSEABLE_HTTP_CLIENT; - ObjectMapper json = PicSureWarInit.objectMapper; - - HttpPost post = new HttpPost(token_introspection_url); - post.setEntity(new StringEntity("{}")); - post.setHeader("Content-Type", "application/json"); - //Authorize into the token introspection endpoint - post.setHeader("Authorization", "Bearer " + token_introspection_token); - CloseableHttpResponse response = null; - try { - response = client.execute(post, buildHttpClientContext()); - if (response.getStatusLine().getStatusCode() != 200){ - logger.error("callTokenIntroEndpoint() error back from token intro host server [" - + token_introspection_url + "]: " + EntityUtils.toString(response.getEntity())); - throw new ApplicationException("Token Introspection host server return " + response.getStatusLine().getStatusCode() + - ". Please see the log"); - } - JsonNode responseContent = json.readTree(response.getEntity().getContent()); - if (!responseContent.get("active").asBoolean()){ - // This is actually the expected response as we did not send a token in the token_introspection_request. - return true; - } - - return true; - } finally { - try { - if (response != null) - response.close(); - } catch (IOException ex) { - logger.error("callTokenIntroEndpoint() IOExcpetion when closing http response: " + ex.getMessage()); - } - } - } - - private boolean testResourcesRespond(List resourcesToTest) { - for(Resource resource : resourcesToTest) { - ResourceInfo info = new ResourceWebClient().info(resource.getResourceRSPath(), new GeneralQueryRequest()); - if(info==null) { - return false; - } - } - return true; - } -} - +package edu.harvard.dbmi.avillach.service; + +import static edu.harvard.dbmi.avillach.util.Utilities.buildHttpClientContext; + +import java.io.IOException; +import java.util.List; +import java.util.UUID; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; + +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import edu.harvard.dbmi.avillach.PicSureWarInit; +import edu.harvard.dbmi.avillach.data.entity.Resource; +import edu.harvard.dbmi.avillach.data.repository.ResourceRepository; +import edu.harvard.dbmi.avillach.domain.GeneralQueryRequest; +import edu.harvard.dbmi.avillach.domain.ResourceInfo; +import edu.harvard.dbmi.avillach.util.exception.ApplicationException; + +@Path("/system") +public class SystemService { + static int max_test_frequency = 60000; + + static final String RUNNING = "RUNNING"; + + static final String ONE_OR_MORE_COMPONENTS_DEGRADED = "ONE OR MORE COMPONENTS DEGRADED"; + + Logger logger = LoggerFactory.getLogger(SystemService.class); + + @Inject + PicSureWarInit picSureWarInit; + + String lastStatus = "UNTESTED"; + long lastStatusCheck = 0l; + + @Inject + ResourceRepository resourceRepo; + + String token_introspection_url; + String token_introspection_token; + + String defaultApplicationUUID; + + @PostConstruct + public void init() { + token_introspection_url = picSureWarInit.getToken_introspection_url(); + token_introspection_token = picSureWarInit.getToken_introspection_token(); + defaultApplicationUUID = picSureWarInit.getDefaultApplicationUUID(); + if (token_introspection_url == null || token_introspection_token == null) { + throw new RuntimeException("token_introspection_url and token_introspection_token not configured"); + } + } + + @GET + @Path("/status") + @Produces("text/plain") + public String status() { + // Because there is no auth on this service we limit actually performing the checking to 1 per minute to avoid DOS scenarios. + long timeOfRequest = System.currentTimeMillis(); + if (timeOfRequest - lastStatusCheck < max_test_frequency) { + return lastStatus; + } else { + lastStatusCheck = timeOfRequest; + try { + List resourcesToTest = resourceRepo.list(); + if (resourcesToTest == null || resourcesToTest.isEmpty()) { + lastStatus = ONE_OR_MORE_COMPONENTS_DEGRADED; + return lastStatus; + } + + // convert the default application uuid to an uuid object + UUID defaultApplicationUUID = UUID.fromString(this.defaultApplicationUUID); + + // We need to remove open or auth HPDS from the resource list depending on the environment deployed. + // This because both are included in the database, but only one is actually deployed. + // if the name contains hpds and is not the default application uuid, remove it. + resourcesToTest.removeIf( + resource -> resource.getName().toLowerCase().contains("hpds") && !resource.getUuid().equals(defaultApplicationUUID) + ); + + // This proves the MySQL database is serving queries + // This proves at least one resources is configured + // This proves we can perform token introspection + if (testPSAMAResponds() && testResourcesRespond(resourcesToTest)) { // This proves all resources are at least serving info + // requests. + lastStatus = RUNNING; + return lastStatus; + } else { + lastStatus = ONE_OR_MORE_COMPONENTS_DEGRADED; + } + } catch (Exception e) { + e.printStackTrace(); + lastStatus = ONE_OR_MORE_COMPONENTS_DEGRADED; + } + return lastStatus; + } + } + + private boolean testPSAMAResponds() throws UnsupportedOperationException, IOException { + CloseableHttpClient client = PicSureWarInit.CLOSEABLE_HTTP_CLIENT; + ObjectMapper json = PicSureWarInit.objectMapper; + + HttpPost post = new HttpPost(token_introspection_url); + post.setEntity(new StringEntity("{}")); + post.setHeader("Content-Type", "application/json"); + // Authorize into the token introspection endpoint + post.setHeader("Authorization", "Bearer " + token_introspection_token); + CloseableHttpResponse response = null; + try { + response = client.execute(post, buildHttpClientContext()); + if (response.getStatusLine().getStatusCode() != 200) { + logger.error( + "callTokenIntroEndpoint() error back from token intro host server [" + token_introspection_url + "]: " + + EntityUtils.toString(response.getEntity()) + ); + throw new ApplicationException( + "Token Introspection host server return " + response.getStatusLine().getStatusCode() + ". Please see the log" + ); + } + JsonNode responseContent = json.readTree(response.getEntity().getContent()); + if (!responseContent.get("active").asBoolean()) { + // This is actually the expected response as we did not send a token in the token_introspection_request. + return true; + } + + return true; + } finally { + try { + if (response != null) response.close(); + } catch (IOException ex) { + logger.error("callTokenIntroEndpoint() IOExcpetion when closing http response: " + ex.getMessage()); + } + } + } + + private boolean testResourcesRespond(List resourcesToTest) { + for (Resource resource : resourcesToTest) { + ResourceInfo info = new ResourceWebClient().info(resource.getResourceRSPath(), new GeneralQueryRequest()); + if (info == null) { + return false; + } + } + return true; + } +} +