mirror of
https://github.com/digital-asset/daml.git
synced 2024-11-05 03:56:26 +03:00
Delete projects that are no longer used by any releasable component (#18180)
* remove pureconfig-utils * remove db-utils * remove doobie slf4j * remove flyway testing * remove grpc-reverse-proxy * remove grpc-server-reflection-client * remove oracle-testing * update release artifacts
This commit is contained in:
parent
c6cf5b787b
commit
47c9baa28c
@ -1,62 +0,0 @@
|
||||
# Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
load(
|
||||
"//bazel_tools:scala.bzl",
|
||||
"da_scala_library",
|
||||
"da_scala_test",
|
||||
"lf_scalacopts",
|
||||
)
|
||||
|
||||
da_scala_library(
|
||||
name = "pureconfig-utils",
|
||||
srcs = glob(["src/main/scala/**/*.scala"]),
|
||||
scala_deps = [
|
||||
"@maven//:com_chuusai_shapeless",
|
||||
"@maven//:com_github_pureconfig_pureconfig_core",
|
||||
"@maven//:com_github_pureconfig_pureconfig_generic",
|
||||
"@maven//:org_apache_pekko_pekko_http_core",
|
||||
"@maven//:com_typesafe_scala_logging_scala_logging",
|
||||
"@maven//:com_github_scopt_scopt",
|
||||
"@maven//:org_scalaz_scalaz_core",
|
||||
"@maven//:org_parboiled_parboiled",
|
||||
],
|
||||
scalacopts = lf_scalacopts,
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
runtime_deps = [
|
||||
"@maven//:ch_qos_logback_logback_classic",
|
||||
],
|
||||
deps = [
|
||||
"//ledger/ledger-api-common",
|
||||
"//libs-scala/db-utils",
|
||||
"//libs-scala/jwt",
|
||||
"//observability/metrics",
|
||||
"@maven//:com_auth0_java_jwt",
|
||||
"@maven//:com_typesafe_config",
|
||||
"@maven//:io_netty_netty_handler",
|
||||
],
|
||||
)
|
||||
|
||||
da_scala_test(
|
||||
name = "tests",
|
||||
size = "medium",
|
||||
srcs = glob(["src/test/scala/**/*.scala"]),
|
||||
scala_deps = [
|
||||
"@maven//:com_chuusai_shapeless",
|
||||
"@maven//:com_github_pureconfig_pureconfig_core",
|
||||
"@maven//:com_github_pureconfig_pureconfig_generic",
|
||||
"@maven//:org_scalatest_scalatest_core",
|
||||
"@maven//:org_scalatest_scalatest_matchers_core",
|
||||
"@maven//:org_scalatest_scalatest_shouldmatchers",
|
||||
"@maven//:org_scalatest_scalatest_wordspec",
|
||||
],
|
||||
scalacopts = lf_scalacopts,
|
||||
deps = [
|
||||
":pureconfig-utils",
|
||||
"//libs-scala/jwt",
|
||||
"//observability/metrics",
|
||||
"@maven//:org_scalatest_scalatest_compatible",
|
||||
],
|
||||
)
|
@ -1,148 +0,0 @@
|
||||
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.pureconfigutils
|
||||
|
||||
import org.apache.pekko.http.scaladsl.model.Uri
|
||||
import com.auth0.jwt.algorithms.Algorithm
|
||||
import com.daml.dbutils.JdbcConfig
|
||||
import com.daml.jwt.{ECDSAVerifier, HMAC256Verifier, JwksVerifier, JwtVerifierBase, RSA256Verifier}
|
||||
import com.daml.ledger.api.tls.TlsConfiguration
|
||||
import com.daml.platform.services.time.TimeProviderType
|
||||
import io.netty.handler.ssl.ClientAuth
|
||||
import pureconfig.error.{CannotConvert, ConvertFailure, FailureReason}
|
||||
import pureconfig.{ConfigObjectCursor, ConfigReader, ConvertHelpers}
|
||||
import pureconfig.generic.semiauto.deriveReader
|
||||
import com.daml.jwt.{Error => JwtError}
|
||||
import scalaz.\/
|
||||
import scalaz.syntax.std.option._
|
||||
|
||||
import java.nio.file.Path
|
||||
import java.io.File
|
||||
import com.daml.metrics.api.reporters.MetricsReporter
|
||||
|
||||
final case class HttpServerConfig(
|
||||
address: String,
|
||||
port: Int,
|
||||
portFile: Option[Path] = None,
|
||||
https: Option[HttpsConfig] = None,
|
||||
)
|
||||
final case class HttpsConfig(
|
||||
certChainFile: File,
|
||||
privateKeyFile: File,
|
||||
trustCollectionFile: Option[File] = None,
|
||||
) {
|
||||
def tlsConfiguration: TlsConfiguration =
|
||||
TlsConfiguration(
|
||||
enabled = true,
|
||||
certChainFile = Some(certChainFile),
|
||||
privateKeyFile = Some(privateKeyFile),
|
||||
trustCollectionFile = trustCollectionFile,
|
||||
clientAuth = ClientAuth.NONE,
|
||||
)
|
||||
}
|
||||
final case class LedgerTlsConfig(
|
||||
enabled: Boolean = false,
|
||||
certChainFile: Option[File] = None,
|
||||
privateKeyFile: Option[File] = None,
|
||||
trustCollectionFile: Option[File] = None,
|
||||
) {
|
||||
def tlsConfiguration: TlsConfiguration =
|
||||
TlsConfiguration(enabled, certChainFile, privateKeyFile, trustCollectionFile)
|
||||
}
|
||||
final case class LedgerApiConfig(
|
||||
address: String,
|
||||
port: Int,
|
||||
tls: LedgerTlsConfig = LedgerTlsConfig(),
|
||||
)
|
||||
|
||||
object TokenVerifierConfig {
|
||||
private val knownTokenVerifiers: Map[String, String => JwtError \/ JwtVerifierBase] =
|
||||
Map(
|
||||
"rs256-crt" -> (RSA256Verifier.fromCrtFile(_)),
|
||||
"es256-crt" -> (ECDSAVerifier
|
||||
.fromCrtFile(_, Algorithm.ECDSA256(_, null))),
|
||||
"es512-crt" -> (ECDSAVerifier
|
||||
.fromCrtFile(_, Algorithm.ECDSA512(_, null))),
|
||||
"rs256-jwks" -> (valueStr =>
|
||||
\/.attempt(JwksVerifier(valueStr))(e => JwtError(Symbol("RS256"), e.getMessage))
|
||||
),
|
||||
)
|
||||
private val unsafeTokenVerifier: (String, String => JwtError \/ JwtVerifierBase) =
|
||||
"hs256-unsafe" -> (HMAC256Verifier(_))
|
||||
|
||||
def extractByType(
|
||||
typeStr: String,
|
||||
valueStr: String,
|
||||
objectCursor: ConfigObjectCursor,
|
||||
): ConfigReader.Result[JwtVerifierBase] = {
|
||||
def convertFailure(msg: String) = {
|
||||
ConfigReader.Result.fail(
|
||||
ConvertFailure(
|
||||
CannotConvert(typeStr, "JwtVerifier", msg),
|
||||
objectCursor,
|
||||
)
|
||||
)
|
||||
}
|
||||
(knownTokenVerifiers + unsafeTokenVerifier)
|
||||
.get(typeStr)
|
||||
.cata(
|
||||
{ conv =>
|
||||
conv(valueStr).fold(
|
||||
err => convertFailure(s"Failed to create $typeStr verifier: $err"),
|
||||
(Right(_)),
|
||||
)
|
||||
},
|
||||
convertFailure(s"value not one of ${knownTokenVerifiers.keys.mkString(", ")}"),
|
||||
)
|
||||
}
|
||||
}
|
||||
object SharedConfigReaders {
|
||||
|
||||
def catchConvertError[A, B](f: String => Either[String, B])(implicit
|
||||
B: reflect.ClassTag[B]
|
||||
): String => Either[FailureReason, B] =
|
||||
s => f(s).left.map(CannotConvert(s, B.toString, _))
|
||||
|
||||
implicit val tokenVerifierCfgRead: ConfigReader[JwtVerifierBase] =
|
||||
ConfigReader.fromCursor { cur =>
|
||||
for {
|
||||
objCur <- cur.asObjectCursor
|
||||
typeCur <- objCur.atKey("type")
|
||||
typeStr <- typeCur.asString
|
||||
valueCur <- objCur.atKey("uri")
|
||||
valueStr <- valueCur.asString
|
||||
ident <- TokenVerifierConfig.extractByType(typeStr, valueStr, objCur)
|
||||
} yield ident
|
||||
}
|
||||
|
||||
implicit val uriCfgReader: ConfigReader[Uri] =
|
||||
ConfigReader.fromString[Uri](ConvertHelpers.catchReadError(s => Uri(s)))
|
||||
|
||||
implicit val timeProviderTypeCfgReader: ConfigReader[TimeProviderType] = {
|
||||
ConfigReader.fromString[TimeProviderType](catchConvertError { s =>
|
||||
s.toLowerCase() match {
|
||||
case "static" => Right(TimeProviderType.Static)
|
||||
case "wall-clock" => Right(TimeProviderType.WallClock)
|
||||
case _ => Left("not one of 'static' or 'wall-clock'")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
implicit val metricReporterReader: ConfigReader[MetricsReporter] = {
|
||||
ConfigReader.fromString[MetricsReporter](ConvertHelpers.catchReadError { s =>
|
||||
MetricsReporter.parseMetricsReporter(s.toLowerCase())
|
||||
})
|
||||
}
|
||||
implicit val jdbcCfgReader: ConfigReader[JdbcConfig] = deriveReader[JdbcConfig]
|
||||
|
||||
implicit val httpsCfgReader: ConfigReader[HttpsConfig] = deriveReader[HttpsConfig]
|
||||
|
||||
implicit val httpServerCfgReader: ConfigReader[HttpServerConfig] =
|
||||
deriveReader[HttpServerConfig]
|
||||
|
||||
implicit val ledgerTlsCfgReader: ConfigReader[LedgerTlsConfig] =
|
||||
deriveReader[LedgerTlsConfig]
|
||||
implicit val ledgerApiConfReader: ConfigReader[LedgerApiConfig] =
|
||||
deriveReader[LedgerApiConfig]
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.pureconfigutils
|
||||
|
||||
import com.daml.jwt.JwtVerifierBase
|
||||
import com.daml.metrics.{HistogramDefinition, MetricsConfig}
|
||||
import org.scalatest.Inside.inside
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
import org.scalatest.wordspec.AsyncWordSpec
|
||||
import pureconfig.error.{ConfigReaderFailures, ConvertFailure}
|
||||
import pureconfig.generic.semiauto.deriveReader
|
||||
import pureconfig.{ConfigReader, ConfigSource}
|
||||
import java.nio.file.Paths
|
||||
|
||||
import com.daml.metrics.api.reporters.MetricsReporter
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class SharedConfigReadersTest extends AsyncWordSpec with Matchers {
|
||||
import SharedConfigReaders._
|
||||
|
||||
case class SampleServiceConfig(
|
||||
server: HttpServerConfig,
|
||||
ledgerApi: LedgerApiConfig,
|
||||
metrics: MetricsConfig,
|
||||
)
|
||||
implicit val serviceConfigReader: ConfigReader[SampleServiceConfig] =
|
||||
deriveReader[SampleServiceConfig]
|
||||
|
||||
case class DummyConfig(tokenVerifier: JwtVerifierBase)
|
||||
implicit val dummyCfgReader: ConfigReader[DummyConfig] = deriveReader[DummyConfig]
|
||||
|
||||
"should be able to parse a sample config with shared config objects" in {
|
||||
val conf = """
|
||||
|{
|
||||
| server {
|
||||
| address = "127.0.0.1"
|
||||
| port = 8890
|
||||
| port-file = "port-file"
|
||||
| }
|
||||
| ledger-api {
|
||||
| address = "127.0.0.1"
|
||||
| port = 8098
|
||||
| }
|
||||
| metrics {
|
||||
| reporter = "console"
|
||||
| reporting-interval = 10s
|
||||
| histograms = [
|
||||
| {
|
||||
| name-regex = "test"
|
||||
| bucket-boundaries = [ 2.1 ]
|
||||
| }
|
||||
| ]
|
||||
| }
|
||||
|}
|
||||
|""".stripMargin
|
||||
|
||||
val expectedConf = SampleServiceConfig(
|
||||
HttpServerConfig("127.0.0.1", 8890, Some(Paths.get("port-file"))),
|
||||
LedgerApiConfig("127.0.0.1", 8098),
|
||||
MetricsConfig(
|
||||
MetricsReporter.Console,
|
||||
10.seconds,
|
||||
histograms = Seq(
|
||||
HistogramDefinition(
|
||||
"test",
|
||||
Seq(2.1d),
|
||||
)
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
ConfigSource.string(conf).load[SampleServiceConfig] shouldBe Right(expectedConf)
|
||||
}
|
||||
|
||||
"should be able to parse minimal required metrics config" in {
|
||||
val conf = """
|
||||
|{
|
||||
| reporter = "console"
|
||||
| reporting-interval = 10s
|
||||
|}
|
||||
|""".stripMargin
|
||||
|
||||
val expectedMetricsConfig = MetricsConfig(
|
||||
MetricsReporter.Console,
|
||||
10.seconds,
|
||||
histograms = Seq.empty,
|
||||
)
|
||||
|
||||
ConfigSource.string(conf).load[MetricsConfig] shouldBe Right(expectedMetricsConfig)
|
||||
}
|
||||
|
||||
"should fail on loading unknown tokenVerifiers" in {
|
||||
val conf = """
|
||||
|{
|
||||
| token-verifier {
|
||||
| type = "foo"
|
||||
| uri = "bar"
|
||||
| }
|
||||
|}
|
||||
|""".stripMargin
|
||||
|
||||
val cfg = ConfigSource.string(conf).load[DummyConfig]
|
||||
inside(cfg) { case Left(ConfigReaderFailures(ex)) =>
|
||||
ex shouldBe a[ConvertFailure]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
# Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
load(
|
||||
"//bazel_tools:scala.bzl",
|
||||
"da_scala_library",
|
||||
"da_scala_test",
|
||||
"lf_scalacopts",
|
||||
)
|
||||
|
||||
da_scala_library(
|
||||
name = "db-utils",
|
||||
srcs = glob(["src/main/scala/**/*.scala"]),
|
||||
scala_deps = [
|
||||
"@maven//:com_github_scopt_scopt",
|
||||
"@maven//:org_scalaz_scalaz_core",
|
||||
"@maven//:org_tpolecat_doobie_core",
|
||||
"@maven//:org_typelevel_cats_core",
|
||||
"@maven//:org_typelevel_cats_effect",
|
||||
"@maven//:org_typelevel_cats_kernel",
|
||||
"@maven//:com_typesafe_scala_logging_scala_logging",
|
||||
],
|
||||
scala_runtime_deps = [
|
||||
"@maven//:org_tpolecat_doobie_postgres",
|
||||
],
|
||||
scalacopts = lf_scalacopts,
|
||||
tags = ["maven_coordinates=com.daml:db-utils:__VERSION__"],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
runtime_deps = [
|
||||
"@maven//:ch_qos_logback_logback_classic",
|
||||
],
|
||||
deps = [
|
||||
"//libs-scala/scala-utils",
|
||||
"@maven//:com_zaxxer_HikariCP",
|
||||
"@maven//:io_dropwizard_metrics_metrics_core",
|
||||
],
|
||||
)
|
||||
|
||||
da_scala_test(
|
||||
name = "tests",
|
||||
size = "medium",
|
||||
srcs = glob(["src/test/scala/**/*.scala"]),
|
||||
scala_deps = [
|
||||
"@maven//:org_scalatest_scalatest_core",
|
||||
"@maven//:org_scalatest_scalatest_matchers_core",
|
||||
"@maven//:org_scalatest_scalatest_shouldmatchers",
|
||||
"@maven//:org_scalatest_scalatest_wordspec",
|
||||
"@maven//:org_scalaz_scalaz_core",
|
||||
"@maven//:org_tpolecat_doobie_core",
|
||||
"@maven//:org_typelevel_cats_core",
|
||||
"@maven//:org_typelevel_cats_effect",
|
||||
"@maven//:org_typelevel_cats_kernel",
|
||||
],
|
||||
scalacopts = lf_scalacopts,
|
||||
deps = [
|
||||
":db-utils",
|
||||
"//libs-scala/scala-utils",
|
||||
"@maven//:org_scalatest_scalatest_compatible",
|
||||
],
|
||||
)
|
@ -1,78 +0,0 @@
|
||||
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.dbutils
|
||||
|
||||
import cats.effect.{Blocker, ContextShift, IO}
|
||||
import com.codahale.metrics.MetricRegistry
|
||||
import com.zaxxer.hikari.{HikariConfig, HikariDataSource}
|
||||
import doobie._
|
||||
|
||||
import java.io.Closeable
|
||||
import java.util.concurrent.Executors.newWorkStealingPool
|
||||
import javax.sql.DataSource
|
||||
import scala.concurrent.ExecutionContext
|
||||
|
||||
object Connection {
|
||||
|
||||
type T = Transactor.Aux[IO, Unit]
|
||||
|
||||
def connect(cfg: JdbcConfig)(implicit
|
||||
cs: ContextShift[IO]
|
||||
): T =
|
||||
Transactor
|
||||
.fromDriverManager[IO](cfg.driver, cfg.url, cfg.user, cfg.password)(
|
||||
IO.ioConcurrentEffect(cs),
|
||||
cs,
|
||||
)
|
||||
}
|
||||
|
||||
object ConnectionPool {
|
||||
|
||||
type PoolSize = Int
|
||||
object PoolSize {
|
||||
val Integration = 2
|
||||
val Production = 8
|
||||
}
|
||||
|
||||
type T = Transactor.Aux[IO, _ <: DataSource with Closeable]
|
||||
|
||||
def connect(
|
||||
c: JdbcConfig,
|
||||
metricRegistry: Option[MetricRegistry] = None,
|
||||
)(implicit
|
||||
ec: ExecutionContext,
|
||||
cs: ContextShift[IO],
|
||||
): (DataSource with Closeable, T) = {
|
||||
val ds = dataSource(c, metricRegistry)
|
||||
(
|
||||
ds,
|
||||
Transactor
|
||||
.fromDataSource[IO](
|
||||
ds,
|
||||
connectEC = ec,
|
||||
blocker = Blocker liftExecutorService newWorkStealingPool(c.poolSize),
|
||||
)(IO.ioConcurrentEffect(cs), cs),
|
||||
)
|
||||
}
|
||||
|
||||
private[this] def dataSource(
|
||||
jc: JdbcConfig,
|
||||
metricRegistry: Option[MetricRegistry],
|
||||
) = {
|
||||
import jc._
|
||||
val c = new HikariConfig
|
||||
c.setJdbcUrl(url)
|
||||
c.setUsername(user)
|
||||
c.setPassword(password)
|
||||
c.setMinimumIdle(jc.minIdle)
|
||||
c.setConnectionTimeout(jc.connectionTimeout.toMillis)
|
||||
c.setMaximumPoolSize(poolSize)
|
||||
c.setIdleTimeout(jc.idleTimeout.toMillis)
|
||||
metricRegistry match {
|
||||
case Some(mr) => c.setMetricRegistry(mr)
|
||||
case None =>
|
||||
}
|
||||
new HikariDataSource(c)
|
||||
}
|
||||
}
|
@ -1,196 +0,0 @@
|
||||
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.dbutils
|
||||
|
||||
import ConnectionPool.PoolSize
|
||||
import com.typesafe.scalalogging.StrictLogging
|
||||
import scalaz.std.either._
|
||||
import scalaz.std.option._
|
||||
import scalaz.syntax.std.option._
|
||||
import scalaz.syntax.traverse._
|
||||
import scalaz.{Show, StateT, \/}
|
||||
|
||||
import java.io.File
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.Try
|
||||
|
||||
object DBConfig {
|
||||
final case class JdbcConfigDefaults(
|
||||
supportedJdbcDrivers: Set[String],
|
||||
defaultDriver: Option[String] = None,
|
||||
)
|
||||
}
|
||||
|
||||
final case class JdbcConfig(
|
||||
driver: String,
|
||||
url: String,
|
||||
user: String,
|
||||
password: String,
|
||||
poolSize: Int,
|
||||
minIdle: Int = JdbcConfig.MinIdle,
|
||||
connectionTimeout: FiniteDuration = JdbcConfig.ConnectionTimeout,
|
||||
idleTimeout: FiniteDuration = JdbcConfig.IdleTimeout,
|
||||
tablePrefix: String = "",
|
||||
)
|
||||
|
||||
abstract class ConfigCompanion[A, ReadCtx](name: String) {
|
||||
import com.daml.scalautil.ExceptionOps._
|
||||
|
||||
protected val indent: String = List.fill(8)(" ").mkString
|
||||
|
||||
// If we don't DRY our keys, we will definitely forget to remove one. We're
|
||||
// less likely to make a mistake in backend-specific conf if redundant data
|
||||
// isn't there. -SC
|
||||
type Fields[Z] = StateT[({ type l[a] = Either[String, a] })#l, Map[String, String], Z]
|
||||
|
||||
protected[this] def create(implicit
|
||||
readCtx: ReadCtx
|
||||
): Fields[A]
|
||||
|
||||
implicit final def `read instance`(implicit ctx: ReadCtx): scopt.Read[A] =
|
||||
scopt.Read.reads { s =>
|
||||
val x = implicitly[scopt.Read[Map[String, String]]].reads(s)
|
||||
create.eval(x).fold(e => throw new IllegalArgumentException(e), identity)
|
||||
}
|
||||
|
||||
protected def requiredField(k: String): Fields[String] =
|
||||
StateT { m =>
|
||||
m.get(k)
|
||||
.filter(_.nonEmpty)
|
||||
.map((m - k, _))
|
||||
.toRight(s"Invalid $name, must contain '$k' field")
|
||||
}
|
||||
|
||||
protected def optionalStringField(
|
||||
k: String
|
||||
): Fields[Option[String]] =
|
||||
StateT { m => Right((m - k, m get k)) }
|
||||
|
||||
protected def optionalBooleanField(
|
||||
k: String
|
||||
): Fields[Option[Boolean]] =
|
||||
optionalStringField(k).flatMap(ov => StateT liftM (ov traverse parseBoolean(k)).toEither)
|
||||
|
||||
protected def optionalIntField(k: String): Fields[Option[Int]] =
|
||||
optionalStringField(k).flatMap(ov => StateT liftM (ov traverse parseInt(k)).toEither)
|
||||
|
||||
protected def optionalLongField(k: String): Fields[Option[Long]] =
|
||||
optionalStringField(k).flatMap(ov => StateT liftM (ov traverse parseLong(k)).toEither)
|
||||
|
||||
import scalaz.syntax.std.string._
|
||||
|
||||
protected def parseBoolean(k: String)(v: String): String \/ Boolean =
|
||||
v.parseBoolean.leftMap(e => s"$k=$v must be a boolean value: ${e.description}").disjunction
|
||||
|
||||
protected def parseLong(k: String)(v: String): String \/ Long =
|
||||
v.parseLong.leftMap(e => s"$k=$v must be a int value: ${e.description}").disjunction
|
||||
|
||||
protected def parseInt(k: String)(v: String): String \/ Int =
|
||||
v.parseInt.leftMap(e => s"$k=$v must be an int value: ${e.description}").disjunction
|
||||
|
||||
protected def requiredDirectoryField(k: String): Fields[File] =
|
||||
requiredField(k).flatMap(s => StateT liftM directory(s))
|
||||
|
||||
protected def directory(s: String): Either[String, File] =
|
||||
Try(new File(s).getAbsoluteFile).toEither.left
|
||||
.map(e => e.description)
|
||||
.flatMap { d =>
|
||||
if (d.isDirectory) Right(d)
|
||||
else Left(s"Directory does not exist: ${d.getAbsolutePath}")
|
||||
}
|
||||
}
|
||||
|
||||
object JdbcConfig
|
||||
extends ConfigCompanion[JdbcConfig, DBConfig.JdbcConfigDefaults]("JdbcConfig")
|
||||
with StrictLogging {
|
||||
|
||||
final val MinIdle = 8
|
||||
final val IdleTimeout = 10000.millis // minimum according to log, defaults to 600s
|
||||
final val ConnectionTimeout = 5000.millis
|
||||
|
||||
@scala.deprecated("do I need this?", since = "SC")
|
||||
implicit val showInstance: Show[JdbcConfig] =
|
||||
Show.shows(a =>
|
||||
s"JdbcConfig(driver=${a.driver}, url=${a.url}, user=${a.user}, poolSize=${a.poolSize}, " +
|
||||
s"minIdle=${a.minIdle}, connectionTimeout=${a.connectionTimeout}, idleTimeout=${a.idleTimeout}"
|
||||
)
|
||||
|
||||
def help(otherOptions: String = "")(implicit jcd: DBConfig.JdbcConfigDefaults): String =
|
||||
"Contains comma-separated key-value pairs. Where:\n" +
|
||||
s"${indent}driver -- JDBC driver class name, ${jcd.supportedJdbcDrivers.mkString(", ")} supported right now,\n" +
|
||||
s"${indent}url -- JDBC connection URL,\n" +
|
||||
s"${indent}user -- database user name,\n" +
|
||||
s"${indent}password -- database user password,\n" +
|
||||
s"${indent}tablePrefix -- prefix for table names to avoid collisions, empty by default,\n" +
|
||||
s"${indent}poolSize -- int value, specifies the max pool size for the database connection pool.\n" +
|
||||
s"${indent}minIdle -- int value, specifies the min idle connections for database connection pool.\n" +
|
||||
s"${indent}connectionTimeout -- long value, specifies the connection timeout for database connection pool.\n" +
|
||||
s"${indent}idleTimeout -- long value, specifies the idle timeout for the database connection pool.\n" +
|
||||
otherOptions +
|
||||
s"${indent}Example: " + helpString(
|
||||
"org.postgresql.Driver",
|
||||
"jdbc:postgresql://localhost:5432/test?&ssl=true",
|
||||
"postgres",
|
||||
"password",
|
||||
"table_prefix_",
|
||||
PoolSize.Production.toString,
|
||||
MinIdle.toString,
|
||||
ConnectionTimeout.toString,
|
||||
IdleTimeout.toString,
|
||||
)
|
||||
|
||||
private[daml] def create(x: Map[String, String])(implicit
|
||||
readCtx: DBConfig.JdbcConfigDefaults
|
||||
): Either[String, JdbcConfig] =
|
||||
create.eval(x)
|
||||
|
||||
override def create(implicit
|
||||
readCtx: DBConfig.JdbcConfigDefaults
|
||||
): Fields[JdbcConfig] = for {
|
||||
driver <-
|
||||
readCtx.defaultDriver.cata(
|
||||
dd => optionalStringField("driver").map(_ getOrElse dd),
|
||||
requiredField("driver"),
|
||||
)
|
||||
_ <- StateT liftM Either.cond(
|
||||
readCtx.supportedJdbcDrivers(driver),
|
||||
(),
|
||||
s"$driver unsupported. Supported drivers: ${readCtx.supportedJdbcDrivers.mkString(", ")}",
|
||||
)
|
||||
url <- requiredField("url")
|
||||
user <- requiredField("user")
|
||||
password <- requiredField("password")
|
||||
tablePrefix <- optionalStringField("tablePrefix").map(_ getOrElse "")
|
||||
maxPoolSize <- optionalIntField("poolSize").map(_ getOrElse PoolSize.Production)
|
||||
minIdle <- optionalIntField("minIdle").map(_ getOrElse MinIdle)
|
||||
connTimeout <- optionalLongField("connectionTimeout")
|
||||
.map(x => x.map(_.millis) getOrElse ConnectionTimeout)
|
||||
idleTimeout <- optionalLongField("idleTimeout")
|
||||
.map(x => x.map(_.millis) getOrElse IdleTimeout)
|
||||
} yield JdbcConfig(
|
||||
driver = driver,
|
||||
url = url,
|
||||
user = user,
|
||||
password = password,
|
||||
tablePrefix = tablePrefix,
|
||||
poolSize = maxPoolSize,
|
||||
minIdle = minIdle,
|
||||
connectionTimeout = connTimeout,
|
||||
idleTimeout = idleTimeout,
|
||||
)
|
||||
|
||||
private def helpString(
|
||||
driver: String,
|
||||
url: String,
|
||||
user: String,
|
||||
password: String,
|
||||
tablePrefix: String,
|
||||
poolSize: String,
|
||||
minIdle: String,
|
||||
connectionTimeout: String,
|
||||
idleTimeout: String,
|
||||
): String =
|
||||
s"""\"driver=$driver,url=$url,user=$user,password=$password,tablePrefix=$tablePrefix,poolSize=$poolSize,
|
||||
|minIdle=$minIdle, connectionTimeout=$connectionTimeout,idleTimeout=$idleTimeout\"""".stripMargin
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
# Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
load(
|
||||
"//bazel_tools:scala.bzl",
|
||||
"da_scala_library",
|
||||
"da_scala_test_suite",
|
||||
)
|
||||
|
||||
da_scala_library(
|
||||
name = "doobie-slf4j",
|
||||
srcs = glob(["src/main/scala/**/*.scala"]),
|
||||
scala_deps = [
|
||||
"@maven//:org_tpolecat_doobie_core",
|
||||
],
|
||||
tags = ["maven_coordinates=com.daml:doobie-slf4j:__VERSION__"],
|
||||
visibility = [
|
||||
"//:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"@maven//:org_slf4j_slf4j_api",
|
||||
],
|
||||
)
|
@ -1,43 +0,0 @@
|
||||
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.doobie.logging
|
||||
|
||||
import org.slf4j.{Logger, LoggerFactory}
|
||||
import doobie.util.log.{ExecFailure, LogHandler, ProcessingFailure, Success}
|
||||
|
||||
object Slf4jLogHandler {
|
||||
|
||||
def apply(clazz: Class[_]): LogHandler =
|
||||
apply(LoggerFactory.getLogger(clazz))
|
||||
|
||||
def apply(logger: Logger): LogHandler =
|
||||
LogHandler {
|
||||
case Success(s, a, e1, e2) =>
|
||||
logger.debug(s"""Successful Statement Execution:
|
||||
|
|
||||
| ${s.linesIterator.dropWhile(_.trim.isEmpty).mkString("\n ")}
|
||||
|
|
||||
| arguments = [${a.mkString(", ")}]
|
||||
| elapsed = ${e1.toMillis.toString} ms exec + ${e2.toMillis.toString} ms processing (${(e1 + e2).toMillis.toString} ms total)
|
||||
""".stripMargin)
|
||||
case ProcessingFailure(s, a, e1, e2, t) =>
|
||||
logger.error(s"""Failed Resultset Processing:
|
||||
|
|
||||
| ${s.linesIterator.dropWhile(_.trim.isEmpty).mkString("\n ")}
|
||||
|
|
||||
| arguments = [${a.mkString(", ")}]
|
||||
| elapsed = ${e1.toMillis.toString} ms exec + ${e2.toMillis.toString} ms processing (failed) (${(e1 + e2).toMillis.toString} ms total)
|
||||
| failure = ${t.getMessage}
|
||||
""".stripMargin)
|
||||
case ExecFailure(s, a, e1, t) =>
|
||||
logger.error(s"""Failed Statement Execution:
|
||||
|
|
||||
| ${s.linesIterator.dropWhile(_.trim.isEmpty).mkString("\n ")}
|
||||
|
|
||||
| arguments = [${a.mkString(", ")}]
|
||||
| elapsed = ${e1.toMillis.toString} ms exec (failed)
|
||||
| failure = ${t.getMessage}
|
||||
""".stripMargin)
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
# Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
load("//bazel_tools:scala.bzl", "da_scala_library")
|
||||
|
||||
da_scala_library(
|
||||
name = "flyway-testing",
|
||||
srcs = glob(["src/main/scala/**/*.scala"]),
|
||||
scala_deps = [
|
||||
"@maven//:org_scalactic_scalactic",
|
||||
"@maven//:org_scalatest_scalatest_core",
|
||||
"@maven//:org_scalatest_scalatest_matchers_core",
|
||||
"@maven//:org_scalatest_scalatest_shouldmatchers",
|
||||
"@maven//:org_scalatest_scalatest_wordspec",
|
||||
],
|
||||
tags = ["maven_coordinates=com.daml:flyway-testing:__VERSION__"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//libs-scala/crypto",
|
||||
"@maven//:org_flywaydb_flyway_core",
|
||||
"@maven//:org_scalatest_scalatest_compatible",
|
||||
],
|
||||
)
|
@ -1,10 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
set -eou pipefail
|
||||
|
||||
for file in "$@"; do
|
||||
shasum -a 256 "$file" | awk '{ print $1 }' > "$file.sha256"
|
||||
done
|
@ -1,81 +0,0 @@
|
||||
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.flyway
|
||||
|
||||
import com.daml.crypto.MessageDigestPrototype
|
||||
import org.flywaydb.core.Flyway
|
||||
import org.flywaydb.core.api.configuration.FluentConfiguration
|
||||
import org.flywaydb.core.api.resource.LoadableResource
|
||||
import org.flywaydb.core.internal.scanner.{LocationScannerCache, ResourceNameCache, Scanner}
|
||||
import org.scalatest.matchers.should.Matchers._
|
||||
import org.scalatest.wordspec.AnyWordSpec
|
||||
|
||||
import java.io.{BufferedReader, FileNotFoundException}
|
||||
import java.math.BigInteger
|
||||
import java.nio.charset.Charset
|
||||
import java.util
|
||||
import scala.jdk.CollectionConverters._
|
||||
|
||||
abstract class AbstractImmutableMigrationsSpec extends AnyWordSpec {
|
||||
protected def migrationsResourcePath: String
|
||||
protected def migrationsMinSize: Int
|
||||
protected def hashMigrationsScriptPath: String
|
||||
|
||||
private def flywayScanner(configuration: FluentConfiguration) =
|
||||
new Scanner(
|
||||
classOf[Object],
|
||||
util.Arrays.asList(configuration.getLocations: _*),
|
||||
getClass.getClassLoader,
|
||||
configuration.getEncoding,
|
||||
false,
|
||||
false,
|
||||
new ResourceNameCache,
|
||||
new LocationScannerCache,
|
||||
false,
|
||||
)
|
||||
|
||||
private def readExpectedDigest(
|
||||
digestFile: String,
|
||||
resourceScanner: Scanner[_],
|
||||
): String = {
|
||||
val resource = Option(resourceScanner.getResource(digestFile))
|
||||
.getOrElse(
|
||||
throw new FileNotFoundException(
|
||||
s"""\"$digestFile\" is missing. If you are introducing a new Flyway migration step, you need to create an SHA-256 digest file by running $hashMigrationsScriptPath."""
|
||||
)
|
||||
)
|
||||
new BufferedReader(resource.read()).readLine()
|
||||
}
|
||||
|
||||
private def computeCurrentDigest(resource: LoadableResource, encoding: Charset): String = {
|
||||
val sha256 = MessageDigestPrototype.Sha256.newDigest
|
||||
new BufferedReader(resource.read())
|
||||
.lines()
|
||||
.forEach(line => sha256.update((line + "\n").getBytes(encoding)))
|
||||
val digest = sha256.digest()
|
||||
String.format(s"%0${digest.length * 2}x", new BigInteger(1, digest))
|
||||
}
|
||||
|
||||
"migration files" should {
|
||||
"never change, according to their accompanying digest file" in {
|
||||
val configuration = Flyway
|
||||
.configure()
|
||||
.locations(s"classpath:/$migrationsResourcePath")
|
||||
val resourceScanner = flywayScanner(configuration)
|
||||
val resources = resourceScanner.getResources("", ".sql").asScala.toSeq
|
||||
resources.size should be >= migrationsMinSize
|
||||
|
||||
resources.foreach { resource =>
|
||||
val migrationFile = resource.getRelativePath
|
||||
val digestFile = migrationFile + ".sha256"
|
||||
val expectedDigest = readExpectedDigest(digestFile, resourceScanner)
|
||||
val currentDigest = computeCurrentDigest(resource, configuration.getEncoding)
|
||||
assert(
|
||||
currentDigest == expectedDigest,
|
||||
s"""The contents of the migration file "$migrationFile" have changed! Migrations are immutable; you must not change their contents or their digest.""",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
# Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
load(
|
||||
"//bazel_tools:scala.bzl",
|
||||
"da_scala_library",
|
||||
"da_scala_test_suite",
|
||||
)
|
||||
|
||||
da_scala_library(
|
||||
name = "grpc-reverse-proxy",
|
||||
srcs = glob(["src/main/scala/**/*.scala"]),
|
||||
tags = ["maven_coordinates=com.daml:grpc-reverse-proxy:__VERSION__"],
|
||||
visibility = [
|
||||
"//:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//libs-scala/grpc-server-reflection-client",
|
||||
"//libs-scala/resources",
|
||||
"//libs-scala/resources-grpc",
|
||||
"@maven//:com_google_guava_guava",
|
||||
"@maven//:io_grpc_grpc_api",
|
||||
"@maven//:io_grpc_grpc_services",
|
||||
"@maven//:io_grpc_grpc_stub",
|
||||
"@maven//:io_netty_netty_common",
|
||||
"@maven//:io_netty_netty_transport",
|
||||
],
|
||||
)
|
||||
|
||||
da_scala_test_suite(
|
||||
name = "test",
|
||||
srcs = glob(["src/test/scala/**/*.scala"]),
|
||||
deps = [
|
||||
":grpc-reverse-proxy",
|
||||
"//libs-scala/grpc-test-utils",
|
||||
"//libs-scala/grpc-utils",
|
||||
"//libs-scala/resources",
|
||||
"//libs-scala/resources-grpc",
|
||||
"@maven//:com_google_protobuf_protobuf_java",
|
||||
"@maven//:io_grpc_grpc_api",
|
||||
"@maven//:io_grpc_grpc_core",
|
||||
"@maven//:io_grpc_grpc_inprocess",
|
||||
"@maven//:io_grpc_grpc_services",
|
||||
"@maven//:io_grpc_grpc_stub",
|
||||
"@maven//:io_netty_netty_common",
|
||||
"@maven//:io_netty_netty_transport",
|
||||
],
|
||||
)
|
@ -1,59 +0,0 @@
|
||||
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.grpc
|
||||
|
||||
import io.grpc.MethodDescriptor.MethodType
|
||||
import io.grpc.{CallOptions, Channel, ClientCall, MethodDescriptor, ServerMethodDefinition}
|
||||
import io.grpc.stub.{ClientCalls, ServerCalls, StreamObserver}
|
||||
|
||||
private[grpc] object ForwardCall {
|
||||
|
||||
def apply[ReqT, RespT](
|
||||
method: MethodDescriptor[ReqT, RespT],
|
||||
backend: Channel,
|
||||
options: CallOptions,
|
||||
): ServerMethodDefinition[ReqT, RespT] = {
|
||||
val forward = () => backend.newCall(method, options)
|
||||
ServerMethodDefinition.create[ReqT, RespT](
|
||||
method,
|
||||
method.getType match {
|
||||
case MethodType.UNARY =>
|
||||
ServerCalls.asyncUnaryCall(new UnaryMethod(forward))
|
||||
case MethodType.CLIENT_STREAMING =>
|
||||
ServerCalls.asyncClientStreamingCall(new ClientStreamingMethod(forward))
|
||||
case MethodType.SERVER_STREAMING =>
|
||||
ServerCalls.asyncServerStreamingCall(new ServerStreamingMethod(forward))
|
||||
case MethodType.BIDI_STREAMING =>
|
||||
ServerCalls.asyncBidiStreamingCall(new BidiStreamMethod(forward))
|
||||
case MethodType.UNKNOWN =>
|
||||
sys.error(s"${method.getFullMethodName} has MethodType.UNKNOWN")
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private final class UnaryMethod[ReqT, RespT](call: () => ClientCall[ReqT, RespT])
|
||||
extends ServerCalls.UnaryMethod[ReqT, RespT] {
|
||||
override def invoke(request: ReqT, responseObserver: StreamObserver[RespT]): Unit =
|
||||
ClientCalls.asyncUnaryCall(call(), request, responseObserver)
|
||||
}
|
||||
|
||||
private final class ClientStreamingMethod[ReqT, RespT](call: () => ClientCall[ReqT, RespT])
|
||||
extends ServerCalls.ClientStreamingMethod[ReqT, RespT] {
|
||||
override def invoke(responseObserver: StreamObserver[RespT]): StreamObserver[ReqT] =
|
||||
ClientCalls.asyncClientStreamingCall(call(), responseObserver)
|
||||
}
|
||||
|
||||
private final class ServerStreamingMethod[ReqT, RespT](call: () => ClientCall[ReqT, RespT])
|
||||
extends ServerCalls.ServerStreamingMethod[ReqT, RespT] {
|
||||
override def invoke(request: ReqT, responseObserver: StreamObserver[RespT]): Unit =
|
||||
ClientCalls.asyncServerStreamingCall(call(), request, responseObserver)
|
||||
}
|
||||
|
||||
private final class BidiStreamMethod[ReqT, RespT](call: () => ClientCall[ReqT, RespT])
|
||||
extends ServerCalls.BidiStreamingMethod[ReqT, RespT] {
|
||||
override def invoke(responseObserver: StreamObserver[RespT]): StreamObserver[ReqT] =
|
||||
ClientCalls.asyncBidiStreamingCall(call(), responseObserver)
|
||||
}
|
||||
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.grpc
|
||||
|
||||
import java.io.{ByteArrayInputStream, InputStream}
|
||||
|
||||
import com.daml.grpc.reflection.ServiceDescriptorInfo
|
||||
import com.google.common.io.ByteStreams
|
||||
import io.grpc.{CallOptions, Channel, MethodDescriptor, ServerServiceDefinition}
|
||||
|
||||
private[grpc] object ForwardService {
|
||||
|
||||
def apply(backend: Channel, service: ServiceDescriptorInfo): ServerServiceDefinition =
|
||||
service.methods
|
||||
.map(_.toMethodDescriptor(ByteArrayMarshaller, ByteArrayMarshaller))
|
||||
.map(ForwardCall(_, backend, CallOptions.DEFAULT))
|
||||
.foldLeft(ServerServiceDefinition.builder(service.fullServiceName))(_ addMethod _)
|
||||
.build()
|
||||
|
||||
private object ByteArrayMarshaller extends MethodDescriptor.Marshaller[Array[Byte]] {
|
||||
override def parse(input: InputStream): Array[Byte] = ByteStreams.toByteArray(input)
|
||||
override def stream(bytes: Array[Byte]): InputStream = new ByteArrayInputStream(bytes)
|
||||
}
|
||||
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.grpc
|
||||
|
||||
import com.daml.grpc.reflection.{ServerReflectionClient, ServiceDescriptorInfo}
|
||||
import com.daml.resources.grpc.GrpcResourceOwnerFactories
|
||||
import com.daml.resources.{AbstractResourceOwner, HasExecutionContext, ResourceOwnerFactories}
|
||||
import io.grpc.{Channel, Server, ServerBuilder, ServerInterceptor, ServerInterceptors}
|
||||
import io.grpc.reflection.v1alpha.ServerReflectionGrpc
|
||||
|
||||
import scala.concurrent.duration.DurationInt
|
||||
|
||||
object ReverseProxy {
|
||||
|
||||
private def proxyServices(
|
||||
backend: Channel,
|
||||
serverBuilder: ServerBuilder[_],
|
||||
services: Set[ServiceDescriptorInfo],
|
||||
interceptors: Map[String, Seq[ServerInterceptor]],
|
||||
): Unit =
|
||||
for (service <- services) {
|
||||
serverBuilder.addService(
|
||||
ServerInterceptors.interceptForward(
|
||||
ForwardService(backend, service),
|
||||
interceptors.getOrElse(service.fullServiceName, Seq.empty): _*
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def owner[Context](
|
||||
backend: Channel,
|
||||
serverBuilder: ServerBuilder[_],
|
||||
interceptors: Map[String, Seq[ServerInterceptor]],
|
||||
)(implicit context: HasExecutionContext[Context]): AbstractResourceOwner[Context, Server] = {
|
||||
val factory = new ResourceOwnerFactories[Context] with GrpcResourceOwnerFactories[Context] {
|
||||
override protected implicit val hasExecutionContext: HasExecutionContext[Context] =
|
||||
context
|
||||
}
|
||||
val stub = ServerReflectionGrpc.newStub(backend)
|
||||
val client = new ServerReflectionClient(stub)
|
||||
for {
|
||||
services <- factory.forFuture(() => client.getAllServices())
|
||||
_ = proxyServices(backend, serverBuilder, services, interceptors)
|
||||
server <- factory.forServer(serverBuilder, shutdownTimeout = 5.seconds)
|
||||
} yield server
|
||||
}
|
||||
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.grpc.interceptors
|
||||
|
||||
import io.grpc.ForwardingServerCall.SimpleForwardingServerCall
|
||||
import io.grpc.ServerCall
|
||||
|
||||
final class ForwardingServerCall[ReqT, RespT](call: ServerCall[ReqT, RespT])
|
||||
extends SimpleForwardingServerCall[ReqT, RespT](call) {}
|
@ -1,15 +0,0 @@
|
||||
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.grpc.interceptors
|
||||
|
||||
import io.grpc.ForwardingServerCallListener.SimpleForwardingServerCallListener
|
||||
import io.grpc.{Metadata, ServerCall, ServerCallHandler}
|
||||
|
||||
abstract class ForwardingServerCallListener[ReqT, RespT](
|
||||
call: ServerCall[ReqT, RespT],
|
||||
headers: Metadata,
|
||||
next: ServerCallHandler[ReqT, RespT],
|
||||
) extends SimpleForwardingServerCallListener[ReqT](
|
||||
next.startCall(new ForwardingServerCall(call), headers)
|
||||
)
|
@ -1,100 +0,0 @@
|
||||
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.grpc
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
import com.daml.grpc.interceptors.ForwardingServerCallListener
|
||||
import com.daml.grpc.test.GrpcServer
|
||||
import io.grpc.{Metadata, ServerCall, ServerCallHandler, ServerInterceptor, StatusRuntimeException}
|
||||
import io.grpc.health.v1.{HealthCheckRequest, HealthGrpc}
|
||||
import io.grpc.inprocess.{InProcessChannelBuilder, InProcessServerBuilder}
|
||||
import org.scalatest.Inside
|
||||
import org.scalatest.flatspec.AsyncFlatSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
final class ReverseProxySpec extends AsyncFlatSpec with Matchers with GrpcServer with Inside {
|
||||
|
||||
import ReverseProxySpec._
|
||||
import Services._
|
||||
|
||||
behavior of "ReverseProxy.create"
|
||||
|
||||
it should "fail if the backend does not support reflection" in withServices(
|
||||
Health.newInstance
|
||||
) { backend =>
|
||||
val proxyBuilder = InProcessServerBuilder.forName(InProcessServerBuilder.generateName())
|
||||
ReverseProxy
|
||||
.owner(backend, proxyBuilder, interceptors = Map.empty)
|
||||
.use(_ => fail("The proxy started but the backend does not support reflection"))
|
||||
.failed
|
||||
.map(_ shouldBe a[StatusRuntimeException])
|
||||
}
|
||||
|
||||
it should "expose the backend's own services" in withServices(
|
||||
Health.newInstance,
|
||||
Reflection.newInstance,
|
||||
) { backend =>
|
||||
val proxyName = InProcessServerBuilder.generateName()
|
||||
val proxyBuilder = InProcessServerBuilder.forName(proxyName)
|
||||
ReverseProxy.owner(backend, proxyBuilder, interceptors = Map.empty).use { _ =>
|
||||
val proxy = InProcessChannelBuilder.forName(proxyName).build()
|
||||
Health.getHealthStatus(backend) shouldEqual Health.getHealthStatus(proxy)
|
||||
Reflection.listServices(backend) shouldEqual Reflection.listServices(proxy)
|
||||
}
|
||||
}
|
||||
|
||||
it should "correctly set up an interceptor" in withServices(
|
||||
Health.newInstance,
|
||||
Reflection.newInstance,
|
||||
) { backend =>
|
||||
val proxyName = InProcessServerBuilder.generateName()
|
||||
val proxyBuilder = InProcessServerBuilder.forName(proxyName)
|
||||
val recorder = new RecordingInterceptor
|
||||
ReverseProxy
|
||||
.owner(backend, proxyBuilder, Map(HealthGrpc.SERVICE_NAME -> Seq(recorder)))
|
||||
.use { _ =>
|
||||
val proxy = InProcessChannelBuilder.forName(proxyName).build()
|
||||
Health.getHealthStatus(backend)
|
||||
recorder.latestRequest() shouldBe empty
|
||||
Health.getHealthStatus(proxy)
|
||||
inside(recorder.latestRequest()) { case Some(request: Array[Byte]) =>
|
||||
HealthCheckRequest.parseFrom(request)
|
||||
succeed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object ReverseProxySpec {
|
||||
|
||||
final class Callback[ReqT, RespT](
|
||||
call: ServerCall[ReqT, RespT],
|
||||
headers: Metadata,
|
||||
next: ServerCallHandler[ReqT, RespT],
|
||||
callback: ReqT => Unit,
|
||||
) extends ForwardingServerCallListener(call, headers, next) {
|
||||
override def onMessage(message: ReqT): Unit = {
|
||||
callback(message)
|
||||
super.onMessage(message)
|
||||
}
|
||||
}
|
||||
|
||||
final class RecordingInterceptor extends ServerInterceptor {
|
||||
|
||||
private val latestRequestReference = new AtomicReference[Any]()
|
||||
|
||||
def latestRequest(): Option[Any] = Option(latestRequestReference.get)
|
||||
|
||||
override def interceptCall[ReqT, RespT](
|
||||
call: ServerCall[ReqT, RespT],
|
||||
headers: Metadata,
|
||||
next: ServerCallHandler[ReqT, RespT],
|
||||
): ServerCall.Listener[ReqT] = {
|
||||
new Callback(call, headers, next, latestRequestReference.set)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
# Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
load(
|
||||
"//bazel_tools:scala.bzl",
|
||||
"da_scala_library",
|
||||
"da_scala_test_suite",
|
||||
)
|
||||
|
||||
da_scala_library(
|
||||
name = "grpc-server-reflection-client",
|
||||
srcs = glob(["src/main/scala/**/*.scala"]),
|
||||
tags = ["maven_coordinates=com.daml:grpc-server-reflection-client:__VERSION__"],
|
||||
visibility = [
|
||||
"//:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"@maven//:com_google_protobuf_protobuf_java",
|
||||
"@maven//:io_grpc_grpc_api",
|
||||
"@maven//:io_grpc_grpc_services",
|
||||
"@maven//:io_grpc_grpc_stub",
|
||||
],
|
||||
)
|
||||
|
||||
da_scala_test_suite(
|
||||
name = "test",
|
||||
srcs = glob(["src/test/scala/**/*.scala"]),
|
||||
deps = [
|
||||
":grpc-server-reflection-client",
|
||||
"//libs-scala/grpc-test-utils",
|
||||
"@maven//:com_google_protobuf_protobuf_java",
|
||||
"@maven//:io_grpc_grpc_api",
|
||||
"@maven//:io_grpc_grpc_core",
|
||||
"@maven//:io_grpc_grpc_services",
|
||||
"@maven//:io_grpc_grpc_stub",
|
||||
],
|
||||
)
|
@ -1,63 +0,0 @@
|
||||
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.grpc.reflection
|
||||
|
||||
import com.google.protobuf.DescriptorProtos.MethodDescriptorProto
|
||||
import io.grpc.MethodDescriptor
|
||||
import io.grpc.MethodDescriptor.{MethodType, generateFullMethodName}
|
||||
|
||||
final case class MethodDescriptorInfo(
|
||||
fullMethodName: String,
|
||||
methodType: MethodType,
|
||||
) {
|
||||
|
||||
def toMethodDescriptor[ReqT, RespT](
|
||||
requestMarshaller: MethodDescriptor.Marshaller[ReqT],
|
||||
responseMarshaller: MethodDescriptor.Marshaller[RespT],
|
||||
): MethodDescriptor[ReqT, RespT] =
|
||||
MethodDescriptor
|
||||
.newBuilder(requestMarshaller, responseMarshaller)
|
||||
.setType(methodType)
|
||||
.setFullMethodName(fullMethodName)
|
||||
.build()
|
||||
|
||||
}
|
||||
|
||||
object MethodDescriptorInfo {
|
||||
|
||||
def apply(fullServiceName: String, method: MethodDescriptorProto): MethodDescriptorInfo =
|
||||
MethodDescriptorInfo(
|
||||
methodName = generateFullMethodName(fullServiceName, method.getName),
|
||||
clientStreaming = method.getClientStreaming,
|
||||
serverStreaming = method.getServerStreaming,
|
||||
)
|
||||
|
||||
def apply(method: MethodDescriptor[_, _]): MethodDescriptorInfo =
|
||||
MethodDescriptorInfo(
|
||||
fullMethodName = method.getFullMethodName,
|
||||
methodType = method.getType,
|
||||
)
|
||||
|
||||
def apply(
|
||||
methodName: String,
|
||||
clientStreaming: Boolean,
|
||||
serverStreaming: Boolean,
|
||||
): MethodDescriptorInfo =
|
||||
MethodDescriptorInfo(
|
||||
fullMethodName = methodName,
|
||||
methodType = methodType(clientStreaming, serverStreaming),
|
||||
)
|
||||
|
||||
private def methodType(clientStreaming: Boolean, serverStreaming: Boolean): MethodType =
|
||||
if (clientStreaming && serverStreaming) {
|
||||
MethodType.BIDI_STREAMING
|
||||
} else if (clientStreaming) {
|
||||
MethodType.CLIENT_STREAMING
|
||||
} else if (serverStreaming) {
|
||||
MethodType.SERVER_STREAMING
|
||||
} else {
|
||||
MethodType.UNARY
|
||||
}
|
||||
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.grpc.reflection
|
||||
|
||||
import io.grpc.reflection.v1alpha.ServerReflectionGrpc.ServerReflectionStub
|
||||
import io.grpc.reflection.v1alpha.ServerReflectionRequest
|
||||
import io.grpc.stub.StreamObserver
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
final class ServerReflectionClient(stub: ServerReflectionStub) {
|
||||
|
||||
def getAllServices(): Future[Set[ServiceDescriptorInfo]] = {
|
||||
|
||||
lazy val serviceDescriptorInfo: ServiceDescriptorInfoObserver =
|
||||
new ServiceDescriptorInfoObserver(serverReflectionStream)
|
||||
|
||||
lazy val serverReflectionStream: StreamObserver[ServerReflectionRequest] =
|
||||
stub.serverReflectionInfo(serviceDescriptorInfo)
|
||||
|
||||
serviceDescriptorInfo.result
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.grpc.reflection
|
||||
|
||||
import io.grpc.reflection.v1alpha.ServerReflectionRequest
|
||||
|
||||
private[reflection] object ServerReflectionRequests {
|
||||
|
||||
val ListServices: ServerReflectionRequest =
|
||||
ServerReflectionRequest.newBuilder().setListServices("").build()
|
||||
|
||||
def fileContaining(symbol: String): ServerReflectionRequest =
|
||||
ServerReflectionRequest.newBuilder().setFileContainingSymbol(symbol).build()
|
||||
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.grpc.reflection
|
||||
|
||||
import com.google.protobuf.DescriptorProtos.MethodDescriptorProto
|
||||
|
||||
final case class ServiceDescriptorInfo(
|
||||
fullServiceName: String,
|
||||
methods: Set[MethodDescriptorInfo],
|
||||
)
|
||||
|
||||
object ServiceDescriptorInfo {
|
||||
|
||||
def apply(
|
||||
packageName: String,
|
||||
serviceName: String,
|
||||
methods: Iterable[MethodDescriptorProto],
|
||||
): ServiceDescriptorInfo = {
|
||||
val fullServiceName: String = s"$packageName.$serviceName"
|
||||
ServiceDescriptorInfo(
|
||||
fullServiceName = fullServiceName,
|
||||
methods = methods.view.map(MethodDescriptorInfo(fullServiceName, _)).toSet,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.grpc.reflection
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
import com.google.protobuf.DescriptorProtos.FileDescriptorProto
|
||||
import io.grpc.Status
|
||||
import io.grpc.reflection.v1alpha.{ServerReflectionRequest, ServerReflectionResponse}
|
||||
import io.grpc.stub.StreamObserver
|
||||
|
||||
import scala.jdk.CollectionConverters._
|
||||
import scala.concurrent.{Future, Promise}
|
||||
import scala.util.Success
|
||||
|
||||
private[reflection] final class ServiceDescriptorInfoObserver(
|
||||
serverReflectionStream: => StreamObserver[ServerReflectionRequest]
|
||||
) extends StreamObserver[ServerReflectionResponse] {
|
||||
|
||||
private val builder = Set.newBuilder[ServiceDescriptorInfo]
|
||||
private val promise = Promise[Set[ServiceDescriptorInfo]]()
|
||||
private val servicesLeft = new AtomicInteger(0)
|
||||
|
||||
lazy val result: Future[Set[ServiceDescriptorInfo]] = {
|
||||
serverReflectionStream.onNext(ServerReflectionRequests.ListServices)
|
||||
promise.future
|
||||
}
|
||||
|
||||
override def onNext(response: ServerReflectionResponse): Unit = {
|
||||
if (response.hasListServicesResponse) {
|
||||
servicesLeft.set(response.getListServicesResponse.getServiceCount)
|
||||
for (service <- response.getListServicesResponse.getServiceList.asScala) {
|
||||
serverReflectionStream.onNext(ServerReflectionRequests.fileContaining(service.getName))
|
||||
}
|
||||
} else if (response.hasFileDescriptorResponse) {
|
||||
for (bytes <- response.getFileDescriptorResponse.getFileDescriptorProtoList.asScala) {
|
||||
val fileDescriptorProto = FileDescriptorProto.parseFrom(bytes)
|
||||
for (service <- fileDescriptorProto.getServiceList.asScala) {
|
||||
builder +=
|
||||
ServiceDescriptorInfo(
|
||||
packageName = fileDescriptorProto.getPackage,
|
||||
serviceName = service.getName,
|
||||
methods = service.getMethodList.asScala,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (servicesLeft.decrementAndGet() < 1) {
|
||||
serverReflectionStream.onCompleted()
|
||||
}
|
||||
} else if (response.hasErrorResponse) {
|
||||
val error = response.getErrorResponse
|
||||
val throwable = Status
|
||||
.fromCodeValue(error.getErrorCode)
|
||||
.withDescription(error.getErrorMessage)
|
||||
.asRuntimeException()
|
||||
serverReflectionStream.onError(throwable)
|
||||
}
|
||||
}
|
||||
|
||||
override def onError(throwable: Throwable): Unit = {
|
||||
val _ = promise.tryFailure(throwable)
|
||||
}
|
||||
|
||||
override def onCompleted(): Unit = {
|
||||
val _ = promise.tryComplete(Success(builder.result()))
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.grpc.reflection
|
||||
|
||||
import com.daml.grpc.test.GrpcServer
|
||||
import io.grpc.StatusRuntimeException
|
||||
import io.grpc.health.v1.HealthGrpc
|
||||
import io.grpc.reflection.v1alpha.ServerReflectionGrpc
|
||||
import org.scalatest.flatspec.AsyncFlatSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
final class ServerReflectionClientSpec extends AsyncFlatSpec with Matchers with GrpcServer {
|
||||
|
||||
import ServerReflectionClientSpec._
|
||||
import Services._
|
||||
|
||||
behavior of "getAllServices"
|
||||
|
||||
it should "fail if reflection is not supported" in withServices(Health.newInstance) { channel =>
|
||||
val stub = ServerReflectionGrpc.newStub(channel)
|
||||
val client = new ServerReflectionClient(stub)
|
||||
client.getAllServices().failed.map(_ shouldBe a[StatusRuntimeException])
|
||||
}
|
||||
|
||||
it should "show all if reflection is supported" in withServices(
|
||||
Health.newInstance,
|
||||
Reflection.newInstance,
|
||||
) { channel =>
|
||||
val expected = Set(healthDescriptor, reflectionDescriptor)
|
||||
val stub = ServerReflectionGrpc.newStub(channel)
|
||||
val client = new ServerReflectionClient(stub)
|
||||
client.getAllServices().map(_ should contain theSameElementsAs expected)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object ServerReflectionClientSpec {
|
||||
|
||||
private val healthDescriptor =
|
||||
ServiceDescriptorInfo(
|
||||
fullServiceName = HealthGrpc.SERVICE_NAME,
|
||||
methods = Set(
|
||||
MethodDescriptorInfo(HealthGrpc.getCheckMethod),
|
||||
MethodDescriptorInfo(HealthGrpc.getWatchMethod),
|
||||
),
|
||||
)
|
||||
|
||||
private val reflectionDescriptor =
|
||||
ServiceDescriptorInfo(
|
||||
fullServiceName = ServerReflectionGrpc.SERVICE_NAME,
|
||||
methods = Set(MethodDescriptorInfo(ServerReflectionGrpc.getServerReflectionInfoMethod)),
|
||||
)
|
||||
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
# Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
load(
|
||||
"//bazel_tools:scala.bzl",
|
||||
"da_scala_library",
|
||||
"da_scala_test",
|
||||
)
|
||||
|
||||
da_scala_library(
|
||||
name = "oracle-testing",
|
||||
srcs = glob(["src/main/scala/**/*.scala"]),
|
||||
data = [
|
||||
"//ci:oracle_image",
|
||||
],
|
||||
scala_deps = [
|
||||
"@maven//:org_scalactic_scalactic",
|
||||
"@maven//:org_scalatest_scalatest_core",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
deps = [
|
||||
"//libs-scala/ports",
|
||||
"@maven//:org_scalatest_scalatest_compatible",
|
||||
"@maven//:org_slf4j_slf4j_api",
|
||||
],
|
||||
)
|
@ -1,136 +0,0 @@
|
||||
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.testing.oracle
|
||||
|
||||
import com.daml.ports._
|
||||
import java.sql._
|
||||
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.util.{Failure, Random, Success, Try, Using}
|
||||
|
||||
object OracleAround {
|
||||
|
||||
case class RichOracleUser(
|
||||
oracleUser: OracleUser,
|
||||
jdbcUrlWithoutCredentials: String,
|
||||
jdbcUrl: String,
|
||||
drop: () => Unit,
|
||||
) {
|
||||
|
||||
/** In CI we re-use the same Oracle instance for testing, so non-colliding DB-lock-ids need to be assigned
|
||||
*
|
||||
* @return A positive integer, which defines a unique / mutually exclusive range of usable lock ids: [seed, seed + 10)
|
||||
*/
|
||||
def lockIdSeed: Int = {
|
||||
assert(oracleUser.id > 0, "Lock ID seeding is not supported, cannot ensure unique lock-ids")
|
||||
assert(oracleUser.id < 10 * 1000 * 1000)
|
||||
oracleUser.id * 10
|
||||
}
|
||||
}
|
||||
|
||||
case class OracleUser(name: String, id: Int) {
|
||||
val pwd: String = "hunter2"
|
||||
}
|
||||
|
||||
private val logger = LoggerFactory.getLogger(getClass)
|
||||
|
||||
def createNewUniqueRandomUser(): RichOracleUser = createRichOracleUser { stmt =>
|
||||
val id = Random.nextInt(1000 * 1000) + 1
|
||||
val user = OracleUser(s"U$id", id)
|
||||
createUser(stmt, user.name, user.pwd)
|
||||
logger.info(s"New unique random Oracle user created $user")
|
||||
user
|
||||
}
|
||||
|
||||
def createOrReuseUser(name: String): RichOracleUser = createRichOracleUser { stmt =>
|
||||
val user = OracleUser(name.toUpperCase, -1)
|
||||
if (!userExists(stmt, user.name)) {
|
||||
createUser(stmt, user.name, user.pwd)
|
||||
logger.info(s"User $name not found: new Oracle user created $user")
|
||||
} else {
|
||||
logger.info(s"User $name already created: re-using existing Oracle user $user")
|
||||
}
|
||||
user
|
||||
}
|
||||
|
||||
private def userExists(stmt: Statement, name: String): Boolean = {
|
||||
val res = stmt.executeQuery(
|
||||
s"""SELECT count(*) AS user_count FROM all_users WHERE username='$name'"""
|
||||
)
|
||||
res.next()
|
||||
res.getInt("user_count") > 0
|
||||
}
|
||||
|
||||
private def createUser(stmt: Statement, name: String, pwd: String): Unit = {
|
||||
stmt.execute(s"""create user $name identified by $pwd""")
|
||||
stmt.execute(s"""grant connect, resource to $name""")
|
||||
stmt.execute(
|
||||
s"""grant create table, create materialized view, create view, create procedure, create sequence, create type to $name"""
|
||||
)
|
||||
stmt.execute(s"""alter user $name quota unlimited on users""")
|
||||
|
||||
// for DBMS_LOCK access
|
||||
stmt.execute(s"""GRANT EXECUTE ON SYS.DBMS_LOCK TO $name""")
|
||||
stmt.execute(s"""GRANT SELECT ON V_$$MYSTAT TO $name""")
|
||||
stmt.execute(s"""GRANT SELECT ON V_$$LOCK TO $name""")
|
||||
()
|
||||
}
|
||||
|
||||
private def createRichOracleUser(createBody: Statement => OracleUser): RichOracleUser = {
|
||||
val systemUser = sys.env("ORACLE_USERNAME")
|
||||
val systemPwd = sys.env("ORACLE_PWD")
|
||||
val host = sys.env.getOrElse("ORACLE_HOST", "localhost")
|
||||
val port = Port(sys.env("ORACLE_PORT").toInt)
|
||||
val jdbcUrlWithoutCredentials = s"jdbc:oracle:thin:@$host:$port/ORCLPDB1"
|
||||
|
||||
def withStmt[T](connectingUserName: String)(body: Statement => T): T =
|
||||
Using.resource(
|
||||
DriverManager.getConnection(
|
||||
jdbcUrlWithoutCredentials,
|
||||
connectingUserName,
|
||||
systemPwd,
|
||||
)
|
||||
) { connection =>
|
||||
connection.setAutoCommit(false)
|
||||
val result = Using.resource(connection.createStatement())(body)
|
||||
connection.commit()
|
||||
result
|
||||
}
|
||||
|
||||
@tailrec
|
||||
def retry[T](times: Int, sleepMillisBeforeReTry: Long)(body: => T): T = Try(body) match {
|
||||
case Success(t) => t
|
||||
case Failure(_) if times > 0 =>
|
||||
if (sleepMillisBeforeReTry > 0) Thread.sleep(sleepMillisBeforeReTry)
|
||||
retry(times - 1, sleepMillisBeforeReTry)(body)
|
||||
case Failure(t) => throw t
|
||||
}
|
||||
|
||||
retry(20, 100) {
|
||||
withStmt(
|
||||
"sys as sysdba" // TODO this is needed for being able to grant the execute access for the sys.dbms_lock below. Consider making this configurable
|
||||
) { stmt =>
|
||||
logger.info("Trying to create Oracle user")
|
||||
val oracleUser = createBody(stmt)
|
||||
logger.info(s"Oracle user ready $oracleUser")
|
||||
RichOracleUser(
|
||||
oracleUser = oracleUser,
|
||||
jdbcUrlWithoutCredentials = jdbcUrlWithoutCredentials,
|
||||
jdbcUrl = s"jdbc:oracle:thin:${oracleUser.name}/${oracleUser.pwd}@$host:$port/ORCLPDB1",
|
||||
drop = () => {
|
||||
retry(10, 1000) {
|
||||
logger.info(s"Trying to remove Oracle user ${oracleUser.name}")
|
||||
withStmt(systemUser)(_.execute(s"""drop user ${oracleUser.name} cascade"""))
|
||||
}
|
||||
logger.info(s"Oracle user removed successfully ${oracleUser.name}")
|
||||
()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.testing.oracle
|
||||
|
||||
import org.scalatest.{BeforeAndAfterAll, Suite}
|
||||
|
||||
trait OracleAroundAll extends OracleAroundSuite with BeforeAndAfterAll {
|
||||
self: Suite =>
|
||||
|
||||
override protected def beforeAll(): Unit = {
|
||||
Class.forName("oracle.jdbc.OracleDriver")
|
||||
createNewUser()
|
||||
super.beforeAll()
|
||||
}
|
||||
|
||||
override protected def afterAll(): Unit = {
|
||||
super.afterAll()
|
||||
dropUser()
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.testing.oracle
|
||||
|
||||
import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach, Suite}
|
||||
|
||||
trait OracleAroundEach extends OracleAroundSuite with BeforeAndAfterAll with BeforeAndAfterEach {
|
||||
self: Suite =>
|
||||
|
||||
override protected def beforeAll(): Unit = {
|
||||
Class.forName("oracle.jdbc.OracleDriver")
|
||||
super.beforeAll()
|
||||
}
|
||||
|
||||
override protected def afterAll(): Unit = {
|
||||
super.afterAll()
|
||||
}
|
||||
|
||||
override protected def beforeEach(): Unit = {
|
||||
createNewUser()
|
||||
super.beforeEach()
|
||||
}
|
||||
|
||||
override protected def afterEach(): Unit = {
|
||||
super.afterEach()
|
||||
dropUser()
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.testing.oracle
|
||||
|
||||
import com.daml.testing.oracle.OracleAround.RichOracleUser
|
||||
import org.scalatest.Suite
|
||||
|
||||
trait OracleAroundSuite {
|
||||
self: Suite =>
|
||||
|
||||
@volatile
|
||||
private var user: RichOracleUser = _
|
||||
|
||||
def jdbcUrl: String = user.jdbcUrl
|
||||
def lockIdSeed: Int = user.lockIdSeed
|
||||
def oracleUserName: String = user.oracleUser.name
|
||||
def oracleUserPwd: String = user.oracleUser.pwd
|
||||
def oracleJdbcUrlWithoutCredentials: String = user.jdbcUrlWithoutCredentials
|
||||
|
||||
protected def createNewUser(): Unit = user = OracleAround.createNewUniqueRandomUser()
|
||||
|
||||
protected def dropUser(): Unit = user.drop()
|
||||
}
|
@ -171,8 +171,6 @@
|
||||
type: jar-scala
|
||||
- target: //libs-scala/crypto:crypto
|
||||
type: jar-scala
|
||||
- target: //libs-scala/doobie-slf4j:doobie-slf4j
|
||||
type: jar-scala
|
||||
- target: //libs-scala/grpc-test-utils:grpc-test-utils
|
||||
type: jar-scala
|
||||
- target: //libs-scala/grpc-utils:grpc-utils
|
||||
|
@ -42,7 +42,6 @@ write_scalatest_runpath(
|
||||
],
|
||||
runtime_deps = [
|
||||
"//ledger/error:error-test-lib",
|
||||
"//libs-scala/flyway-testing",
|
||||
"//libs-scala/jwt",
|
||||
"//libs-scala/scalatest-utils",
|
||||
],
|
||||
|
Loading…
Reference in New Issue
Block a user