Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CORE-216: validate per-workflow cost cap values #3148

Merged
merged 3 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
}
}

}
Loading