Skip to content

Commit

Permalink
Merge b1e99fe into 625e21b
Browse files Browse the repository at this point in the history
  • Loading branch information
rtitle committed Feb 10, 2020
2 parents 625e21b + b1e99fe commit 3a0c3e5
Show file tree
Hide file tree
Showing 41 changed files with 1,820 additions and 895 deletions.
13 changes: 13 additions & 0 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version = 2.0.1
align = none
align.openParenCallSite = true
align.openParenDefnSite = true
maxColumn = 120
continuationIndent.defnSite = 2
assumeStandardLibraryStripMargin = true
danglingParentheses = true
rewrite.rules = [SortImports, RedundantBraces, RedundantParens, SortModifiers]
docstrings = JavaDoc
project.excludeFilters = [
Dependencies.scala
]
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,19 @@ Latest SBT dependency: `"org.broadinstitute.dsde.workbench" %% "workbench-metric

Contains utility functions for talking to Google APIs and DAOs for Google PubSub, Google Directory, Google IAM, and Google BigQuery.

Latest SBT dependency: `"org.broadinstitute.dsde.workbench" %% "workbench-google" % "0.21-890a74f"`
Latest SBT dependency: `"org.broadinstitute.dsde.workbench" %% "workbench-google" % "0.21-TRAVIS-REPLACE-ME"`

To depend on the `MockGoogle*` classes, additionally depend on:

`"org.broadinstitute.dsde.workbench" %% "workbench-google" % "0.21-890a74f" % "test" classifier "tests"`
`"org.broadinstitute.dsde.workbench" %% "workbench-google" % "0.21-TRAVIS-REPLACE-ME" % "test" classifier "tests"`

[Changelog](google/CHANGELOG.md)

## workbench-google2

Contains utility functions for talking to Google APIs via com.google.cloud client library (more recent) via gRPC.

Latest SBT dependency: `"org.broadinstitute.dsde.workbench" %% "workbench-google2" % "0.6-31cacc4"`
Latest SBT dependency: `"org.broadinstitute.dsde.workbench" %% "workbench-google2" % "0.7-TRAVIS-REPLACE-ME"`

To start the Google PubSub emulator for unit testing:

Expand Down
1 change: 1 addition & 0 deletions google/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ SBT dependency: `"org.broadinstitute.dsde.workbench" %% "workbench-google" % "0.
- These methods accept a member type of the newly created ADT `MemberType`.
- The now deprecated `addIamRolesForUser` and `removeIamRolesForUser` call the aforementioned methods
for backwards compatibility.
- `getProjectNumber` in `GoogleProjectDAO`

### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ trait GoogleProjectDAO {
def getLabels(projectName: String): Future[Map[String, String]]

def getAncestry(projectName: String): Future[Seq[Ancestor]]

def getProjectNumber(projectName: String): Future[Option[Long]]
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,13 @@ class HttpGoogleProjectDAO(appName: String,
Option(ancestry.getAncestor).map(_.asScala).getOrElse(Seq.empty)
}
}

