Skip to content

Commit

Permalink
include credits and deal with edge cases
Browse files Browse the repository at this point in the history
  • Loading branch information
calypsomatic committed Nov 25, 2024
1 parent e1e31a1 commit e4a13db
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 192 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import slick.jdbc.JdbcType
import java.sql.Timestamp
import java.time.Instant
import java.util.UUID
import scala.util.Try

final case class RawlsBillingProjectRecord(projectName: String,
creationStatus: String,
Expand Down Expand Up @@ -342,6 +343,7 @@ trait RawlsBillingProjectComponent {
def clearBillingProjectSpendConfiguration(billingProjectName: RawlsBillingProjectName): WriteAction[Int] =
setBillingProjectSpendConfiguration(billingProjectName, None, None, None)

// Throws an error if the Billing Project does not have a Billing Account
def getBillingProjectSpendConfiguration(
billingProjectName: RawlsBillingProjectName
): ReadAction[Option[BillingProjectSpendExport]] =
Expand All @@ -350,14 +352,15 @@ trait RawlsBillingProjectComponent {
.result
.map(_.headOption.map(RawlsBillingProjectRecord.toBillingProjectSpendExport))

// Ignores any Billing Projects that don't have Billing Accounts
def getBillingProjectsSpendConfiguration(
billingProjectNames: Seq[RawlsBillingProjectName]
): ReadAction[Seq[Option[BillingProjectSpendExport]]] =
rawlsBillingProjectQuery
.withProjectNames(billingProjectNames)
.result
.map(projectRecords =>
projectRecords.map(record => Some(RawlsBillingProjectRecord.toBillingProjectSpendExport(record)))
projectRecords.map(record => Try(RawlsBillingProjectRecord.toBillingProjectSpendExport(record)).toOption)
)

def insertOperations(operations: Seq[RawlsBillingProjectOperationRecord]): WriteAction[Unit] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,13 @@ object SpendReportingService {
): SpendReportingResults = {

var total = BigDecimal(0.0)
var total_credits = BigDecimal(0.0)

val currency = allRows.map(_.get("currency").getStringValue).distinct match {
case head :: _ => Currency.getInstance(head)
case _ => throw RawlsExceptionWithErrorReport(StatusCodes.NotFound, "No currencies found for spend data")
}

val all = allRows.map { row =>
val currencyString = row.get("currency").getStringValue
val currencyCode = Currency.getInstance(currencyString)
Expand All @@ -171,32 +178,34 @@ object SpendReportingService {
val subAggregation = List(
SpendReportingForDateRange(
getRoundedNumericValue("other_cost").toString,
"0.0",
getRoundedNumericValue("credits").toString,
currencyCode.toString,
Option(start),
Option(end),
category = Option(TerraSpendCategories.Other)
),
SpendReportingForDateRange(
getRoundedNumericValue("storage_cost").toString,
"0.0",
getRoundedNumericValue("credits").toString,
currencyCode.toString,
category = Option(TerraSpendCategories.Storage)
),
SpendReportingForDateRange(
getRoundedNumericValue("compute_cost").toString,
"0.0",
getRoundedNumericValue("credits").toString,
currencyCode.toString,
category = Option(TerraSpendCategories.Compute)
)
)

val total_cost = getRoundedNumericValue("total_cost")
val credits = getRoundedNumericValue("credits")
total = total + total_cost
total_credits = total_credits + credits

val workspaceTotal = SpendReportingForDateRange(
total_cost.toString,
"0.0",
credits.toString,
currencyCode.toString,
Option(start),
Option(end),
Expand All @@ -211,8 +220,8 @@ object SpendReportingService {

val summary = SpendReportingForDateRange(
total.toString,
"0.0", // TODO
"USD", // TODO
total_credits.toString,
currency.toString, // TODO: what to do about combined summary for currencies?
Option(start),
Option(end)
)
Expand Down Expand Up @@ -285,7 +294,6 @@ class SpendReportingService(
)
}

// TODO if there is a problem with just one BP, the whole thing fails.
def getSpendExportConfigurations(projects: Seq[RawlsBillingProjectName]): Future[Seq[BillingProjectSpendExport]] =
dataSource
.inTransaction(_.rawlsBillingProjectQuery.getBillingProjectsSpendConfiguration(projects))
Expand Down Expand Up @@ -355,6 +363,7 @@ class SpendReportingService(
| project.id AS project_id,
| project.name AS project_name,
| currency,
| SUM(IFNULL((SELECT SUM(c.amount) FROM UNNEST(credits) c), 0)) as credits,
| CASE
| WHEN service.description IN ('Cloud Storage') THEN 'Storage'
| WHEN service.description IN ('Compute Engine', 'Google Kubernetes Engine') THEN 'Compute'
Expand All @@ -369,8 +378,8 @@ class SpendReportingService(
| GROUP BY
| project_id,
| project_name,
| currency,
| spend_category""".stripMargin.trim
| spend_category,
| currency""".stripMargin.trim

val bpSubQuery = billingProjects
.map { bp =>
Expand All @@ -393,7 +402,8 @@ class SpendReportingService(
| SUM(CASE WHEN spend_category = 'Storage' THEN category_cost ELSE 0 END) AS storage_cost,
| SUM(CASE WHEN spend_category = 'Compute' THEN category_cost ELSE 0 END) AS compute_cost,
| SUM(CASE WHEN spend_category = 'Other' THEN category_cost ELSE 0 END) AS other_cost,
| currency
| currency,
| SUM(credits) as credits
|FROM
| spend_categories
|GROUP BY
Expand Down Expand Up @@ -547,12 +557,14 @@ class SpendReportingService(
end: DateTime,
pageSize: Int,
offset: Int
): Future[SpendReportingResults] = {
): Future[Option[SpendReportingResults]] = {
validateReportParameters(start, end)
for {
billingMap <- getBillingWithSpendPermission()
_ = if (billingMap.isEmpty) {
return Future.successful(None)
}
projectNames: Map[GoogleProjectId, WorkspaceName] = billingMap.values.flatten.toMap
// TODO if there's no workspaces returned, don't run the query
query = getAllUserWorkspaceQuery(billingMap, pageSize, offset)
queryJob = setUpAllUserWorkspaceQuery(query, start, end)

Expand All @@ -561,11 +573,8 @@ class SpendReportingService(
result = job.getQueryResults()
} yield result.getValues.asScala.toList match {
case Nil =>
throw RawlsExceptionWithErrorReport(
StatusCodes.NotFound,
s"no spend data found between dates ${toISODateString(start)} and ${toISODateString(end)}"
)
case rows => extractCrossBillingProjectSpendReportingResults(rows, start, end, projectNames)
None
case rows => Some(extractCrossBillingProjectSpendReportingResults(rows, start, end, projectNames))
}
}

Expand All @@ -577,12 +586,23 @@ class SpendReportingService(
SamWorkspaceActions.readSpendReport,
ctx
)
groupedWorkspaces <- workspaceServiceConstructor(ctx).getGCPWorkspacesByBillingProjects(
ownerWorkspaces.map(_.resourceId).toList
)
groupedWorkspaces <-
if (ownerWorkspaces.isEmpty) {
Future.successful(Map.empty[RawlsBillingProjectName, Seq[Workspace]])
} else {
// Ignore non-UUID workspaceIds; these shouldn't happen but if they do, we don't want them
workspaceServiceConstructor(ctx).getGCPWorkspacesByBillingProjects(
ownerWorkspaces.map(_.resourceId).filter(resourceId => Try(UUID.fromString(resourceId)).isSuccess).toList
)
}
// Only use the BPs we know exist in the DB and are GCP
spendConfigs <- getSpendExportConfigurations(groupedWorkspaces.keys.toList)
spendConfigs <-
if (groupedWorkspaces.isEmpty) {
Future.successful(Seq.empty[BillingProjectSpendExport])
} else { getSpendExportConfigurations(groupedWorkspaces.keys.toList) }
} yield spendConfigs.map { config =>
config -> groupedWorkspaces(config.billingProjectName).map(ws => (ws.googleProjectId, ws.toWorkspaceName))
config -> groupedWorkspaces
.getOrElse(RawlsBillingProjectName(config.billingProjectName.value), Seq.empty)
.map(ws => (ws.googleProjectId, ws.toWorkspaceName))
}.toMap
}
Loading

0 comments on commit e4a13db

Please sign in to comment.