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

[WOR-849][WOR-850][WOR-852] Create BPM billing profile for GCP billing projects. #2698

Merged
merged 18 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from 10 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
12 changes: 6 additions & 6 deletions core/src/main/scala/org/broadinstitute/dsde/rawls/Boot.scala
Original file line number Diff line number Diff line change
Expand Up @@ -538,12 +538,12 @@ object Boot extends IOApp with LazyLogging {
samDAO,
notificationDAO,
billingRepository,
new GoogleBillingProjectLifecycle(billingRepository, samDAO, gcsDAO),
new BpmBillingProjectLifecycle(samDAO,
billingRepository,
billingProfileManagerDAO,
workspaceManagerDAO,
workspaceManagerResourceMonitorRecordDao
new GoogleBillingProjectLifecycle(billingRepository, billingProfileManagerDAO, samDAO, gcsDAO),
new AzureBillingProjectLifecycle(samDAO,
billingRepository,
billingProfileManagerDAO,
workspaceManagerDAO,
workspaceManagerResourceMonitorRecordDao
),
workspaceManagerResourceMonitorRecordDao,
multiCloudWorkspaceConfig
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@ import akka.http.scaladsl.model.StatusCodes.InternalServerError
import akka.http.scaladsl.model.{StatusCode, StatusCodes}
import bio.terra.profile.model.ProfileModel
import bio.terra.workspace.client.ApiException
import bio.terra.profile.client.{ApiException => BpmApiException}
import bio.terra.workspace.model.{CreateLandingZoneResult, DeleteAzureLandingZoneResult}
import cats.implicits.{catsSyntaxFlatMapOps, toTraverseOps}
import org.apache.http.HttpStatus
import org.broadinstitute.dsde.rawls.billing.BillingProfileManagerDAO.ProfilePolicy
import org.broadinstitute.dsde.rawls.config.MultiCloudWorkspaceConfig
import org.broadinstitute.dsde.rawls.dataaccess.slick.WorkspaceManagerResourceMonitorRecord
import org.broadinstitute.dsde.rawls.dataaccess.slick.WorkspaceManagerResourceMonitorRecord.JobType.{
Expand All @@ -34,10 +31,10 @@ import scala.util.{Failure, Success, Try}
* This class knows how to validate Rawls billing project requests and instantiate linked billing profiles in the
* billing profile manager service.
*/
class BpmBillingProjectLifecycle(
class AzureBillingProjectLifecycle(
val samDAO: SamDAO,
val billingRepository: BillingRepository,
billingProfileManagerDAO: BillingProfileManagerDAO,
val billingProfileManagerDAO: BillingProfileManagerDAO,
workspaceManagerDAO: WorkspaceManagerDAO,
resourceMonitorRecordDao: WorkspaceManagerResourceMonitorRecordDao
)(implicit val executionContext: ExecutionContext)
Expand Down Expand Up @@ -91,23 +88,6 @@ class BpmBillingProjectLifecycle(
): Future[CreationStatus] = {
val projectName = createProjectRequest.projectName

def createBillingProfile: Future[ProfileModel] =
Future(blocking {
val policies: Map[String, List[(String, String)]] =
if (createProjectRequest.protectedData.getOrElse(false)) Map("protected-data" -> List[(String, String)]())
else Map.empty
val profileModel = billingProfileManagerDAO.createBillingProfile(
projectName.value,
createProjectRequest.billingInfo,
policies,
ctx
)
logger.info(
s"Creating BPM-backed billing project ${projectName.value}, created profile with ID ${profileModel.getId}."
)
profileModel
})

// This starts a landing zone creation job. There is a separate monitor that polls to see when it
// completes and then updates the billing project status accordingly.
def createLandingZone(profileModel: ProfileModel): Future[CreateLandingZoneResult] = {
Expand Down Expand Up @@ -140,21 +120,8 @@ class BpmBillingProjectLifecycle(
})
}

def addMembersToBillingProfile(profileModel: ProfileModel): Future[Set[Unit]] = {
val members = createProjectRequest.members.getOrElse(Set.empty)
Future.traverse(members) { member =>
Future(blocking {
billingProfileManagerDAO.addProfilePolicyMember(profileModel.getId,
ProfilePolicy.fromProjectRole(member.role),
member.email,
ctx
)
})
}
}

createBillingProfile.flatMap { profileModel =>
addMembersToBillingProfile(profileModel).flatMap { _ =>
createBillingProfile(createProjectRequest, ctx).flatMap { profileModel =>
addMembersToBillingProfile(profileModel, createProjectRequest, ctx).flatMap { _ =>
createLandingZone(profileModel)
.flatMap { landingZone =>
(for {
Expand Down Expand Up @@ -297,5 +264,4 @@ class BpmBillingProjectLifecycle(
Future.successful()
}
} yield unregisterBillingProject(projectName, ctx)

}
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,6 @@ class BillingProfileManagerDAOImpl(
policies: Map[String, List[(String, String)]] = Map.empty,
ctx: RawlsRequestContext
): ProfileModel = {
val azureManagedAppCoordinates = billingInfo match {
case Left(_) => throw new NotImplementedError("Google billing accounts not supported in billing profiles")
case Right(coords) => coords
}

val policyInputs = new BpmApiPolicyInputs().inputs(
policies
Expand All @@ -154,15 +150,29 @@ class BillingProfileManagerDAOImpl(

// create the profile
val profileApi = apiClientProvider.getProfileApi(ctx)
val createProfileRequest = new CreateProfileRequest()
.tenantId(azureManagedAppCoordinates.tenantId)
.subscriptionId(azureManagedAppCoordinates.subscriptionId)
.managedResourceGroupId(azureManagedAppCoordinates.managedResourceGroupId)
.displayName(displayName)
.id(UUID.randomUUID())
.biller("direct") // community terra is always 'direct' (i.e., no reseller)
.cloudPlatform(CloudPlatform.AZURE)
.policies(policyInputs)

val commonCreateProfileRequest =
new CreateProfileRequest()
.displayName(displayName)
.id(UUID.randomUUID())
.biller("direct") // community terra is always 'direct' (i.e., no reseller)
.policies(policyInputs)

val createProfileRequest = billingInfo match {
case Left(billingAccountName) =>
val rawlsBillingAccountName = billingAccountName
commonCreateProfileRequest
.billingAccountId(rawlsBillingAccountName.withoutPrefix())
.cloudPlatform(CloudPlatform.GCP)

case Right(coords) =>
val azureManagedAppCoordinates = coords
commonCreateProfileRequest
.tenantId(azureManagedAppCoordinates.tenantId)
.subscriptionId(azureManagedAppCoordinates.subscriptionId)
.managedResourceGroupId(azureManagedAppCoordinates.managedResourceGroupId)
.cloudPlatform(CloudPlatform.AZURE)
}

logger.info(s"Creating billing profile [id=${createProfileRequest.getId}]")
profileApi.createProfile(createProfileRequest)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package org.broadinstitute.dsde.rawls.billing

import bio.terra.profile.model.ProfileModel
import com.typesafe.scalalogging.LazyLogging
import org.broadinstitute.dsde.rawls.RawlsExceptionWithErrorReport
import org.broadinstitute.dsde.rawls.billing.BillingProfileManagerDAO.ProfilePolicy
import org.broadinstitute.dsde.rawls.config.MultiCloudWorkspaceConfig
import org.broadinstitute.dsde.rawls.dataaccess.SamDAO
import org.broadinstitute.dsde.rawls.dataaccess.slick.WorkspaceManagerResourceMonitorRecord.JobType.JobType
Expand All @@ -15,7 +17,7 @@ import org.broadinstitute.dsde.rawls.model.{
}

import java.util.UUID
import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.{blocking, ExecutionContext, Future}

/**
* Handles provisioning and deleting billing projects with external providers. Implementors of this trait are not concerned
Expand All @@ -25,9 +27,10 @@ import scala.concurrent.{ExecutionContext, Future}
*/
trait BillingProjectLifecycle extends LazyLogging {

// It's probably reasonable to expect that the billing project lifecyle always includes these two resources
// Resources common to all implementations.
val samDAO: SamDAO
val billingRepository: BillingRepository
val billingProfileManagerDAO: BillingProfileManagerDAO

// The type of WorkspaceManagerResourceMonitorRecord job that should be created to finalize deletion when necessary
val deleteJobType: JobType
Expand Down Expand Up @@ -74,6 +77,40 @@ trait BillingProjectLifecycle extends LazyLogging {
executionContext: ExecutionContext
): Future[Unit]

def createBillingProfile(createProjectRequest: CreateRawlsV2BillingProjectFullRequest, ctx: RawlsRequestContext)(
implicit executionContext: ExecutionContext
): Future[ProfileModel] =
Future(blocking {
val policies: Map[String, List[(String, String)]] =
if (createProjectRequest.protectedData.getOrElse(false)) Map("protected-data" -> List[(String, String)]())
else Map.empty
val profileModel = billingProfileManagerDAO.createBillingProfile(
createProjectRequest.projectName.value,
createProjectRequest.billingInfo,
policies,
ctx
)
logger.info(
s"Creating BPM-backed billing project ${createProjectRequest.projectName.value}, created profile with ID ${profileModel.getId}."
)
profileModel
})(executionContext)

def addMembersToBillingProfile(profileModel: ProfileModel,
createProjectRequest: CreateRawlsV2BillingProjectFullRequest,
ctx: RawlsRequestContext
)(implicit executionContext: ExecutionContext): Future[Set[Unit]] = {
val members = createProjectRequest.members.getOrElse(Set.empty)
Future.traverse(members) { member =>
Future(blocking {
billingProfileManagerDAO.addProfilePolicyMember(profileModel.getId,
ProfilePolicy.fromProjectRole(member.role),
member.email,
ctx
)
})
}
}
}

class DuplicateBillingProjectException(errorReport: ErrorReport) extends RawlsExceptionWithErrorReport(errorReport)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ object BillingProjectOrchestrator {
notificationDAO: NotificationDAO,
billingRepository: BillingRepository,
googleBillingProjectLifecycle: GoogleBillingProjectLifecycle,
bpmBillingProjectLifecycle: BpmBillingProjectLifecycle,
bpmBillingProjectLifecycle: AzureBillingProjectLifecycle,
resourceMonitorRecordDao: WorkspaceManagerResourceMonitorRecordDao,
config: MultiCloudWorkspaceConfig
)(ctx: RawlsRequestContext)(implicit executionContext: ExecutionContext): BillingProjectOrchestrator =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import scala.concurrent.{ExecutionContext, Future}

class GoogleBillingProjectLifecycle(
val billingRepository: BillingRepository,
val billingProfileManagerDAO: BillingProfileManagerDAO,
val samDAO: SamDAO,
gcsDAO: GoogleServicesDAO
)(implicit
Expand Down Expand Up @@ -62,6 +63,9 @@ class GoogleBillingProjectLifecycle(
ctx: RawlsRequestContext
): Future[CreationStatus] =
for {
profileModel <- createBillingProfile(createProjectRequest, ctx)
_ <- addMembersToBillingProfile(profileModel, createProjectRequest, ctx)
_ <- billingRepository.setBillingProfileId(createProjectRequest.projectName, profileModel.getId)
_ <- syncBillingProjectOwnerPolicyToGoogleAndGetEmail(samDAO, createProjectRequest.projectName)
} yield CreationStatuses.Ready

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import com.typesafe.config.{Config, ConfigRenderOptions}
import com.typesafe.scalalogging.LazyLogging
import net.ceedubs.ficus.Ficus.{optionValueReader, toFicusConfig}
import org.broadinstitute.dsde.rawls.billing.{BillingProfileManagerDAO, BillingRepository, BpmBillingProjectLifecycle}
import org.broadinstitute.dsde.rawls.billing.{AzureBillingProjectLifecycle, BillingProfileManagerDAO, BillingRepository}
import org.broadinstitute.dsde.rawls.config.FastPassConfig
import org.broadinstitute.dsde.rawls.coordination.{
CoordinatedDataSourceAccess,
Expand Down Expand Up @@ -476,11 +476,11 @@
gcsDAO,
workspaceManagerDAO,
billingRepo,
new BpmBillingProjectLifecycle(samDAO,
billingRepo,
billingProfileManagerDAO,
workspaceManagerDAO,
monitorRecordDao
new AzureBillingProjectLifecycle(samDAO,

Check warning on line 479 in core/src/main/scala/org/broadinstitute/dsde/rawls/monitor/BootMonitors.scala

View check run for this annotation

Codecov / codecov/patch

core/src/main/scala/org/broadinstitute/dsde/rawls/monitor/BootMonitors.scala#L479

Added line #L479 was not covered by tests
billingRepo,
billingProfileManagerDAO,
workspaceManagerDAO,
monitorRecordDao
)
)
)
Expand Down
Loading
Loading