[DPP-572] Add ledger API test case for verifying conformance to --min-tls-version flag. (#10898)

Add CLI flag to select minimum enabled TLS version for participant server

CHANGELOG_BEGIN
Sandbox: Add CLI flag `--min-tls-version` to select minimum enabled TLS version for participant server.
CHANGELOG_END
This commit is contained in:
pbatko-da 2021-09-20 12:17:00 +02:00 committed by GitHub
parent 3e13e3d87e
commit 855ecdf1a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 304 additions and 78 deletions

View File

@ -91,6 +91,7 @@ da_scala_binary(
"//ledger/test-common:model-tests-%s.scala" % lf_version, "//ledger/test-common:model-tests-%s.scala" % lf_version,
"//ledger/test-common:dar-files-%s-lib" % lf_version, "//ledger/test-common:dar-files-%s-lib" % lf_version,
"//ledger-api/grpc-definitions:ledger_api_proto_scala", "//ledger-api/grpc-definitions:ledger_api_proto_scala",
"//ledger/ledger-api-common",
"//libs-scala/build-info", "//libs-scala/build-info",
"//libs-scala/grpc-utils", "//libs-scala/grpc-utils",
"//libs-scala/resources", "//libs-scala/resources",
@ -124,6 +125,10 @@ da_scala_binary(
"//daml-lf/data", "//daml-lf/data",
"//language-support/scala/bindings", "//language-support/scala/bindings",
"//ledger/ledger-api-common", "//ledger/ledger-api-common",
"//ledger/ledger-resources",
"//libs-scala/resources",
"//libs-scala/resources-akka",
"//libs-scala/resources-grpc",
"//ledger/test-common:test-common-%s" % lf_version, "//ledger/test-common:test-common-%s" % lf_version,
"//ledger/test-common:package_management-tests-%s.scala" % lf_version, "//ledger/test-common:package_management-tests-%s.scala" % lf_version,
"//ledger/test-common:model-tests-%s.scala" % lf_version, "//ledger/test-common:model-tests-%s.scala" % lf_version,
@ -134,6 +139,8 @@ da_scala_binary(
"//libs-scala/timer-utils", "//libs-scala/timer-utils",
"@maven//:io_grpc_grpc_api", "@maven//:io_grpc_grpc_api",
"@maven//:io_grpc_grpc_context", "@maven//:io_grpc_grpc_context",
"@maven//:io_grpc_grpc_netty",
"@maven//:io_netty_netty_handler",
"@maven//:org_slf4j_slf4j_api", "@maven//:org_slf4j_slf4j_api",
], ],
), ),
@ -245,6 +252,7 @@ conformance_test(
# Retired tests will be eventually removed. # Retired tests will be eventually removed.
"--additional=LotsOfPartiesIT", "--additional=LotsOfPartiesIT",
"--additional=TransactionScaleIT", "--additional=TransactionScaleIT",
"--additional=TLSOnePointThreeIT",
"--exclude=CommandDeduplicationIT", "--exclude=CommandDeduplicationIT",
# Makes sure that deprecated CLI options can still be used to make sure existing CI pipelines are not broken. # Makes sure that deprecated CLI options can still be used to make sure existing CI pipelines are not broken.
# This test should fail if any deprecated CLI option has any effect whatsoever -- they are preserved # This test should fail if any deprecated CLI option has any effect whatsoever -- they are preserved

View File

@ -73,11 +73,13 @@ object Cli {
head( head(
"""The Ledger API Test Tool is a command line tool for testing the correctness of """The Ledger API Test Tool is a command line tool for testing the correctness of
|ledger implementations based on Daml and Ledger API.""".stripMargin |ledger implementations based on Daml and Ledger API.""".stripMargin
) )
arg[(String, Int)]("[endpoints...]")(endpointRead) arg[(String, Int)]("[endpoints...]")(endpointRead)
.action((address, config) => config.copy(participants = config.participants :+ address)) .action((address, config) =>
config.copy(participantsEndpoints = config.participantsEndpoints :+ address)
)
.unbounded() .unbounded()
.optional() .optional()
.text("Addresses of the participants to test, specified as `<host>:<port>`.") .text("Addresses of the participants to test, specified as `<host>:<port>`.")
@ -105,7 +107,7 @@ object Cli {
.optional() .optional()
.text( .text(
"""TLS: The crt file to be used as the cert chain. """TLS: The crt file to be used as the cert chain.
|Required if any other TLS parameters are set. Applied to all endpoints.""".stripMargin |Required if any other TLS parameters are set. Applied to all endpoints.""".stripMargin
) )
.action(crtConfig) .action(crtConfig)
@ -119,9 +121,9 @@ object Cli {
.action((v, c) => c.copy(timeoutScaleFactor = v)) .action((v, c) => c.copy(timeoutScaleFactor = v))
.text( .text(
"""Scale factor for timeouts used in all test suites. Useful to tune timeouts """Scale factor for timeouts used in all test suites. Useful to tune timeouts
|depending on the environment and the Ledger implementation under test. |depending on the environment and the Ledger implementation under test.
|Defaults to 1.0. Use numbers higher than 1.0 to make test timeouts more lax, |Defaults to 1.0. Use numbers higher than 1.0 to make test timeouts more lax,
|use numbers lower than 1.0 to make test timeouts more strict.""".stripMargin |use numbers lower than 1.0 to make test timeouts more strict.""".stripMargin
) )
opt[String](name = "load-scale-factor") opt[String](name = "load-scale-factor")
@ -144,16 +146,16 @@ object Cli {
.action((_, c) => c.copy(mustFail = true)) .action((_, c) => c.copy(mustFail = true))
.text( .text(
"""Reverse success status logic of the tool. Use this flag if you expect one or """Reverse success status logic of the tool. Use this flag if you expect one or
|more or the scenario tests to fail. If enabled, the tool will succeed when at |more or the scenario tests to fail. If enabled, the tool will succeed when at
|least one test fails, and it will fail when all tests succeed. Defaults to |least one test fails, and it will fail when all tests succeed. Defaults to
|false.""".stripMargin |false.""".stripMargin
) )
opt[Unit]('x', "extract") opt[Unit]('x', "extract")
.action((_, c) => c.copy(extract = true)) .action((_, c) => c.copy(extract = true))
.text( .text(
"""Extract a DAR necessary to test a Daml ledger and exit without running tests. """Extract a DAR necessary to test a Daml ledger and exit without running tests.
|The DAR needs to be manually loaded into a Daml ledger for the tool to work.""".stripMargin |The DAR needs to be manually loaded into a Daml ledger for the tool to work.""".stripMargin
) )
opt[Seq[String]]("exclude") opt[Seq[String]]("exclude")
@ -161,8 +163,8 @@ object Cli {
.unbounded() .unbounded()
.text( .text(
"""A comma-separated list of exclusion prefixes. Tests whose name start with """A comma-separated list of exclusion prefixes. Tests whose name start with
|any of the given prefixes will be skipped. Can be specified multiple times, |any of the given prefixes will be skipped. Can be specified multiple times,
|i.e. `--exclude=a,b` is the same as `--exclude=a --exclude=b`.""".stripMargin |i.e. `--exclude=a,b` is the same as `--exclude=a --exclude=b`.""".stripMargin
) )
opt[Seq[String]]("include") opt[Seq[String]]("include")
@ -170,10 +172,10 @@ object Cli {
.unbounded() .unbounded()
.text( .text(
"""A comma-separated list of inclusion prefixes. If not specified, """A comma-separated list of inclusion prefixes. If not specified,
|all default tests are included. If specified, only tests that match at least one |all default tests are included. If specified, only tests that match at least one
|of the given inclusion prefixes (and none of the given exclusion prefixes) will be run. |of the given inclusion prefixes (and none of the given exclusion prefixes) will be run.
|Can be specified multiple times, i.e. `--include=a,b` is the same as `--include=a --include=b`. |Can be specified multiple times, i.e. `--include=a,b` is the same as `--include=a --include=b`.
|Mutually exclusive with `--additional`.""".stripMargin |Mutually exclusive with `--additional`.""".stripMargin
) )
opt[Seq[String]]("additional") opt[Seq[String]]("additional")
@ -182,9 +184,9 @@ object Cli {
.unbounded() .unbounded()
.text( .text(
"""A comma-separated list of additional prefixes. If specified, also tests that match at least one """A comma-separated list of additional prefixes. If specified, also tests that match at least one
|of the given inclusion prefixes (and none of the given exclusion prefixes) will be run. |of the given inclusion prefixes (and none of the given exclusion prefixes) will be run.
|Can be specified multiple times, i.e. `--additional=a,b` is the same as `--additional=a --additional=b`. |Can be specified multiple times, i.e. `--additional=a,b` is the same as `--additional=a --additional=b`.
|Mutually exclusive with `--include`.""".stripMargin |Mutually exclusive with `--include`.""".stripMargin
) )
opt[Seq[String]]("perf-tests") opt[Seq[String]]("perf-tests")
@ -209,7 +211,7 @@ object Cli {
.action((_, c) => c.copy(shuffleParticipants = true)) .action((_, c) => c.copy(shuffleParticipants = true))
.text( .text(
"""Shuffle the list of participants used in a test. """Shuffle the list of participants used in a test.
|By default participants are used in the order they're given.""".stripMargin |By default participants are used in the order they're given.""".stripMargin
) )
opt[Unit]("no-wait-for-parties") opt[Unit]("no-wait-for-parties")
@ -221,16 +223,16 @@ object Cli {
.action((_, c) => c.copy(partyAllocation = PartyAllocationConfiguration.OpenWorld)) .action((_, c) => c.copy(partyAllocation = PartyAllocationConfiguration.OpenWorld))
.text( .text(
"""Do not allocate parties explicitly. """Do not allocate parties explicitly.
|Instead, expect the ledger to allocate parties dynamically. |Instead, expect the ledger to allocate parties dynamically.
|Party names must be their hints.""".stripMargin |Party names must be their hints.""".stripMargin
) )
opt[Unit]("list") opt[Unit]("list")
.action((_, c) => c.copy(listTestSuites = true)) .action((_, c) => c.copy(listTestSuites = true))
.text( .text(
"""Lists all available test suites that can be used in the include and exclude options. """Lists all available test suites that can be used in the include and exclude options.
|Test names always start with their suite name, so using the suite name as a prefix |Test names always start with their suite name, so using the suite name as a prefix
|matches all tests in a given suite.""".stripMargin |matches all tests in a given suite.""".stripMargin
) )
opt[Unit]("list-all") opt[Unit]("list-all")
@ -252,7 +254,7 @@ object Cli {
.action((x, c) => c.copy(ledgerClockGranularity = x)) .action((x, c) => c.copy(ledgerClockGranularity = x))
.text( .text(
"""Specify the largest interval that you will see between clock ticks """Specify the largest interval that you will see between clock ticks
|on the ledger under test. The default is \"1s\" (1 second).""".stripMargin |on the ledger under test. The default is \"1s\" (1 second).""".stripMargin
) )
opt[Unit]("skip-dar-upload") opt[Unit]("skip-dar-upload")

View File

@ -3,16 +3,15 @@
package com.daml.ledger.api.testtool package com.daml.ledger.api.testtool
import java.io.File
import java.nio.file.Path
import com.daml.ledger.api.testtool.infrastructure.PartyAllocationConfiguration import com.daml.ledger.api.testtool.infrastructure.PartyAllocationConfiguration
import com.daml.ledger.api.tls.TlsConfiguration import com.daml.ledger.api.tls.TlsConfiguration
import java.io.File
import java.nio.file.Path
import scala.concurrent.duration.FiniteDuration import scala.concurrent.duration.FiniteDuration
final case class Config( final case class Config(
participants: Vector[(String, Int)], participantsEndpoints: Vector[(String, Int)],
maxConnectionAttempts: Int, maxConnectionAttempts: Int,
darPackages: List[File], darPackages: List[File],
mustFail: Boolean, mustFail: Boolean,
@ -32,11 +31,16 @@ final case class Config(
partyAllocation: PartyAllocationConfiguration, partyAllocation: PartyAllocationConfiguration,
ledgerClockGranularity: FiniteDuration, ledgerClockGranularity: FiniteDuration,
uploadDars: Boolean, uploadDars: Boolean,
) ) {
def withTlsConfig(modify: TlsConfiguration => TlsConfiguration): Config = {
val base = tlsConfig.getOrElse(TlsConfiguration.Empty)
copy(tlsConfig = Some(modify(base)))
}
}
object Config { object Config {
val default: Config = Config( val default: Config = Config(
participants = Vector.empty, participantsEndpoints = Vector.empty,
maxConnectionAttempts = 10, maxConnectionAttempts = 10,
darPackages = Nil, darPackages = Nil,
mustFail = false, mustFail = false,

View File

@ -3,10 +3,6 @@
package com.daml.ledger.api.testtool package com.daml.ledger.api.testtool
import java.io.File
import java.nio.file.{Files, Paths, StandardCopyOption}
import java.util.concurrent.Executors
import com.daml.ledger.api.testtool.infrastructure.Reporter.ColorizedPrintStreamReporter import com.daml.ledger.api.testtool.infrastructure.Reporter.ColorizedPrintStreamReporter
import com.daml.ledger.api.testtool.infrastructure.Result.Excluded import com.daml.ledger.api.testtool.infrastructure.Result.Excluded
import com.daml.ledger.api.testtool.infrastructure._ import com.daml.ledger.api.testtool.infrastructure._
@ -17,6 +13,9 @@ import io.grpc.netty.{NegotiationType, NettyChannelBuilder}
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import scala.collection.compat._ import scala.collection.compat._
import java.io.File
import java.nio.file.{Files, Paths, StandardCopyOption}
import java.util.concurrent.Executors
import scala.concurrent.duration.DurationInt import scala.concurrent.duration.DurationInt
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success} import scala.util.{Failure, Success}
@ -57,6 +56,7 @@ object LedgerApiTestTool {
println() println()
Tests.PerformanceTestsKeys.foreach(println(_)) Tests.PerformanceTestsKeys.foreach(println(_))
} }
private def printAvailableTestSuites(testSuites: Vector[LedgerTestSuite]): Unit = { private def printAvailableTestSuites(testSuites: Vector[LedgerTestSuite]): Unit = {
println("Listing test suites. Run with --list-all to see individual tests.") println("Listing test suites. Run with --list-all to see individual tests.")
printListOfTests(testSuites)(_.name) printListOfTests(testSuites)(_.name)
@ -126,7 +126,7 @@ object LedgerApiTestTool {
sys.exit(0) sys.exit(0)
} }
if (config.participants.isEmpty) { if (config.participantsEndpoints.isEmpty) {
println("No participant to test, exiting.") println("No participant to test, exiting.")
sys.exit(0) sys.exit(0)
} }
@ -220,8 +220,11 @@ object LedgerApiTestTool {
cases: Vector[LedgerTestCase], cases: Vector[LedgerTestCase],
concurrentTestRuns: Int, concurrentTestRuns: Int,
)(implicit executionContext: ExecutionContext): Future[LedgerTestCasesRunner] = { )(implicit executionContext: ExecutionContext): Future[LedgerTestCasesRunner] = {
initializeParticipantChannels(config.participants, config.tlsConfig).asFuture.map( initializeParticipantChannels(
participants => config.participantsEndpoints,
config.tlsConfig,
).asFuture
.map((participants: Vector[ChannelEndpoint]) =>
new LedgerTestCasesRunner( new LedgerTestCasesRunner(
testCases = cases, testCases = cases,
participants = participants, participants = participants,
@ -232,8 +235,9 @@ object LedgerApiTestTool {
concurrentTestRuns = concurrentTestRuns, concurrentTestRuns = concurrentTestRuns,
uploadDars = config.uploadDars, uploadDars = config.uploadDars,
identifierSuffix = identifierSuffix, identifierSuffix = identifierSuffix,
clientTlsConfiguration = config.tlsConfig,
) )
) )
} }
private def initializeParticipantChannel( private def initializeParticipantChannel(
@ -257,12 +261,18 @@ object LedgerApiTestTool {
private def initializeParticipantChannels( private def initializeParticipantChannels(
participants: Vector[(String, Int)], participants: Vector[(String, Int)],
tlsConfig: Option[TlsConfiguration], tlsConfig: Option[TlsConfiguration],
)(implicit executionContext: ExecutionContext): Resource[Vector[Channel]] = { )(implicit executionContext: ExecutionContext): Resource[Vector[ChannelEndpoint]] = {
val participantChannelOwners = val channelResources: Seq[Resource[ChannelEndpoint]] =
for ((host, port) <- participants) yield { for ((host, port) <- participants) yield {
initializeParticipantChannel(host, port, tlsConfig) val channelOwner: ResourceOwner[Channel] =
initializeParticipantChannel(host, port, tlsConfig)
channelOwner
.acquire()
.map(channel =>
ChannelEndpoint.forRemote(channel = channel, hostname = host, port = port)
)
} }
Resource.sequence(participantChannelOwners.map(_.acquire())) Resource.sequence(channelResources)
} }
} }

View File

@ -0,0 +1,27 @@
// Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
import io.grpc.Channel
sealed trait Endpoint
object Endpoint {
final case object InProcess extends Endpoint
final case class Remote(hostname: String, port: Int) extends Endpoint
}
final case class ChannelEndpoint(channel: Channel, endpoint: Endpoint)
object ChannelEndpoint {
def forRemote(channel: Channel, hostname: String, port: Int): ChannelEndpoint =
ChannelEndpoint(channel, Endpoint.Remote(hostname, port))
def forInProcess(channel: Channel): ChannelEndpoint = ChannelEndpoint(channel, Endpoint.InProcess)
}

View File

@ -4,6 +4,7 @@
package com.daml.ledger.api.testtool.infrastructure package com.daml.ledger.api.testtool.infrastructure
import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantSession import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantSession
import com.daml.ledger.api.tls.TlsConfiguration
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.{ExecutionContext, Future}
import scala.util.Random import scala.util.Random
@ -11,6 +12,7 @@ import scala.util.Random
private[infrastructure] final class LedgerSession private ( private[infrastructure] final class LedgerSession private (
participantSessions: Vector[(String, ParticipantSession)], participantSessions: Vector[(String, ParticipantSession)],
shuffleParticipants: Boolean, shuffleParticipants: Boolean,
clientTlsConfiguration: Option[TlsConfiguration],
)(implicit val executionContext: ExecutionContext) { )(implicit val executionContext: ExecutionContext) {
private[infrastructure] def createTestContext( private[infrastructure] def createTestContext(
applicationId: String, applicationId: String,
@ -21,7 +23,12 @@ private[infrastructure] final class LedgerSession private (
else participantSessions else participantSessions
Future Future
.traverse(sessions) { case (endpointId, session) => .traverse(sessions) { case (endpointId, session) =>
session.createTestContext(endpointId, applicationId, identifierSuffix) session.createTestContext(
endpointId,
applicationId,
identifierSuffix,
clientTlsConfiguration,
)
} }
.map(new LedgerTestContext(_)) .map(new LedgerTestContext(_))
} }
@ -31,10 +38,15 @@ object LedgerSession {
def apply( def apply(
participantSessions: Vector[ParticipantSession], participantSessions: Vector[ParticipantSession],
shuffleParticipants: Boolean, shuffleParticipants: Boolean,
clientTlsConfiguration: Option[TlsConfiguration],
)(implicit executionContext: ExecutionContext): LedgerSession = { )(implicit executionContext: ExecutionContext): LedgerSession = {
val endpointIdProvider = val endpointIdProvider =
Identification.circularWithIndex(Identification.greekAlphabet) Identification.circularWithIndex(Identification.greekAlphabet)
val sessions = participantSessions.map(endpointIdProvider() -> _) val sessions = participantSessions.map(endpointIdProvider() -> _)
new LedgerSession(sessions, shuffleParticipants) new LedgerSession(
sessions,
shuffleParticipants,
clientTlsConfiguration,
)
} }
} }

View File

@ -3,9 +3,6 @@
package com.daml.ledger.api.testtool.infrastructure package com.daml.ledger.api.testtool.infrastructure
import java.util.concurrent.{ExecutionException, TimeoutException}
import java.util.{Timer, TimerTask}
import akka.actor.ActorSystem import akka.actor.ActorSystem
import akka.stream.Materializer import akka.stream.Materializer
import akka.stream.scaladsl.{Sink, Source} import akka.stream.scaladsl.{Sink, Source}
@ -16,9 +13,12 @@ import com.daml.ledger.api.testtool.infrastructure.participant.{
ParticipantSession, ParticipantSession,
ParticipantTestContext, ParticipantTestContext,
} }
import io.grpc.{Channel, ClientInterceptor} import com.daml.ledger.api.tls.TlsConfiguration
import io.grpc.ClientInterceptor
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.util.concurrent.{ExecutionException, TimeoutException}
import java.util.{Timer, TimerTask}
import scala.concurrent.duration.{Duration, DurationInt} import scala.concurrent.duration.{Duration, DurationInt}
import scala.concurrent.{ExecutionContext, Future, Promise} import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.util.Try import scala.util.Try
@ -41,7 +41,7 @@ object LedgerTestCasesRunner {
final class LedgerTestCasesRunner( final class LedgerTestCasesRunner(
testCases: Vector[LedgerTestCase], testCases: Vector[LedgerTestCase],
participants: Vector[Channel], participants: Vector[ChannelEndpoint],
maxConnectionAttempts: Int = 10, maxConnectionAttempts: Int = 10,
partyAllocation: PartyAllocationConfiguration = ClosedWorldWaitingForAllParticipants, partyAllocation: PartyAllocationConfiguration = ClosedWorldWaitingForAllParticipants,
shuffleParticipants: Boolean = false, shuffleParticipants: Boolean = false,
@ -50,6 +50,7 @@ final class LedgerTestCasesRunner(
uploadDars: Boolean = true, uploadDars: Boolean = true,
identifierSuffix: String = "test", identifierSuffix: String = "test",
commandInterceptors: Seq[ClientInterceptor] = Seq.empty, commandInterceptors: Seq[ClientInterceptor] = Seq.empty,
clientTlsConfiguration: Option[TlsConfiguration],
) { ) {
private[this] val verifyRequirements: Try[Unit] = private[this] val verifyRequirements: Try[Unit] =
Try { Try {
@ -150,13 +151,18 @@ final class LedgerTestCasesRunner(
} }
private def uploadDarsIfRequired( private def uploadDarsIfRequired(
sessions: Vector[ParticipantSession] sessions: Vector[ParticipantSession],
clientTlsConfiguration: Option[TlsConfiguration],
)(implicit executionContext: ExecutionContext): Future[Unit] = )(implicit executionContext: ExecutionContext): Future[Unit] =
if (uploadDars) { if (uploadDars) {
Future Future
.sequence(sessions.map { session => .sequence(sessions.map { session =>
for { for {
context <- session.createInitContext("upload-dars", identifierSuffix) context <- session.createInitContext(
applicationId = "upload-dars",
identifierSuffix = identifierSuffix,
clientTlsConfiguration = clientTlsConfiguration,
)
_ <- Future.sequence(Dars.resources.map(uploadDar(context, _))) _ <- Future.sequence(Dars.resources.map(uploadDar(context, _)))
} yield () } yield ()
}) })
@ -188,18 +194,22 @@ final class LedgerTestCasesRunner(
} }
private def run( private def run(
participants: Vector[Channel] participants: Vector[ChannelEndpoint]
)(implicit )(implicit
materializer: Materializer, materializer: Materializer,
executionContext: ExecutionContext, executionContext: ExecutionContext,
): Future[Vector[LedgerTestSummary]] = { ): Future[Vector[LedgerTestSummary]] = {
val (concurrentTestCases, sequentialTestCases) = testCases.partition(_.runConcurrently) val (concurrentTestCases, sequentialTestCases) = testCases.partition(_.runConcurrently)
ParticipantSession(partyAllocation, participants, maxConnectionAttempts, commandInterceptors) ParticipantSession(partyAllocation, participants, maxConnectionAttempts, commandInterceptors)
.flatMap { sessions => .flatMap { sessions: Vector[ParticipantSession] =>
val ledgerSession = LedgerSession(sessions, shuffleParticipants) val ledgerSession = LedgerSession(
sessions,
shuffleParticipants,
clientTlsConfiguration = clientTlsConfiguration,
)
val testResults = val testResults =
for { for {
_ <- uploadDarsIfRequired(sessions) _ <- uploadDarsIfRequired(sessions, clientTlsConfiguration)
concurrentTestResults <- runTestCases( concurrentTestResults <- runTestCases(
ledgerSession, ledgerSession,
concurrentTestCases, concurrentTestCases,

View File

@ -42,10 +42,11 @@ private[testtool] final class LedgerTestContext private[infrastructure] (
val participantAllocations = allocation.partyCounts.map(nextParticipant() -> _) val participantAllocations = allocation.partyCounts.map(nextParticipant() -> _)
val participantsUnderTest = participantAllocations.map(_._1) val participantsUnderTest = participantAllocations.map(_._1)
Future Future
.sequence(participantAllocations.map { case (participant, partyCount) => .sequence(participantAllocations.map {
participant case (participant: ParticipantTestContext, partyCount) =>
.preallocateParties(partyCount.count, participantsUnderTest) participant
.map(parties => Participant(participant, parties: _*)) .preallocateParties(partyCount.count, participantsUnderTest)
.map(parties => Participant(participant, parties: _*))
}) })
.map(allocatedParticipants => Participants(allocatedParticipants: _*)) .map(allocatedParticipants => Participants(allocatedParticipants: _*))
} }

View File

@ -32,6 +32,7 @@ private[testtool] abstract class LedgerTestSuite {
repeated, repeated,
)((ec: ExecutionContext) => (_: Seq[ParticipantTestContext]) => testCase(ec)) )((ec: ExecutionContext) => (_: Seq[ParticipantTestContext]) => testCase(ec))
} }
protected final def testGivenAllParticipants( protected final def testGivenAllParticipants(
shortIdentifier: String, shortIdentifier: String,
description: String, description: String,

View File

@ -4,14 +4,17 @@
package com.daml.ledger.api.testtool.infrastructure.participant package com.daml.ledger.api.testtool.infrastructure.participant
import com.daml.ledger.api.testtool.infrastructure.{ import com.daml.ledger.api.testtool.infrastructure.{
ChannelEndpoint,
Endpoint,
Errors, Errors,
LedgerServices, LedgerServices,
PartyAllocationConfiguration, PartyAllocationConfiguration,
} }
import com.daml.ledger.api.tls.TlsConfiguration
import com.daml.ledger.api.v1.ledger_identity_service.GetLedgerIdentityRequest import com.daml.ledger.api.v1.ledger_identity_service.GetLedgerIdentityRequest
import com.daml.ledger.api.v1.transaction_service.GetLedgerEndRequest import com.daml.ledger.api.v1.transaction_service.GetLedgerEndRequest
import com.daml.timer.RetryStrategy import com.daml.timer.RetryStrategy
import io.grpc.{Channel, ClientInterceptor} import io.grpc.ClientInterceptor
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import scala.concurrent.duration.DurationInt import scala.concurrent.duration.DurationInt
@ -27,28 +30,39 @@ private[infrastructure] final class ParticipantSession private (
// The test tool is designed to run tests in an isolated environment but changing the // The test tool is designed to run tests in an isolated environment but changing the
// global state of the ledger breaks this assumption, no matter what. // global state of the ledger breaks this assumption, no matter what.
ledgerId: String, ledgerId: String,
ledgerEndpoint: Endpoint,
)(implicit val executionContext: ExecutionContext) { )(implicit val executionContext: ExecutionContext) {
private[testtool] def createInitContext( private[testtool] def createInitContext(
applicationId: String, applicationId: String,
identifierSuffix: String, identifierSuffix: String,
clientTlsConfiguration: Option[TlsConfiguration],
): Future[ParticipantTestContext] = ): Future[ParticipantTestContext] =
createTestContext("init", applicationId, identifierSuffix) createTestContext(
"init",
applicationId,
identifierSuffix,
clientTlsConfiguration = clientTlsConfiguration,
)
private[testtool] def createTestContext( private[testtool] def createTestContext(
endpointId: String, endpointId: String,
applicationId: String, applicationId: String,
identifierSuffix: String, identifierSuffix: String,
clientTlsConfiguration: Option[TlsConfiguration],
): Future[ParticipantTestContext] = ): Future[ParticipantTestContext] =
for { for {
end <- services.transaction.getLedgerEnd(new GetLedgerEndRequest(ledgerId)).map(_.getOffset) end <- services.transaction.getLedgerEnd(new GetLedgerEndRequest(ledgerId)).map(_.getOffset)
} yield new ParticipantTestContext( } yield new ParticipantTestContext(
ledgerId, ledgerId = ledgerId,
endpointId, endpointId = endpointId,
applicationId, applicationId = applicationId,
identifierSuffix, identifierSuffix = identifierSuffix,
end, referenceOffset = end,
services, services = services,
partyAllocation, partyAllocation = partyAllocation,
ledgerEndpoint = ledgerEndpoint,
clientTlsConfiguration = clientTlsConfiguration,
) )
} }
@ -57,14 +71,14 @@ object ParticipantSession {
def apply( def apply(
partyAllocation: PartyAllocationConfiguration, partyAllocation: PartyAllocationConfiguration,
participants: Vector[Channel], participants: Vector[ChannelEndpoint],
maxConnectionAttempts: Int, maxConnectionAttempts: Int,
commandInterceptors: Seq[ClientInterceptor], commandInterceptors: Seq[ClientInterceptor],
)(implicit )(implicit
executionContext: ExecutionContext executionContext: ExecutionContext
): Future[Vector[ParticipantSession]] = ): Future[Vector[ParticipantSession]] =
Future.traverse(participants) { participant => Future.traverse(participants) { participant: ChannelEndpoint =>
val services = new LedgerServices(participant, commandInterceptors) val services = new LedgerServices(participant.channel, commandInterceptors)
for { for {
ledgerId <- RetryStrategy ledgerId <- RetryStrategy
.exponentialBackoff(attempts = maxConnectionAttempts, 100.millis) { (attempt, wait) => .exponentialBackoff(attempts = maxConnectionAttempts, 100.millis) { (attempt, wait) =>
@ -80,7 +94,12 @@ object ParticipantSession {
.recoverWith { case NonFatal(exception) => .recoverWith { case NonFatal(exception) =>
Future.failed(new Errors.ParticipantConnectionException(exception)) Future.failed(new Errors.ParticipantConnectionException(exception))
} }
} yield new ParticipantSession(partyAllocation, services, ledgerId) } yield new ParticipantSession(
partyAllocation = partyAllocation,
services = services,
ledgerId = ledgerId,
ledgerEndpoint = participant.endpoint,
)
} }
} }

View File

@ -4,15 +4,16 @@
package com.daml.ledger.api.testtool.infrastructure.participant package com.daml.ledger.api.testtool.infrastructure.participant
import java.time.{Clock, Instant} import java.time.{Clock, Instant}
import com.daml.ledger.api.refinements.ApiTypes.TemplateId import com.daml.ledger.api.refinements.ApiTypes.TemplateId
import com.daml.ledger.api.testtool.infrastructure.Eventually.eventually import com.daml.ledger.api.testtool.infrastructure.Eventually.eventually
import com.daml.ledger.api.testtool.infrastructure.ProtobufConverters._ import com.daml.ledger.api.testtool.infrastructure.ProtobufConverters._
import com.daml.ledger.api.testtool.infrastructure.{ import com.daml.ledger.api.testtool.infrastructure.{
Endpoint,
Identification, Identification,
LedgerServices, LedgerServices,
PartyAllocationConfiguration, PartyAllocationConfiguration,
} }
import com.daml.ledger.api.tls.TlsConfiguration
import com.daml.ledger.api.v1.active_contracts_service.{ import com.daml.ledger.api.v1.active_contracts_service.{
GetActiveContractsRequest, GetActiveContractsRequest,
GetActiveContractsResponse, GetActiveContractsResponse,
@ -105,6 +106,8 @@ private[testtool] final class ParticipantTestContext private[participant] (
referenceOffset: LedgerOffset, referenceOffset: LedgerOffset,
services: LedgerServices, services: LedgerServices,
partyAllocation: PartyAllocationConfiguration, partyAllocation: PartyAllocationConfiguration,
val ledgerEndpoint: Endpoint,
val clientTlsConfiguration: Option[TlsConfiguration],
)(implicit ec: ExecutionContext) { )(implicit ec: ExecutionContext) {
import ParticipantTestContext._ import ParticipantTestContext._

View File

@ -0,0 +1,128 @@
// Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites
import com.daml.ledger.api.testtool.infrastructure.Allocation.{NoParties, allocate}
import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantTestContext
import com.daml.ledger.api.testtool.infrastructure.{Endpoint, LedgerTestSuite}
import com.daml.ledger.api.tls.TlsVersion
import com.daml.ledger.api.tls.TlsVersion.TlsVersion
import com.daml.ledger.api.v1.ledger_identity_service.LedgerIdentityServiceGrpc.LedgerIdentityServiceBlockingStub
import com.daml.ledger.api.v1.ledger_identity_service.{
GetLedgerIdentityRequest,
LedgerIdentityServiceGrpc,
}
import com.daml.ledger.resources.{ResourceContext, ResourceOwner}
import io.grpc.StatusRuntimeException
import io.grpc.netty.NettyChannelBuilder
import scala.concurrent.Future
import scala.concurrent.duration._
import scala.util.{Failure, Success, Try}
/** Verifies that the given participant server correctly handles TLSv1.3 only mode.
*
* It works by creating and exercising a series of client service stubs, each over different TLS version.
* Only TLSv1.3 connection is expected to succeed.
* Connections over lower TLS versions are expected to fail.
*/
final class TLSOnePointThreeIT extends LedgerTestSuite {
testTlsConnection(clientTlsVersion = TlsVersion.V1_3, assertConnectionOk = true)
testTlsConnection(clientTlsVersion = TlsVersion.V1_2, assertConnectionOk = false)
testTlsConnection(clientTlsVersion = TlsVersion.V1_1, assertConnectionOk = false)
testTlsConnection(clientTlsVersion = TlsVersion.V1, assertConnectionOk = false)
def testTlsConnection(clientTlsVersion: TlsVersion, assertConnectionOk: Boolean): Unit = {
val (what, assertionOnServerResponse) =
if (assertConnectionOk)
("accept", assertSuccessfulConnection)
else
("reject", assertFailedConnection)
testGivenAllParticipants(
s"ConnectionOnTLSv13FromClientOn${clientTlsVersion.version.replace(".", "")}",
s"A ledger API server should ${what} a ${clientTlsVersion} connection",
allocate(NoParties),
) { implicit ec => (testContexts: Seq[ParticipantTestContext]) =>
{ case _ =>
// preconditions
assume(testContexts.nonEmpty, "Missing an expected participant test context!")
val firstTextContext = testContexts.head
assume(
firstTextContext.clientTlsConfiguration.isDefined,
"Missing required TLS configuration!",
)
val tlsConfiguration = firstTextContext.clientTlsConfiguration.get
assume(
tlsConfiguration.enabled,
"TLS configuration is disabled but expected to be enabled!",
)
assume(
firstTextContext.ledgerEndpoint.isInstanceOf[Endpoint.Remote],
"Expected a remote (i.e. with a hostname and port) ledger endpoint!",
)
val Endpoint.Remote(ledgerHostname, ledgerPort) =
firstTextContext.ledgerEndpoint.asInstanceOf[Endpoint.Remote]
// given
val sslContext = tlsConfiguration
.client(enabledProtocols = Seq(clientTlsVersion))
.getOrElse(throw new IllegalStateException("Missing SslContext!"))
val serviceStubOwner: ResourceOwner[LedgerIdentityServiceBlockingStub] = for {
channel <- ResourceOwner.forChannel(
builder = NettyChannelBuilder
.forAddress(ledgerHostname, ledgerPort)
.useTransportSecurity()
.sslContext(sslContext),
shutdownTimeout = 2.seconds,
)
} yield LedgerIdentityServiceGrpc.blockingStub(channel)
// when
val response: Future[String] = serviceStubOwner.use { identityService =>
val response = identityService.getLedgerIdentity(new GetLedgerIdentityRequest())
Future.successful(response.ledgerId)
}(ResourceContext(ec))
// then
response.transform[Unit] {
assertionOnServerResponse
}
}
}
}
private lazy val assertSuccessfulConnection: Try[String] => Try[Unit] = {
case Success(ledgerId) =>
Try[Unit] {
assert(
assertion = ledgerId ne null,
message = s"Expected a not null ledger id!",
)
}
case Failure(exception) =>
throw new AssertionError(s"Failed to receive a successful server response!", exception)
}
private lazy val assertFailedConnection: Try[String] => Try[Unit] = {
case Success(ledgerId) =>
Try[Unit] {
assert(
assertion = false,
message =
s"Connection succeeded and returned ledgerId: ${ledgerId} but expected connection failure!",
)
}
case Failure(_: StatusRuntimeException) => Success[Unit](())
case Failure(other) =>
Try[Unit] {
assert(
assertion = false,
message = s"Unexpected failure: ${other}",
)
}
}
}

View File

@ -75,6 +75,7 @@ object Tests {
new MultiPartySubmissionIT, new MultiPartySubmissionIT,
new ParticipantPruningIT, new ParticipantPruningIT,
new MonotonicRecordTimeIT, new MonotonicRecordTimeIT,
new TLSOnePointThreeIT,
) )
val retired: Vector[LedgerTestSuite] = val retired: Vector[LedgerTestSuite] =

View File

@ -167,7 +167,6 @@ private[daml] object ApiServices {
optTimeServiceBackend.map(tsb => optTimeServiceBackend.map(tsb =>
new TimeServiceAuthorization(ApiTimeService.create(ledgerId, tsb), authorizer) new TimeServiceAuthorization(ApiTimeService.create(ledgerId, tsb), authorizer)
) )
val writeServiceBackedApiServices = val writeServiceBackedApiServices =
intitializeWriteServiceBackedApiServices( intitializeWriteServiceBackedApiServices(
ledgerId, ledgerId,

View File

@ -3,9 +3,8 @@
package com.daml.nonrepudiation.postgresql package com.daml.nonrepudiation.postgresql
import java.time.{Clock, Duration}
import com.daml.doobie.logging.Slf4jLogHandler import com.daml.doobie.logging.Slf4jLogHandler
import com.daml.ledger.api.testtool.infrastructure
import com.daml.ledger.api.testtool.infrastructure.{ import com.daml.ledger.api.testtool.infrastructure.{
LedgerTestCase, LedgerTestCase,
LedgerTestCasesRunner, LedgerTestCasesRunner,
@ -34,6 +33,7 @@ import org.scalatest.flatspec.AsyncFlatSpec
import org.scalatest.matchers.should.Matchers import org.scalatest.matchers.should.Matchers
import org.scalatest.{Inside, OptionValues} import org.scalatest.{Inside, OptionValues}
import java.time.{Clock, Duration}
import scala.concurrent.duration.DurationInt import scala.concurrent.duration.DurationInt
final class NonRepudiationProxyConformance final class NonRepudiationProxyConformance
@ -109,10 +109,11 @@ final class NonRepudiationProxyConformance
proxy.use { _ => proxy.use { _ =>
val runner = new LedgerTestCasesRunner( val runner = new LedgerTestCasesRunner(
testCases = conformanceTestCases, testCases = conformanceTestCases,
participants = Vector(proxyChannel), participants = Vector(infrastructure.ChannelEndpoint.forInProcess(proxyChannel)),
commandInterceptors = Seq( commandInterceptors = Seq(
SigningInterceptor.signCommands(key, certificate) SigningInterceptor.signCommands(key, certificate)
), ),
clientTlsConfiguration = config.tlsConfig,
) )
runner.runTests.map { summaries => runner.runTests.map { summaries =>