Skip to content

Commit

Permalink
add functionality to get wds client set up and working
Browse files Browse the repository at this point in the history
add tests for wds service and client changes

move sam configuration block into its own section in application yml
  • Loading branch information
Jose Soto committed Nov 28, 2023
1 parent 9f9e8c1 commit 7d26e4d
Show file tree
Hide file tree
Showing 23 changed files with 613 additions and 33 deletions.
1 change: 1 addition & 0 deletions service/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ dependencies {
// terra clients
implementation 'org.broadinstitute.dsde.workbench:sam-client_2.12:0.1-61135c7'
implementation "org.broadinstitute.dsde.workbench:leonardo-client_2.13:1.3.6-66d9fcf"
implementation 'org.databiosphere:workspacedataservice-client-okhttp-javax:0.2.104-SNAPSHOT'

liquibaseRuntime 'org.liquibase:liquibase-core:3.10.0'
liquibaseRuntime 'info.picocli:picocli:4.6.1'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "pipelines.sam")
public record SamConfiguration(String basePath) {}
@ConfigurationProperties(prefix = "sam")
public record SamConfiguration(String baseUri) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package bio.terra.pipelines.app.configuration.external;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "wds")
public record WdsServerConfiguration(String apiV, Boolean debugApiLogging) {}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public JobsApiController(
private static final Logger logger = LoggerFactory.getLogger(JobsApiController.class);

private SamUser getAuthenticatedInfo() {
return samUserFactory.from(request, samConfiguration.basePath());
return samUserFactory.from(request, samConfiguration.baseUri());
}

// -- Jobs --
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package bio.terra.pipelines.dependencies.leonardo;
package bio.terra.pipelines.dependencies.common;

import bio.terra.common.exception.ErrorReportException;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package bio.terra.pipelines.dependencies.common;

public interface HealthCheckWorkspaceApps {

record Result(boolean isOk, String message) {}

Result checkHealth(String baseUri, String accessToken);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package bio.terra.pipelines.dependencies.leonardo;

import bio.terra.pipelines.app.configuration.external.LeonardoServerConfiguration;
import bio.terra.pipelines.dependencies.common.DependencyNotAvailableException;
import java.time.OffsetDateTime;
import java.util.*;
import java.util.stream.Collectors;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@
public class LeonardoService implements HealthCheck {

private final LeonardoClient leonardoClient;

private final AppUtils appUtils;
private final RetryTemplate listenerResetRetryTemplate;

@Autowired
public LeonardoService(LeonardoClient leonardoClient, RetryTemplate listenerResetRetryTemplate) {
public LeonardoService(
LeonardoClient leonardoClient, AppUtils appUtils, RetryTemplate listenerResetRetryTemplate) {
this.leonardoClient = leonardoClient;
this.appUtils = appUtils;
this.listenerResetRetryTemplate = listenerResetRetryTemplate;
}

Expand All @@ -41,6 +45,10 @@ public List<ListAppResponse> getApps(String workspaceId, String authToken, boole
getAppsApi(authToken).listAppsV2(workspaceId, null, null, null, creatorRoleSpecifier));
}

public String getWdsUrlFromApps(String workspaceId, String authToken, boolean creatorOnly) {
return appUtils.findUrlForWds(getApps(workspaceId, authToken, creatorOnly), workspaceId);
}

@Override
public Result checkHealth() {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ private ApiClient getApiClient() {
.newBuilder()
.addInterceptor(new OkHttpClientTracingInterceptor(Tracing.getTracer()))
.build();
return new ApiClient().setHttpClient(okHttpClientWithTracing).setBasePath(samConfig.basePath());
return new ApiClient().setHttpClient(okHttpClientWithTracing).setBasePath(samConfig.baseUri());
}

UsersApi usersApi(String accessToken) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package bio.terra.pipelines.dependencies.wds;

import bio.terra.pipelines.app.configuration.external.WdsServerConfiguration;
import bio.terra.pipelines.dependencies.common.DependencyNotAvailableException;
import okhttp3.OkHttpClient;
import org.databiosphere.workspacedata.api.GeneralWdsInformationApi;
import org.databiosphere.workspacedata.api.RecordsApi;
import org.databiosphere.workspacedata.api.SchemaApi;
import org.databiosphere.workspacedata.client.ApiClient;
import org.springframework.stereotype.Component;

@Component
public class WdsClient {

private final OkHttpClient singletonHttpClient;
private final WdsServerConfiguration wdsServerConfiguration;

public WdsClient(WdsServerConfiguration wdsServerConfiguration) {
this.wdsServerConfiguration = wdsServerConfiguration;
singletonHttpClient = new ApiClient().getHttpClient().newBuilder().build();
}

protected ApiClient getApiClient(String wdsBaseUri, String accessToken)
throws DependencyNotAvailableException {

ApiClient apiClient = new ApiClient().setBasePath(wdsBaseUri);
apiClient.setHttpClient(singletonHttpClient);
apiClient.addDefaultHeader("Authorization", "Bearer " + accessToken);
// By closing the connection after each request, we avoid the problem of the open connection
// being force-closed ungracefully by the Azure Relay/Listener infrastructure:
apiClient.addDefaultHeader("Connection", "close");
apiClient.setDebugging(wdsServerConfiguration.debugApiLogging());
return apiClient;
}

RecordsApi recordsApi(String wdsBaseUri, String accessToken)
throws DependencyNotAvailableException {
return new RecordsApi(getApiClient(wdsBaseUri, accessToken));
}

GeneralWdsInformationApi generalWdsInformationApi(String wdsBaseUri, String accessToken)
throws DependencyNotAvailableException {
return new GeneralWdsInformationApi(getApiClient(wdsBaseUri, accessToken));
}

SchemaApi schemaApi(String wdsBaseUri, String accessToken)
throws DependencyNotAvailableException {
return new SchemaApi(getApiClient(wdsBaseUri, accessToken));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package bio.terra.pipelines.dependencies.wds;

import bio.terra.pipelines.app.configuration.external.WdsServerConfiguration;
import bio.terra.pipelines.dependencies.common.DependencyNotAvailableException;
import bio.terra.pipelines.dependencies.common.HealthCheckWorkspaceApps;
import java.util.List;
import java.util.Objects;
import org.databiosphere.workspacedata.client.ApiException;
import org.databiosphere.workspacedata.model.RecordRequest;
import org.databiosphere.workspacedata.model.RecordResponse;
import org.databiosphere.workspacedata.model.RecordTypeSchema;
import org.databiosphere.workspacedata.model.StatusResponse;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.stereotype.Service;

/** class to encapsulate interacting with WDS client */
@Service
public class WdsService implements HealthCheckWorkspaceApps {

private final WdsClient wdsClient;
private final WdsServerConfiguration wdsServerConfiguration;
private final RetryTemplate listenerResetRetryTemplate;

public WdsService(
WdsClient wdsClient,
WdsServerConfiguration wdsServerConfiguration,
RetryTemplate listenerResetRetryTemplate) {
this.wdsClient = wdsClient;
this.wdsServerConfiguration = wdsServerConfiguration;
this.listenerResetRetryTemplate = listenerResetRetryTemplate;
}

/**
* query for a record from WDS table
*
* @param wdsBaseUri - URI of WDS app, retrieved from Leonardo
* @param bearerToken - SA token, retrieved from google default credentials
* @param recordType - WDS table name
* @param workspaceId - workspace id WDS app is deployed in
* @param recordId - id for the record
* @return - returns Record details
* @throws WdsServiceException ;
*/
public RecordResponse getRecord(
String wdsBaseUri, String bearerToken, String recordType, String workspaceId, String recordId)
throws WdsServiceException {
return executionWithRetryTemplate(
listenerResetRetryTemplate,
() ->
wdsClient
.recordsApi(wdsBaseUri, bearerToken)
.getRecord(workspaceId, wdsServerConfiguration.apiV(), recordType, recordId));
}

/**
* Update a record in a table
*
* @param wdsBaseUri - URI of WDS app, retrieved from Leonardo
* @param bearerToken - SA token, retrieved from google default credentials
* @param request - request for what fields to update
* @param workspaceId - workspace id WDS app is deployed in
* @param type - WDS table name
* @param id - id of record to update
* @return - the updated record
* @throws WdsServiceException ;
*/
public RecordResponse updateRecord(
String wdsBaseUri,
String bearerToken,
RecordRequest request,
String workspaceId,
String type,
String id)
throws WdsServiceException {
return executionWithRetryTemplate(
listenerResetRetryTemplate,
() ->
wdsClient
.recordsApi(wdsBaseUri, bearerToken)
.updateRecord(request, workspaceId, wdsServerConfiguration.apiV(), type, id));
}

/**
* Query for all the tables and their structure for this WDS app
*
* @param wdsBaseUri - URI of WDS app, retrieved from Leonardo
* @param bearerToken - SA token, retrieved from google default credentials
* @param workspaceId - workspace id WDS app is deployed in
* @return - list of all the tables in this WDS app
* @throws WdsServiceException ;
*/
public List<RecordTypeSchema> querySchema(
String wdsBaseUri, String bearerToken, String workspaceId) throws WdsServiceException {
return executionWithRetryTemplate(
listenerResetRetryTemplate,
() ->
wdsClient
.schemaApi(wdsBaseUri, bearerToken)
.describeAllRecordTypes(workspaceId, wdsServerConfiguration.apiV()));
}

@Override
public Result checkHealth(String wdsBaseUri, String accessToken) {
try {
StatusResponse result =
wdsClient.generalWdsInformationApi(wdsBaseUri, accessToken).statusGet();
return new Result(Objects.equals(result.getStatus(), "UP"), result.toString());
} catch (ApiException e) {
return new Result(false, e.getMessage());
}
}

interface WdsAction<T> {
T execute() throws ApiException, DependencyNotAvailableException;
}

@SuppressWarnings("java:S125") // The comment here isn't "commented code"
static <T> T executionWithRetryTemplate(RetryTemplate retryTemplate, WdsAction<T> action)
throws WdsServiceException {

// Why all this song and dance to catch exceptions and map them to almost identical exceptions?
// Because the RetryTemplate's execute function only allows us to declare one Throwable type.
// So we have a top-level WdsServiceException that we can catch and handle, and then we have
// subclasses of that exception representing the types of exception that can be thrown. This
// way, we can keep well typed exceptions (no "catch (Exception e)") and still make use of the
// retry framework.
return retryTemplate.execute(
context -> {
try {
return action.execute();
} catch (ApiException e) {
throw new WdsServiceApiException(e);
} catch (DependencyNotAvailableException e) {
throw new WdsServiceNotAvailableException(e);
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package bio.terra.pipelines.dependencies.wds;

import org.databiosphere.workspacedata.client.ApiException;

public class WdsServiceApiException extends WdsServiceException {
private final ApiException exception;

public WdsServiceApiException(ApiException exception) {
this.exception = exception;
}

@Override
public synchronized ApiException getCause() {
return exception;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package bio.terra.pipelines.dependencies.wds;

public abstract class WdsServiceException extends Exception {
@Override
public String getMessage() {
return getCause().getMessage();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package bio.terra.pipelines.dependencies.wds;

import bio.terra.pipelines.dependencies.common.DependencyNotAvailableException;

public class WdsServiceNotAvailableException extends WdsServiceException {
private final DependencyNotAvailableException exception;

public WdsServiceNotAvailableException(DependencyNotAvailableException exception) {
this.exception = exception;
}

@Override
public synchronized DependencyNotAvailableException getCause() {
return exception;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import bio.terra.pipelines.dependencies.leonardo.LeonardoService;
import bio.terra.pipelines.dependencies.leonardo.LeonardoServiceException;
import bio.terra.pipelines.dependencies.sam.SamService;
import bio.terra.pipelines.dependencies.wds.WdsService;
import bio.terra.pipelines.dependencies.wds.WdsServiceException;
import java.util.Collections;
import java.util.List;
import org.broadinstitute.dsde.workbench.client.leonardo.model.ListAppResponse;
Expand All @@ -18,15 +20,18 @@ public class ImputationService {
private static final Logger logger = LoggerFactory.getLogger(ImputationService.class);
private LeonardoService leonardoService;
private SamService samService;
private WdsService wdsService;
private ImputationConfiguration imputationConfiguration;

@Autowired
ImputationService(
LeonardoService leonardoService,
SamService samService,
WdsService wdsService,
ImputationConfiguration imputationConfiguration) {
this.leonardoService = leonardoService;
this.samService = samService;
this.wdsService = wdsService;
this.imputationConfiguration = imputationConfiguration;
}

Expand All @@ -40,10 +45,29 @@ public List<ListAppResponse> queryForWorkspaceApps() {
"GetAppsResponse for workspace id {}: {}",
imputationConfiguration.workspaceId(),
getAppsResponse);

String wdsUri =
leonardoService.getWdsUrlFromApps(
workspaceId, samService.getTspsServiceAccountToken(), false);
logger.info("Wds uri for workspace id {}: {}", imputationConfiguration.workspaceId(), wdsUri);
logger.info(
"Wds health: {}",
wdsService.checkHealth(wdsUri, samService.getTspsServiceAccountToken()));

logger.info(
"Wds schema: {}",
wdsService.querySchema(
wdsUri,
samService.getTspsServiceAccountToken(),
imputationConfiguration.workspaceId()));

return getAppsResponse;
} catch (LeonardoServiceException e) {
logger.error("Get Apps called for workspace id {} failed", workspaceId);
return Collections.emptyList();
} catch (WdsServiceException e) {
logger.error("Calls to Wds for workspace id {} failed", workspaceId);
return Collections.emptyList();
}
}
}
Loading

0 comments on commit 7d26e4d

Please sign in to comment.