Skip to content

Commit

Permalink
Merge branch 'develop' into ps/dt-1097-better-quota-error-reporting-2
Browse files Browse the repository at this point in the history
  • Loading branch information
pshapiro4broad committed Jan 21, 2025
2 parents d156cc4 + e9b54e1 commit c21867b
Show file tree
Hide file tree
Showing 73 changed files with 582 additions and 422 deletions.
7 changes: 4 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ plugins {
id 'jacoco'
// After merging a spotless version update which requires a large-scale code reformat, add the
// commit hash to .git-blame-ignore-revs to avoid cluttering git blame.
id 'com.diffplug.spotless' version '6.25.0' // SEE ABOVE.
id 'com.diffplug.spotless' version '7.0.1' // SEE ABOVE.
id 'com.dorongold.task-tree' version '4.0.0'
// enables release info in sentry events
id 'com.gorylenko.gradle-git-properties' version '2.4.2'
Expand All @@ -50,7 +50,7 @@ plugins {

allprojects {
group 'bio.terra'
version '2.208.0-SNAPSHOT'
version '2.213.0-SNAPSHOT'

ext {
resourceDir = "${rootDir}/src/main/resources/api"
Expand Down Expand Up @@ -214,6 +214,7 @@ dependencies {

implementation 'com.squareup.okhttp3:okhttp'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.hateoas:spring-hateoas'
implementation 'io.micrometer:micrometer-registry-prometheus'

implementation 'com.fasterxml.jackson.core:jackson-core'
Expand All @@ -224,7 +225,7 @@ dependencies {
implementation 'com.azure:azure-identity:1.15.0'
implementation 'com.azure.resourcemanager:azure-resourcemanager:2.46.0'
implementation 'com.azure.resourcemanager:azure-resourcemanager-loganalytics:1.1.0'
implementation 'com.azure.resourcemanager:azure-resourcemanager-securityinsights:1.0.0-beta.5'
implementation 'com.azure.resourcemanager:azure-resourcemanager-securityinsights:1.0.0'
implementation 'com.azure:azure-storage-common:12.28.0'
implementation 'com.azure:azure-storage-file-datalake:12.22.0'
implementation 'com.azure:azure-data-tables:12.5.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,8 @@ public NamedParameterJdbcTemplate synapseJdbcTemplate(
return new NamedParameterJdbcTemplate(ds);
}

// Use Primary to fix an issue with unqualified ObjectMapper injections in spring-hateoas.
@Primary
@Bean("objectMapper")
public ObjectMapper objectMapper() {
return new ObjectMapper()
Expand Down
26 changes: 13 additions & 13 deletions src/main/java/bio/terra/app/usermetrics/UserLoggingMetrics.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package bio.terra.app.usermetrics;

import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;

/**
* This class wraps a ThreadLocal variable to store API request properties to log (e.g. method,
Expand All @@ -11,38 +13,36 @@
* API request will get grouped together even if they are set in different methods.
*/
@Component
@RequestScope
public class UserLoggingMetrics {

private static final ThreadLocal<HashMap<String, Object>> metrics =
ThreadLocal.withInitial(() -> new HashMap<>());
private final Map<String, Object> metrics = new HashMap<>();

/**
* Get the current thread's metrics instance. If no metrics have been set, return the default
* empty HashMap.
*
* @return HashMap<String, Object> metrics
* @return Map<String, Object> metrics
*/
public HashMap<String, Object> get() {
return metrics.get();
public Map<String, Object> get() {
return metrics;
}

/**
* Add a new value to the current thread's metrics map. If the map already contains a value for
* this key it will be replaced.
* Add a new value to the metrics map. If the map already contains a value for this key it will be
* replaced.
*/
public void set(String key, Object value) {
metrics.get().put(key, value);
metrics.put(key, value);
}

/**
* Add multiple values to the current thread's metrics map. Any existing values with the same key
* will get replaced.
*
* @param value HashMap of metrics to add
* @param value Map of metrics to add
*/
public void setAll(HashMap<String, Object> value) {
HashMap<String, Object> properties = metrics.get();
properties.putAll(value);
metrics.set(properties);
public void setAll(Map<String, Object> value) {
metrics.putAll(value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,23 @@ public class UserMetricsInterceptor implements HandlerInterceptor {
private final AuthenticatedUserRequestFactory authenticatedUserRequestFactory;
private final ApplicationConfiguration applicationConfiguration;
private final UserMetricsConfiguration metricsConfig;
private final UserLoggingMetrics userLoggingMetrics;
private final ExecutorService metricsPerformanceThreadpool;
private final UserLoggingMetrics eventProperties;

@Autowired
public UserMetricsInterceptor(
BardClient bardClient,
AuthenticatedUserRequestFactory authenticatedUserRequestFactory,
ApplicationConfiguration applicationConfiguration,
UserMetricsConfiguration metricsConfig,
UserLoggingMetrics eventProperties,
UserLoggingMetrics userLoggingMetrics,
@Qualifier("metricsReportingThreadpool") ExecutorService metricsPerformanceThreadpool) {
this.bardClient = bardClient;
this.authenticatedUserRequestFactory = authenticatedUserRequestFactory;
this.applicationConfiguration = applicationConfiguration;
this.metricsConfig = metricsConfig;
this.userLoggingMetrics = userLoggingMetrics;
this.metricsPerformanceThreadpool = metricsPerformanceThreadpool;
this.eventProperties = eventProperties;
}

@Override
Expand All @@ -61,16 +61,13 @@ public void afterCompletion(
if (StringUtils.isEmpty(metricsConfig.bardBasePath()) || ignoreEventForPath(path)) {
return;
}

HashMap<String, Object> properties =
new HashMap<>(
Map.of(
BardEventProperties.METHOD_FIELD_NAME, method,
BardEventProperties.PATH_FIELD_NAME, path));
Map<String, Object> properties = new HashMap<>(userLoggingMetrics.get());
properties.putAll(
Map.of(
BardEventProperties.METHOD_FIELD_NAME, method,
BardEventProperties.PATH_FIELD_NAME, path));
addToPropertiesIfPresentInHeader(
request, properties, "X-Transaction-Id", BardEventProperties.TRANSACTION_ID_FIELD_NAME);
eventProperties.setAll(properties);
HashMap<String, Object> bardEventProperties = eventProperties.get();

// Spawn a thread so that sending the metric doesn't slow down the initial request
metricsPerformanceThreadpool.submit(
Expand All @@ -79,7 +76,7 @@ public void afterCompletion(
userRequest,
new BardEvent(
API_EVENT_NAME,
bardEventProperties,
properties,
metricsConfig.appId(),
applicationConfiguration.getDnsName())));
}
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/bio/terra/common/SynapseColumn.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,13 @@ public static String translateDataType(
case INTEGER -> "numeric(10, 0)";
case INT64 -> "numeric(19, 0)";
case NUMERIC -> "real";
// DIRREF and FILEREF store a UUID on ingest
// But, are translated to DRS URI on Snapshot Creation
// Note that the Synapse CSV parser does not support varchars larger than 8000 bytes
// DIRREF and FILEREF store a UUID on ingest
// But, are translated to DRS URI on Snapshot Creation
// Note that the Synapse CSV parser does not support varchars larger than 8000 bytes
case DIRREF, FILEREF, TEXT, STRING -> "varchar(%s)".formatted(isForCsv ? "8000" : "max");
case TIME -> "time";
// Data of type RECORD contains table-like that can be nested or repeated
// It's provided in JSON format, making it hard to parse from inside a CSV/JSON ingest
// Data of type RECORD contains table-like that can be nested or repeated
// It's provided in JSON format, making it hard to parse from inside a CSV/JSON ingest
case RECORD ->
throw new NotSupportedException("RECORD type is not yet supported for synapse");
};
Expand Down
1 change: 1 addition & 0 deletions src/main/java/bio/terra/service/auth/iam/IamAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public enum IamAction {
// billing profiles
UPDATE_BILLING_ACCOUNT,
LINK,
READ_SPEND_REPORT,
// journal
VIEW_JOURNAL,
// lock/unlock resources
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/bio/terra/service/auth/iam/sam/SamIam.java
Original file line number Diff line number Diff line change
Expand Up @@ -935,8 +935,8 @@ public static ErrorReportException convertSamExToDataRepoEx(final ApiException s
case HttpStatusCodes.STATUS_CODE_FORBIDDEN -> new IamForbiddenException(message, samEx);
case HttpStatusCodes.STATUS_CODE_NOT_FOUND -> new IamNotFoundException(message, samEx);
case HttpStatusCodes.STATUS_CODE_CONFLICT -> new IamConflictException(message, samEx);
// SAM does not use a 501 NOT_IMPLEMENTED status code, so that case is skipped here
// A 401 error will only occur when OpenDJ is down and should be raised as a 500 error
// SAM does not use a 501 NOT_IMPLEMENTED status code, so that case is skipped here
// A 401 error will only occur when OpenDJ is down and should be raised as a 500 error
default -> new IamInternalServerErrorException(message, samEx);
};
}
Expand Down
26 changes: 11 additions & 15 deletions src/main/java/bio/terra/service/profile/ProfileApiController.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import bio.terra.model.BillingProfileRequestModel;
import bio.terra.model.BillingProfileUpdateModel;
import bio.terra.model.EnumerateBillingProfileModel;
import bio.terra.model.EnumerateBillingProfileResourcesModel;
import bio.terra.model.JobModel;
import bio.terra.model.PolicyMemberRequest;
import bio.terra.model.PolicyModel;
Expand All @@ -20,13 +21,11 @@
import bio.terra.service.auth.iam.IamService;
import bio.terra.service.auth.iam.PolicyMemberValidator;
import bio.terra.service.job.JobService;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.annotations.Api;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
Expand All @@ -42,7 +41,6 @@
@Api(tags = {"profiles"})
public class ProfileApiController implements ProfilesApi {

private final ObjectMapper objectMapper;
private final HttpServletRequest request;
private final ProfileService profileService;
private final ProfileRequestValidator billingProfileRequestValidator;
Expand All @@ -56,7 +54,6 @@ public class ProfileApiController implements ProfilesApi {

@Autowired
public ProfileApiController(
ObjectMapper objectMapper,
HttpServletRequest request,
ProfileService profileService,
ProfileRequestValidator billingProfileRequestValidator,
Expand All @@ -66,7 +63,6 @@ public ProfileApiController(
AuthenticatedUserRequestFactory authenticatedUserRequestFactory,
IamService iamService,
ApplicationConfiguration applicationConfiguration) {
this.objectMapper = objectMapper;
this.request = request;
this.profileService = profileService;
this.billingProfileRequestValidator = billingProfileRequestValidator;
Expand All @@ -78,16 +74,6 @@ public ProfileApiController(
this.applicationConfiguration = applicationConfiguration;
}

@Override
public Optional<ObjectMapper> getObjectMapper() {
return Optional.ofNullable(objectMapper);
}

@Override
public Optional<HttpServletRequest> getRequest() {
return Optional.ofNullable(request);
}

@InitBinder
protected void initBinder(final WebDataBinder binder) {
binder.addValidators(profileUpdateRequestValidator);
Expand Down Expand Up @@ -199,4 +185,14 @@ private void verifyAuthorization(
// Verify permissions
iamService.verifyAuthorization(userReq, resourceType, resourceId, action);
}

@Override
public ResponseEntity<EnumerateBillingProfileResourcesModel> getProfileResources(UUID id) {
AuthenticatedUserRequest user = authenticatedUserRequestFactory.from(request);
var resources =
profileService.getProfileResources(id, user).stream()
.map(ProfileOwnedResource::toModel)
.toList();
return ResponseEntity.ok(new EnumerateBillingProfileResourcesModel().items(resources));
}
}
13 changes: 13 additions & 0 deletions src/main/java/bio/terra/service/profile/ProfileOwnedResource.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package bio.terra.service.profile;

import bio.terra.model.ProfileOwnedResourceModel;
import java.time.Instant;
import java.util.UUID;

Expand All @@ -9,4 +10,16 @@ public enum Type {
DATASET,
SNAPSHOT,
}

public ProfileOwnedResourceModel toModel() {
return new ProfileOwnedResourceModel()
.id(id)
.name(name)
.description(description)
.createdDate(createdDate.toString())
.type(
Type.DATASET == type
? ProfileOwnedResourceModel.TypeEnum.DATASET
: ProfileOwnedResourceModel.TypeEnum.SNAPSHOT);
}
}
7 changes: 7 additions & 0 deletions src/main/java/bio/terra/service/profile/ProfileService.java
Original file line number Diff line number Diff line change
Expand Up @@ -303,4 +303,11 @@ public void verifyDeployedApplication(
+ "operation");
}
}

public List<ProfileOwnedResource> getProfileResources(
UUID profileId, AuthenticatedUserRequest user) {
iamService.verifyAuthorization(
user, IamResourceType.SPEND_PROFILE, profileId.toString(), IamAction.READ_SPEND_REPORT);
return profileDao.listProfileOwnedResources(profileId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1334,7 +1334,7 @@ private LegacySQLTypeName translateType(TableDataType datatype) {
return LegacySQLTypeName.INTEGER; // match the SQL type
case NUMERIC:
return LegacySQLTypeName.NUMERIC;
// case RECORD: return LegacySQLTypeName.RECORD;
// case RECORD: return LegacySQLTypeName.RECORD;
case STRING:
return LegacySQLTypeName.STRING;
case TEXT:
Expand Down
63 changes: 63 additions & 0 deletions src/main/resources/api/data-repository-openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,42 @@ paths:
schema:
$ref: '#/components/schemas/ErrorModel'

/api/resources/v1/profiles/{id}/resources:
get:
tags:
- profiles
- resources
description: >
Given a profile ID, return the resources associated with that profile.
operationId: getProfileResources
parameters:
- $ref: '#/components/parameters/Id'
responses:
200:
description: List of resources
content:
application/json:
schema:
$ref: '#/components/schemas/EnumerateBillingProfileResourcesModel'
400:
description: Bad request - invalid id, badly formed
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorModel'
403:
description: No permission to retreive resources
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorModel'
404:
description: Not found - profile id does not exist
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorModel'

/api/repository/v1/snapshots:
get:
tags:
Expand Down Expand Up @@ -5242,6 +5278,33 @@ components:
$ref: '#/components/schemas/BillingProfileModel'
description: >
The total number of billing profiles available and a page of profiles
EnumerateBillingProfileResourcesModel:
type: object
properties:
items:
type: array
items:
$ref: '#/components/schemas/ProfileOwnedResourceModel'
description: >
The resources created using this billing profile
ProfileOwnedResourceModel:
type: object
properties:
id:
$ref: '#/components/schemas/UniqueIdProperty'
name:
type: string
description: Name of the resource
description:
type: string
description: Description of the resource
createdDate:
type: string
description: Date the resource was created
type:
type: string
description: Type of the resource
enum: [ DATASET, SNAPSHOT ]
DatasetModel:
type: object
properties:
Expand Down
Loading

0 comments on commit c21867b

Please sign in to comment.