Skip to content

Commit

Permalink
CORE-216: validate per-workflow cost cap values (#3148)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidangb authored Dec 9, 2024
1 parent 4def381 commit cefaea5
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.broadinstitute.dsde.rawls.submissions

import akka.http.scaladsl.model.StatusCodes
import com.google.common.annotations.VisibleForTesting
import com.typesafe.scalalogging.LazyLogging
import org.broadinstitute.dsde.rawls.config.WorkspaceServiceConfig
import org.broadinstitute.dsde.rawls.dataaccess.slick.{DataAccess, ReadWriteAction, WorkflowRecord}
Expand Down Expand Up @@ -531,10 +532,34 @@ class SubmissionsService(
ps.inputs.filter(_.inputResolutions.forall(_.error.isEmpty))
)

@VisibleForTesting
def validateCostCap(costCap: Option[BigDecimal]): Unit = {
// must be a positive number, no more than two decimal places, and a max of ... 10 billion?
val maybeErrorMessage = costCap.collectFirst {
case cap if cap.sign != 1 => "per-workflow cost cap must be positive"
case cap if cap.compare(BigDecimal.valueOf(10000000000L)) >= 0 =>
"per-workflow cost cap must be less than 10,000,000,000"
case cap if !(cap * 100).isWhole =>
"per-workflow cost cap must have a max of two decimal places"
}

maybeErrorMessage.foreach { msg =>
throw new RawlsExceptionWithErrorReport(
errorReport = ErrorReport(
StatusCodes.BadRequest,
msg
)
)
}

}

private def prepareSubmission(workspaceName: WorkspaceName,
submissionRequest: SubmissionRequest
): Future[PreparedSubmission] = {

validateCostCap(submissionRequest.perWorkflowCostCap)

val submissionId: UUID = UUID.randomUUID()

for {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,14 @@ import org.broadinstitute.dsde.rawls.serviceperimeter.ServicePerimeterServiceImp
import org.broadinstitute.dsde.rawls.user.UserService
import org.broadinstitute.dsde.rawls.util.MockitoTestUtils
import org.broadinstitute.dsde.rawls.webservice._
import org.broadinstitute.dsde.rawls.workspace.{MultiCloudWorkspaceAclManager, MultiCloudWorkspaceService, RawlsWorkspaceAclManager, WorkspaceRepository, WorkspaceService, WorkspaceSettingRepository}
import org.broadinstitute.dsde.rawls.workspace.{
MultiCloudWorkspaceAclManager,
MultiCloudWorkspaceService,
RawlsWorkspaceAclManager,
WorkspaceRepository,
WorkspaceService,
WorkspaceSettingRepository
}
import org.broadinstitute.dsde.rawls.{RawlsExceptionWithErrorReport, RawlsTestUtils}
import org.broadinstitute.dsde.workbench.dataaccess.{NotificationDAO, PubSubNotificationDAO}
import org.broadinstitute.dsde.workbench.google.mock.{MockGoogleBigQueryDAO, MockGoogleIamDAO, MockGoogleStorageDAO}
Expand Down Expand Up @@ -581,4 +588,49 @@ class SubmissionsServiceSpec
assert(result.deletedDate.isDefined)
}

behavior of "per-workflow cost cap validation"
// all tests can use the same db and services; none of these tests perform writes
withTestDataServices { services =>
it should "pass when no cap is specified" in {
val input = Option.empty
services.submissionsService.validateCostCap(input)
}
it should "pass for a reasonable number" in {
val input = Option(BigDecimal(25.99))
services.submissionsService.validateCostCap(input)
}
it should "fail for zero" in {
val input = Option(BigDecimal(0))
val actual = intercept[RawlsExceptionWithErrorReport] {
services.submissionsService.validateCostCap(input)
}
actual.errorReport.statusCode should contain(StatusCodes.BadRequest)
actual.errorReport.message shouldBe "per-workflow cost cap must be positive"
}
it should "fail for a negative number" in {
val input = Option(BigDecimal(-1))
val actual = intercept[RawlsExceptionWithErrorReport] {
services.submissionsService.validateCostCap(input)
}
actual.errorReport.statusCode should contain(StatusCodes.BadRequest)
actual.errorReport.message shouldBe "per-workflow cost cap must be positive"
}
it should "fail for too many decimal places" in {
val input = Option(BigDecimal(12.345))
val actual = intercept[RawlsExceptionWithErrorReport] {
services.submissionsService.validateCostCap(input)
}
actual.errorReport.statusCode should contain(StatusCodes.BadRequest)
actual.errorReport.message shouldBe "per-workflow cost cap must have a max of two decimal places"
}
it should "fail when too large too many decimal places" in {
val input = Option(BigDecimal.valueOf(10000000000L))
val actual = intercept[RawlsExceptionWithErrorReport] {
services.submissionsService.validateCostCap(input)
}
actual.errorReport.statusCode should contain(StatusCodes.BadRequest)
actual.errorReport.message shouldBe "per-workflow cost cap must be less than 10,000,000,000"
}
}

}

0 comments on commit cefaea5

Please sign in to comment.