override def getProjectNumber(projectName: String): Future[Option[Long]] = {
retryWithRecover(when5xx, whenUsageLimited, whenInvalidValueOnBucketCreation, whenNonHttpIOException) { () =>
Option(executeGoogleRequest(cloudResManager.projects().get(projectName))).map(_.getProjectNumber).map(_.toLong)
} {
// if the project doesn't exist, don't fail
case e: HttpResponseException if e.getStatusCode == StatusCodes.NotFound.intValue => None
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ class MockGoogleProjectDAO extends GoogleProjectDAO {
override def getLabels(projectName: String): Future[Map[String, String]] = Future.successful(Map.empty)

override def getAncestry(projectName: String): Future[Seq[Ancestor]] = Future.successful(Seq(new Ancestor().setResourceId(new ResourceId().setId("mock-org-number").setType("organization"))))

override def getProjectNumber(projectName: String): Future[Option[Long]] = Future.successful(Some(1234))
}
11 changes: 11 additions & 0 deletions google2/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

This file documents changes to the `workbench-google2` library, including notes on how to upgrade to new versions.

## 0.7
Changed:
- Renamed `GoogleDataproc` to `GoogleDataprocService`
- Updated `GoogleDataprocService` methods to take a `GoogleProject
- Added `scalafmt` plugin and formatted the `google2` module
Added:
- `GoogleComputeService` and `GoogleComputeInterpreter`
- `com.google.cloud" % "google-cloud-compute` SBT dependency

SBT dependency: `"org.broadinstitute.dsde.workbench" %% "workbench-google2" % "0.7-TRAVIS-REPLACE-ME"`

## 0.6
Changed
- Bump `fs2-io` to `2.0.1`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package org.broadinstitute.dsde.workbench
package google2

import cats.effect.concurrent.Semaphore
import cats.effect.{Async, Blocker, ContextShift, Timer}
import cats.implicits._
import cats.mtl.ApplicativeAsk
import com.google.cloud.compute.v1._
import io.chrisdavenport.log4cats.StructuredLogger
import org.broadinstitute.dsde.workbench.google2.util.RetryPredicates._
import org.broadinstitute.dsde.workbench.model.google.GoogleProject
import org.broadinstitute.dsde.workbench.model.{TraceId, WorkbenchException}

import scala.collection.JavaConverters._

private[google2] class GoogleComputeInterpreter[F[_]: Async: StructuredLogger: Timer: ContextShift](
instanceClient: InstanceClient,
firewallClient: FirewallClient,
diskClient: DiskClient,
zoneClient: ZoneClient,
machineTypeClient: MachineTypeClient,
retryConfig: RetryConfig,
blocker: Blocker,
blockerBound: Semaphore[F]
) extends GoogleComputeService[F] {

override def createInstance(project: GoogleProject, zone: ZoneName, instance: Instance)(
implicit ev: ApplicativeAsk[F, TraceId]
): F[Operation] = {
val projectZone = ProjectZoneName.of(project.value, zone.value)
retryF(
Async[F].delay(instanceClient.insertInstance(projectZone, instance)),
s"com.google.cloud.compute.v1.InstanceClient.insertInstance(${projectZone.toString}, ${instance.getName})"
)
}

override def deleteInstance(project: GoogleProject, zone: ZoneName, instanceName: InstanceName)(
implicit ev: ApplicativeAsk[F, TraceId]
): F[Operation] = {
val projectZoneInstanceName = ProjectZoneInstanceName.of(instanceName.value, project.value, zone.value)
retryF(
Async[F].delay(instanceClient.deleteInstance(projectZoneInstanceName)),
s"com.google.cloud.compute.v1.InstanceClient.deleteInstance(${projectZoneInstanceName.toString})"
)
}

override def getInstance(project: GoogleProject, zone: ZoneName, instanceName: InstanceName)(
implicit ev: ApplicativeAsk[F, TraceId]
): F[Option[Instance]] = {
val projectZoneInstanceName = ProjectZoneInstanceName.of(instanceName.value, project.value, zone.value)
retryF(
recoverF(
Async[F].delay(instanceClient.getInstance(projectZoneInstanceName)),
whenStatusCode(404)
),
s"com.google.cloud.compute.v1.InstanceClient.getInstance(${projectZoneInstanceName.toString})"
)
}

override def stopInstance(project: GoogleProject, zone: ZoneName, instanceName: InstanceName)(
implicit ev: ApplicativeAsk[F, TraceId]
): F[Operation] = {
val projectZoneInstanceName = ProjectZoneInstanceName.of(instanceName.value, project.value, zone.value)
retryF(
Async[F].delay(instanceClient.stopInstance(projectZoneInstanceName)),
s"com.google.cloud.compute.v1.InstanceClient.stopInstance(${projectZoneInstanceName.toString})"
)
}

override def startInstance(project: GoogleProject, zone: ZoneName, instanceName: InstanceName)(
implicit ev: ApplicativeAsk[F, TraceId]
): F[Operation] = {
val projectZoneInstanceName = ProjectZoneInstanceName.of(instanceName.value, project.value, zone.value)
retryF(
Async[F].delay(instanceClient.startInstance(projectZoneInstanceName)),
s"com.google.cloud.compute.v1.InstanceClient.startInstance(${projectZoneInstanceName.toString})"
)
}

override def addInstanceMetadata(project: GoogleProject,
zone: ZoneName,
instanceName: InstanceName,
metadata: Map[String, String])(implicit ev: ApplicativeAsk[F, TraceId]): F[Unit] = {
val projectZoneInstanceName = ProjectZoneInstanceName.of(instanceName.value, project.value, zone.value)
val readAndUpdate = for {
instanceOpt <- recoverF(Async[F].delay(instanceClient.getInstance(projectZoneInstanceName)), whenStatusCode(404))
instance <- instanceOpt.fold(
Async[F]
.raiseError[Instance](new WorkbenchException(s"Instance not found: ${projectZoneInstanceName.toString}"))
)(Async[F].pure)
curMetadataOpt = Option(instance.getMetadata)

fingerprint = curMetadataOpt.map(_.getFingerprint).orNull
curItems = curMetadataOpt.flatMap(m => Option(m.getItemsList)).map(_.asScala).getOrElse(List.empty)
newMetadata = Metadata
.newBuilder()
.setFingerprint(fingerprint)
.addAllItems((curItems.filterNot(i => metadata.contains(i.getKey)) ++ metadata.toList.map {
case (k, v) =>
Items.newBuilder().setKey(k).setValue(v).build()
}).asJava)
.build

_ <- Async[F].delay(instanceClient.setMetadataInstance(projectZoneInstanceName, newMetadata))
} yield ()

// block and retry the read-modify-write as an atomic unit
retryF(
readAndUpdate,
s"com.google.cloud.compute.v1.InstanceClient.setMetadataInstance(${projectZoneInstanceName.toString})"
)
}

override def addFirewallRule(project: GoogleProject,
firewall: Firewall)(implicit ev: ApplicativeAsk[F, TraceId]): F[Unit] =
retryF(
Async[F].delay(firewallClient.insertFirewall(project.value, firewall)),
s"com.google.cloud.compute.v1.FirewallClient.insertFirewall(${project.value}, ${firewall.getName})"
).void

override def getFirewallRule(project: GoogleProject, firewallRuleName: FirewallRuleName)(
implicit ev: ApplicativeAsk[F, TraceId]
): F[Option[Firewall]] = {
val projectFirewallRuleName = ProjectGlobalFirewallName.of(firewallRuleName.value, project.value)
retryF(
recoverF(
Async[F].delay(firewallClient.getFirewall(projectFirewallRuleName)),
whenStatusCode(404)
),
s"com.google.cloud.compute.v1.FirewallClient.insertFirewall(${project.value}, ${firewallRuleName.value})"
)
}

override def setMachineType(project: GoogleProject,
zone: ZoneName,
instanceName: InstanceName,
machineTypeName: MachineTypeName)(implicit ev: ApplicativeAsk[F, TraceId]): F[Unit] = {
val projectZoneInstanceName = ProjectZoneInstanceName.of(instanceName.value, project.value, zone.value)
val request =
InstancesSetMachineTypeRequest.newBuilder().setMachineType(buildMachineTypeUri(zone, machineTypeName)).build()
retryF(
Async[F].delay(instanceClient.setMachineTypeInstance(projectZoneInstanceName, request)),
s"com.google.cloud.compute.v1.InstanceClient.setMachineTypeInstance(${projectZoneInstanceName.toString}, ${machineTypeName.value})"
)
}

override def resizeDisk(project: GoogleProject, zone: ZoneName, diskName: DiskName, newSizeGb: Int)(
implicit ev: ApplicativeAsk[F, TraceId]
): F[Unit] = {
val projectZoneDiskName = ProjectZoneDiskName.of(diskName.value, project.value, zone.value)
val request = DisksResizeRequest.newBuilder().setSizeGb(newSizeGb.toString).build()
retryF(
Async[F].delay(diskClient.resizeDisk(projectZoneDiskName, request)),
s"com.google.cloud.compute.v1.DiskClient.resizeDisk(${projectZoneDiskName.toString}, $newSizeGb)"
)
}

def getZones(project: GoogleProject,
regionName: RegionName)(implicit ev: ApplicativeAsk[F, TraceId]): F[List[Zone]] = {
val request = ListZonesHttpRequest
.newBuilder()
.setProject(project.value)
.setFilter(s"region eq ${buildRegionUri(project, regionName)}")
.build()

retryF(
Async[F].delay(zoneClient.listZones(request)),
s"com.google.cloud.compute.v1.ZoneClient.listZones(${project.value}, ${regionName.value})"
).map(_.iterateAll.asScala.toList)
}

def getMachineType(project: GoogleProject, zone: ZoneName, machineTypeName: MachineTypeName)(
implicit ev: ApplicativeAsk[F, TraceId]
): F[Option[MachineType]] = {
val projectZoneMachineTypeName = ProjectZoneMachineTypeName.of(machineTypeName.value, project.value, zone.value)
retryF(
recoverF(Async[F].delay(machineTypeClient.getMachineType(projectZoneMachineTypeName)), whenStatusCode(404)),
s"com.google.cloud.compute.v1.MachineTypeClient.getMachineType(${projectZoneMachineTypeName.toString})"
)
}

private def buildMachineTypeUri(zone: ZoneName, machineTypeName: MachineTypeName): String =
s"zones/${zone.value}/machineTypes/${machineTypeName.value}"

private def buildRegionUri(googleProject: GoogleProject, regionName: RegionName): String =
s"https://www.googleapis.com/compute/v1/projects/${googleProject.value}/regions/${regionName.value}"

private def retryF[A](fa: F[A], loggingMsg: String)(implicit ev: ApplicativeAsk[F, TraceId]): F[A] =
tracedRetryGoogleF(retryConfig)(blockerBound.withPermit(blocker.blockOn(fa)), loggingMsg).compile.lastOrError

}
Loading

0 comments on commit 3a0c3e5

Please sign in to comment.