diff --git a/build.gradle b/build.gradle index 2dc85abe74..69bd078166 100644 --- a/build.gradle +++ b/build.gradle @@ -50,7 +50,7 @@ plugins { allprojects { group 'bio.terra' - version '2.217.0-SNAPSHOT' + version '2.219.0-SNAPSHOT' ext { resourceDir = "${rootDir}/src/main/resources/api" diff --git a/src/main/java/bio/terra/common/AclUtils.java b/src/main/java/bio/terra/common/AclUtils.java index ba2fff94a9..cc691d7cda 100644 --- a/src/main/java/bio/terra/common/AclUtils.java +++ b/src/main/java/bio/terra/common/AclUtils.java @@ -1,7 +1,9 @@ package bio.terra.common; +import bio.terra.service.resourcemanagement.exception.BigQueryAclExhaustionException; import bio.terra.service.resourcemanagement.exception.GoogleResourceException; import bio.terra.service.resourcemanagement.exception.UpdatePermissionsFailedException; +import com.google.cloud.bigquery.BigQueryException; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; @@ -31,6 +33,17 @@ public static T aclUpdateRetry(Callable aclUpdate) throws InterruptedExce ex); lastException = ex.getCause(); } catch (Exception ex) { + // If an exception is thrown due to the user exhausting the number of authorized entities + // in the BigQuery dataset, detect that case and return a custom exception so it can be + // better reported to the user. + if (ex instanceof BigQueryException bqe + && bqe.getMessage().startsWith("Too many authorized entities in this dataset.")) { + throw new BigQueryAclExhaustionException( + bqe.getMessage() + + " Resolve this by deleting snapshots or creating a second Terra Data Repo dataset.", + bqe); + } + throw new GoogleResourceException("Error while performing ACL update", ex); } diff --git a/src/main/java/bio/terra/service/resourcemanagement/exception/BigQueryAclExhaustionException.java b/src/main/java/bio/terra/service/resourcemanagement/exception/BigQueryAclExhaustionException.java new file mode 100644 index 0000000000..b35774f79c --- /dev/null +++ b/src/main/java/bio/terra/service/resourcemanagement/exception/BigQueryAclExhaustionException.java @@ -0,0 +1,14 @@ +package bio.terra.service.resourcemanagement.exception; + +import bio.terra.common.exception.BadRequestException; + +public class BigQueryAclExhaustionException extends BadRequestException { + // This constructor is required so this can be deserialized using StairwayExceptionSerializer. + public BigQueryAclExhaustionException(String message) { + super(message); + } + + public BigQueryAclExhaustionException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/test/java/bio/terra/common/AclUtilsTest.java b/src/test/java/bio/terra/common/AclUtilsTest.java new file mode 100644 index 0000000000..c6b0573283 --- /dev/null +++ b/src/test/java/bio/terra/common/AclUtilsTest.java @@ -0,0 +1,26 @@ +package bio.terra.common; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import bio.terra.common.category.Unit; +import bio.terra.service.resourcemanagement.exception.BigQueryAclExhaustionException; +import com.google.cloud.bigquery.BigQueryException; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(Unit.TAG) +class AclUtilsTest { + + @Test + void aclUpdateRetry() { + assertThrows( + BigQueryAclExhaustionException.class, + () -> + AclUtils.aclUpdateRetry( + () -> { + throw new BigQueryException( + 0, + "Too many authorized entities in this dataset. The maximum number of authorized views, routines, and datasets combined is 2500."); + })); + } +} diff --git a/src/test/java/bio/terra/integration/IntegrationTestConfiguration.java b/src/test/java/bio/terra/integration/IntegrationTestConfiguration.java index c10e39f292..0bfb6e5e34 100644 --- a/src/test/java/bio/terra/integration/IntegrationTestConfiguration.java +++ b/src/test/java/bio/terra/integration/IntegrationTestConfiguration.java @@ -16,6 +16,7 @@ import bio.terra.service.resourcemanagement.google.GoogleResourceManagerService; import com.fasterxml.jackson.databind.ObjectMapper; import io.opentelemetry.api.OpenTelemetry; +import java.io.IOException; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; @@ -53,10 +54,10 @@ public class IntegrationTestConfiguration { @Bean("tdrServiceAccountEmail") - public String tdrServiceAccountEmail() { - // Provide a default value for the service account email when running a spring-context aware - // test to avoid having to set it in the test environment. - return ""; + public String tdrServiceAccountEmail() throws IOException { + // Delegate to ApplicationConfiguration to get the service account email. Without this set, + // the service account would be deleted by tests on teardown. + return new ApplicationConfiguration().tdrServiceAccountEmail(); } @Bean