From 74893943310ff4f6d2feabfabd76672f011ab2b3 Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Sun, 10 Sep 2017 15:28:06 +0100 Subject: [PATCH 01/36] project cleanup. first successful test --- build.sbt | 78 ++----- .../whisk/docker/config/DockerKitConfig.scala | 16 -- .../docker/config/DockerTypesafeConfig.scala | 83 ------- config/src/test/resources/application.conf | 123 ----------- .../docker/config/test/DockerConfigSpec.scala | 52 ----- .../scala/com/whisk/docker/Container.scala | 106 +++++++++ .../docker/ContainerCommandExecutor.scala | 126 +++++++++++ .../com/whisk/docker/ContainerPort.scala | 17 ++ .../com/whisk/docker/ContainerSpec.scala | 30 +++ .../whisk/docker/DockerCommandExecutor.scala | 56 ----- .../com/whisk/docker/DockerContainer.scala | 80 ------- .../whisk/docker/DockerContainerManager.scala | 188 ++++++++-------- .../whisk/docker/DockerContainerState.scala | 113 ---------- .../com/whisk/docker/DockerFactory.scala | 6 - .../scala/com/whisk/docker/DockerKit.scala | 72 ------ .../com/whisk/docker/DockerReadyChecker.scala | 58 ++--- .../whisk/docker/DockerTestKitForAll.scala | 47 ++++ .../com/whisk/docker/DockerTestTimeouts.scala | 13 ++ .../main/scala/com/whisk/docker/package.scala | 24 ++ .../whisk/docker/impl/dockerjava/Docker.scala | 14 -- .../impl/dockerjava/DockerJavaExecutor.scala | 190 ---------------- .../DockerJavaExecutorFactory.scala | 9 - .../impl/dockerjava/DockerKitDockerJava.scala | 10 - .../impl/spotify/DockerKitSpotify.scala | 10 - .../SpotifyDockerCommandExecutor.scala | 208 ------------------ .../impl/spotify/SpotifyDockerFactory.scala | 11 - .../whisk/docker/DockerCassandraService.scala | 11 +- .../docker/DockerElasticsearchService.scala | 13 +- .../com/whisk/docker/DockerKafkaService.scala | 30 +-- .../whisk/docker/DockerMongodbService.scala | 28 +-- .../com/whisk/docker/DockerNeo4jService.scala | 42 ++-- .../whisk/docker/DockerPostgresService.scala | 80 +++---- .../whisk/docker/DockerZookeeperService.scala | 22 +- .../docker/scalatest/DockerTestKit.scala | 29 --- scalatest/src/test/resources/logback-test.xml | 14 -- .../com/whisk/docker/AllAtOnceSpec.scala | 23 -- .../whisk/docker/CassandraServiceSpec.scala | 25 --- .../DependencyGraphReadyCheckSpec.scala | 65 ------ .../docker/DockerContainerLinkingSpec.scala | 43 ---- .../docker/DockerContainerManagerSpec.scala | 113 ---------- .../docker/ElasticsearchServiceSpec.scala | 23 -- .../com/whisk/docker/KafkaServiceSpec.scala | 21 -- .../com/whisk/docker/MongodbServiceSpec.scala | 22 -- .../com/whisk/docker/Neo4jServiceSpec.scala | 21 -- .../com/whisk/docker/PingContainerKit.scala | 16 -- .../whisk/docker/PostgresServiceSpec.scala | 23 -- .../whisk/docker/ZookeeperServiceSpec.scala | 21 -- .../specs2/BeforeAfterAllStopOnError.scala | 18 -- .../whisk/docker/specs2/DockerTestKit.scala | 16 -- .../com/whisk/docker/AllAtOnceSpec.scala | 27 --- .../whisk/docker/CassandraServiceSpec.scala | 21 -- .../docker/DockerTestKitDockerJava.scala | 6 - .../docker/ElasticsearchServiceSpec.scala | 22 -- .../com/whisk/docker/KafkaServiceSpec.scala | 21 -- .../com/whisk/docker/MongodbServiceSpec.scala | 21 -- .../com/whisk/docker/Neo4jServiceSpec.scala | 24 -- .../com/whisk/docker/PingContainerKit.scala | 15 -- .../whisk/docker/PostgresServiceSpec.scala | 18 -- .../whisk/docker/ZookeeperServiceSpec.scala | 18 -- .../test/ElasticsearchServiceTest.scala | 11 + 60 files changed, 636 insertions(+), 2027 deletions(-) delete mode 100644 config/src/main/scala/com/whisk/docker/config/DockerKitConfig.scala delete mode 100644 config/src/main/scala/com/whisk/docker/config/DockerTypesafeConfig.scala delete mode 100644 config/src/test/resources/application.conf delete mode 100644 config/src/test/scala/com/whisk/docker/config/test/DockerConfigSpec.scala create mode 100644 core/src/main/scala/com/whisk/docker/Container.scala create mode 100644 core/src/main/scala/com/whisk/docker/ContainerCommandExecutor.scala create mode 100644 core/src/main/scala/com/whisk/docker/ContainerPort.scala create mode 100644 core/src/main/scala/com/whisk/docker/ContainerSpec.scala delete mode 100644 core/src/main/scala/com/whisk/docker/DockerCommandExecutor.scala delete mode 100644 core/src/main/scala/com/whisk/docker/DockerContainer.scala delete mode 100644 core/src/main/scala/com/whisk/docker/DockerContainerState.scala delete mode 100644 core/src/main/scala/com/whisk/docker/DockerFactory.scala delete mode 100644 core/src/main/scala/com/whisk/docker/DockerKit.scala create mode 100644 core/src/main/scala/com/whisk/docker/DockerTestKitForAll.scala create mode 100644 core/src/main/scala/com/whisk/docker/DockerTestTimeouts.scala delete mode 100644 impl/docker-java/src/main/scala/com/whisk/docker/impl/dockerjava/Docker.scala delete mode 100644 impl/docker-java/src/main/scala/com/whisk/docker/impl/dockerjava/DockerJavaExecutor.scala delete mode 100644 impl/docker-java/src/main/scala/com/whisk/docker/impl/dockerjava/DockerJavaExecutorFactory.scala delete mode 100644 impl/docker-java/src/main/scala/com/whisk/docker/impl/dockerjava/DockerKitDockerJava.scala delete mode 100644 impl/spotify/src/main/scala/com/whisk/docker/impl/spotify/DockerKitSpotify.scala delete mode 100644 impl/spotify/src/main/scala/com/whisk/docker/impl/spotify/SpotifyDockerCommandExecutor.scala delete mode 100644 impl/spotify/src/main/scala/com/whisk/docker/impl/spotify/SpotifyDockerFactory.scala delete mode 100644 scalatest/src/main/scala/com/whisk/docker/scalatest/DockerTestKit.scala delete mode 100644 scalatest/src/test/resources/logback-test.xml delete mode 100644 scalatest/src/test/scala/com/whisk/docker/AllAtOnceSpec.scala delete mode 100644 scalatest/src/test/scala/com/whisk/docker/CassandraServiceSpec.scala delete mode 100644 scalatest/src/test/scala/com/whisk/docker/DependencyGraphReadyCheckSpec.scala delete mode 100644 scalatest/src/test/scala/com/whisk/docker/DockerContainerLinkingSpec.scala delete mode 100644 scalatest/src/test/scala/com/whisk/docker/DockerContainerManagerSpec.scala delete mode 100644 scalatest/src/test/scala/com/whisk/docker/ElasticsearchServiceSpec.scala delete mode 100644 scalatest/src/test/scala/com/whisk/docker/KafkaServiceSpec.scala delete mode 100644 scalatest/src/test/scala/com/whisk/docker/MongodbServiceSpec.scala delete mode 100644 scalatest/src/test/scala/com/whisk/docker/Neo4jServiceSpec.scala delete mode 100644 scalatest/src/test/scala/com/whisk/docker/PingContainerKit.scala delete mode 100644 scalatest/src/test/scala/com/whisk/docker/PostgresServiceSpec.scala delete mode 100644 scalatest/src/test/scala/com/whisk/docker/ZookeeperServiceSpec.scala delete mode 100644 specs2/src/main/scala/com/whisk/docker/specs2/BeforeAfterAllStopOnError.scala delete mode 100644 specs2/src/main/scala/com/whisk/docker/specs2/DockerTestKit.scala delete mode 100644 specs2/src/test/scala/com/whisk/docker/AllAtOnceSpec.scala delete mode 100644 specs2/src/test/scala/com/whisk/docker/CassandraServiceSpec.scala delete mode 100644 specs2/src/test/scala/com/whisk/docker/DockerTestKitDockerJava.scala delete mode 100644 specs2/src/test/scala/com/whisk/docker/ElasticsearchServiceSpec.scala delete mode 100644 specs2/src/test/scala/com/whisk/docker/KafkaServiceSpec.scala delete mode 100644 specs2/src/test/scala/com/whisk/docker/MongodbServiceSpec.scala delete mode 100644 specs2/src/test/scala/com/whisk/docker/Neo4jServiceSpec.scala delete mode 100644 specs2/src/test/scala/com/whisk/docker/PingContainerKit.scala delete mode 100644 specs2/src/test/scala/com/whisk/docker/PostgresServiceSpec.scala delete mode 100644 specs2/src/test/scala/com/whisk/docker/ZookeeperServiceSpec.scala create mode 100644 tests/src/test/scala/com/whisk/docker/test/ElasticsearchServiceTest.scala diff --git a/build.sbt b/build.sbt index 7e066a1..491d59c 100644 --- a/build.sbt +++ b/build.sbt @@ -1,8 +1,8 @@ lazy val commonSettings = Seq( organization := "com.whisk", - version := "0.9.5", + version := "0.10.0-wip", scalaVersion := "2.12.3", - crossScalaVersions := Seq("2.12.3", "2.11.11", "2.10.6"), + crossScalaVersions := Seq("2.12.3", "2.11.11"), scalacOptions ++= Seq("-feature", "-deprecation"), fork in Test := true, licenses += ("MIT", url("http://opensource.org/licenses/MIT")), @@ -36,35 +36,22 @@ lazy val root = .in(file(".")) .settings(commonSettings: _*) .settings(publish := {}, publishLocal := {}, packagedArtifacts := Map.empty) - .aggregate(core, testkitSpotifyImpl, testkitDockerJavaImpl, config, scalatest, specs2, samples) + .aggregate(core, samples) lazy val core = project - .settings(commonSettings: _*) - .settings(name := "docker-testkit-core", - libraryDependencies += "org.slf4j" % "slf4j-api" % "1.7.22") - -lazy val testkitSpotifyImpl = - project - .in(file("impl/spotify")) - .settings(commonSettings: _*) - .settings(name := "docker-testkit-impl-spotify", - libraryDependencies ++= - Seq("com.spotify" % "docker-client" % "8.9.0", - "com.google.code.findbugs" % "jsr305" % "3.0.1")) - .dependsOn(core) - -lazy val testkitDockerJavaImpl = - project - .in(file("impl/docker-java")) .settings(commonSettings: _*) .settings( - name := "docker-testkit-impl-docker-java", - libraryDependencies ++= - Seq("com.github.docker-java" % "docker-java" % "3.0.13", - "com.google.code.findbugs" % "jsr305" % "3.0.1") + name := "docker-testkit-core", + libraryDependencies ++= Seq( + "org.slf4j" % "slf4j-api" % "1.7.25", + "com.spotify" % "docker-client" % "8.9.0", + "com.google.code.findbugs" % "jsr305" % "3.0.1", + "org.scalatest" %% "scalatest" % "3.0.4", + "ch.qos.logback" % "logback-classic" % "1.2.3" % "test", + "org.postgresql" % "postgresql" % "9.4.1210" % "test" + ) ) - .dependsOn(core) lazy val samples = project @@ -72,43 +59,8 @@ lazy val samples = .settings(name := "docker-testkit-samples") .dependsOn(core) -lazy val scalatest = - project - .settings(commonSettings: _*) - .settings( - name := "docker-testkit-scalatest", - libraryDependencies ++= - Seq("org.scalatest" %% "scalatest" % "3.0.4", - "ch.qos.logback" % "logback-classic" % "1.2.1" % "test", - "org.postgresql" % "postgresql" % "9.4.1210" % "test") - ) - .dependsOn(core, testkitSpotifyImpl % "test", testkitDockerJavaImpl % "test", samples % "test") - -lazy val specs2 = - project - .settings(commonSettings: _*) - .settings( - name := "docker-testkit-specs2", - libraryDependencies ++= - Seq("org.specs2" %% "specs2-core" % "3.8.6", - "ch.qos.logback" % "logback-classic" % "1.2.1" % "test", - "org.postgresql" % "postgresql" % "9.4.1210" % "test") - ) - .dependsOn(core, samples % "test", testkitDockerJavaImpl % "test") - -lazy val config = +lazy val tests = project .settings(commonSettings: _*) - .settings( - name := "docker-testkit-config", - libraryDependencies ++= - Seq( - "com.iheart" %% "ficus" % "1.4.1", - "org.scalatest" %% "scalatest" % "3.0.4" % "test" - ), - publish := scalaVersion map { - case x if x.startsWith("2.10") => {} - case _ => publish.value - } - ) - .dependsOn(core, testkitDockerJavaImpl) + .settings(name := "docker-testkit-tests") + .dependsOn(core % "test", samples % "test") diff --git a/config/src/main/scala/com/whisk/docker/config/DockerKitConfig.scala b/config/src/main/scala/com/whisk/docker/config/DockerKitConfig.scala deleted file mode 100644 index 3569fbb..0000000 --- a/config/src/main/scala/com/whisk/docker/config/DockerKitConfig.scala +++ /dev/null @@ -1,16 +0,0 @@ -package com.whisk.docker.config - -import com.typesafe.config.ConfigFactory -import com.whisk.docker.DockerContainer -import com.whisk.docker.config.DockerTypesafeConfig._ -import com.whisk.docker.impl.dockerjava.DockerKitDockerJava -import net.ceedubs.ficus.Ficus._ -import net.ceedubs.ficus.readers.ArbitraryTypeReader._ - -trait DockerKitConfig extends DockerKitDockerJava { - def dockerConfig = ConfigFactory.load() - - def configureDockerContainer(configurationName: String): DockerContainer = { - dockerConfig.as[DockerConfig](configurationName).toDockerContainer() - } -} diff --git a/config/src/main/scala/com/whisk/docker/config/DockerTypesafeConfig.scala b/config/src/main/scala/com/whisk/docker/config/DockerTypesafeConfig.scala deleted file mode 100644 index 279386a..0000000 --- a/config/src/main/scala/com/whisk/docker/config/DockerTypesafeConfig.scala +++ /dev/null @@ -1,83 +0,0 @@ -package com.whisk.docker.config - -import com.whisk.docker.impl.dockerjava.DockerKitDockerJava -import com.whisk.docker.{DockerContainer, DockerPortMapping, DockerReadyChecker, HostConfig, VolumeMapping} - -import scala.concurrent.duration._ - -object DockerTypesafeConfig extends DockerKitDockerJava { - val EmptyPortBindings: Map[Int, Option[Int]] = Map.empty - val AlwaysReady = DockerReadyChecker.Always - - case class DockerConfigPortMap(internal: Int, external: Option[Int]) { - def asTuple = (internal, external) - } - - case class DockerConfigReadyCheckerLooped(attempts: Int, delay: Int) - - case class DockerConfigHttpResponseReady(port: Int, - path: String = "/", - host: Option[String], - code: Int = 200, - within: Option[Int], - looped: Option[DockerConfigReadyCheckerLooped]) - - case class DockerConfigReadyChecker( - `log-line`: Option[String], - `http-response-code`: Option[DockerConfigHttpResponseReady]) { - - def httpResponseCodeReadyChecker(rr: DockerConfigHttpResponseReady) = { - val codeChecker: DockerReadyChecker = - DockerReadyChecker.HttpResponseCode(rr.port, rr.path, rr.host, rr.code) - val within = rr.within.fold(codeChecker)(w => codeChecker.within(w.millis)) - rr.looped.fold(within)(l => within.looped(l.attempts, l.delay.millis)) - } - - // log line checker takes priority - def toReadyChecker = { - (`log-line`, `http-response-code`) match { - case (None, None) => DockerReadyChecker.Always - case (None, Some(rr)) => httpResponseCodeReadyChecker(rr) - case (Some(ll), _) => DockerReadyChecker.LogLineContains(ll) - } - } - } - - case class DockerConfig(`image-name`: String, - `container-name`: Option[String], - command: Option[Seq[String]], - entrypoint: Option[Seq[String]], - `environmental-variables`: Seq[String] = Seq.empty, - `port-maps`: Option[Map[String, DockerConfigPortMap]], - `ready-checker`: Option[DockerConfigReadyChecker], - `volume-maps`: Seq[VolumeMapping] = Seq.empty, - memory: Option[Long], - `memory-reservation`: Option[Long]) { - - def toDockerContainer(): DockerContainer = { - val bindPorts = `port-maps`.fold(EmptyPortBindings) { _.values.map(_.asTuple).toMap } mapValues { - maybeHostPort => - DockerPortMapping(maybeHostPort) - } - - val readyChecker = `ready-checker`.fold[DockerReadyChecker](AlwaysReady) { _.toReadyChecker } - - val hostConfig = HostConfig( - memory = memory, - memoryReservation = `memory-reservation` - ) - - DockerContainer( - image = `image-name`, - name = `container-name`, - command = command, - entrypoint = entrypoint, - bindPorts = bindPorts, - env = `environmental-variables`, - readyChecker = readyChecker, - volumeMappings = `volume-maps`, - hostConfig = Some(hostConfig) - ) - } - } -} diff --git a/config/src/test/resources/application.conf b/config/src/test/resources/application.conf deleted file mode 100644 index afe4988..0000000 --- a/config/src/test/resources/application.conf +++ /dev/null @@ -1,123 +0,0 @@ -docker { - - postgres { - image-name = "postgres:9.4.4" - environmental-variables = ["POSTGRES_USER=nph", "POSTGRES_PASSWORD=suitup"] - ready-checker { - log-line = "database system is ready to accept connections" - } - port-maps { - default-postgres-port { - internal = 5432 - } - } - } - - zookeeper { - image-name = "jplock/zookeeper:3.4.6" - ready-checker { - log-line = "binding to port" - } - port-maps { - default-zookeeper-port { - internal = 2181 - } - } - } - - cassandra { - image-name = "whisk/cassandra:2.1.8" - ready-checker { - log-line = "Starting listening for CQL clients on" - } - port-maps { - default-cql-port { - internal = 9042 - } - } - volume-maps = [ - { - container = "/opt/data" - host = "/opt/docker/data" - }, - { - container = "/opt/log" - host = "/opt/docker/log" - rw = true - } - ] - } - - mongodb { - image-name = "mongo:3.0.6" - command = ["mongod", "--nojournal", "--smallfiles", "--syncdelay", "0"] - ready-checker { - log-line = "waiting for connections on port" - } - port-maps { - default-mongodb-port { - internal = 27017 - } - } - } - - neo4j { - image-name = "whisk/neo4j:2.1.8" - ready-checker { - http-response-code { - port = 7474 - path = "/db/data/" - within = 100 - looped { - attempts = 20 - delay = 1250 - } - } - } - port-maps { - default-neo4j-port { - internal = 7474 - } - } - } - - elasticsearch { - image-name = "elasticsearch:1.7.1" - entrypoint = [ "my", "custom", "entrypoint" ] - memory = 536870912 # 512MB - memory-reservation = 268435456 # 256MB - ready-checker { - http-response-code { - port = 9200 - path = "/" - within = 100 - looped { - attempts = 20 - delay = 1250 - } - } - } - port-maps { - default-elasticsearch-http-port { - internal = 9200 - } - default-elasticsearch-client-port { - internal = 9300 - } - } - } - - kafka { - image-name = "wurstmeister/kafka:0.8.2.1" - environmental-variables = ["KAFKA_ADVERTISED_PORT=9092", KAFKA_ADVERTISED_HOST_NAME"="${?DOCKER_IP}] - ready-checker { - log-line = "started (kafka.server.KafkaServer)" - } - port-maps { - default-kafka-port { - internal = 9092 - external = 9092 - } - } - } -} diff --git a/config/src/test/scala/com/whisk/docker/config/test/DockerConfigSpec.scala b/config/src/test/scala/com/whisk/docker/config/test/DockerConfigSpec.scala deleted file mode 100644 index 834dcce..0000000 --- a/config/src/test/scala/com/whisk/docker/config/test/DockerConfigSpec.scala +++ /dev/null @@ -1,52 +0,0 @@ -package com.whisk.docker.config.test - -import com.whisk.docker.{DockerContainer, DockerReadyChecker, HostConfig, VolumeMapping} -import com.whisk.docker.config.DockerKitConfig -import org.scalatest._ - -import scala.concurrent.duration._ - -class DockerConfigSpec extends FlatSpec with Matchers with DockerKitConfig { - - "Config-based configurations" should "produce same containers as code-based ones" in { - val volumes = Seq( - VolumeMapping(container = "/opt/data", host = "/opt/docker/data", rw = false), - VolumeMapping(container = "/opt/log", host = "/opt/docker/log", rw = true) - ) - - val cassandraExpected = DockerContainer("whisk/cassandra:2.1.8") - .withPorts(9042 -> None) - .withReadyChecker( - DockerReadyChecker.LogLineContains("Starting listening for CQL clients on")) - .withVolumes(volumes) - - configureDockerContainer("docker.cassandra") shouldBe cassandraExpected - - val postgresExpected = DockerContainer("postgres:9.4.4") - .withPorts((5432, None)) - .withEnv(s"POSTGRES_USER=nph", s"POSTGRES_PASSWORD=suitup") - .withReadyChecker( - DockerReadyChecker.LogLineContains("database system is ready to accept connections")) - - configureDockerContainer("docker.postgres") shouldBe postgresExpected - - val mongodbExpected = DockerContainer("mongo:3.0.6") - .withPorts(27017 -> None) - .withReadyChecker(DockerReadyChecker.LogLineContains("waiting for connections on port")) - .withCommand("mongod", "--nojournal", "--smallfiles", "--syncdelay", "0") - - configureDockerContainer("docker.mongodb") shouldBe mongodbExpected - - val elasticExpected = DockerContainer("elasticsearch:1.7.1") - .withEntrypoint("my", "custom", "entrypoint") - .withPorts(9200 -> None, 9300 -> None) - .withHostConfig(HostConfig(memory = Some(536870912), memoryReservation = Some(268435456))) - .withReadyChecker( - DockerReadyChecker - .HttpResponseCode(9200, "/") - .within(100.millis) - .looped(20, 1250.millis)) - - configureDockerContainer("docker.elasticsearch") shouldBe elasticExpected - } -} diff --git a/core/src/main/scala/com/whisk/docker/Container.scala b/core/src/main/scala/com/whisk/docker/Container.scala new file mode 100644 index 0000000..6763e13 --- /dev/null +++ b/core/src/main/scala/com/whisk/docker/Container.scala @@ -0,0 +1,106 @@ +package com.whisk.docker + +import java.util.concurrent.atomic.AtomicReference + +import com.spotify.docker.client.messages.ContainerInfo +import org.slf4j.LoggerFactory + +import scala.collection.JavaConverters._ + +sealed trait ContainerState + +object ContainerState { + + trait HasId extends ContainerState { + val id: String + } + + trait IsRunning extends HasId { + val info: ContainerInfo + override val id: String = info.id + } + + object NotStarted extends ContainerState + case class Created(id: String) extends ContainerState with HasId + case class Starting(id: String) extends ContainerState with HasId + case class Running(info: ContainerInfo) extends ContainerState with IsRunning + case class Ready(info: ContainerInfo) extends ContainerState with IsRunning + case class Failed(id: String) extends ContainerState + object Stopped extends ContainerState +} + +class Container(val spec: ContainerSpec) { + + private lazy val log = LoggerFactory.getLogger(this.getClass) + + private val _state = new AtomicReference[ContainerState](ContainerState.NotStarted) + + def state(): ContainerState = { + _state.get() + } + + private def updateState(state: ContainerState): Unit = { + _state.set(state) + } + + private[docker] def created(id: String): Unit = { + updateState(ContainerState.Created(id)) + } + + private[docker] def starting(id: String): Unit = { + updateState(ContainerState.Starting(id)) + } + + private[docker] def running(info: ContainerInfo): Unit = { + updateState(ContainerState.Running(info)) + } + + private[docker] def ready(info: ContainerInfo): Unit = { + updateState(ContainerState.Ready(info)) + } + + private def addresses(info: ContainerInfo): Seq[String] = { + val addrs: Iterable[String] = for { + networks <- Option(info.networkSettings().networks()).map(_.asScala).toSeq + (key, network) <- networks + ip <- Option(network.ipAddress) + } yield { + ip + } + addrs.toList + } + + private def portsFrom(info: ContainerInfo): Map[Int, Int] = { + info + .networkSettings() + .ports() + .asScala + .collect { + case (portStr, bindings) if Option(bindings).exists(!_.isEmpty) => + val port = ContainerPort.parsed(portStr).port + val hostPort = bindings.get(0).hostPort().toInt + port -> hostPort + } + .toMap + } + + def ipAddresses(): Seq[String] = { + state() match { + case s: ContainerState.IsRunning => + addresses(s.info) + case _ => + throw new Exception("can't get addresses of not running container") + } + } + + def mappedPorts(): Map[Int, Int] = { + state() match { + case s: ContainerState.IsRunning => + portsFrom(s.info) + case _ => + throw new Exception("can't get ports of not running container") + } + } + + def toManagedContainer: SingleContainer = SingleContainer(this) +} diff --git a/core/src/main/scala/com/whisk/docker/ContainerCommandExecutor.scala b/core/src/main/scala/com/whisk/docker/ContainerCommandExecutor.scala new file mode 100644 index 0000000..b375ffa --- /dev/null +++ b/core/src/main/scala/com/whisk/docker/ContainerCommandExecutor.scala @@ -0,0 +1,126 @@ +package com.whisk.docker + +import java.nio.charset.StandardCharsets +import java.util.Collections +import java.util.concurrent.TimeUnit + +import com.google.common.io.Closeables +import com.spotify.docker.client.DockerClient.{AttachParameter, RemoveContainerParam} +import com.spotify.docker.client.messages._ +import com.spotify.docker.client.{DockerClient, LogMessage, LogStream} + +import scala.collection.JavaConverters._ +import scala.concurrent.{ExecutionContext, Future, Promise} + +class StartFailedException(msg: String) extends Exception(msg) + +class ContainerCommandExecutor(val client: DockerClient) { + + def createContainer(spec: ContainerSpec)( + implicit ec: ExecutionContext): Future[ContainerCreation] = { + + val portBindings: Map[String, java.util.List[PortBinding]] = spec.portBindings.map { + case (guestPort, binding) => + guestPort.toString -> Collections.singletonList(binding) + } + val hostConfig = + HostConfig + .builder() + .portBindings(portBindings.asJava) + .build() + + val containerConfig = ContainerConfig + .builder() + .image(spec.image) + .hostConfig(hostConfig) + .exposedPorts(spec.portBindings.keySet.map(_.toString).asJava) + .env(spec.env: _*) + .withOption(spec.command) { case (config, command) => config.cmd(command: _*) } + .build() + + Future(scala.concurrent.blocking(client.createContainer(containerConfig, spec.name.orNull))) + } + + def startContainer(id: String)(implicit ec: ExecutionContext): Future[Unit] = { + Future(scala.concurrent.blocking(client.startContainer(id))) + } + + def runningContainer(id: String)(implicit ec: ExecutionContext): Future[ContainerInfo] = { + def inspect() = { + Future(scala.concurrent.blocking(client.inspectContainer(id))).flatMap { info => + val status = info.state().status() + val badStates = Set("removing", "paused", "exited", "dead") + if (status == "running") { + Future.successful(info) + } else if (badStates(status)) { + Future.failed(new StartFailedException("container is in unexpected state: " + status)) + } else { + Future.failed(new Exception("not running yet")) + } + } + } + + def attempt(rest: Int): Future[ContainerInfo] = { + inspect().recoverWith { + case e: StartFailedException => Future.failed(e) + case _ if rest > 0 => + RetryUtils.withDelay(TimeUnit.SECONDS.toMillis(1))(attempt(rest - 1)) + case _ => + Future.failed(new StartFailedException("failed to get container in running state")) + } + } + + attempt(10) + } + + private def logStreamFuture(id: String, withErr: Boolean)( + implicit docker: ContainerCommandExecutor, + ec: ExecutionContext): Future[LogStream] = { + val baseParams = List(AttachParameter.STDOUT, AttachParameter.STREAM, AttachParameter.LOGS) + val logParams = if (withErr) AttachParameter.STDERR :: baseParams else baseParams + Future(scala.concurrent.blocking(client.attachContainer(id, logParams: _*))) + } + + def withLogStreamLines(id: String, withErr: Boolean)( + f: String => Unit)(implicit docker: ContainerCommandExecutor, ec: ExecutionContext): Unit = { + + logStreamFuture(id, withErr).foreach { stream => + stream.forEachRemaining((t: LogMessage) => { + val str = StandardCharsets.US_ASCII.decode(t.content()).toString + f(s"[$id] $str") + }) + } + } + + def withLogStreamLinesRequirement(id: String, withErr: Boolean)(f: (String) => Boolean)( + implicit docker: ContainerCommandExecutor, + ec: ExecutionContext): Future[Unit] = { + + logStreamFuture(id, withErr).flatMap { stream => + val p = Promise[Unit]() + Future { + stream.forEachRemaining((t: LogMessage) => { + val str = StandardCharsets.US_ASCII.decode(t.content()).toString + if (f(str)) { + p.trySuccess(()) + Closeables.close(stream, true) + } + }) + } + p.future + } + } + + def remove(id: String, force: Boolean, removeVolumes: Boolean)( + implicit ec: ExecutionContext): Future[Unit] = { + Future( + scala.concurrent.blocking( + client.removeContainer(id, + RemoveContainerParam.forceKill(force), + RemoveContainerParam.removeVolumes(removeVolumes)))) + } + + def close(): Unit = { + Closeables.close(client, true) + } +} diff --git a/core/src/main/scala/com/whisk/docker/ContainerPort.scala b/core/src/main/scala/com/whisk/docker/ContainerPort.scala new file mode 100644 index 0000000..af3d9c1 --- /dev/null +++ b/core/src/main/scala/com/whisk/docker/ContainerPort.scala @@ -0,0 +1,17 @@ +package com.whisk.docker + +object PortProtocol extends Enumeration { + val TCP, UDP = Value +} + +case class ContainerPort(port: Int, protocol: PortProtocol.Value) + +object ContainerPort { + def parsed(str: String): ContainerPort = { + val Array(p, rest @ _*) = str.split("/") + val proto = rest.headOption + .flatMap(pr => PortProtocol.values.find(_.toString.equalsIgnoreCase(pr))) + .getOrElse(PortProtocol.TCP) + ContainerPort(p.toInt, proto) + } +} \ No newline at end of file diff --git a/core/src/main/scala/com/whisk/docker/ContainerSpec.scala b/core/src/main/scala/com/whisk/docker/ContainerSpec.scala new file mode 100644 index 0000000..fb8fe6d --- /dev/null +++ b/core/src/main/scala/com/whisk/docker/ContainerSpec.scala @@ -0,0 +1,30 @@ +package com.whisk.docker + +import com.spotify.docker.client.messages.PortBinding + +case class ContainerSpec(image: String, + name: Option[String] = None, + command: Option[Seq[String]] = None, + portBindings: Map[Int, PortBinding] = Map.empty, + env: Seq[String] = Seq.empty, + readyChecker: Option[DockerReadyChecker] = None) { + + def withCommand(cmd: String*): ContainerSpec = copy(command = Some(cmd)) + + def withExposedPorts(ports: Int*): ContainerSpec = { + val binds: Map[Int, PortBinding] = + ports.map(p => p -> PortBinding.randomPort("0.0.0.0"))(collection.breakOut) //TODO check + copy(portBindings = binds) + } + + def withPortBindings(ps: (Int, PortBinding)*): ContainerSpec = copy(portBindings = ps.toMap) + + def withReadyChecker(checker: DockerReadyChecker): ContainerSpec = + copy(readyChecker = Some(checker)) + + def withEnv(env: String*): ContainerSpec = copy(env = env) + + def toContainer: Container = new Container(this) + + def toManagedContainer: SingleContainer = SingleContainer(this.toContainer) +} diff --git a/core/src/main/scala/com/whisk/docker/DockerCommandExecutor.scala b/core/src/main/scala/com/whisk/docker/DockerCommandExecutor.scala deleted file mode 100644 index d4a9b40..0000000 --- a/core/src/main/scala/com/whisk/docker/DockerCommandExecutor.scala +++ /dev/null @@ -1,56 +0,0 @@ -package com.whisk.docker - -import scala.concurrent.{ExecutionContext, Future} - -object PortProtocol extends Enumeration { - val TCP, UDP = Value -} - -case class ContainerPort(port: Int, protocol: PortProtocol.Value) - -object ContainerPort { - def parse(str: String) = { - val Array(p, rest @ _*) = str.split("/") - val proto = rest.headOption - .flatMap(pr => PortProtocol.values.find(_.toString.equalsIgnoreCase(pr))) - .getOrElse(PortProtocol.TCP) - ContainerPort(p.toInt, proto) - } -} - -case class PortBinding(hostIp: String, hostPort: Int) - -case class InspectContainerResult(running: Boolean, - ports: Map[ContainerPort, Seq[PortBinding]], - name: String, - ipAddresses: Seq[String]) - -trait DockerCommandExecutor { - - def host: String - - def createContainer(spec: DockerContainer)(implicit ec: ExecutionContext): Future[String] - - def startContainer(id: String)(implicit ec: ExecutionContext): Future[Unit] - - def inspectContainer(id: String)( - implicit ec: ExecutionContext): Future[Option[InspectContainerResult]] - - def withLogStreamLines(id: String, withErr: Boolean)(f: String => Unit)( - implicit docker: DockerCommandExecutor, - ec: ExecutionContext - ): Unit - - def withLogStreamLinesRequirement(id: String, withErr: Boolean)(f: String => Boolean)( - implicit docker: DockerCommandExecutor, - ec: ExecutionContext): Future[Unit] - - def listImages()(implicit ec: ExecutionContext): Future[Set[String]] - - def pullImage(image: String)(implicit ec: ExecutionContext): Future[Unit] - - def remove(id: String, force: Boolean = true, removeVolumes: Boolean = true)( - implicit ec: ExecutionContext): Future[Unit] - - def close(): Unit -} diff --git a/core/src/main/scala/com/whisk/docker/DockerContainer.scala b/core/src/main/scala/com/whisk/docker/DockerContainer.scala deleted file mode 100644 index a13a413..0000000 --- a/core/src/main/scala/com/whisk/docker/DockerContainer.scala +++ /dev/null @@ -1,80 +0,0 @@ -package com.whisk.docker - -case class VolumeMapping(host: String, container: String, rw: Boolean = false) - -case class ContainerLink(container: DockerContainer, alias: String) { - require(container.name.nonEmpty, "Container must have a name") -} - -case class LogLineReceiver(withErr: Boolean, f: String => Unit) - -case class DockerPortMapping(hostPort: Option[Int] = None, address: String = "0.0.0.0") - -case class HostConfig( - - tmpfs: Option[Map[String, String]] = None, - - /** - * the hard limit on memory usage (in bytes) - */ - memory: Option[Long] = None, - - /** - * the soft limit on memory usage (in bytes) - */ - memoryReservation: Option[Long] = None - -) - -case class DockerContainer(image: String, - name: Option[String] = None, - command: Option[Seq[String]] = None, - entrypoint: Option[Seq[String]] = None, - bindPorts: Map[Int, DockerPortMapping] = Map.empty, - tty: Boolean = false, - stdinOpen: Boolean = false, - links: Seq[ContainerLink] = Seq.empty, - unlinkedDependencies: Seq[DockerContainer] = Seq.empty, - env: Seq[String] = Seq.empty, - networkMode: Option[String] = None, - readyChecker: DockerReadyChecker = DockerReadyChecker.Always, - volumeMappings: Seq[VolumeMapping] = Seq.empty, - logLineReceiver: Option[LogLineReceiver] = None, - user: Option[String] = None, - hostname: Option[String] = None, - hostConfig: Option[HostConfig] = None) { - - def withCommand(cmd: String*) = copy(command = Some(cmd)) - - def withEntrypoint(entrypoint: String*) = copy(entrypoint = Some(entrypoint)) - - def withPorts(ps: (Int, Option[Int])*) = - copy(bindPorts = ps.toMap.mapValues(hostPort => DockerPortMapping(hostPort))) - - def withPortMapping(ps: (Int, DockerPortMapping)*) = copy(bindPorts = ps.toMap) - - def withLinks(links: ContainerLink*) = copy(links = links.toSeq) - - def withUnlinkedDependencies(unlinkedDependencies: DockerContainer*) = - copy(unlinkedDependencies = unlinkedDependencies.toSeq) - - def dependencies: Seq[DockerContainer] = links.map(_.container) ++ unlinkedDependencies - - def withReadyChecker(checker: DockerReadyChecker) = copy(readyChecker = checker) - - def withEnv(env: String*) = copy(env = env) - - def withNetworkMode(networkMode: String) = copy(networkMode = Some(networkMode)) - - def withVolumes(volumeMappings: Seq[VolumeMapping]) = copy(volumeMappings = volumeMappings) - - def withLogLineReceiver(logLineReceiver: LogLineReceiver) = - copy(logLineReceiver = Some(logLineReceiver)) - - def withUser(user: String) = copy(user = Some(user)) - - def withHostname(hostname: String) = copy(hostname = Some(hostname)) - - def withHostConfig(hostConfig: HostConfig) = copy(hostConfig = Some(hostConfig)) - -} diff --git a/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala b/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala index a763fb8..a5ca69e 100644 --- a/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala +++ b/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala @@ -1,119 +1,127 @@ package com.whisk.docker +import java.util.concurrent.{ConcurrentHashMap, TimeUnit} + +import com.google.common.collect.ImmutableList +import com.spotify.docker.client.messages.ContainerCreation import org.slf4j.LoggerFactory -import scala.annotation.tailrec import scala.concurrent.{Await, ExecutionContext, Future} +import scala.collection.JavaConverters._ import scala.language.postfixOps -import scala.concurrent.duration._ -class DockerContainerManager(containers: Seq[DockerContainer], executor: DockerCommandExecutor)( - implicit ec: ExecutionContext) { +trait ManagedContainers - private lazy val log = LoggerFactory.getLogger(this.getClass) - private implicit val dockerExecutor = executor +case class SingleContainer(container: Container) extends ManagedContainers + +case class ContainerGroup(containers: Seq[Container]) extends ManagedContainers { + require(containers.nonEmpty, "container group should be non-empty") +} + +class DockerContainerManager(managedContainers: ManagedContainers, + executor: ContainerCommandExecutor, + dockerTestTimeouts: DockerTestTimeouts, + executionContext: ExecutionContext) { - private val dockerStatesMap: Map[DockerContainer, DockerContainerState] = - containers.map(c => c -> new DockerContainerState(c))(collection.breakOut) + private implicit val ec: ExecutionContext = executionContext - val states = dockerStatesMap.values.toList + private lazy val log = LoggerFactory.getLogger(this.getClass) - def getContainerState(container: DockerContainer): DockerContainerState = { - dockerStatesMap(container) + private val registeredContainers = new ConcurrentHashMap[String, String]() + + private def waitUntilReady(container: Container): Future[Unit] = { + container.spec.readyChecker match { + case None => + Future.unit + case Some(checker) => + checker(container)(executor, executionContext) + .flatMap { + case false => + Future.failed(new Exception("ready check failed")) + case true => + Future.unit + } + } } - def isReady(container: DockerContainer): Future[Boolean] = { - dockerStatesMap(container).isReady() + def printWarningsIfExist(creation: ContainerCreation): Unit = { + Option(creation.warnings()) + .getOrElse(ImmutableList.of[String]()) + .forEach(w => log.warn(s"creating container: $w")) } - def pullImages(): Future[Seq[String]] = { - executor.listImages().flatMap { images => - val imagesToPull: Seq[String] = containers.map(_.image).filterNot { image => - val cImage = if (image.contains(":")) image else image + ":latest" - images(cImage) - } - Future.traverse(imagesToPull)(i => executor.pullImage(i)).map(_ => imagesToPull) + //TODO log listeners + def startContainer(container: Container): Future[Unit] = { + val image = container.spec.image + val startTime = System.nanoTime() + log.debug("Starting container: {}", image) + for { + creation <- executor.createContainer(container.spec) + id = creation.id() + _ = registeredContainers.put(id, image) + _ = container.created(id) + _ = printWarningsIfExist(creation) + _ = log.info(s"starting container with id: $id") + _ <- executor.startContainer(id) + _ = container.starting(id) + _ = log.info(s"container is starting. id=$id") + runningContainer <- executor.runningContainer(id) + _ = log.debug(s"container entered running state. id=$id") + _ = container.running(runningContainer) + _ = log.debug(s"preparing to execute ready check for container") + res <- waitUntilReady(container) + _ = log.debug(s"container is ready. id=$id") + } yield { + container.ready(runningContainer) + val timeTaken = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + log.info(s"container $image is ready after ${timeTaken / 1000.0}s") + res } + } - def initReadyAll(containerStartTimeout: Duration): Future[Seq[(DockerContainerState, Boolean)]] = { - import DockerContainerManager._ - - @tailrec - def initGraph(graph: ContainerDependencyGraph, - previousInits: Future[Seq[DockerContainerState]] = Future.successful(Seq.empty)) - : Future[Seq[DockerContainerState]] = { - val initializedContainers = previousInits.flatMap { prev => - Future.traverse(graph.containers.map(dockerStatesMap))(_.init()).map(prev ++ _) - } - - graph.dependants match { - case None => initializedContainers - case Some(dependants) => - val readyInits: Future[Seq[Future[Boolean]]] = - initializedContainers.map(_.map(state => state.isReady())) - val simplifiedReadyInits: Future[Seq[Boolean]] = readyInits.flatMap(Future.sequence(_)) - Await.result(simplifiedReadyInits, containerStartTimeout) - initGraph(dependants, initializedContainers) - } + def start(): Unit = { + log.debug("Starting containers") + val containers: Seq[Container] = managedContainers match { + case SingleContainer(c) => Seq(c) + case ContainerGroup(cs) => cs + case _ => throw new Exception("unsupported type of managed containers") } - initGraph(buildDependencyGraph(containers)).flatMap(Future.traverse(_) { c => - c.isReady().map(c -> _).recover { - case e => - log.error(e.getMessage, e) - c -> false - } - }) + val startedContainersF = Future.traverse(containers)(startContainer) + + sys.addShutdownHook( + stop() + ) + + try { + Await.result(startedContainersF, dockerTestTimeouts.init) + } catch { + case e: Exception => + log.error("Exception during container initialization", e) + stop() + throw new RuntimeException("Cannot run all required containers") + } + } + + def stop(): Unit = { + try { + Await.ready(stopRmAll(), dockerTestTimeouts.stop) + } catch { + case e: Throwable => + log.error(e.getMessage, e) + } } def stopRmAll(): Future[Unit] = { - val future = Future.traverse(states)(_.remove(force = true, removeVolumes = true)).map(_ => ()) + val future = Future.traverse(registeredContainers.asScala) { + case (cid, _) => + executor.remove(cid, force = true, removeVolumes = true) + } future.onComplete { _ => executor.close() } - future + future.map(_ => ()) } } - -object DockerContainerManager { - case class ContainerDependencyGraph(containers: Seq[DockerContainer], - dependants: Option[ContainerDependencyGraph] = None) - - def buildDependencyGraph(containers: Seq[DockerContainer]): ContainerDependencyGraph = { - @tailrec def buildDependencyGraph(graph: ContainerDependencyGraph): ContainerDependencyGraph = - graph match { - case ContainerDependencyGraph(containers, dependants) => - containers.partition(_.dependencies.isEmpty) match { - case (containersWithoutLinks, Nil) => graph - case (containersWithoutLinks, containersWithLinks) => - val linkedContainers = containers.foldLeft(Seq[DockerContainer]()) { - case (links, container) => (links ++ container.dependencies) - } - val (containersWithLinksAndLinked, containersWithLinksNotLinked) = - containersWithLinks.partition(linkedContainers.contains) - val (containersToBeLeftAtCurrentPosition, containersToBeMovedUpALevel) = dependants - .map(_.containers) - .getOrElse(List.empty) - .partition( - _.dependencies.exists(containersWithLinksNotLinked.contains) - ) - - buildDependencyGraph( - ContainerDependencyGraph( - containers = containersWithoutLinks ++ containersWithLinksAndLinked, - dependants = Some( - ContainerDependencyGraph( - containers = containersWithLinksNotLinked ++ containersToBeMovedUpALevel, - dependants = - dependants.map(_.copy(containers = containersToBeLeftAtCurrentPosition)) - )) - ) - ) - } - } - - buildDependencyGraph(ContainerDependencyGraph(containers)) - } -} diff --git a/core/src/main/scala/com/whisk/docker/DockerContainerState.scala b/core/src/main/scala/com/whisk/docker/DockerContainerState.scala deleted file mode 100644 index a3d1245..0000000 --- a/core/src/main/scala/com/whisk/docker/DockerContainerState.scala +++ /dev/null @@ -1,113 +0,0 @@ -package com.whisk.docker - -import java.util.concurrent.atomic.AtomicBoolean - -import org.slf4j.LoggerFactory - -import scala.concurrent.{ExecutionContext, Future, Promise} - -class DockerContainerState(spec: DockerContainer) { - - private lazy val log = LoggerFactory.getLogger(this.getClass) - - class SinglePromise[T] { - val promise = Promise[T]() - - def future = promise.future - - val flag = new AtomicBoolean(false) - - def init(f: => Future[T]): Future[T] = { - if (!flag.getAndSet(true)) { - promise.tryCompleteWith(f) - } - future - } - } - - object SinglePromise { - def apply[T] = new SinglePromise[T] - } - - private val _id = SinglePromise[String] - - def id: Future[String] = _id.future - - private val _image = SinglePromise[Unit] - - private val _isReady = SinglePromise[Boolean] - - def isReady(): Future[Boolean] = _isReady.future - - def isRunning()(implicit docker: DockerCommandExecutor, ec: ExecutionContext): Future[Boolean] = - getRunningContainer().map(_.isDefined) - - def init()(implicit docker: DockerCommandExecutor, ec: ExecutionContext): Future[this.type] = { - for { - s <- _id.init(docker.createContainer(spec)) - _ <- docker.startContainer(s) - } yield { - spec.logLineReceiver.foreach { - case LogLineReceiver(withErr, f) => docker.withLogStreamLines(s, withErr)(f) - } - runReadyCheck - this - } - } - - private def runReadyCheck()(implicit docker: DockerCommandExecutor, - ec: ExecutionContext): Future[Boolean] = - _isReady.init( - (for { - r <- isRunning() if r - b <- spec.readyChecker(this) if b - } yield b) recoverWith { - case _: NoSuchElementException => - log.error("Not ready: " + { - spec.image - }) - Future.successful(false) - case e => - log.error(e.getMessage, e) - Future.successful(false) - } - ) - - protected def getRunningContainer()( - implicit docker: DockerCommandExecutor, - ec: ExecutionContext): Future[Option[InspectContainerResult]] = - id.flatMap(docker.inspectContainer) - - def getName()(implicit docker: DockerCommandExecutor, ec: ExecutionContext): Future[String] = - getRunningContainer.flatMap { - case Some(res) => Future.successful(res.name) - case None => Future.failed(new RuntimeException(s"Container ${spec.image} is not running")) - } - - def getIpAddresses()(implicit docker: DockerCommandExecutor, - ec: ExecutionContext): Future[Seq[String]] = getRunningContainer.flatMap { - case Some(res) => Future.successful(res.ipAddresses) - case None => Future.failed(new RuntimeException(s"Container ${spec.image} is not running")) - } - - private val _ports = SinglePromise[Map[Int, Int]] - - def getPorts()(implicit docker: DockerCommandExecutor, - ec: ExecutionContext): Future[Map[Int, Int]] = { - def portsFuture: Future[Map[Int, Int]] = getRunningContainer().flatMap { - case None => Future.failed(new RuntimeException(s"Container ${spec.image} is not running")) - case Some(c) => - val ports: Map[Int, Int] = c.ports.collect { - case (exposedPort, Seq(binding, _*)) => - exposedPort.port -> binding.hostPort - } - Future.successful(ports) - } - _ports.init(portsFuture) - } - - def remove(force: Boolean = true, removeVolumes: Boolean = true)( - implicit docker: DockerCommandExecutor, - ec: ExecutionContext): Future[Unit] = - id.flatMap(x => docker.remove(x, force, removeVolumes)) -} diff --git a/core/src/main/scala/com/whisk/docker/DockerFactory.scala b/core/src/main/scala/com/whisk/docker/DockerFactory.scala deleted file mode 100644 index 6673924..0000000 --- a/core/src/main/scala/com/whisk/docker/DockerFactory.scala +++ /dev/null @@ -1,6 +0,0 @@ -package com.whisk.docker - -trait DockerFactory { - - def createExecutor(): DockerCommandExecutor -} diff --git a/core/src/main/scala/com/whisk/docker/DockerKit.scala b/core/src/main/scala/com/whisk/docker/DockerKit.scala deleted file mode 100644 index bd987d8..0000000 --- a/core/src/main/scala/com/whisk/docker/DockerKit.scala +++ /dev/null @@ -1,72 +0,0 @@ -package com.whisk.docker - -import java.util.concurrent.Executors - -import org.slf4j.LoggerFactory - -import scala.concurrent.duration._ -import scala.concurrent.{Await, ExecutionContext, Future} -import scala.language.implicitConversions - -trait DockerKit { - implicit def dockerFactory: DockerFactory - - private lazy val log = LoggerFactory.getLogger(this.getClass) - - val PullImagesTimeout = 20.minutes - val StartContainersTimeout = 20.seconds - val StopContainersTimeout = 10.seconds - - def dockerContainers: List[DockerContainer] = Nil - - // we need ExecutionContext in order to run docker.init() / docker.stop() there - implicit lazy val dockerExecutionContext: ExecutionContext = { - // using Math.max to prevent unexpected zero length of docker containers - ExecutionContext.fromExecutor( - Executors.newFixedThreadPool(Math.max(1, dockerContainers.length * 2))) - } - implicit lazy val dockerExecutor = dockerFactory.createExecutor() - - lazy val containerManager = new DockerContainerManager(dockerContainers, dockerExecutor) - - def isContainerReady(container: DockerContainer): Future[Boolean] = - containerManager.isReady(container) - - def getContainerState(container: DockerContainer): DockerContainerState = { - containerManager.getContainerState(container) - } - - implicit def containerToState(c: DockerContainer): DockerContainerState = { - getContainerState(c) - } - - def startAllOrFail(): Unit = { - Await.result(containerManager.pullImages(), PullImagesTimeout) - val allRunning: Boolean = try { - val future: Future[Boolean] = - containerManager.initReadyAll(StartContainersTimeout).map(_.map(_._2).forall(identity)) - sys.addShutdownHook( - Await.ready(containerManager.stopRmAll(), StopContainersTimeout) - ) - Await.result(future, StartContainersTimeout) - } catch { - case e: Exception => - log.error("Exception during container initialization", e) - false - } - if (!allRunning) { - Await.ready(containerManager.stopRmAll(), StopContainersTimeout) - throw new RuntimeException("Cannot run all required containers") - } - } - - def stopAllQuietly(): Unit = { - try { - Await.ready(containerManager.stopRmAll(), StopContainersTimeout) - } catch { - case e: Throwable => - log.error(e.getMessage, e) - } - } - -} diff --git a/core/src/main/scala/com/whisk/docker/DockerReadyChecker.scala b/core/src/main/scala/com/whisk/docker/DockerReadyChecker.scala index e21f5bc..7ddbb56 100644 --- a/core/src/main/scala/com/whisk/docker/DockerReadyChecker.scala +++ b/core/src/main/scala/com/whisk/docker/DockerReadyChecker.scala @@ -8,10 +8,10 @@ import scala.concurrent.{ExecutionContext, Future, Promise, TimeoutException} trait DockerReadyChecker { - def apply(container: DockerContainerState)(implicit docker: DockerCommandExecutor, - ec: ExecutionContext): Future[Boolean] + def apply(container: Container)(implicit docker: ContainerCommandExecutor, + ec: ExecutionContext): Future[Boolean] - def and(other: DockerReadyChecker)(implicit docker: DockerCommandExecutor, + def and(other: DockerReadyChecker)(implicit docker: ContainerCommandExecutor, ec: ExecutionContext) = { val s = this DockerReadyChecker.F { container => @@ -24,7 +24,8 @@ trait DockerReadyChecker { } } - def or(other: DockerReadyChecker)(implicit docker: DockerCommandExecutor, ec: ExecutionContext) = { + def or(other: DockerReadyChecker)(implicit docker: ContainerCommandExecutor, + ec: ExecutionContext) = { val s = this DockerReadyChecker.F { container => val aF = s(container) @@ -101,8 +102,8 @@ object RetryUtils { object DockerReadyChecker { object Always extends DockerReadyChecker { - override def apply(container: DockerContainerState)(implicit docker: DockerCommandExecutor, - ec: ExecutionContext): Future[Boolean] = + override def apply(container: Container)(implicit docker: ContainerCommandExecutor, + ec: ExecutionContext): Future[Boolean] = Future.successful(true) } @@ -111,11 +112,13 @@ object DockerReadyChecker { host: Option[String] = None, code: Int = 200) extends DockerReadyChecker { - override def apply(container: DockerContainerState)(implicit docker: DockerCommandExecutor, - ec: ExecutionContext): Future[Boolean] = { - container.getPorts().map(_(port)).flatMap { p => - val url = new URL("http", host.getOrElse(docker.host), p, path) - Future { + override def apply(container: Container)(implicit docker: ContainerCommandExecutor, + ec: ExecutionContext): Future[Boolean] = { + + val p = container.mappedPorts()(port) + val url = new URL("http", host.getOrElse(docker.client.getHost), p, path) + Future { + scala.concurrent.blocking { val con = url.openConnection().asInstanceOf[HttpURLConnection] try { con.getResponseCode == code @@ -129,13 +132,18 @@ object DockerReadyChecker { } case class LogLineContains(str: String) extends DockerReadyChecker { - override def apply(container: DockerContainerState)(implicit docker: DockerCommandExecutor, - ec: ExecutionContext): Future[Boolean] = { - for { - id <- container.id - _ <- docker.withLogStreamLinesRequirement(id, withErr = true)(_.contains(str)) - } yield { - true + + override def apply(container: Container)(implicit docker: ContainerCommandExecutor, + ec: ExecutionContext): Future[Boolean] = { + container.state() match { + case ContainerState.Ready(_) => + Future.successful(true) + case state: ContainerState.HasId => + docker + .withLogStreamLinesRequirement(state.id, withErr = true)(_.contains(str)) + .map(_ => true) + case _ => + Future.successful(false) } } } @@ -143,8 +151,8 @@ object DockerReadyChecker { private[docker] case class TimeLimited(underlying: DockerReadyChecker, duration: FiniteDuration) extends DockerReadyChecker { - override def apply(container: DockerContainerState)(implicit docker: DockerCommandExecutor, - ec: ExecutionContext): Future[Boolean] = { + override def apply(container: Container)(implicit docker: ContainerCommandExecutor, + ec: ExecutionContext): Future[Boolean] = { RetryUtils.runWithin(underlying(container), duration).recover { case _: TimeoutException => false @@ -157,15 +165,15 @@ object DockerReadyChecker { delay: FiniteDuration) extends DockerReadyChecker { - override def apply(container: DockerContainerState)(implicit docker: DockerCommandExecutor, - ec: ExecutionContext): Future[Boolean] = { + override def apply(container: Container)(implicit docker: ContainerCommandExecutor, + ec: ExecutionContext): Future[Boolean] = { RetryUtils.looped(underlying(container).filter(identity), attempts, delay) } } - case class F(f: DockerContainerState => Future[Boolean]) extends DockerReadyChecker { - override def apply(container: DockerContainerState)(implicit docker: DockerCommandExecutor, - ec: ExecutionContext): Future[Boolean] = + case class F(f: Container => Future[Boolean]) extends DockerReadyChecker { + override def apply(container: Container)(implicit docker: ContainerCommandExecutor, + ec: ExecutionContext): Future[Boolean] = f(container) } diff --git a/core/src/main/scala/com/whisk/docker/DockerTestKitForAll.scala b/core/src/main/scala/com/whisk/docker/DockerTestKitForAll.scala new file mode 100644 index 0000000..b63fc80 --- /dev/null +++ b/core/src/main/scala/com/whisk/docker/DockerTestKitForAll.scala @@ -0,0 +1,47 @@ +package com.whisk.docker + +import java.util.concurrent.ForkJoinPool + +import com.spotify.docker.client.{DefaultDockerClient, DockerClient} +import org.scalatest.{Args, Status, Suite, SuiteMixin} + +import scala.concurrent.ExecutionContext +import scala.language.implicitConversions + +trait DockerTestKitForAll extends SuiteMixin { self: Suite => + + val dockerClient: DockerClient = DefaultDockerClient.fromEnv().build() + + val dockerExecutionContext: ExecutionContext = ExecutionContext.fromExecutor(new ForkJoinPool()) + + val managedContainers: ManagedContainers + + val dockerTestTimeouts: DockerTestTimeouts = DockerTestTimeouts.Default + + implicit lazy val dockerExecutor: ContainerCommandExecutor = + new ContainerCommandExecutor(dockerClient) + + lazy val containerManager = new DockerContainerManager(managedContainers, + dockerExecutor, + dockerTestTimeouts, + dockerExecutionContext) + + abstract override def run(testName: Option[String], args: Args): Status = { + containerManager.start() + afterStart() + try { + super.run(testName, args) + } finally { + try { + beforeStop() + } finally { + containerManager.stop() + } + } + } + + def afterStart(): Unit = {} + + def beforeStop(): Unit = {} + +} diff --git a/core/src/main/scala/com/whisk/docker/DockerTestTimeouts.scala b/core/src/main/scala/com/whisk/docker/DockerTestTimeouts.scala new file mode 100644 index 0000000..4fae801 --- /dev/null +++ b/core/src/main/scala/com/whisk/docker/DockerTestTimeouts.scala @@ -0,0 +1,13 @@ +package com.whisk.docker + +import scala.concurrent.duration._ + +case class DockerTestTimeouts( + init: FiniteDuration = 60.seconds, + stop: FiniteDuration = 10.seconds +) + +object DockerTestTimeouts { + + val Default = DockerTestTimeouts() +} diff --git a/core/src/main/scala/com/whisk/docker/package.scala b/core/src/main/scala/com/whisk/docker/package.scala index e2e2c9a..93c6228 100644 --- a/core/src/main/scala/com/whisk/docker/package.scala +++ b/core/src/main/scala/com/whisk/docker/package.scala @@ -1,5 +1,9 @@ package com.whisk +import java.util.concurrent.atomic.AtomicBoolean + +import scala.concurrent.{Future, Promise} + /** * General utility functions */ @@ -10,4 +14,24 @@ package object docker { case Some(x) => f(content, x) } } + + private[docker] class SinglePromise[T] { + val promise: Promise[T] = Promise[T]() + + def future: Future[T] = promise.future + + val flag = new AtomicBoolean(false) + + def init(f: => Future[T]): Future[T] = { + if (!flag.getAndSet(true)) { + promise.tryCompleteWith(f) + } + future + } + } + + private[docker] object SinglePromise { + def apply[T] = new SinglePromise[T] + } + } diff --git a/impl/docker-java/src/main/scala/com/whisk/docker/impl/dockerjava/Docker.scala b/impl/docker-java/src/main/scala/com/whisk/docker/impl/dockerjava/Docker.scala deleted file mode 100644 index 1ba081f..0000000 --- a/impl/docker-java/src/main/scala/com/whisk/docker/impl/dockerjava/Docker.scala +++ /dev/null @@ -1,14 +0,0 @@ -package com.whisk.docker.impl.dockerjava - -import java.net.InetAddress - -import com.github.dockerjava.api.command.DockerCmdExecFactory -import com.github.dockerjava.core.{DockerClientBuilder, DockerClientConfig} -import com.github.dockerjava.jaxrs.JerseyDockerCmdExecFactory - -class Docker(val config: DockerClientConfig, - protected val factory: DockerCmdExecFactory = new JerseyDockerCmdExecFactory) { - val client = DockerClientBuilder.getInstance(config).withDockerCmdExecFactory(factory).build() - val host = - Option(config.getDockerHost.getHost).getOrElse(InetAddress.getLoopbackAddress.getHostAddress) -} diff --git a/impl/docker-java/src/main/scala/com/whisk/docker/impl/dockerjava/DockerJavaExecutor.scala b/impl/docker-java/src/main/scala/com/whisk/docker/impl/dockerjava/DockerJavaExecutor.scala deleted file mode 100644 index e4e18a3..0000000 --- a/impl/docker-java/src/main/scala/com/whisk/docker/impl/dockerjava/DockerJavaExecutor.scala +++ /dev/null @@ -1,190 +0,0 @@ -package com.whisk.docker.impl.dockerjava - -import java.util.concurrent.TimeUnit - -import com.github.dockerjava.api.DockerClient -import com.github.dockerjava.api.exception.NotFoundException -import com.github.dockerjava.api.model.{PortBinding => _, ContainerPort => _, _} -import com.github.dockerjava.core.command.{LogContainerResultCallback, PullImageResultCallback} -import com.google.common.io.Closeables -import com.whisk.docker._ - -import scala.collection.JavaConverters._ -import scala.concurrent.duration.FiniteDuration -import scala.concurrent.{ExecutionContext, Future, Promise} - -class DockerJavaExecutor(override val host: String, client: DockerClient) - extends DockerCommandExecutor { - - override def createContainer(spec: DockerContainer)( - implicit ec: ExecutionContext): Future[String] = { - val volumeToBind: Seq[(Volume, Bind)] = spec.volumeMappings.map { mapping => - val volume: Volume = new Volume(mapping.container) - (volume, new Bind(mapping.host, volume, AccessMode.fromBoolean(mapping.rw))) - } - - val hostConfig = new com.github.dockerjava.api.model.HostConfig() - .withOption(spec.networkMode)({ case (config, value) => config.withNetworkMode(value) }) - .withPortBindings(spec.bindPorts.foldLeft(new Ports()) { - case (ps, (guestPort, DockerPortMapping(Some(hostPort), address))) => - ps.bind(ExposedPort.tcp(guestPort), Ports.Binding.bindPort(hostPort)) - ps - case (ps, (guestPort, DockerPortMapping(None, address))) => - ps.bind(ExposedPort.tcp(guestPort), Ports.Binding.empty()) - ps - }) - .withLinks( - new Links(spec.links.map { - case ContainerLink(container, alias) => - new Link(container.name.get, alias) - }: _*) - ) - .withBinds(new Binds(volumeToBind.map(_._2): _*)) - .withOption(spec.hostConfig.flatMap(_.memory)) { - case (config, memory) => config.withMemory(memory) - } - .withOption(spec.hostConfig.flatMap(_.memoryReservation)) { - case (config, memoryReservation) => config.withMemoryReservation(memoryReservation) - } - - val cmd = client - .createContainerCmd(spec.image) - .withHostConfig(hostConfig) - .withPortSpecs(spec.bindPorts - .map({ - case (guestPort, DockerPortMapping(Some(hostPort), address)) => - s"$address:$hostPort:$guestPort" - case (guestPort, DockerPortMapping(None, address)) => s"$address::$guestPort" - }) - .toSeq: _*) - .withExposedPorts(spec.bindPorts.keys.map(ExposedPort.tcp).toSeq: _*) - .withTty(spec.tty) - .withStdinOpen(spec.stdinOpen) - .withEnv(spec.env: _*) - .withVolumes(volumeToBind.map(_._1): _*) - .withOption(spec.user) { case (config, user) => config.withUser(user) } - .withOption(spec.hostname) { case (config, hostName) => config.withHostName(hostName) } - .withOption(spec.name) { case (config, name) => config.withName(name) } - .withOption(spec.command) { case (config, c) => config.withCmd(c: _*) } - .withOption(spec.entrypoint) { case (config, entrypoint) => config.withEntrypoint(entrypoint: _*) } - - Future(cmd.exec()).map { resp => - if (resp.getId != null && resp.getId != "") { - resp.getId - } else { - throw new RuntimeException( - s"Cannot run container ${spec.image}: ${resp.getWarnings.mkString(", ")}") - } - } - } - - override def startContainer(id: String)(implicit ec: ExecutionContext): Future[Unit] = { - Future(client.startContainerCmd(id).exec()).map(_ => ()) - } - - override def inspectContainer(id: String)( - implicit ec: ExecutionContext): Future[Option[InspectContainerResult]] = { - val resp = Future(Some(client.inspectContainerCmd(id).exec())).recover { - case x: NotFoundException => None - } - val future = resp.map(_.map { result => - val containerBindings = Option(result.getNetworkSettings.getPorts) - .map(_.getBindings.asScala.toMap) - .getOrElse(Map()) - val portMap = containerBindings.collect { - case (exposedPort, bindings) if Option(bindings).isDefined => - val p = - ContainerPort(exposedPort.getPort, - PortProtocol.withName(exposedPort.getProtocol.toString.toUpperCase)) - val hostBindings: Seq[PortBinding] = bindings.map { b => - PortBinding(b.getHostIp, b.getHostPortSpec.toInt) - } - p -> hostBindings - } - - val addresses: Iterable[String] = for { - networks <- Option(result.getNetworkSettings.getNetworks).map(_.asScala).toSeq - (key, network) <- networks - ip <- Option(network.getIpAddress) - } yield { - ip - } - - InspectContainerResult(running = true, - ports = portMap, - name = result.getName, - ipAddresses = addresses.toSeq) - }) - RetryUtils.looped( - future.flatMap { - case Some(x) if x.running => Future.successful(Some(x)) - case None => Future.successful(None) - case _ => Future.failed(throw new Exception("container is not running")) - }, - 5, - FiniteDuration(2, TimeUnit.SECONDS) - ) - } - - override def withLogStreamLines(id: String, withErr: Boolean)(f: String => Unit)( - implicit docker: DockerCommandExecutor, - ec: ExecutionContext - ): Unit = { - val cmd = - client.logContainerCmd(id).withStdOut(true).withStdErr(withErr).withFollowStream(true) - - cmd.exec(new LogContainerResultCallback { - override def onNext(item: Frame): Unit = { - super.onNext(item) - f(s"[$id] ${item.toString}") - } - }) - } - - override def withLogStreamLinesRequirement(id: String, withErr: Boolean)(f: String => Boolean)( - implicit docker: DockerCommandExecutor, - ec: ExecutionContext): Future[Unit] = { - - val cmd = - client.logContainerCmd(id).withStdOut(true).withStdErr(withErr).withFollowStream(true) - for { - - res <- { - val p = Promise[Unit]() - cmd.exec(new LogContainerResultCallback { - override def onNext(item: Frame): Unit = { - super.onNext(item) - if (f(item.toString)) { - p.trySuccess(()) - onComplete() - } - } - }) - p.future - } - } yield { - res - } - } - - override def listImages()(implicit ec: ExecutionContext): Future[Set[String]] = { - Future( - client - .listImagesCmd() - .exec() - .asScala - .flatMap(img => Option(img.getRepoTags).getOrElse(Array())) - .toSet) - } - - override def pullImage(image: String)(implicit ec: ExecutionContext): Future[Unit] = { - Future(client.pullImageCmd(image).exec(new PullImageResultCallback()).awaitSuccess()) - } - - override def remove(id: String, force: Boolean, removeVolumes: Boolean)( - implicit ec: ExecutionContext): Future[Unit] = { - Future(client.removeContainerCmd(id).withForce(force).withRemoveVolumes(true).exec()) - } - - override def close(): Unit = Closeables.close(client, true) -} diff --git a/impl/docker-java/src/main/scala/com/whisk/docker/impl/dockerjava/DockerJavaExecutorFactory.scala b/impl/docker-java/src/main/scala/com/whisk/docker/impl/dockerjava/DockerJavaExecutorFactory.scala deleted file mode 100644 index 275a32b..0000000 --- a/impl/docker-java/src/main/scala/com/whisk/docker/impl/dockerjava/DockerJavaExecutorFactory.scala +++ /dev/null @@ -1,9 +0,0 @@ -package com.whisk.docker.impl.dockerjava - -import com.whisk.docker.{DockerCommandExecutor, DockerFactory} - -class DockerJavaExecutorFactory(docker: Docker) extends DockerFactory { - override def createExecutor(): DockerCommandExecutor = { - new DockerJavaExecutor(docker.host, docker.client) - } -} diff --git a/impl/docker-java/src/main/scala/com/whisk/docker/impl/dockerjava/DockerKitDockerJava.scala b/impl/docker-java/src/main/scala/com/whisk/docker/impl/dockerjava/DockerKitDockerJava.scala deleted file mode 100644 index 5f03853..0000000 --- a/impl/docker-java/src/main/scala/com/whisk/docker/impl/dockerjava/DockerKitDockerJava.scala +++ /dev/null @@ -1,10 +0,0 @@ -package com.whisk.docker.impl.dockerjava - -import com.github.dockerjava.core.DefaultDockerClientConfig -import com.whisk.docker.{DockerFactory, DockerKit} - -trait DockerKitDockerJava extends DockerKit { - - override implicit val dockerFactory: DockerFactory = new DockerJavaExecutorFactory( - new Docker(DefaultDockerClientConfig.createDefaultConfigBuilder().build())) -} diff --git a/impl/spotify/src/main/scala/com/whisk/docker/impl/spotify/DockerKitSpotify.scala b/impl/spotify/src/main/scala/com/whisk/docker/impl/spotify/DockerKitSpotify.scala deleted file mode 100644 index 7026a53..0000000 --- a/impl/spotify/src/main/scala/com/whisk/docker/impl/spotify/DockerKitSpotify.scala +++ /dev/null @@ -1,10 +0,0 @@ -package com.whisk.docker.impl.spotify - -import com.spotify.docker.client.DefaultDockerClient -import com.whisk.docker.{DockerFactory, DockerKit} - -trait DockerKitSpotify extends DockerKit { - - override implicit val dockerFactory: DockerFactory = new SpotifyDockerFactory( - DefaultDockerClient.fromEnv().build()) -} diff --git a/impl/spotify/src/main/scala/com/whisk/docker/impl/spotify/SpotifyDockerCommandExecutor.scala b/impl/spotify/src/main/scala/com/whisk/docker/impl/spotify/SpotifyDockerCommandExecutor.scala deleted file mode 100644 index 1ccef6c..0000000 --- a/impl/spotify/src/main/scala/com/whisk/docker/impl/spotify/SpotifyDockerCommandExecutor.scala +++ /dev/null @@ -1,208 +0,0 @@ -package com.whisk.docker.impl.spotify - -import java.nio.charset.StandardCharsets -import java.util -import java.util.Collections -import java.util.concurrent.TimeUnit -import java.util.function.Consumer - -import com.google.common.io.Closeables -import com.spotify.docker.client.DockerClient.{AttachParameter, RemoveContainerParam} -import com.spotify.docker.client.exceptions.ContainerNotFoundException -import com.spotify.docker.client.messages.{ - ContainerConfig, - HostConfig, - PortBinding -} -import com.spotify.docker.client.{DockerClient, LogMessage} -import com.whisk.docker._ - -import scala.collection.JavaConverters._ -import scala.concurrent.duration.FiniteDuration -import scala.concurrent.{ExecutionContext, Future, Promise} - -class SpotifyDockerCommandExecutor(override val host: String, client: DockerClient) - extends DockerCommandExecutor { - - override def createContainer(spec: DockerContainer)( - implicit ec: ExecutionContext): Future[String] = { - val portBindings: Map[String, util.List[PortBinding]] = spec.bindPorts.map { - case (guestPort, DockerPortMapping(Some(hostPort), address)) => - guestPort.toString -> Collections.singletonList(PortBinding.of(address, hostPort)) - case (guestPort, DockerPortMapping(None, address)) => - guestPort.toString -> Collections.singletonList(PortBinding.randomPort(address)) - } - val binds: Seq[String] = spec.volumeMappings.map { volumeMapping => - val rw = if (volumeMapping.rw) ":rw" else "" - volumeMapping.host + ":" + volumeMapping.container + rw - } - - val hostConfig = { - val hostConfigBase = - HostConfig.builder().portBindings(portBindings.asJava).binds(binds.asJava) - - val links = spec.links.map { - case ContainerLink(container, alias) => s"${container.name.get}:$alias" - } - - val hostConfigBuilder = - if (links.isEmpty) hostConfigBase else hostConfigBase.links(links.asJava) - hostConfigBuilder - .withOption(spec.networkMode) { - case (config, networkMode) => config.networkMode(networkMode) - } - .withOption(spec.hostConfig.flatMap(_.tmpfs)) { - case (config, value) => config.tmpfs(value.asJava) - } - .withOption(spec.hostConfig.flatMap(_.memory)) { - case (config, memory) => config.memory(memory) - } - .withOption(spec.hostConfig.flatMap(_.memoryReservation)) { - case (config, reservation) => config.memoryReservation(reservation) - } - .build() - } - - val containerConfig = ContainerConfig - .builder() - .image(spec.image) - .hostConfig(hostConfig) - .exposedPorts(spec.bindPorts.map(_._1.toString).toSeq: _*) - .tty(spec.tty) - .attachStdin(spec.stdinOpen) - .env(spec.env: _*) - .withOption(spec.user) { case (config, user) => config.user(user) } - .withOption(spec.hostname) { case (config, hostname) => config.hostname(hostname) } - .withOption(spec.command) { case (config, command) => config.cmd(command: _*) } - .withOption(spec.entrypoint) { case (config, entrypoint) => config.entrypoint(entrypoint: _*) } - .build() - - val creation = Future( - spec.name.fold(client.createContainer(containerConfig))( - client.createContainer(containerConfig, _)) - ) - - creation.map(_.id) - } - - override def startContainer(id: String)(implicit ec: ExecutionContext): Future[Unit] = { - Future(client.startContainer(id)) - } - - override def inspectContainer(id: String)( - implicit ec: ExecutionContext): Future[Option[InspectContainerResult]] = { - - def inspect() = - Future(client.inspectContainer(id)) - .flatMap { info => - val networkPorts = Option(info.networkSettings().ports()) - networkPorts match { - case Some(p) => - val ports = info - .networkSettings() - .ports() - .asScala - .collect { - case (cPort, bindings) if Option(bindings).exists(!_.isEmpty) => - val binds = bindings.asScala - .map(b => com.whisk.docker.PortBinding(b.hostIp(), b.hostPort().toInt)) - .toList - ContainerPort.parse(cPort) -> binds - } - .toMap - - val addresses: Iterable[String] = for { - networks <- Option(info.networkSettings().networks()).map(_.asScala).toSeq - (key, network) <- networks - ip <- Option(network.ipAddress) - } yield { - ip - } - - Future.successful( - Some( - InspectContainerResult(info.state().running(), - ports, - info.name(), - addresses.toSeq))) - case None => - Future.failed(new Exception("can't extract ports")) - } - } - .recover { - case t: ContainerNotFoundException => - None - } - - RetryUtils.looped(inspect(), attempts = 5, delay = FiniteDuration(1, TimeUnit.SECONDS)) - } - - override def withLogStreamLines(id: String, withErr: Boolean)( - f: String => Unit)(implicit docker: DockerCommandExecutor, ec: ExecutionContext): Unit = { - val baseParams = List(AttachParameter.STDOUT, AttachParameter.STREAM, AttachParameter.LOGS) - val logParams = if (withErr) AttachParameter.STDERR :: baseParams else baseParams - val streamF = Future(client.attachContainer(id, logParams: _*)) - - streamF.flatMap { stream => - Future { - stream.forEachRemaining(new Consumer[LogMessage] { - override def accept(t: LogMessage): Unit = { - val str = StandardCharsets.US_ASCII.decode(t.content()).toString - f(s"[$id] $str") - } - }) - } - } - } - - override def withLogStreamLinesRequirement(id: String, withErr: Boolean)(f: (String) => Boolean)( - implicit docker: DockerCommandExecutor, - ec: ExecutionContext): Future[Unit] = { - - val baseParams = List(AttachParameter.STDOUT, AttachParameter.STREAM, AttachParameter.LOGS) - val logParams = if (withErr) AttachParameter.STDERR :: baseParams else baseParams - - val streamF = Future(client.attachContainer(id, logParams: _*)) - - streamF.flatMap { stream => - val p = Promise[Unit]() - Future { - stream.forEachRemaining(new Consumer[LogMessage] { - override def accept(t: LogMessage): Unit = { - val str = StandardCharsets.US_ASCII.decode(t.content()).toString - if (f(str)) { - p.trySuccess(()) - Closeables.close(stream, true) - } - } - }) - } - p.future - } - } - - override def listImages()(implicit ec: ExecutionContext): Future[Set[String]] = { - Future( - client - .listImages() - .asScala - .flatMap(img => Option(img.repoTags()).map(_.asScala).getOrElse(Seq.empty)) - .toSet) - } - - override def pullImage(image: String)(implicit ec: ExecutionContext): Future[Unit] = { - Future(client.pull(image)) - } - - override def remove(id: String, force: Boolean, removeVolumes: Boolean)( - implicit ec: ExecutionContext): Future[Unit] = { - Future( - client.removeContainer(id, - RemoveContainerParam.forceKill(force), - RemoveContainerParam.removeVolumes(removeVolumes))) - } - - override def close(): Unit = { - Closeables.close(client, true) - } -} diff --git a/impl/spotify/src/main/scala/com/whisk/docker/impl/spotify/SpotifyDockerFactory.scala b/impl/spotify/src/main/scala/com/whisk/docker/impl/spotify/SpotifyDockerFactory.scala deleted file mode 100644 index ba9d866..0000000 --- a/impl/spotify/src/main/scala/com/whisk/docker/impl/spotify/SpotifyDockerFactory.scala +++ /dev/null @@ -1,11 +0,0 @@ -package com.whisk.docker.impl.spotify - -import com.spotify.docker.client.DockerClient -import com.whisk.docker.{DockerCommandExecutor, DockerFactory} - -class SpotifyDockerFactory(client: DockerClient) extends DockerFactory { - - override def createExecutor(): DockerCommandExecutor = { - new SpotifyDockerCommandExecutor(client.getHost, client) - } -} diff --git a/samples/src/main/scala/com/whisk/docker/DockerCassandraService.scala b/samples/src/main/scala/com/whisk/docker/DockerCassandraService.scala index 1975c5b..37d32ba 100644 --- a/samples/src/main/scala/com/whisk/docker/DockerCassandraService.scala +++ b/samples/src/main/scala/com/whisk/docker/DockerCassandraService.scala @@ -1,12 +1,15 @@ package com.whisk.docker -trait DockerCassandraService extends DockerKit { +import org.scalatest.Suite + +trait DockerCassandraService extends DockerTestKitForAll { self: Suite => val DefaultCqlPort = 9042 - val cassandraContainer = DockerContainer("whisk/cassandra:2.1.8") - .withPorts(DefaultCqlPort -> None) + val cassandraContainer = ContainerSpec("whisk/cassandra:2.1.8") + .withExposedPorts(DefaultCqlPort) .withReadyChecker(DockerReadyChecker.LogLineContains("Starting listening for CQL clients on")) + .toContainer - abstract override def dockerContainers = cassandraContainer :: super.dockerContainers + override val managedContainers = cassandraContainer.toManagedContainer } diff --git a/samples/src/main/scala/com/whisk/docker/DockerElasticsearchService.scala b/samples/src/main/scala/com/whisk/docker/DockerElasticsearchService.scala index f71784d..db60cd1 100644 --- a/samples/src/main/scala/com/whisk/docker/DockerElasticsearchService.scala +++ b/samples/src/main/scala/com/whisk/docker/DockerElasticsearchService.scala @@ -1,21 +1,24 @@ package com.whisk.docker +import org.scalatest.Suite + import scala.concurrent.duration._ -trait DockerElasticsearchService extends DockerKit { +trait DockerElasticsearchService extends DockerTestKitForAll { + self: Suite => val DefaultElasticsearchHttpPort = 9200 val DefaultElasticsearchClientPort = 9300 - val elasticsearchContainer = DockerContainer("elasticsearch:1.7.1") - .withPorts(DefaultElasticsearchHttpPort -> None, DefaultElasticsearchClientPort -> None) + val elasticsearchContainer = ContainerSpec("elasticsearch:1.7.1") + .withExposedPorts(DefaultElasticsearchHttpPort, DefaultElasticsearchClientPort) .withReadyChecker( DockerReadyChecker .HttpResponseCode(DefaultElasticsearchHttpPort, "/") .within(100.millis) .looped(20, 1250.millis) ) + .toContainer - abstract override def dockerContainers: List[DockerContainer] = - elasticsearchContainer :: super.dockerContainers + override val managedContainers = elasticsearchContainer.toManagedContainer } diff --git a/samples/src/main/scala/com/whisk/docker/DockerKafkaService.scala b/samples/src/main/scala/com/whisk/docker/DockerKafkaService.scala index 2359fa5..83c8ea2 100644 --- a/samples/src/main/scala/com/whisk/docker/DockerKafkaService.scala +++ b/samples/src/main/scala/com/whisk/docker/DockerKafkaService.scala @@ -1,15 +1,15 @@ -package com.whisk.docker - -trait DockerKafkaService extends DockerKit { - - def KafkaAdvertisedPort = 9092 - val ZookeeperDefaultPort = 2181 - - lazy val kafkaContainer = DockerContainer("spotify/kafka") - .withPorts(KafkaAdvertisedPort -> Some(KafkaAdvertisedPort), ZookeeperDefaultPort -> None) - .withEnv(s"ADVERTISED_PORT=$KafkaAdvertisedPort", s"ADVERTISED_HOST=${dockerExecutor.host}") - .withReadyChecker(DockerReadyChecker.LogLineContains("kafka entered RUNNING state")) - - abstract override def dockerContainers: List[DockerContainer] = - kafkaContainer :: super.dockerContainers -} +//package com.whisk.docker +// +//trait DockerKafkaService extends DockerKit { +// +// def KafkaAdvertisedPort = 9092 +// val ZookeeperDefaultPort = 2181 +// +// lazy val kafkaContainer = ContainerSpec("spotify/kafka") +// .withPorts(KafkaAdvertisedPort -> Some(KafkaAdvertisedPort), ZookeeperDefaultPort -> None) +// .withEnv(s"ADVERTISED_PORT=$KafkaAdvertisedPort", s"ADVERTISED_HOST=${dockerExecutor.host}") +// .withReadyChecker(DockerReadyChecker.LogLineContains("kafka entered RUNNING state")) +// +// abstract override def dockerContainers: List[ContainerSpec] = +// kafkaContainer :: super.dockerContainers +//} diff --git a/samples/src/main/scala/com/whisk/docker/DockerMongodbService.scala b/samples/src/main/scala/com/whisk/docker/DockerMongodbService.scala index b5cb30c..3986acc 100644 --- a/samples/src/main/scala/com/whisk/docker/DockerMongodbService.scala +++ b/samples/src/main/scala/com/whisk/docker/DockerMongodbService.scala @@ -1,14 +1,14 @@ -package com.whisk.docker - -trait DockerMongodbService extends DockerKit { - - val DefaultMongodbPort = 27017 - - val mongodbContainer = DockerContainer("mongo:3.0.6") - .withPorts(DefaultMongodbPort -> None) - .withReadyChecker(DockerReadyChecker.LogLineContains("waiting for connections on port")) - .withCommand("mongod", "--nojournal", "--smallfiles", "--syncdelay", "0") - - abstract override def dockerContainers: List[DockerContainer] = - mongodbContainer :: super.dockerContainers -} +//package com.whisk.docker +// +//trait DockerMongodbService extends DockerKit { +// +// val DefaultMongodbPort = 27017 +// +// val mongodbContainer = ContainerSpec("mongo:3.0.6") +// .withPorts(DefaultMongodbPort -> None) +// .withReadyChecker(DockerReadyChecker.LogLineContains("waiting for connections on port")) +// .withCommand("mongod", "--nojournal", "--smallfiles", "--syncdelay", "0") +// +// abstract override def dockerContainers: List[ContainerSpec] = +// mongodbContainer :: super.dockerContainers +//} diff --git a/samples/src/main/scala/com/whisk/docker/DockerNeo4jService.scala b/samples/src/main/scala/com/whisk/docker/DockerNeo4jService.scala index a2c32f3..14bc364 100644 --- a/samples/src/main/scala/com/whisk/docker/DockerNeo4jService.scala +++ b/samples/src/main/scala/com/whisk/docker/DockerNeo4jService.scala @@ -1,21 +1,21 @@ -package com.whisk.docker - -import scala.concurrent.duration._ - -trait DockerNeo4jService extends DockerKit { - - val DefaultNeo4jHttpPort = 7474 - - val neo4jContainer = DockerContainer("neo4j:3.0.3") - .withPorts(DefaultNeo4jHttpPort -> None) - .withEnv("NEO4J_AUTH=none") - .withReadyChecker( - DockerReadyChecker - .HttpResponseCode(DefaultNeo4jHttpPort, "/db/data/") - .within(100.millis) - .looped(20, 1250.millis) - ) - - abstract override def dockerContainers: List[DockerContainer] = - neo4jContainer :: super.dockerContainers -} +//package com.whisk.docker +// +//import scala.concurrent.duration._ +// +//trait DockerNeo4jService extends DockerKit { +// +// val DefaultNeo4jHttpPort = 7474 +// +// val neo4jContainer = ContainerSpec("neo4j:3.0.3") +// .withPorts(DefaultNeo4jHttpPort -> None) +// .withEnv("NEO4J_AUTH=none") +// .withReadyChecker( +// DockerReadyChecker +// .HttpResponseCode(DefaultNeo4jHttpPort, "/db/data/") +// .within(100.millis) +// .looped(20, 1250.millis) +// ) +// +// abstract override def dockerContainers: List[ContainerSpec] = +// neo4jContainer :: super.dockerContainers +//} diff --git a/samples/src/main/scala/com/whisk/docker/DockerPostgresService.scala b/samples/src/main/scala/com/whisk/docker/DockerPostgresService.scala index 17469a0..d62ae1b 100644 --- a/samples/src/main/scala/com/whisk/docker/DockerPostgresService.scala +++ b/samples/src/main/scala/com/whisk/docker/DockerPostgresService.scala @@ -1,40 +1,40 @@ -package com.whisk.docker - -import java.sql.DriverManager - -import scala.concurrent.ExecutionContext -import scala.util.Try - -trait DockerPostgresService extends DockerKit { - import scala.concurrent.duration._ - def PostgresAdvertisedPort = 5432 - def PostgresExposedPort = 44444 - val PostgresUser = "nph" - val PostgresPassword = "suitup" - - val postgresContainer = DockerContainer("postgres:9.5.3") - .withPorts((PostgresAdvertisedPort, Some(PostgresExposedPort))) - .withEnv(s"POSTGRES_USER=$PostgresUser", s"POSTGRES_PASSWORD=$PostgresPassword") - .withReadyChecker( - new PostgresReadyChecker(PostgresUser, PostgresPassword, Some(PostgresExposedPort)) - .looped(15, 1.second) - ) - - abstract override def dockerContainers: List[DockerContainer] = - postgresContainer :: super.dockerContainers -} - -class PostgresReadyChecker(user: String, password: String, port: Option[Int] = None) - extends DockerReadyChecker { - - override def apply(container: DockerContainerState)(implicit docker: DockerCommandExecutor, - ec: ExecutionContext) = - container - .getPorts() - .map(ports => - Try { - Class.forName("org.postgresql.Driver") - val url = s"jdbc:postgresql://${docker.host}:${port.getOrElse(ports.values.head)}/" - Option(DriverManager.getConnection(url, user, password)).map(_.close).isDefined - }.getOrElse(false)) -} +//package com.whisk.docker +// +//import java.sql.DriverManager +// +//import scala.concurrent.ExecutionContext +//import scala.util.Try +// +//trait DockerPostgresService extends DockerKit { +// import scala.concurrent.duration._ +// def PostgresAdvertisedPort = 5432 +// def PostgresExposedPort = 44444 +// val PostgresUser = "nph" +// val PostgresPassword = "suitup" +// +// val postgresContainer = ContainerSpec("postgres:9.5.3") +// .withPorts((PostgresAdvertisedPort, Some(PostgresExposedPort))) +// .withEnv(s"POSTGRES_USER=$PostgresUser", s"POSTGRES_PASSWORD=$PostgresPassword") +// .withReadyChecker( +// new PostgresReadyChecker(PostgresUser, PostgresPassword, Some(PostgresExposedPort)) +// .looped(15, 1.second) +// ) +// +// abstract override def dockerContainers: List[ContainerSpec] = +// postgresContainer :: super.dockerContainers +//} +// +//class PostgresReadyChecker(user: String, password: String, port: Option[Int] = None) +// extends DockerReadyChecker { +// +// override def apply(container: Container)(implicit docker: DockerCommandExecutor, +// ec: ExecutionContext) = +// container +// .getPorts() +// .map(ports => +// Try { +// Class.forName("org.postgresql.Driver") +// val url = s"jdbc:postgresql://${docker.host}:${port.getOrElse(ports.values.head)}/" +// Option(DriverManager.getConnection(url, user, password)).map(_.close).isDefined +// }.getOrElse(false)) +//} diff --git a/samples/src/main/scala/com/whisk/docker/DockerZookeeperService.scala b/samples/src/main/scala/com/whisk/docker/DockerZookeeperService.scala index f90ea37..e939f00 100644 --- a/samples/src/main/scala/com/whisk/docker/DockerZookeeperService.scala +++ b/samples/src/main/scala/com/whisk/docker/DockerZookeeperService.scala @@ -1,11 +1,11 @@ -package com.whisk.docker - -trait DockerZookeeperService extends DockerKit { - - val zookeeperContainer = DockerContainer("jplock/zookeeper:3.4.6") - .withPorts(2181 -> None) - .withReadyChecker(DockerReadyChecker.LogLineContains("binding to port")) - - abstract override def dockerContainers: List[DockerContainer] = - zookeeperContainer :: super.dockerContainers -} +//package com.whisk.docker +// +//trait DockerZookeeperService extends DockerKit { +// +// val zookeeperContainer = ContainerSpec("jplock/zookeeper:3.4.6") +// .withPorts(2181 -> None) +// .withReadyChecker(DockerReadyChecker.LogLineContains("binding to port")) +// +// abstract override def dockerContainers: List[ContainerSpec] = +// zookeeperContainer :: super.dockerContainers +//} diff --git a/scalatest/src/main/scala/com/whisk/docker/scalatest/DockerTestKit.scala b/scalatest/src/main/scala/com/whisk/docker/scalatest/DockerTestKit.scala deleted file mode 100644 index aac8267..0000000 --- a/scalatest/src/main/scala/com/whisk/docker/scalatest/DockerTestKit.scala +++ /dev/null @@ -1,29 +0,0 @@ -package com.whisk.docker.scalatest - -import com.whisk.docker.DockerKit -import org.scalatest.concurrent.ScalaFutures -import org.scalatest.time._ -import org.scalatest.{BeforeAndAfterAll, Suite} -import org.slf4j.LoggerFactory - -trait DockerTestKit extends BeforeAndAfterAll with ScalaFutures with DockerKit { self: Suite => - - private lazy val log = LoggerFactory.getLogger(this.getClass) - - def dockerInitPatienceInterval = - PatienceConfig(scaled(Span(20, Seconds)), scaled(Span(10, Millis))) - - def dockerPullImagesPatienceInterval = - PatienceConfig(scaled(Span(1200, Seconds)), scaled(Span(250, Millis))) - - override def beforeAll(): Unit = { - super.beforeAll() - startAllOrFail() - } - - override def afterAll(): Unit = { - stopAllQuietly() - super.afterAll() - - } -} diff --git a/scalatest/src/test/resources/logback-test.xml b/scalatest/src/test/resources/logback-test.xml deleted file mode 100644 index aea0412..0000000 --- a/scalatest/src/test/resources/logback-test.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - \ No newline at end of file diff --git a/scalatest/src/test/scala/com/whisk/docker/AllAtOnceSpec.scala b/scalatest/src/test/scala/com/whisk/docker/AllAtOnceSpec.scala deleted file mode 100644 index 8c0e0b5..0000000 --- a/scalatest/src/test/scala/com/whisk/docker/AllAtOnceSpec.scala +++ /dev/null @@ -1,23 +0,0 @@ -package com.whisk.docker - -import com.whisk.docker.impl.spotify.DockerKitSpotify -import org.scalatest.time.{Second, Seconds, Span} -import org.scalatest.{FlatSpec, Matchers} - -class AllAtOnceSpec - extends FlatSpec - with Matchers - with DockerKitSpotify - with DockerElasticsearchService - with DockerCassandraService - with DockerNeo4jService - with DockerMongodbService - with PingContainerKit { - - implicit val pc = PatienceConfig(Span(20, Seconds), Span(1, Second)) - - "all containers" should "be ready at the same time" in { - dockerContainers.map(_.image).foreach(println) - dockerContainers.forall(c => isContainerReady(c).futureValue) shouldBe true - } -} diff --git a/scalatest/src/test/scala/com/whisk/docker/CassandraServiceSpec.scala b/scalatest/src/test/scala/com/whisk/docker/CassandraServiceSpec.scala deleted file mode 100644 index 24eaf5a..0000000 --- a/scalatest/src/test/scala/com/whisk/docker/CassandraServiceSpec.scala +++ /dev/null @@ -1,25 +0,0 @@ -package com.whisk.docker - -import com.github.dockerjava.core.DefaultDockerClientConfig -import com.github.dockerjava.netty.NettyDockerCmdExecFactory -import com.whisk.docker.impl.dockerjava.{Docker, DockerJavaExecutorFactory} -import com.whisk.docker.scalatest.DockerTestKit -import org.scalatest.time.{Second, Seconds, Span} -import org.scalatest.{FlatSpec, Matchers} - -class CassandraServiceSpec - extends FlatSpec - with Matchers - with DockerCassandraService - with DockerTestKit { - - implicit val pc = PatienceConfig(Span(20, Seconds), Span(1, Second)) - - override implicit val dockerFactory: DockerFactory = new DockerJavaExecutorFactory( - new Docker(DefaultDockerClientConfig.createDefaultConfigBuilder().build(), - factory = new NettyDockerCmdExecFactory())) - - "cassandra node" should "be ready with log line checker" in { - isContainerReady(cassandraContainer).futureValue shouldBe true - } -} diff --git a/scalatest/src/test/scala/com/whisk/docker/DependencyGraphReadyCheckSpec.scala b/scalatest/src/test/scala/com/whisk/docker/DependencyGraphReadyCheckSpec.scala deleted file mode 100644 index 5621c5b..0000000 --- a/scalatest/src/test/scala/com/whisk/docker/DependencyGraphReadyCheckSpec.scala +++ /dev/null @@ -1,65 +0,0 @@ -package com.whisk.docker - -import com.whisk.docker.impl.spotify.DockerKitSpotify -import org.scalatest.{FlatSpec, Matchers} -import org.slf4j.LoggerFactory - -import scala.concurrent.Await -import scala.concurrent.duration._ -import scala.language.postfixOps - -class DependencyGraphReadyCheckSpec extends FlatSpec with Matchers with DockerKitSpotify { - - override val StartContainersTimeout = 45 seconds - - private lazy val log = LoggerFactory.getLogger(this.getClass) - - val zookeeperContainer = - DockerContainer("confluentinc/cp-zookeeper:3.1.2", name = Some("zookeeper")) - .withEnv("ZOOKEEPER_TICK_TIME=2000", "ZOOKEEPER_CLIENT_PORT=2181") - .withReadyChecker(DockerReadyChecker.LogLineContains("binding to port")) - - val kafkaContainer = DockerContainer("confluentinc/cp-kafka:3.1.2", name = Some("kafka")) - .withEnv("KAFKA_BROKER_ID=1", - "KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181", - "KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092") - .withLinks(ContainerLink(zookeeperContainer, "zookeeper")) - .withReadyChecker(DockerReadyChecker.LogLineContains("[Kafka Server 1], started")) - - val schemaRegistryContainer = DockerContainer("confluentinc/cp-schema-registry:3.1.2", - name = Some("schema_registry")) - .withEnv("SCHEMA_REGISTRY_HOST_NAME=schema_registry", - "SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL=zookeeper:2181") - .withLinks(ContainerLink(zookeeperContainer, "zookeeper"), - ContainerLink(kafkaContainer, "kafka")) - .withReadyChecker(DockerReadyChecker.LogLineContains("Server started, listening for requests")) - - override def dockerContainers = - schemaRegistryContainer :: kafkaContainer :: zookeeperContainer :: super.dockerContainers - - "all containers except the leaves of the dependency graph" should "be ready after initialization" in { - startAllOrFail() - - try { - containerManager.isReady(zookeeperContainer).isCompleted shouldBe true - containerManager.isReady(kafkaContainer).isCompleted shouldBe true - containerManager.isReady(schemaRegistryContainer).isCompleted shouldBe false - - Await.ready(containerManager.isReady(schemaRegistryContainer), 45 seconds) - - containerManager.isReady(schemaRegistryContainer).isCompleted shouldBe true - } catch { - case e: RuntimeException => log.error("Test failed during readychecks", e) - } finally { - Await.ready(containerManager.stopRmAll(), StopContainersTimeout) - } - } - - override def startAllOrFail(): Unit = { - Await.result(containerManager.pullImages(), PullImagesTimeout) - containerManager.initReadyAll(StartContainersTimeout).map(_.map(_._2).forall(identity)) - sys.addShutdownHook( - containerManager.stopRmAll() - ) - } -} diff --git a/scalatest/src/test/scala/com/whisk/docker/DockerContainerLinkingSpec.scala b/scalatest/src/test/scala/com/whisk/docker/DockerContainerLinkingSpec.scala deleted file mode 100644 index c96f68d..0000000 --- a/scalatest/src/test/scala/com/whisk/docker/DockerContainerLinkingSpec.scala +++ /dev/null @@ -1,43 +0,0 @@ -package com.whisk.docker - -import org.scalatest._ -import concurrent.ScalaFutures -import time._ - -import impl.dockerjava._ -import impl.spotify._ -import scalatest.DockerTestKit - -abstract class DockerContainerLinkingSpec extends FlatSpec with Matchers with DockerTestKit { - - lazy val cmdExecutor = implicitly[DockerCommandExecutor] - implicit val pc = PatienceConfig(Span(20, Seconds), Span(1, Second)) - - val pingName = "ping" - val pongName = "pong" - val pingAlias = "pang" - - val pingService = DockerContainer("nginx:1.7.11", name = Some(pingName)) - - val pongService = DockerContainer("nginx:1.7.11", name = Some(pongName)) - .withLinks(ContainerLink(pingService, pingAlias)) - - override def dockerContainers = pingService :: pongService :: super.dockerContainers - - "A DockerContainer" should "be linked to the specified containers upon start" in { - val ping = cmdExecutor.inspectContainer(pingName) - val pongPing = cmdExecutor.inspectContainer(s"$pongName/$pingAlias") - - whenReady(ping) { pingState => - whenReady(pongPing) { pongPingState => - pingState should not be empty - pingState shouldBe pongPingState - } - } - } -} - -class SpotifyDockerContainerLinkingSpec extends DockerContainerLinkingSpec with DockerKitSpotify -class DockerJavaDockerContainerLinkingSpec - extends DockerContainerLinkingSpec - with DockerKitDockerJava diff --git a/scalatest/src/test/scala/com/whisk/docker/DockerContainerManagerSpec.scala b/scalatest/src/test/scala/com/whisk/docker/DockerContainerManagerSpec.scala deleted file mode 100644 index 7c255a6..0000000 --- a/scalatest/src/test/scala/com/whisk/docker/DockerContainerManagerSpec.scala +++ /dev/null @@ -1,113 +0,0 @@ -package com.whisk.docker - -import com.whisk.docker.DockerContainerManager._ -import com.whisk.docker.impl.dockerjava._ -import org.scalatest._ - -class DockerContainerManagerSpec extends WordSpecLike with Matchers { - - "The DockerContainerManager" should { - "a list of containers with dependencies" should { - val linkedContainer1 = DockerContainer("nginx:1.7.11", name = Some("linkedContainer1")) - val linkedContainer2a = DockerContainer("nginx:1.7.11", name = Some("linkedContainer2a")) - .withLinks(ContainerLink(linkedContainer1, "linkedContainer1")) - val linkedContainer2b = DockerContainer("nginx:1.7.11", name = Some("linkedContainer2b")) - .withLinks(ContainerLink(linkedContainer1, "linkedContainer1")) - val linkedContainer3 = DockerContainer("nginx:1.7.11", name = Some("linkedContainer3")) - .withLinks(ContainerLink(linkedContainer2a, "linkedContainer2a")) - val linkedContainer4 = DockerContainer("nginx:1.7.11", name = Some("linkedContainer4")) - .withLinks(ContainerLink(linkedContainer3, "linkedContainer4")) - val linkedContainer5 = DockerContainer("nginx:1.7.11", name = Some("linkedContainer5")) - val linkedContainers = List(linkedContainer1, - linkedContainer2a, - linkedContainer2b, - linkedContainer3, - linkedContainer4, - linkedContainer5) - - "build a dependency graph from a list of containers with dependencies" in { - buildDependencyGraph(linkedContainers) shouldBe ContainerDependencyGraph( - containers = Seq(linkedContainer1, linkedContainer5), - dependants = Some( - ContainerDependencyGraph( - containers = Seq(linkedContainer2a, linkedContainer2b), - dependants = Some( - ContainerDependencyGraph( - containers = Seq(linkedContainer3), - dependants = Some(ContainerDependencyGraph( - containers = Seq(linkedContainer4) - )) - )) - )) - ) - } - - "build the dependency graph from an empty list of containers" in { - buildDependencyGraph(Seq.empty) shouldBe ContainerDependencyGraph( - containers = Seq.empty, - dependants = None - ) - } - - "initialize all containers taking into account their dependencies" in { - val dockerKit = new DockerKit with DockerKitDockerJava { - override def dockerContainers = linkedContainers ++ super.dockerContainers - } - dockerKit.startAllOrFail() - dockerKit.stopAllQuietly() - } - } - - "a list of containers with links" should { - - val unlinkedContainer1 = DockerContainer("nginx:1.7.11", name = Some("unlinkedContainer1")) - val unlinkedContainer2a = DockerContainer("nginx:1.7.11", name = Some("unlinkedContainer2a")) - .withUnlinkedDependencies(unlinkedContainer1) - val unlinkedContainer2b = DockerContainer("nginx:1.7.11", name = Some("unlinkedContainer2b")) - .withUnlinkedDependencies(unlinkedContainer1) - val unlinkedContainer3 = DockerContainer("nginx:1.7.11", name = Some("unlinkedContainer3")) - .withUnlinkedDependencies(unlinkedContainer2a) - val unlinkedContainer4 = DockerContainer("nginx:1.7.11", name = Some("unlinkedContainer4")) - .withUnlinkedDependencies(unlinkedContainer3) - val unlinkedContainer5 = DockerContainer("nginx:1.7.11", name = Some("unlinkedContainer5")) - val unlinkedContainers = List(unlinkedContainer1, - unlinkedContainer2a, - unlinkedContainer2b, - unlinkedContainer3, - unlinkedContainer4, - unlinkedContainer5) - - "build a dependency graph from a list of containers" in { - buildDependencyGraph(unlinkedContainers) shouldBe ContainerDependencyGraph( - containers = Seq(unlinkedContainer1, unlinkedContainer5), - dependants = Some( - ContainerDependencyGraph( - containers = Seq(unlinkedContainer2a, unlinkedContainer2b), - dependants = Some( - ContainerDependencyGraph( - containers = Seq(unlinkedContainer3), - dependants = Some(ContainerDependencyGraph( - containers = Seq(unlinkedContainer4) - )) - )) - )) - ) - } - - "build the dependency graph from an empty list of containers" in { - buildDependencyGraph(Seq.empty) shouldBe ContainerDependencyGraph( - containers = Seq.empty, - dependants = None - ) - } - - "initialize all containers taking into account their dependencies" in { - val dockerKit = new DockerKit with DockerKitDockerJava { - override def dockerContainers = unlinkedContainers ++ super.dockerContainers - } - dockerKit.startAllOrFail() - dockerKit.stopAllQuietly() - } - } - } -} diff --git a/scalatest/src/test/scala/com/whisk/docker/ElasticsearchServiceSpec.scala b/scalatest/src/test/scala/com/whisk/docker/ElasticsearchServiceSpec.scala deleted file mode 100644 index 630b557..0000000 --- a/scalatest/src/test/scala/com/whisk/docker/ElasticsearchServiceSpec.scala +++ /dev/null @@ -1,23 +0,0 @@ -package com.whisk.docker - -import com.whisk.docker.impl.dockerjava.DockerKitDockerJava -import com.whisk.docker.scalatest.DockerTestKit -import org.scalatest._ -import org.scalatest.time._ - -class ElasticsearchServiceSpec - extends FlatSpec - with Matchers - with DockerElasticsearchService - with DockerTestKit - with DockerKitDockerJava { - - implicit val pc = PatienceConfig(Span(20, Seconds), Span(1, Second)) - - "elasticsearch container" should "be ready" in { - isContainerReady(elasticsearchContainer).futureValue shouldBe true - elasticsearchContainer.getPorts().futureValue.get(9300) should not be empty - elasticsearchContainer.getIpAddresses().futureValue should not be (Seq.empty) - } - -} diff --git a/scalatest/src/test/scala/com/whisk/docker/KafkaServiceSpec.scala b/scalatest/src/test/scala/com/whisk/docker/KafkaServiceSpec.scala deleted file mode 100644 index 13f39d0..0000000 --- a/scalatest/src/test/scala/com/whisk/docker/KafkaServiceSpec.scala +++ /dev/null @@ -1,21 +0,0 @@ -package com.whisk.docker - -import com.whisk.docker.impl.dockerjava.DockerKitDockerJava -import com.whisk.docker.scalatest.DockerTestKit -import org.scalatest._ -import org.scalatest.time._ - -class KafkaServiceSpec - extends FlatSpec - with Matchers - with DockerKafkaService - with DockerTestKit - with DockerKitDockerJava { - - implicit val pc = PatienceConfig(Span(20, Seconds), Span(1, Second)) - - "kafka container" should "be ready" in { - isContainerReady(kafkaContainer).futureValue shouldBe true - } - -} diff --git a/scalatest/src/test/scala/com/whisk/docker/MongodbServiceSpec.scala b/scalatest/src/test/scala/com/whisk/docker/MongodbServiceSpec.scala deleted file mode 100644 index a04102c..0000000 --- a/scalatest/src/test/scala/com/whisk/docker/MongodbServiceSpec.scala +++ /dev/null @@ -1,22 +0,0 @@ -package com.whisk.docker - -import com.whisk.docker.impl.spotify.DockerKitSpotify -import com.whisk.docker.scalatest.DockerTestKit -import org.scalatest.time.{Second, Seconds, Span} -import org.scalatest.{FlatSpec, Matchers} - -class MongodbServiceSpec - extends FlatSpec - with Matchers - with DockerTestKit - with DockerKitSpotify - with DockerMongodbService { - - implicit val pc = PatienceConfig(Span(20, Seconds), Span(1, Second)) - - "mongodb node" should "be ready with log line checker" in { - isContainerReady(mongodbContainer).futureValue shouldBe true - mongodbContainer.getPorts().futureValue.get(27017) should not be empty - mongodbContainer.getIpAddresses().futureValue should not be Seq.empty - } -} diff --git a/scalatest/src/test/scala/com/whisk/docker/Neo4jServiceSpec.scala b/scalatest/src/test/scala/com/whisk/docker/Neo4jServiceSpec.scala deleted file mode 100644 index c982546..0000000 --- a/scalatest/src/test/scala/com/whisk/docker/Neo4jServiceSpec.scala +++ /dev/null @@ -1,21 +0,0 @@ -package com.whisk.docker - -import com.spotify.docker.client.DefaultDockerClient -import com.whisk.docker.impl.spotify.SpotifyDockerFactory -import com.whisk.docker.scalatest.DockerTestKit -import org.scalatest._ -import org.scalatest.concurrent.ScalaFutures -import org.scalatest.time._ - -class Neo4jServiceSpec extends FlatSpec with Matchers with DockerTestKit with DockerNeo4jService { - - implicit val pc = PatienceConfig(Span(20, Seconds), Span(1, Second)) - - override implicit val dockerFactory: DockerFactory = new SpotifyDockerFactory( - DefaultDockerClient.fromEnv().build()) - - "neo4j container" should "be ready" in { - isContainerReady(neo4jContainer).futureValue shouldBe true - } - -} diff --git a/scalatest/src/test/scala/com/whisk/docker/PingContainerKit.scala b/scalatest/src/test/scala/com/whisk/docker/PingContainerKit.scala deleted file mode 100644 index 6fd3742..0000000 --- a/scalatest/src/test/scala/com/whisk/docker/PingContainerKit.scala +++ /dev/null @@ -1,16 +0,0 @@ -package com.whisk.docker - -import com.whisk.docker.scalatest.DockerTestKit -import org.scalatest.Suite - -trait PingContainerKit extends DockerTestKit { self: Suite => - - val pingContainer = DockerContainer("nginx:1.7.11") - - val pongContainer = DockerContainer("nginx:1.7.11") - .withPorts(80 -> None) - .withReadyChecker( - DockerReadyChecker.HttpResponseCode(port = 80, path = "/", host = None, code = 200)) - - abstract override def dockerContainers = pingContainer :: pongContainer :: super.dockerContainers -} diff --git a/scalatest/src/test/scala/com/whisk/docker/PostgresServiceSpec.scala b/scalatest/src/test/scala/com/whisk/docker/PostgresServiceSpec.scala deleted file mode 100644 index 6140733..0000000 --- a/scalatest/src/test/scala/com/whisk/docker/PostgresServiceSpec.scala +++ /dev/null @@ -1,23 +0,0 @@ -package com.whisk.docker - -import com.spotify.docker.client.DefaultDockerClient -import com.whisk.docker.impl.spotify.SpotifyDockerFactory -import com.whisk.docker.scalatest.DockerTestKit -import org.scalatest.time.{Second, Seconds, Span} -import org.scalatest.{FlatSpec, Matchers} - -class PostgresServiceSpec - extends FlatSpec - with Matchers - with DockerTestKit - with DockerPostgresService { - - implicit val pc = PatienceConfig(Span(20, Seconds), Span(1, Second)) - - override implicit val dockerFactory: DockerFactory = new SpotifyDockerFactory( - DefaultDockerClient.fromEnv().build()) - - "postgres node" should "be ready with log line checker" in { - isContainerReady(postgresContainer).futureValue shouldBe true - } -} diff --git a/scalatest/src/test/scala/com/whisk/docker/ZookeeperServiceSpec.scala b/scalatest/src/test/scala/com/whisk/docker/ZookeeperServiceSpec.scala deleted file mode 100644 index b517f56..0000000 --- a/scalatest/src/test/scala/com/whisk/docker/ZookeeperServiceSpec.scala +++ /dev/null @@ -1,21 +0,0 @@ -package com.whisk.docker - -import com.whisk.docker.impl.dockerjava.DockerKitDockerJava -import com.whisk.docker.scalatest.DockerTestKit -import org.scalatest._ -import org.scalatest.time._ - -class ZookeeperServiceSpec - extends FlatSpec - with Matchers - with DockerZookeeperService - with DockerTestKit - with DockerKitDockerJava { - - implicit val pc = PatienceConfig(Span(20, Seconds), Span(1, Second)) - - "zookeeper container" should "be ready" in { - isContainerReady(zookeeperContainer).futureValue shouldBe true - } - -} diff --git a/specs2/src/main/scala/com/whisk/docker/specs2/BeforeAfterAllStopOnError.scala b/specs2/src/main/scala/com/whisk/docker/specs2/BeforeAfterAllStopOnError.scala deleted file mode 100644 index 135375d..0000000 --- a/specs2/src/main/scala/com/whisk/docker/specs2/BeforeAfterAllStopOnError.scala +++ /dev/null @@ -1,18 +0,0 @@ -package com.whisk.docker.specs2 - -import org.specs2.specification.core.{Fragments, SpecificationStructure} -import org.specs2.specification.create.FragmentsFactory - -trait BeforeAfterAllStopOnError extends SpecificationStructure with FragmentsFactory { - - def beforeAll() - def afterAll() - - override def map(fs: => Fragments) = - super - .map(fs) - .prepend( - fragmentFactory.step(beforeAll()).stopOnError - ) - .append(fragmentFactory.step(afterAll())) -} diff --git a/specs2/src/main/scala/com/whisk/docker/specs2/DockerTestKit.scala b/specs2/src/main/scala/com/whisk/docker/specs2/DockerTestKit.scala deleted file mode 100644 index 0a88859..0000000 --- a/specs2/src/main/scala/com/whisk/docker/specs2/DockerTestKit.scala +++ /dev/null @@ -1,16 +0,0 @@ -package com.whisk.docker.specs2 - -import com.whisk.docker.DockerKit -import org.slf4j.LoggerFactory - -trait DockerTestKit extends BeforeAfterAllStopOnError with DockerKit { - private lazy val log = LoggerFactory.getLogger(this.getClass) - - def beforeAll() = { - startAllOrFail() - } - - def afterAll() = { - stopAllQuietly() - } -} diff --git a/specs2/src/test/scala/com/whisk/docker/AllAtOnceSpec.scala b/specs2/src/test/scala/com/whisk/docker/AllAtOnceSpec.scala deleted file mode 100644 index c77de90..0000000 --- a/specs2/src/test/scala/com/whisk/docker/AllAtOnceSpec.scala +++ /dev/null @@ -1,27 +0,0 @@ -package com.whisk.docker - -import org.specs2._ -import org.specs2.specification.core.Env -import scala.concurrent._ -import scala.concurrent.duration._ - -class AllAtOnceSpec(env: Env) - extends Specification - with DockerTestKitDockerJava - with DockerElasticsearchService - with DockerCassandraService - with DockerNeo4jService - with DockerMongodbService - with PingContainerKit { - - implicit val ee = env.executionEnv - implicit val ec = env.executionContext - - def is = s2""" - The all containers should be ready at the same time $x1 - """ - def x1 = { - dockerContainers.map(_.image).foreach(println) - Future.sequence(dockerContainers.map(isContainerReady)) must contain(beTrue).forall.await - } -} diff --git a/specs2/src/test/scala/com/whisk/docker/CassandraServiceSpec.scala b/specs2/src/test/scala/com/whisk/docker/CassandraServiceSpec.scala deleted file mode 100644 index a141356..0000000 --- a/specs2/src/test/scala/com/whisk/docker/CassandraServiceSpec.scala +++ /dev/null @@ -1,21 +0,0 @@ -package com.whisk.docker - -import com.whisk.docker.specs2.DockerTestKit -import org.specs2._ -import org.specs2.specification.core.Env - -class CassandraServiceSpec(env: Env) - extends Specification - with DockerTestKitDockerJava - with DockerCassandraService - with DockerTestKit { - - implicit val ee = env.executionEnv - - def is = - s2""" - The cassandra node should be ready with log line checker $x1 - """ - - def x1 = isContainerReady(cassandraContainer) must beTrue.await -} diff --git a/specs2/src/test/scala/com/whisk/docker/DockerTestKitDockerJava.scala b/specs2/src/test/scala/com/whisk/docker/DockerTestKitDockerJava.scala deleted file mode 100644 index dfe28ca..0000000 --- a/specs2/src/test/scala/com/whisk/docker/DockerTestKitDockerJava.scala +++ /dev/null @@ -1,6 +0,0 @@ -package com.whisk.docker - -import com.whisk.docker.impl.dockerjava.DockerKitDockerJava -import com.whisk.docker.specs2.DockerTestKit - -trait DockerTestKitDockerJava extends DockerTestKit with DockerKitDockerJava {} diff --git a/specs2/src/test/scala/com/whisk/docker/ElasticsearchServiceSpec.scala b/specs2/src/test/scala/com/whisk/docker/ElasticsearchServiceSpec.scala deleted file mode 100644 index cd2ab93..0000000 --- a/specs2/src/test/scala/com/whisk/docker/ElasticsearchServiceSpec.scala +++ /dev/null @@ -1,22 +0,0 @@ -package com.whisk.docker - -import com.whisk.docker.specs2.DockerTestKit -import org.specs2._ -import org.specs2.specification.core.Env -import scala.concurrent._ - -class ElasticsearchServiceSpec(env: Env) - extends Specification - with DockerTestKitDockerJava - with DockerElasticsearchService - with DockerTestKit { - - implicit val ee = env.executionEnv - - def is = - s2""" - The elasticsearch container should be ready $x1 - """ - - def x1 = isContainerReady(elasticsearchContainer) must beTrue.await -} diff --git a/specs2/src/test/scala/com/whisk/docker/KafkaServiceSpec.scala b/specs2/src/test/scala/com/whisk/docker/KafkaServiceSpec.scala deleted file mode 100644 index 508ceb1..0000000 --- a/specs2/src/test/scala/com/whisk/docker/KafkaServiceSpec.scala +++ /dev/null @@ -1,21 +0,0 @@ -package com.whisk.docker - -import com.whisk.docker.specs2.DockerTestKit -import org.specs2._ -import org.specs2.specification.core.Env - -class KafkaServiceSpec(env: Env) - extends Specification - with DockerTestKitDockerJava - with DockerKafkaService - with DockerTestKit { - - implicit val ee = env.executionEnv - - def is = - s2""" - The Kafka container should be ready $x1 - """ - - def x1 = isContainerReady(kafkaContainer) must beTrue.await -} diff --git a/specs2/src/test/scala/com/whisk/docker/MongodbServiceSpec.scala b/specs2/src/test/scala/com/whisk/docker/MongodbServiceSpec.scala deleted file mode 100644 index 71e5433..0000000 --- a/specs2/src/test/scala/com/whisk/docker/MongodbServiceSpec.scala +++ /dev/null @@ -1,21 +0,0 @@ -package com.whisk.docker - -import com.whisk.docker.specs2.DockerTestKit -import org.specs2._ -import org.specs2.specification.core.Env -import scala.concurrent._ - -class MongodbServiceSpec(env: Env) - extends Specification - with DockerTestKitDockerJava - with DockerTestKit - with DockerMongodbService { - - implicit val ee = env.executionEnv - - def is = s2""" - The mongodb container should be ready $x1 - """ - - def x1 = isContainerReady(mongodbContainer) must beTrue.await -} diff --git a/specs2/src/test/scala/com/whisk/docker/Neo4jServiceSpec.scala b/specs2/src/test/scala/com/whisk/docker/Neo4jServiceSpec.scala deleted file mode 100644 index fce3831..0000000 --- a/specs2/src/test/scala/com/whisk/docker/Neo4jServiceSpec.scala +++ /dev/null @@ -1,24 +0,0 @@ -package com.whisk.docker - -import com.whisk.docker.specs2.DockerTestKit -import org.specs2._ -import org.specs2.specification.core.Env -import scala.concurrent._ -import scala.concurrent.duration._ - -class Neo4jServiceSpec(env: Env) - extends Specification - with DockerTestKitDockerJava - with DockerTestKit - with DockerNeo4jService { - - implicit val ee = env.executionEnv - implicit val ec = env.executionContext - - def is = s2""" - The neo4j container should - be ready $x1 - """ - - def x1 = isContainerReady(neo4jContainer) must beTrue.await -} diff --git a/specs2/src/test/scala/com/whisk/docker/PingContainerKit.scala b/specs2/src/test/scala/com/whisk/docker/PingContainerKit.scala deleted file mode 100644 index a8dfcb1..0000000 --- a/specs2/src/test/scala/com/whisk/docker/PingContainerKit.scala +++ /dev/null @@ -1,15 +0,0 @@ -package com.whisk.docker - -import com.whisk.docker.specs2.DockerTestKit - -trait PingContainerKit extends DockerTestKit { - - val pingContainer = DockerContainer("nginx:1.7.11") - - val pongContainer = DockerContainer("nginx:1.7.11") - .withPorts(80 -> None) - .withReadyChecker( - DockerReadyChecker.HttpResponseCode(port = 80, path = "/", host = None, code = 200)) - - abstract override def dockerContainers = pingContainer :: pongContainer :: super.dockerContainers -} diff --git a/specs2/src/test/scala/com/whisk/docker/PostgresServiceSpec.scala b/specs2/src/test/scala/com/whisk/docker/PostgresServiceSpec.scala deleted file mode 100644 index b80d3d7..0000000 --- a/specs2/src/test/scala/com/whisk/docker/PostgresServiceSpec.scala +++ /dev/null @@ -1,18 +0,0 @@ -package com.whisk.docker - -import org.specs2._ -import org.specs2.specification.core.Env - -class PostgresServiceSpec(env: Env) - extends Specification - with DockerTestKitDockerJava - with DockerPostgresService { - - implicit val ee = env.executionEnv - - def is = s2""" - The Postgres node should be ready with log line checker $x1 - """ - - def x1 = isContainerReady(postgresContainer) must beTrue.await -} diff --git a/specs2/src/test/scala/com/whisk/docker/ZookeeperServiceSpec.scala b/specs2/src/test/scala/com/whisk/docker/ZookeeperServiceSpec.scala deleted file mode 100644 index 343c346..0000000 --- a/specs2/src/test/scala/com/whisk/docker/ZookeeperServiceSpec.scala +++ /dev/null @@ -1,18 +0,0 @@ -package com.whisk.docker - -import org.specs2._ -import org.specs2.specification.core.Env - -class ZookeeperServiceSpec(env: Env) - extends Specification - with DockerTestKitDockerJava - with DockerZookeeperService { - - implicit val ee = env.executionEnv - - def is = s2""" - The Zookeeper container should be ready $x1 - """ - - def x1 = isContainerReady(zookeeperContainer) must beTrue.await -} diff --git a/tests/src/test/scala/com/whisk/docker/test/ElasticsearchServiceTest.scala b/tests/src/test/scala/com/whisk/docker/test/ElasticsearchServiceTest.scala new file mode 100644 index 0000000..d7218df --- /dev/null +++ b/tests/src/test/scala/com/whisk/docker/test/ElasticsearchServiceTest.scala @@ -0,0 +1,11 @@ +package com.whisk.docker.test + +import com.whisk.docker.{ContainerState, DockerElasticsearchService} +import org.scalatest.FunSuite + +class ElasticsearchServiceTest extends FunSuite with DockerElasticsearchService { + + test("test container started") { + elasticsearchContainer.state().isInstanceOf[ContainerState.Ready] + } +} From 43635b1f3c42179e1d583cd963923ed95a9dad94 Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Sun, 10 Sep 2017 15:47:40 +0100 Subject: [PATCH 02/36] handing non-existing images --- .travis.yml | 5 ++--- .../whisk/docker/ContainerCommandExecutor.scala | 10 +++++----- .../com/whisk/docker/DockerContainerManager.scala | 14 +++++++++++++- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 83d8ca6..95008a6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ services: - docker before_install: - - docker pull mongo:3.0.6 + - docker pull mongo:3.4.8 - docker pull elasticsearch:1.7.1 language: scala @@ -16,8 +16,7 @@ jdk: - oraclejdk8 script: - - sbt "scalatest/testOnly com.whisk.docker.MongodbServiceSpec" #spotify executor - - sbt "scalatest/testOnly com.whisk.docker.ElasticsearchServiceSpec" #docker-java executor + - sbt tests/test cache: directories: diff --git a/core/src/main/scala/com/whisk/docker/ContainerCommandExecutor.scala b/core/src/main/scala/com/whisk/docker/ContainerCommandExecutor.scala index b375ffa..ca870dd 100644 --- a/core/src/main/scala/com/whisk/docker/ContainerCommandExecutor.scala +++ b/core/src/main/scala/com/whisk/docker/ContainerCommandExecutor.scala @@ -6,6 +6,7 @@ import java.util.concurrent.TimeUnit import com.google.common.io.Closeables import com.spotify.docker.client.DockerClient.{AttachParameter, RemoveContainerParam} +import com.spotify.docker.client.exceptions.ImageNotFoundException import com.spotify.docker.client.messages._ import com.spotify.docker.client.{DockerClient, LogMessage, LogStream} @@ -74,15 +75,15 @@ class ContainerCommandExecutor(val client: DockerClient) { } private def logStreamFuture(id: String, withErr: Boolean)( - implicit docker: ContainerCommandExecutor, + implicit ec: ExecutionContext): Future[LogStream] = { val baseParams = List(AttachParameter.STDOUT, AttachParameter.STREAM, AttachParameter.LOGS) val logParams = if (withErr) AttachParameter.STDERR :: baseParams else baseParams Future(scala.concurrent.blocking(client.attachContainer(id, logParams: _*))) } - def withLogStreamLines(id: String, withErr: Boolean)( - f: String => Unit)(implicit docker: ContainerCommandExecutor, ec: ExecutionContext): Unit = { + def withLogStreamLines(id: String, withErr: Boolean)(f: String => Unit)( + implicit ec: ExecutionContext): Unit = { logStreamFuture(id, withErr).foreach { stream => stream.forEachRemaining((t: LogMessage) => { @@ -93,8 +94,7 @@ class ContainerCommandExecutor(val client: DockerClient) { } def withLogStreamLinesRequirement(id: String, withErr: Boolean)(f: (String) => Boolean)( - implicit docker: ContainerCommandExecutor, - ec: ExecutionContext): Future[Unit] = { + implicit ec: ExecutionContext): Future[Unit] = { logStreamFuture(id, withErr).flatMap { stream => val p = Promise[Unit]() diff --git a/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala b/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala index a5ca69e..0948236 100644 --- a/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala +++ b/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala @@ -3,6 +3,7 @@ package com.whisk.docker import java.util.concurrent.{ConcurrentHashMap, TimeUnit} import com.google.common.collect.ImmutableList +import com.spotify.docker.client.exceptions.ImageNotFoundException import com.spotify.docker.client.messages.ContainerCreation import org.slf4j.LoggerFactory @@ -44,18 +45,29 @@ class DockerContainerManager(managedContainers: ManagedContainers, } } - def printWarningsIfExist(creation: ContainerCreation): Unit = { + private def printWarningsIfExist(creation: ContainerCreation): Unit = { Option(creation.warnings()) .getOrElse(ImmutableList.of[String]()) .forEach(w => log.warn(s"creating container: $w")) } + private def ensureImage(image: String): Future[Unit] = { + Future(scala.concurrent.blocking(executor.client.inspectImage(image))) + .map(_ => ()) + .recoverWith { + case x: ImageNotFoundException => + log.info(s"image [$image] not found. pulling...") + Future(scala.concurrent.blocking(executor.client.pull(image))) + } + } + //TODO log listeners def startContainer(container: Container): Future[Unit] = { val image = container.spec.image val startTime = System.nanoTime() log.debug("Starting container: {}", image) for { + _ <- ensureImage(image) creation <- executor.createContainer(container.spec) id = creation.id() _ = registeredContainers.put(id, image) From 8a151a16013b05382a4df2484e726b502bf2a69b Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Sun, 10 Sep 2017 15:59:46 +0100 Subject: [PATCH 03/36] mongo container test --- .../whisk/docker/DockerContainerManager.scala | 4 ++- .../com/whisk/docker/DockerTestTimeouts.scala | 1 + .../whisk/docker/DockerMongodbService.scala | 30 ++++++++++--------- .../test/ElasticsearchServiceTest.scala | 4 ++- .../docker/test/MongodbServiceTest.scala | 12 ++++++++ 5 files changed, 35 insertions(+), 16 deletions(-) create mode 100644 tests/src/test/scala/com/whisk/docker/test/MongodbServiceTest.scala diff --git a/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala b/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala index 0948236..f5071e8 100644 --- a/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala +++ b/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala @@ -67,7 +67,6 @@ class DockerContainerManager(managedContainers: ManagedContainers, val startTime = System.nanoTime() log.debug("Starting container: {}", image) for { - _ <- ensureImage(image) creation <- executor.createContainer(container.spec) id = creation.id() _ = registeredContainers.put(id, image) @@ -100,6 +99,9 @@ class DockerContainerManager(managedContainers: ManagedContainers, case _ => throw new Exception("unsupported type of managed containers") } + val imagesF = Future.traverse(containers.map(_.spec.image))(ensureImage) + Await.result(imagesF, dockerTestTimeouts.pull) + val startedContainersF = Future.traverse(containers)(startContainer) sys.addShutdownHook( diff --git a/core/src/main/scala/com/whisk/docker/DockerTestTimeouts.scala b/core/src/main/scala/com/whisk/docker/DockerTestTimeouts.scala index 4fae801..7222527 100644 --- a/core/src/main/scala/com/whisk/docker/DockerTestTimeouts.scala +++ b/core/src/main/scala/com/whisk/docker/DockerTestTimeouts.scala @@ -3,6 +3,7 @@ package com.whisk.docker import scala.concurrent.duration._ case class DockerTestTimeouts( + pull: FiniteDuration = 5.minutes, init: FiniteDuration = 60.seconds, stop: FiniteDuration = 10.seconds ) diff --git a/samples/src/main/scala/com/whisk/docker/DockerMongodbService.scala b/samples/src/main/scala/com/whisk/docker/DockerMongodbService.scala index 3986acc..f75189c 100644 --- a/samples/src/main/scala/com/whisk/docker/DockerMongodbService.scala +++ b/samples/src/main/scala/com/whisk/docker/DockerMongodbService.scala @@ -1,14 +1,16 @@ -//package com.whisk.docker -// -//trait DockerMongodbService extends DockerKit { -// -// val DefaultMongodbPort = 27017 -// -// val mongodbContainer = ContainerSpec("mongo:3.0.6") -// .withPorts(DefaultMongodbPort -> None) -// .withReadyChecker(DockerReadyChecker.LogLineContains("waiting for connections on port")) -// .withCommand("mongod", "--nojournal", "--smallfiles", "--syncdelay", "0") -// -// abstract override def dockerContainers: List[ContainerSpec] = -// mongodbContainer :: super.dockerContainers -//} +package com.whisk.docker + +import org.scalatest.Suite + +trait DockerMongodbService extends DockerTestKitForAll { + self: Suite => + + val DefaultMongodbPort = 27017 + + val mongodbContainer = ContainerSpec("mongo:3.4.8") + .withExposedPorts(DefaultMongodbPort) + .withReadyChecker(DockerReadyChecker.LogLineContains("waiting for connections on port")) + .toContainer + + override val managedContainers = mongodbContainer.toManagedContainer +} diff --git a/tests/src/test/scala/com/whisk/docker/test/ElasticsearchServiceTest.scala b/tests/src/test/scala/com/whisk/docker/test/ElasticsearchServiceTest.scala index d7218df..0efb60f 100644 --- a/tests/src/test/scala/com/whisk/docker/test/ElasticsearchServiceTest.scala +++ b/tests/src/test/scala/com/whisk/docker/test/ElasticsearchServiceTest.scala @@ -6,6 +6,8 @@ import org.scalatest.FunSuite class ElasticsearchServiceTest extends FunSuite with DockerElasticsearchService { test("test container started") { - elasticsearchContainer.state().isInstanceOf[ContainerState.Ready] + assert(elasticsearchContainer.state().isInstanceOf[ContainerState.Ready], + "elasticsearch container is ready") + assert(elasticsearchContainer.mappedPorts().get(9200).nonEmpty, "elasticsearch port is exposed") } } diff --git a/tests/src/test/scala/com/whisk/docker/test/MongodbServiceTest.scala b/tests/src/test/scala/com/whisk/docker/test/MongodbServiceTest.scala new file mode 100644 index 0000000..22c4a43 --- /dev/null +++ b/tests/src/test/scala/com/whisk/docker/test/MongodbServiceTest.scala @@ -0,0 +1,12 @@ +package com.whisk.docker.test + +import com.whisk.docker.{ContainerState, DockerMongodbService} +import org.scalatest.FunSuite + +class MongodbServiceTest extends FunSuite with DockerMongodbService { + + test("test container started") { + assert(mongodbContainer.state().isInstanceOf[ContainerState.Ready], "mongodb is ready") + assert(mongodbContainer.mappedPorts().get(27017).nonEmpty, "port 2017 is exposed") + } +} From f3828567cd41716cea0b12478989e96baf14c342 Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Sun, 10 Sep 2017 16:06:30 +0100 Subject: [PATCH 04/36] multi-container test --- .../whisk/docker/DockerContainerManager.scala | 5 ++++ .../docker/DockerElasticsearchService.scala | 4 +-- .../whisk/docker/DockerMongodbService.scala | 2 +- .../docker/test/MultiContainerTest.scala | 26 +++++++++++++++++++ 4 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 tests/src/test/scala/com/whisk/docker/test/MultiContainerTest.scala diff --git a/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala b/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala index f5071e8..aec6bd5 100644 --- a/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala +++ b/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala @@ -19,6 +19,11 @@ case class ContainerGroup(containers: Seq[Container]) extends ManagedContainers require(containers.nonEmpty, "container group should be non-empty") } +object ContainerGroup { + + def of(containers: Container*): ContainerGroup = ContainerGroup(containers) +} + class DockerContainerManager(managedContainers: ManagedContainers, executor: ContainerCommandExecutor, dockerTestTimeouts: DockerTestTimeouts, diff --git a/samples/src/main/scala/com/whisk/docker/DockerElasticsearchService.scala b/samples/src/main/scala/com/whisk/docker/DockerElasticsearchService.scala index db60cd1..46a0800 100644 --- a/samples/src/main/scala/com/whisk/docker/DockerElasticsearchService.scala +++ b/samples/src/main/scala/com/whisk/docker/DockerElasticsearchService.scala @@ -10,7 +10,7 @@ trait DockerElasticsearchService extends DockerTestKitForAll { val DefaultElasticsearchHttpPort = 9200 val DefaultElasticsearchClientPort = 9300 - val elasticsearchContainer = ContainerSpec("elasticsearch:1.7.1") + val elasticsearchContainer: Container = ContainerSpec("elasticsearch:1.7.1") .withExposedPorts(DefaultElasticsearchHttpPort, DefaultElasticsearchClientPort) .withReadyChecker( DockerReadyChecker @@ -20,5 +20,5 @@ trait DockerElasticsearchService extends DockerTestKitForAll { ) .toContainer - override val managedContainers = elasticsearchContainer.toManagedContainer + override val managedContainers: ManagedContainers = elasticsearchContainer.toManagedContainer } diff --git a/samples/src/main/scala/com/whisk/docker/DockerMongodbService.scala b/samples/src/main/scala/com/whisk/docker/DockerMongodbService.scala index f75189c..89f5583 100644 --- a/samples/src/main/scala/com/whisk/docker/DockerMongodbService.scala +++ b/samples/src/main/scala/com/whisk/docker/DockerMongodbService.scala @@ -12,5 +12,5 @@ trait DockerMongodbService extends DockerTestKitForAll { .withReadyChecker(DockerReadyChecker.LogLineContains("waiting for connections on port")) .toContainer - override val managedContainers = mongodbContainer.toManagedContainer + override val managedContainers: ManagedContainers = mongodbContainer.toManagedContainer } diff --git a/tests/src/test/scala/com/whisk/docker/test/MultiContainerTest.scala b/tests/src/test/scala/com/whisk/docker/test/MultiContainerTest.scala new file mode 100644 index 0000000..7980aed --- /dev/null +++ b/tests/src/test/scala/com/whisk/docker/test/MultiContainerTest.scala @@ -0,0 +1,26 @@ +package com.whisk.docker.test + +import com.whisk.docker.{ + ContainerGroup, + ContainerState, + DockerElasticsearchService, + DockerMongodbService +} +import org.scalatest.FunSuite + +class MultiContainerTest + extends FunSuite + with DockerElasticsearchService + with DockerMongodbService { + + override val managedContainers = ContainerGroup.of(elasticsearchContainer, mongodbContainer) + + test("both containers should be ready") { + assert(elasticsearchContainer.state().isInstanceOf[ContainerState.Ready], + "elasticsearch container is ready") + assert(elasticsearchContainer.mappedPorts().get(9200).nonEmpty, "elasticsearch port is exposed") + + assert(mongodbContainer.state().isInstanceOf[ContainerState.Ready], "mongodb is ready") + assert(mongodbContainer.mappedPorts().get(27017).nonEmpty, "port 2017 is exposed") + } +} From 52adc7d1e3f2ba03897296ada4beae4b57e78e2c Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Sun, 10 Sep 2017 17:22:10 +0100 Subject: [PATCH 05/36] reword ready checkers. add mysql --- .travis.yml | 1 + build.sbt | 11 +- .../whisk/docker/DockerContainerManager.scala | 6 - .../com/whisk/docker/DockerReadyChecker.scala | 117 +++++++++++------- .../com/whisk/docker/DockerKafkaService.scala | 15 --- .../com/whisk/docker/DockerMysqlService.scala | 29 +++++ .../com/whisk/docker/DockerNeo4jService.scala | 21 ---- .../whisk/docker/DockerPostgresService.scala | 71 +++++------ .../whisk/docker/DockerZookeeperService.scala | 11 -- .../whisk/docker/test/MysqlServiceTest.scala | 12 ++ .../docker/test/PostgresServiceTest.scala | 14 +++ 11 files changed, 166 insertions(+), 142 deletions(-) delete mode 100644 samples/src/main/scala/com/whisk/docker/DockerKafkaService.scala create mode 100644 samples/src/main/scala/com/whisk/docker/DockerMysqlService.scala delete mode 100644 samples/src/main/scala/com/whisk/docker/DockerNeo4jService.scala delete mode 100644 samples/src/main/scala/com/whisk/docker/DockerZookeeperService.scala create mode 100644 tests/src/test/scala/com/whisk/docker/test/MysqlServiceTest.scala create mode 100644 tests/src/test/scala/com/whisk/docker/test/PostgresServiceTest.scala diff --git a/.travis.yml b/.travis.yml index 95008a6..7003a77 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ services: before_install: - docker pull mongo:3.4.8 - docker pull elasticsearch:1.7.1 + - docker pull elasticsearch:1.7.1 language: scala diff --git a/build.sbt b/build.sbt index 491d59c..68d6f77 100644 --- a/build.sbt +++ b/build.sbt @@ -48,8 +48,7 @@ lazy val core = "com.spotify" % "docker-client" % "8.9.0", "com.google.code.findbugs" % "jsr305" % "3.0.1", "org.scalatest" %% "scalatest" % "3.0.4", - "ch.qos.logback" % "logback-classic" % "1.2.3" % "test", - "org.postgresql" % "postgresql" % "9.4.1210" % "test" + "ch.qos.logback" % "logback-classic" % "1.2.3" % "test" ) ) @@ -62,5 +61,11 @@ lazy val samples = lazy val tests = project .settings(commonSettings: _*) - .settings(name := "docker-testkit-tests") + .settings( + name := "docker-testkit-tests", + libraryDependencies ++= Seq( + "org.postgresql" % "postgresql" % "9.4.1210" % "test", + "mysql" % "mysql-connector-java" % "5.1.44" % "test" + ) + ) .dependsOn(core % "test", samples % "test") diff --git a/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala b/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala index aec6bd5..15f9731 100644 --- a/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala +++ b/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala @@ -41,12 +41,6 @@ class DockerContainerManager(managedContainers: ManagedContainers, Future.unit case Some(checker) => checker(container)(executor, executionContext) - .flatMap { - case false => - Future.failed(new Exception("ready check failed")) - case true => - Future.unit - } } } diff --git a/core/src/main/scala/com/whisk/docker/DockerReadyChecker.scala b/core/src/main/scala/com/whisk/docker/DockerReadyChecker.scala index 7ddbb56..b8f9236 100644 --- a/core/src/main/scala/com/whisk/docker/DockerReadyChecker.scala +++ b/core/src/main/scala/com/whisk/docker/DockerReadyChecker.scala @@ -1,15 +1,18 @@ package com.whisk.docker import java.net.{HttpURLConnection, URL} +import java.sql.DriverManager import java.util.{Timer, TimerTask} import scala.concurrent.duration.FiniteDuration import scala.concurrent.{ExecutionContext, Future, Promise, TimeoutException} +class FailFastCheckException(m: String) extends Exception(m) + trait DockerReadyChecker { def apply(container: Container)(implicit docker: ContainerCommandExecutor, - ec: ExecutionContext): Future[Boolean] + ec: ExecutionContext): Future[Unit] def and(other: DockerReadyChecker)(implicit docker: ContainerCommandExecutor, ec: ExecutionContext) = { @@ -20,26 +23,9 @@ trait DockerReadyChecker { for { a <- aF b <- bF - } yield a && b - } - } - - def or(other: DockerReadyChecker)(implicit docker: ContainerCommandExecutor, - ec: ExecutionContext) = { - val s = this - DockerReadyChecker.F { container => - val aF = s(container) - val bF = other(container) - val p = Promise[Boolean]() - aF.map { - case true => p.trySuccess(true) - case _ => - } - bF.map { - case true => p.trySuccess(true) - case _ => + } yield { + () } - p.future } } @@ -80,18 +66,11 @@ object RetryUtils { implicit ec: ExecutionContext): Future[T] = { def attempt(rest: Int): Future[T] = { future.recoverWith { + case e: FailFastCheckException => Future.failed(e) + case e if rest > 0 => + withDelay(delay.toMillis)(attempt(rest - 1)) case e => - rest match { - case 0 => - Future.failed(e match { - case _: NoSuchElementException => - new NoSuchElementException( - s"Ready checker returned false after $attempts attempts, delayed $delay each") - case _ => e - }) - case n => - withDelay(delay.toMillis)(attempt(n - 1)) - } + Future.failed(e) } } @@ -103,8 +82,8 @@ object DockerReadyChecker { object Always extends DockerReadyChecker { override def apply(container: Container)(implicit docker: ContainerCommandExecutor, - ec: ExecutionContext): Future[Boolean] = - Future.successful(true) + ec: ExecutionContext): Future[Unit] = + Future.unit } case class HttpResponseCode(port: Int, @@ -112,8 +91,9 @@ object DockerReadyChecker { host: Option[String] = None, code: Int = 200) extends DockerReadyChecker { + override def apply(container: Container)(implicit docker: ContainerCommandExecutor, - ec: ExecutionContext): Future[Boolean] = { + ec: ExecutionContext): Future[Unit] = { val p = container.mappedPorts()(port) val url = new URL("http", host.getOrElse(docker.client.getHost), p, path) @@ -121,10 +101,11 @@ object DockerReadyChecker { scala.concurrent.blocking { val con = url.openConnection().asInstanceOf[HttpURLConnection] try { - con.getResponseCode == code + if (con.getResponseCode != code) + throw new Exception("unexpected response code: " + con.getResponseCode) } catch { case e: java.net.ConnectException => - false + throw e } } } @@ -134,16 +115,17 @@ object DockerReadyChecker { case class LogLineContains(str: String) extends DockerReadyChecker { override def apply(container: Container)(implicit docker: ContainerCommandExecutor, - ec: ExecutionContext): Future[Boolean] = { + ec: ExecutionContext): Future[Unit] = { container.state() match { case ContainerState.Ready(_) => - Future.successful(true) + Future.unit case state: ContainerState.HasId => docker .withLogStreamLinesRequirement(state.id, withErr = true)(_.contains(str)) - .map(_ => true) + .map(_ => ()) case _ => - Future.successful(false) + Future.failed( + new FailFastCheckException("can't initialise LogStream to container without Id")) } } } @@ -152,10 +134,10 @@ object DockerReadyChecker { extends DockerReadyChecker { override def apply(container: Container)(implicit docker: ContainerCommandExecutor, - ec: ExecutionContext): Future[Boolean] = { + ec: ExecutionContext): Future[Unit] = { RetryUtils.runWithin(underlying(container), duration).recover { case _: TimeoutException => - false + throw new FailFastCheckException("timeout exception") } } } @@ -166,15 +148,58 @@ object DockerReadyChecker { extends DockerReadyChecker { override def apply(container: Container)(implicit docker: ContainerCommandExecutor, - ec: ExecutionContext): Future[Boolean] = { - RetryUtils.looped(underlying(container).filter(identity), attempts, delay) + ec: ExecutionContext): Future[Unit] = { + + def attempt(attemptsLeft: Int): Future[Unit] = { + underlying(container) + .recoverWith { + case e: FailFastCheckException => Future.failed(e) + case e if attemptsLeft > 0 => + RetryUtils.withDelay(delay.toMillis)(attempt(attemptsLeft - 1)) + case e => + Future.failed(e) + } + } + + attempt(attempts) } } - case class F(f: Container => Future[Boolean]) extends DockerReadyChecker { + private[docker] case class F(f: Container => Future[Unit]) extends DockerReadyChecker { override def apply(container: Container)(implicit docker: ContainerCommandExecutor, - ec: ExecutionContext): Future[Boolean] = + ec: ExecutionContext): Future[Unit] = f(container) } + case class Jdbc(driverClass: String, + urlFunc: Int => String, + user: String, + password: String, + port: Option[Int] = None) + extends DockerReadyChecker { + + override def apply(container: Container)(implicit docker: ContainerCommandExecutor, + ec: ExecutionContext): Future[Unit] = { + + Future(scala.concurrent.blocking { + try { + Class.forName(driverClass) + val p = port match { + case Some(v) => container.mappedPorts().apply(v) + case None => container.mappedPorts().head._2 + } + val url = urlFunc(p) + val connection = Option(DriverManager.getConnection(url, user, password)) + connection.foreach(_.close()) + if (connection.isEmpty) { + throw new Exception(s"can't establish jdbc connection to $url") + } + } catch { + case e: ClassNotFoundException => + throw new FailFastCheckException(s"jdbc class $driverClass not found") + } + }) + } + } + } diff --git a/samples/src/main/scala/com/whisk/docker/DockerKafkaService.scala b/samples/src/main/scala/com/whisk/docker/DockerKafkaService.scala deleted file mode 100644 index 83c8ea2..0000000 --- a/samples/src/main/scala/com/whisk/docker/DockerKafkaService.scala +++ /dev/null @@ -1,15 +0,0 @@ -//package com.whisk.docker -// -//trait DockerKafkaService extends DockerKit { -// -// def KafkaAdvertisedPort = 9092 -// val ZookeeperDefaultPort = 2181 -// -// lazy val kafkaContainer = ContainerSpec("spotify/kafka") -// .withPorts(KafkaAdvertisedPort -> Some(KafkaAdvertisedPort), ZookeeperDefaultPort -> None) -// .withEnv(s"ADVERTISED_PORT=$KafkaAdvertisedPort", s"ADVERTISED_HOST=${dockerExecutor.host}") -// .withReadyChecker(DockerReadyChecker.LogLineContains("kafka entered RUNNING state")) -// -// abstract override def dockerContainers: List[ContainerSpec] = -// kafkaContainer :: super.dockerContainers -//} diff --git a/samples/src/main/scala/com/whisk/docker/DockerMysqlService.scala b/samples/src/main/scala/com/whisk/docker/DockerMysqlService.scala new file mode 100644 index 0000000..353e605 --- /dev/null +++ b/samples/src/main/scala/com/whisk/docker/DockerMysqlService.scala @@ -0,0 +1,29 @@ +package com.whisk.docker + +import org.scalatest.Suite + +import scala.concurrent.duration._ + +trait DockerMysqlService extends DockerTestKitForAll { self: Suite => + + def MysqlAdvertisedPort = 3306 + val MysqlUser = "test" + val MysqlPassword = "test" + val MysqlDatabase = "test" + + val mysqlContainer = ContainerSpec("quay.io/whisk/fastboot-mysql:5.7.19") + .withExposedPorts(MysqlAdvertisedPort) + .withReadyChecker( + DockerReadyChecker + .Jdbc( + driverClass = "com.mysql.jdbc.Driver", + urlFunc = port => s"jdbc:mysql://${dockerClient.getHost}:$port/test", + user = MysqlUser, + password = MysqlPassword + ) + .looped(25, 1.second) + ) + .toContainer + + override val managedContainers: ManagedContainers = mysqlContainer.toManagedContainer +} diff --git a/samples/src/main/scala/com/whisk/docker/DockerNeo4jService.scala b/samples/src/main/scala/com/whisk/docker/DockerNeo4jService.scala deleted file mode 100644 index 14bc364..0000000 --- a/samples/src/main/scala/com/whisk/docker/DockerNeo4jService.scala +++ /dev/null @@ -1,21 +0,0 @@ -//package com.whisk.docker -// -//import scala.concurrent.duration._ -// -//trait DockerNeo4jService extends DockerKit { -// -// val DefaultNeo4jHttpPort = 7474 -// -// val neo4jContainer = ContainerSpec("neo4j:3.0.3") -// .withPorts(DefaultNeo4jHttpPort -> None) -// .withEnv("NEO4J_AUTH=none") -// .withReadyChecker( -// DockerReadyChecker -// .HttpResponseCode(DefaultNeo4jHttpPort, "/db/data/") -// .within(100.millis) -// .looped(20, 1250.millis) -// ) -// -// abstract override def dockerContainers: List[ContainerSpec] = -// neo4jContainer :: super.dockerContainers -//} diff --git a/samples/src/main/scala/com/whisk/docker/DockerPostgresService.scala b/samples/src/main/scala/com/whisk/docker/DockerPostgresService.scala index d62ae1b..2f98273 100644 --- a/samples/src/main/scala/com/whisk/docker/DockerPostgresService.scala +++ b/samples/src/main/scala/com/whisk/docker/DockerPostgresService.scala @@ -1,40 +1,31 @@ -//package com.whisk.docker -// -//import java.sql.DriverManager -// -//import scala.concurrent.ExecutionContext -//import scala.util.Try -// -//trait DockerPostgresService extends DockerKit { -// import scala.concurrent.duration._ -// def PostgresAdvertisedPort = 5432 -// def PostgresExposedPort = 44444 -// val PostgresUser = "nph" -// val PostgresPassword = "suitup" -// -// val postgresContainer = ContainerSpec("postgres:9.5.3") -// .withPorts((PostgresAdvertisedPort, Some(PostgresExposedPort))) -// .withEnv(s"POSTGRES_USER=$PostgresUser", s"POSTGRES_PASSWORD=$PostgresPassword") -// .withReadyChecker( -// new PostgresReadyChecker(PostgresUser, PostgresPassword, Some(PostgresExposedPort)) -// .looped(15, 1.second) -// ) -// -// abstract override def dockerContainers: List[ContainerSpec] = -// postgresContainer :: super.dockerContainers -//} -// -//class PostgresReadyChecker(user: String, password: String, port: Option[Int] = None) -// extends DockerReadyChecker { -// -// override def apply(container: Container)(implicit docker: DockerCommandExecutor, -// ec: ExecutionContext) = -// container -// .getPorts() -// .map(ports => -// Try { -// Class.forName("org.postgresql.Driver") -// val url = s"jdbc:postgresql://${docker.host}:${port.getOrElse(ports.values.head)}/" -// Option(DriverManager.getConnection(url, user, password)).map(_.close).isDefined -// }.getOrElse(false)) -//} +package com.whisk.docker + +import com.spotify.docker.client.messages.PortBinding +import org.scalatest.Suite + +import scala.concurrent.duration._ + +trait DockerPostgresService extends DockerTestKitForAll { self: Suite => + + def PostgresAdvertisedPort = 5432 + def PostgresExposedPort = 44444 + val PostgresUser = "nph" + val PostgresPassword = "suitup" + + val postgresContainer = ContainerSpec("postgres:9.6.5") + .withPortBindings((PostgresAdvertisedPort, PortBinding.of("0.0.0.0", PostgresExposedPort))) + .withEnv(s"POSTGRES_USER=$PostgresUser", s"POSTGRES_PASSWORD=$PostgresPassword") + .withReadyChecker( + DockerReadyChecker + .Jdbc( + driverClass = "org.postgresql.Driver", + urlFunc = port => s"jdbc:postgresql://${dockerClient.getHost}:$port/", + user = PostgresUser, + password = PostgresPassword + ) + .looped(15, 1.second) + ) + .toContainer + + override val managedContainers: ManagedContainers = postgresContainer.toManagedContainer +} diff --git a/samples/src/main/scala/com/whisk/docker/DockerZookeeperService.scala b/samples/src/main/scala/com/whisk/docker/DockerZookeeperService.scala deleted file mode 100644 index e939f00..0000000 --- a/samples/src/main/scala/com/whisk/docker/DockerZookeeperService.scala +++ /dev/null @@ -1,11 +0,0 @@ -//package com.whisk.docker -// -//trait DockerZookeeperService extends DockerKit { -// -// val zookeeperContainer = ContainerSpec("jplock/zookeeper:3.4.6") -// .withPorts(2181 -> None) -// .withReadyChecker(DockerReadyChecker.LogLineContains("binding to port")) -// -// abstract override def dockerContainers: List[ContainerSpec] = -// zookeeperContainer :: super.dockerContainers -//} diff --git a/tests/src/test/scala/com/whisk/docker/test/MysqlServiceTest.scala b/tests/src/test/scala/com/whisk/docker/test/MysqlServiceTest.scala new file mode 100644 index 0000000..3bedb66 --- /dev/null +++ b/tests/src/test/scala/com/whisk/docker/test/MysqlServiceTest.scala @@ -0,0 +1,12 @@ +package com.whisk.docker.test + +import com.whisk.docker.{ContainerState, DockerMysqlService} +import org.scalatest.FunSuite + +class MysqlServiceTest extends FunSuite with DockerMysqlService { + + test("test container started") { + assert(mysqlContainer.state().isInstanceOf[ContainerState.Ready], "mysql is ready") + assert(mysqlContainer.mappedPorts().get(MysqlAdvertisedPort).nonEmpty, "mysql port exposed") + } +} diff --git a/tests/src/test/scala/com/whisk/docker/test/PostgresServiceTest.scala b/tests/src/test/scala/com/whisk/docker/test/PostgresServiceTest.scala new file mode 100644 index 0000000..782c63c --- /dev/null +++ b/tests/src/test/scala/com/whisk/docker/test/PostgresServiceTest.scala @@ -0,0 +1,14 @@ +package com.whisk.docker.test + +import com.whisk.docker.{ContainerState, DockerPostgresService} +import org.scalatest.FunSuite + +class PostgresServiceTest extends FunSuite with DockerPostgresService { + + test("test container started") { + assert(postgresContainer.state().isInstanceOf[ContainerState.Ready], "postgres is ready") + assert( + postgresContainer.mappedPorts().get(PostgresAdvertisedPort) === Some(PostgresExposedPort), + "postgres port exposed") + } +} From 7d685c0456a6ea326b08fa48c19085486e6a7233 Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Sun, 10 Sep 2017 17:29:08 +0100 Subject: [PATCH 06/36] replace Future.unit to keep compatibility with 2.11 --- .../main/scala/com/whisk/docker/DockerContainerManager.scala | 2 +- core/src/main/scala/com/whisk/docker/DockerReadyChecker.scala | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala b/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala index 15f9731..1d2bfa6 100644 --- a/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala +++ b/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala @@ -38,7 +38,7 @@ class DockerContainerManager(managedContainers: ManagedContainers, private def waitUntilReady(container: Container): Future[Unit] = { container.spec.readyChecker match { case None => - Future.unit + Future.successful(()) case Some(checker) => checker(container)(executor, executionContext) } diff --git a/core/src/main/scala/com/whisk/docker/DockerReadyChecker.scala b/core/src/main/scala/com/whisk/docker/DockerReadyChecker.scala index b8f9236..e703b84 100644 --- a/core/src/main/scala/com/whisk/docker/DockerReadyChecker.scala +++ b/core/src/main/scala/com/whisk/docker/DockerReadyChecker.scala @@ -83,7 +83,7 @@ object DockerReadyChecker { object Always extends DockerReadyChecker { override def apply(container: Container)(implicit docker: ContainerCommandExecutor, ec: ExecutionContext): Future[Unit] = - Future.unit + Future.successful(()) } case class HttpResponseCode(port: Int, @@ -118,7 +118,7 @@ object DockerReadyChecker { ec: ExecutionContext): Future[Unit] = { container.state() match { case ContainerState.Ready(_) => - Future.unit + Future.successful(()) case state: ContainerState.HasId => docker .withLogStreamLinesRequirement(state.id, withErr = true)(_.contains(str)) From 987bdaf77dbd08acb6f054287509cc229f04c7f0 Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Sun, 10 Sep 2017 17:35:30 +0100 Subject: [PATCH 07/36] compatibility with 2.11 --- .../docker/ContainerCommandExecutor.scala | 22 ++++++++++++------- .../whisk/docker/DockerContainerManager.scala | 5 +++-- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/core/src/main/scala/com/whisk/docker/ContainerCommandExecutor.scala b/core/src/main/scala/com/whisk/docker/ContainerCommandExecutor.scala index ca870dd..fba91a6 100644 --- a/core/src/main/scala/com/whisk/docker/ContainerCommandExecutor.scala +++ b/core/src/main/scala/com/whisk/docker/ContainerCommandExecutor.scala @@ -86,9 +86,12 @@ class ContainerCommandExecutor(val client: DockerClient) { implicit ec: ExecutionContext): Unit = { logStreamFuture(id, withErr).foreach { stream => - stream.forEachRemaining((t: LogMessage) => { - val str = StandardCharsets.US_ASCII.decode(t.content()).toString - f(s"[$id] $str") + stream.forEachRemaining(new java.util.function.Consumer[LogMessage] { + + override def accept(t: LogMessage): Unit = { + val str = StandardCharsets.US_ASCII.decode(t.content()).toString + f(s"[$id] $str") + } }) } } @@ -99,11 +102,14 @@ class ContainerCommandExecutor(val client: DockerClient) { logStreamFuture(id, withErr).flatMap { stream => val p = Promise[Unit]() Future { - stream.forEachRemaining((t: LogMessage) => { - val str = StandardCharsets.US_ASCII.decode(t.content()).toString - if (f(str)) { - p.trySuccess(()) - Closeables.close(stream, true) + stream.forEachRemaining(new java.util.function.Consumer[LogMessage] { + + override def accept(t: LogMessage): Unit = { + val str = StandardCharsets.US_ASCII.decode(t.content()).toString + if (f(str)) { + p.trySuccess(()) + Closeables.close(stream, true) + } } }) } diff --git a/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala b/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala index 1d2bfa6..8d80230 100644 --- a/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala +++ b/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala @@ -46,8 +46,9 @@ class DockerContainerManager(managedContainers: ManagedContainers, private def printWarningsIfExist(creation: ContainerCreation): Unit = { Option(creation.warnings()) - .getOrElse(ImmutableList.of[String]()) - .forEach(w => log.warn(s"creating container: $w")) + .map(_.asScala.toList) + .getOrElse(Nil) + .foreach(w => log.warn(s"creating container: $w")) } private def ensureImage(image: String): Future[Unit] = { From 732fca541aad6bfbf084058213b783e7a3e7ff01 Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Sun, 10 Sep 2017 17:38:47 +0100 Subject: [PATCH 08/36] fix timelimited check --- .../src/main/scala/com/whisk/docker/DockerReadyChecker.scala | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/src/main/scala/com/whisk/docker/DockerReadyChecker.scala b/core/src/main/scala/com/whisk/docker/DockerReadyChecker.scala index e703b84..ed6c625 100644 --- a/core/src/main/scala/com/whisk/docker/DockerReadyChecker.scala +++ b/core/src/main/scala/com/whisk/docker/DockerReadyChecker.scala @@ -135,10 +135,7 @@ object DockerReadyChecker { override def apply(container: Container)(implicit docker: ContainerCommandExecutor, ec: ExecutionContext): Future[Unit] = { - RetryUtils.runWithin(underlying(container), duration).recover { - case _: TimeoutException => - throw new FailFastCheckException("timeout exception") - } + RetryUtils.runWithin(underlying(container), duration) } } From 69e05744878d5f329a8b49b35f89d7e8957baf98 Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Sat, 16 Sep 2017 00:41:00 +0100 Subject: [PATCH 09/36] extract scalatest into separate module. rework packaging --- .gitignore | 1 + .jvmopts | 8 ++++++++ build.sbt | 16 +++++++++++++--- .../whisk/docker/{ => testkit}/Container.scala | 2 +- .../{ => testkit}/ContainerCommandExecutor.scala | 2 +- .../docker/{ => testkit}/ContainerPort.scala | 2 +- .../docker/{ => testkit}/ContainerSpec.scala | 2 +- .../{ => testkit}/DockerContainerManager.scala | 3 +-- .../{ => testkit}/DockerReadyChecker.scala | 2 +- .../{ => testkit}/DockerTestTimeouts.scala | 2 +- .../com/whisk/docker/{ => testkit}/package.scala | 4 ++-- .../whisk/docker/DockerCassandraService.scala | 15 --------------- .../DockerElasticsearchService.scala | 3 ++- .../{ => testkit}/DockerMongodbService.scala | 3 ++- .../{ => testkit}/DockerMysqlService.scala | 3 ++- .../{ => testkit}/DockerPostgresService.scala | 3 ++- .../testkit/scalatest}/DockerTestKitForAll.scala | 3 ++- .../test/ElasticsearchServiceTest.scala | 4 ++-- .../{ => testkit}/test/MongodbServiceTest.scala | 4 ++-- .../{ => testkit}/test/MultiContainerTest.scala | 9 ++------- .../{ => testkit}/test/MysqlServiceTest.scala | 4 ++-- .../{ => testkit}/test/PostgresServiceTest.scala | 4 ++-- 22 files changed, 51 insertions(+), 48 deletions(-) create mode 100644 .jvmopts rename core/src/main/scala/com/whisk/docker/{ => testkit}/Container.scala (98%) rename core/src/main/scala/com/whisk/docker/{ => testkit}/ContainerCommandExecutor.scala (99%) rename core/src/main/scala/com/whisk/docker/{ => testkit}/ContainerPort.scala (92%) rename core/src/main/scala/com/whisk/docker/{ => testkit}/ContainerSpec.scala (97%) rename core/src/main/scala/com/whisk/docker/{ => testkit}/DockerContainerManager.scala (98%) rename core/src/main/scala/com/whisk/docker/{ => testkit}/DockerReadyChecker.scala (99%) rename core/src/main/scala/com/whisk/docker/{ => testkit}/DockerTestTimeouts.scala (88%) rename core/src/main/scala/com/whisk/docker/{ => testkit}/package.scala (93%) delete mode 100644 samples/src/main/scala/com/whisk/docker/DockerCassandraService.scala rename samples/src/main/scala/com/whisk/docker/{ => testkit}/DockerElasticsearchService.scala (87%) rename samples/src/main/scala/com/whisk/docker/{ => testkit}/DockerMongodbService.scala (82%) rename samples/src/main/scala/com/whisk/docker/{ => testkit}/DockerMysqlService.scala (89%) rename samples/src/main/scala/com/whisk/docker/{ => testkit}/DockerPostgresService.scala (91%) rename {core/src/main/scala/com/whisk/docker => scalatest/src/main/scala/com/whisk/docker/testkit/scalatest}/DockerTestKitForAll.scala (94%) rename tests/src/test/scala/com/whisk/docker/{ => testkit}/test/ElasticsearchServiceTest.scala (76%) rename tests/src/test/scala/com/whisk/docker/{ => testkit}/test/MongodbServiceTest.scala (74%) rename tests/src/test/scala/com/whisk/docker/{ => testkit}/test/MultiContainerTest.scala (82%) rename tests/src/test/scala/com/whisk/docker/{ => testkit}/test/MysqlServiceTest.scala (75%) rename tests/src/test/scala/com/whisk/docker/{ => testkit}/test/PostgresServiceTest.scala (77%) diff --git a/.gitignore b/.gitignore index 2cb1511..c73cbe1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ target/ project/sbt-launch-*.jar .idea/ +.DS_Store diff --git a/.jvmopts b/.jvmopts new file mode 100644 index 0000000..5e4a229 --- /dev/null +++ b/.jvmopts @@ -0,0 +1,8 @@ +-Dfile.encoding=UTF8 +-Xms1G +-Xmx2G +-XX:ReservedCodeCacheSize=250M +-XX:+TieredCompilation +-XX:-UseGCOverheadLimit +-XX:+CMSClassUnloadingEnabled +-XX:+UseConcMarkSweepGC diff --git a/build.sbt b/build.sbt index 68d6f77..e034b85 100644 --- a/build.sbt +++ b/build.sbt @@ -36,7 +36,7 @@ lazy val root = .in(file(".")) .settings(commonSettings: _*) .settings(publish := {}, publishLocal := {}, packagedArtifacts := Map.empty) - .aggregate(core, samples) + .aggregate(core, scalatest, samples) lazy val core = project @@ -47,16 +47,26 @@ lazy val core = "org.slf4j" % "slf4j-api" % "1.7.25", "com.spotify" % "docker-client" % "8.9.0", "com.google.code.findbugs" % "jsr305" % "3.0.1", + ) + ) + +lazy val scalatest = + project + .settings(commonSettings: _*) + .settings( + name := "docker-testkit-scalatest", + libraryDependencies ++= Seq( "org.scalatest" %% "scalatest" % "3.0.4", "ch.qos.logback" % "logback-classic" % "1.2.3" % "test" ) ) + .dependsOn(core) lazy val samples = project .settings(commonSettings: _*) .settings(name := "docker-testkit-samples") - .dependsOn(core) + .dependsOn(core, scalatest) lazy val tests = project @@ -68,4 +78,4 @@ lazy val tests = "mysql" % "mysql-connector-java" % "5.1.44" % "test" ) ) - .dependsOn(core % "test", samples % "test") + .dependsOn(core, scalatest, samples % "test") diff --git a/core/src/main/scala/com/whisk/docker/Container.scala b/core/src/main/scala/com/whisk/docker/testkit/Container.scala similarity index 98% rename from core/src/main/scala/com/whisk/docker/Container.scala rename to core/src/main/scala/com/whisk/docker/testkit/Container.scala index 6763e13..2273cdc 100644 --- a/core/src/main/scala/com/whisk/docker/Container.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/Container.scala @@ -1,4 +1,4 @@ -package com.whisk.docker +package com.whisk.docker.testkit import java.util.concurrent.atomic.AtomicReference diff --git a/core/src/main/scala/com/whisk/docker/ContainerCommandExecutor.scala b/core/src/main/scala/com/whisk/docker/testkit/ContainerCommandExecutor.scala similarity index 99% rename from core/src/main/scala/com/whisk/docker/ContainerCommandExecutor.scala rename to core/src/main/scala/com/whisk/docker/testkit/ContainerCommandExecutor.scala index fba91a6..ac0234e 100644 --- a/core/src/main/scala/com/whisk/docker/ContainerCommandExecutor.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/ContainerCommandExecutor.scala @@ -1,4 +1,4 @@ -package com.whisk.docker +package com.whisk.docker.testkit import java.nio.charset.StandardCharsets import java.util.Collections diff --git a/core/src/main/scala/com/whisk/docker/ContainerPort.scala b/core/src/main/scala/com/whisk/docker/testkit/ContainerPort.scala similarity index 92% rename from core/src/main/scala/com/whisk/docker/ContainerPort.scala rename to core/src/main/scala/com/whisk/docker/testkit/ContainerPort.scala index af3d9c1..8bc836f 100644 --- a/core/src/main/scala/com/whisk/docker/ContainerPort.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/ContainerPort.scala @@ -1,4 +1,4 @@ -package com.whisk.docker +package com.whisk.docker.testkit object PortProtocol extends Enumeration { val TCP, UDP = Value diff --git a/core/src/main/scala/com/whisk/docker/ContainerSpec.scala b/core/src/main/scala/com/whisk/docker/testkit/ContainerSpec.scala similarity index 97% rename from core/src/main/scala/com/whisk/docker/ContainerSpec.scala rename to core/src/main/scala/com/whisk/docker/testkit/ContainerSpec.scala index fb8fe6d..7886861 100644 --- a/core/src/main/scala/com/whisk/docker/ContainerSpec.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/ContainerSpec.scala @@ -1,4 +1,4 @@ -package com.whisk.docker +package com.whisk.docker.testkit import com.spotify.docker.client.messages.PortBinding diff --git a/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala b/core/src/main/scala/com/whisk/docker/testkit/DockerContainerManager.scala similarity index 98% rename from core/src/main/scala/com/whisk/docker/DockerContainerManager.scala rename to core/src/main/scala/com/whisk/docker/testkit/DockerContainerManager.scala index 8d80230..a4277ca 100644 --- a/core/src/main/scala/com/whisk/docker/DockerContainerManager.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/DockerContainerManager.scala @@ -1,8 +1,7 @@ -package com.whisk.docker +package com.whisk.docker.testkit import java.util.concurrent.{ConcurrentHashMap, TimeUnit} -import com.google.common.collect.ImmutableList import com.spotify.docker.client.exceptions.ImageNotFoundException import com.spotify.docker.client.messages.ContainerCreation import org.slf4j.LoggerFactory diff --git a/core/src/main/scala/com/whisk/docker/DockerReadyChecker.scala b/core/src/main/scala/com/whisk/docker/testkit/DockerReadyChecker.scala similarity index 99% rename from core/src/main/scala/com/whisk/docker/DockerReadyChecker.scala rename to core/src/main/scala/com/whisk/docker/testkit/DockerReadyChecker.scala index ed6c625..b949237 100644 --- a/core/src/main/scala/com/whisk/docker/DockerReadyChecker.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/DockerReadyChecker.scala @@ -1,4 +1,4 @@ -package com.whisk.docker +package com.whisk.docker.testkit import java.net.{HttpURLConnection, URL} import java.sql.DriverManager diff --git a/core/src/main/scala/com/whisk/docker/DockerTestTimeouts.scala b/core/src/main/scala/com/whisk/docker/testkit/DockerTestTimeouts.scala similarity index 88% rename from core/src/main/scala/com/whisk/docker/DockerTestTimeouts.scala rename to core/src/main/scala/com/whisk/docker/testkit/DockerTestTimeouts.scala index 7222527..85f641d 100644 --- a/core/src/main/scala/com/whisk/docker/DockerTestTimeouts.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/DockerTestTimeouts.scala @@ -1,4 +1,4 @@ -package com.whisk.docker +package com.whisk.docker.testkit import scala.concurrent.duration._ diff --git a/core/src/main/scala/com/whisk/docker/package.scala b/core/src/main/scala/com/whisk/docker/testkit/package.scala similarity index 93% rename from core/src/main/scala/com/whisk/docker/package.scala rename to core/src/main/scala/com/whisk/docker/testkit/package.scala index 93c6228..84ec042 100644 --- a/core/src/main/scala/com/whisk/docker/package.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/package.scala @@ -1,4 +1,4 @@ -package com.whisk +package com.whisk.docker import java.util.concurrent.atomic.AtomicBoolean @@ -7,7 +7,7 @@ import scala.concurrent.{Future, Promise} /** * General utility functions */ -package object docker { +package object testkit { implicit class OptionalOps[A](val content: A) extends AnyVal { def withOption[B](optional: Option[B])(f: (A, B) => A): A = optional match { case None => content diff --git a/samples/src/main/scala/com/whisk/docker/DockerCassandraService.scala b/samples/src/main/scala/com/whisk/docker/DockerCassandraService.scala deleted file mode 100644 index 37d32ba..0000000 --- a/samples/src/main/scala/com/whisk/docker/DockerCassandraService.scala +++ /dev/null @@ -1,15 +0,0 @@ -package com.whisk.docker - -import org.scalatest.Suite - -trait DockerCassandraService extends DockerTestKitForAll { self: Suite => - - val DefaultCqlPort = 9042 - - val cassandraContainer = ContainerSpec("whisk/cassandra:2.1.8") - .withExposedPorts(DefaultCqlPort) - .withReadyChecker(DockerReadyChecker.LogLineContains("Starting listening for CQL clients on")) - .toContainer - - override val managedContainers = cassandraContainer.toManagedContainer -} diff --git a/samples/src/main/scala/com/whisk/docker/DockerElasticsearchService.scala b/samples/src/main/scala/com/whisk/docker/testkit/DockerElasticsearchService.scala similarity index 87% rename from samples/src/main/scala/com/whisk/docker/DockerElasticsearchService.scala rename to samples/src/main/scala/com/whisk/docker/testkit/DockerElasticsearchService.scala index 46a0800..d6904eb 100644 --- a/samples/src/main/scala/com/whisk/docker/DockerElasticsearchService.scala +++ b/samples/src/main/scala/com/whisk/docker/testkit/DockerElasticsearchService.scala @@ -1,5 +1,6 @@ -package com.whisk.docker +package com.whisk.docker.testkit +import com.whisk.docker.testkit.scalatest.DockerTestKitForAll import org.scalatest.Suite import scala.concurrent.duration._ diff --git a/samples/src/main/scala/com/whisk/docker/DockerMongodbService.scala b/samples/src/main/scala/com/whisk/docker/testkit/DockerMongodbService.scala similarity index 82% rename from samples/src/main/scala/com/whisk/docker/DockerMongodbService.scala rename to samples/src/main/scala/com/whisk/docker/testkit/DockerMongodbService.scala index 89f5583..03411f5 100644 --- a/samples/src/main/scala/com/whisk/docker/DockerMongodbService.scala +++ b/samples/src/main/scala/com/whisk/docker/testkit/DockerMongodbService.scala @@ -1,5 +1,6 @@ -package com.whisk.docker +package com.whisk.docker.testkit +import com.whisk.docker.testkit.scalatest.DockerTestKitForAll import org.scalatest.Suite trait DockerMongodbService extends DockerTestKitForAll { diff --git a/samples/src/main/scala/com/whisk/docker/DockerMysqlService.scala b/samples/src/main/scala/com/whisk/docker/testkit/DockerMysqlService.scala similarity index 89% rename from samples/src/main/scala/com/whisk/docker/DockerMysqlService.scala rename to samples/src/main/scala/com/whisk/docker/testkit/DockerMysqlService.scala index 353e605..eeb9ff9 100644 --- a/samples/src/main/scala/com/whisk/docker/DockerMysqlService.scala +++ b/samples/src/main/scala/com/whisk/docker/testkit/DockerMysqlService.scala @@ -1,5 +1,6 @@ -package com.whisk.docker +package com.whisk.docker.testkit +import com.whisk.docker.testkit.scalatest.DockerTestKitForAll import org.scalatest.Suite import scala.concurrent.duration._ diff --git a/samples/src/main/scala/com/whisk/docker/DockerPostgresService.scala b/samples/src/main/scala/com/whisk/docker/testkit/DockerPostgresService.scala similarity index 91% rename from samples/src/main/scala/com/whisk/docker/DockerPostgresService.scala rename to samples/src/main/scala/com/whisk/docker/testkit/DockerPostgresService.scala index 2f98273..3a998d8 100644 --- a/samples/src/main/scala/com/whisk/docker/DockerPostgresService.scala +++ b/samples/src/main/scala/com/whisk/docker/testkit/DockerPostgresService.scala @@ -1,6 +1,7 @@ -package com.whisk.docker +package com.whisk.docker.testkit import com.spotify.docker.client.messages.PortBinding +import com.whisk.docker.testkit.scalatest.DockerTestKitForAll import org.scalatest.Suite import scala.concurrent.duration._ diff --git a/core/src/main/scala/com/whisk/docker/DockerTestKitForAll.scala b/scalatest/src/main/scala/com/whisk/docker/testkit/scalatest/DockerTestKitForAll.scala similarity index 94% rename from core/src/main/scala/com/whisk/docker/DockerTestKitForAll.scala rename to scalatest/src/main/scala/com/whisk/docker/testkit/scalatest/DockerTestKitForAll.scala index b63fc80..56a18c0 100644 --- a/core/src/main/scala/com/whisk/docker/DockerTestKitForAll.scala +++ b/scalatest/src/main/scala/com/whisk/docker/testkit/scalatest/DockerTestKitForAll.scala @@ -1,8 +1,9 @@ -package com.whisk.docker +package com.whisk.docker.testkit.scalatest import java.util.concurrent.ForkJoinPool import com.spotify.docker.client.{DefaultDockerClient, DockerClient} +import com.whisk.docker.testkit._ import org.scalatest.{Args, Status, Suite, SuiteMixin} import scala.concurrent.ExecutionContext diff --git a/tests/src/test/scala/com/whisk/docker/test/ElasticsearchServiceTest.scala b/tests/src/test/scala/com/whisk/docker/testkit/test/ElasticsearchServiceTest.scala similarity index 76% rename from tests/src/test/scala/com/whisk/docker/test/ElasticsearchServiceTest.scala rename to tests/src/test/scala/com/whisk/docker/testkit/test/ElasticsearchServiceTest.scala index 0efb60f..d464d0b 100644 --- a/tests/src/test/scala/com/whisk/docker/test/ElasticsearchServiceTest.scala +++ b/tests/src/test/scala/com/whisk/docker/testkit/test/ElasticsearchServiceTest.scala @@ -1,6 +1,6 @@ -package com.whisk.docker.test +package com.whisk.docker.testkit.test -import com.whisk.docker.{ContainerState, DockerElasticsearchService} +import com.whisk.docker.testkit.{ContainerState, DockerElasticsearchService} import org.scalatest.FunSuite class ElasticsearchServiceTest extends FunSuite with DockerElasticsearchService { diff --git a/tests/src/test/scala/com/whisk/docker/test/MongodbServiceTest.scala b/tests/src/test/scala/com/whisk/docker/testkit/test/MongodbServiceTest.scala similarity index 74% rename from tests/src/test/scala/com/whisk/docker/test/MongodbServiceTest.scala rename to tests/src/test/scala/com/whisk/docker/testkit/test/MongodbServiceTest.scala index 22c4a43..266c2a2 100644 --- a/tests/src/test/scala/com/whisk/docker/test/MongodbServiceTest.scala +++ b/tests/src/test/scala/com/whisk/docker/testkit/test/MongodbServiceTest.scala @@ -1,6 +1,6 @@ -package com.whisk.docker.test +package com.whisk.docker.testkit.test -import com.whisk.docker.{ContainerState, DockerMongodbService} +import com.whisk.docker.testkit.{ContainerState, DockerMongodbService} import org.scalatest.FunSuite class MongodbServiceTest extends FunSuite with DockerMongodbService { diff --git a/tests/src/test/scala/com/whisk/docker/test/MultiContainerTest.scala b/tests/src/test/scala/com/whisk/docker/testkit/test/MultiContainerTest.scala similarity index 82% rename from tests/src/test/scala/com/whisk/docker/test/MultiContainerTest.scala rename to tests/src/test/scala/com/whisk/docker/testkit/test/MultiContainerTest.scala index 7980aed..e404984 100644 --- a/tests/src/test/scala/com/whisk/docker/test/MultiContainerTest.scala +++ b/tests/src/test/scala/com/whisk/docker/testkit/test/MultiContainerTest.scala @@ -1,11 +1,6 @@ -package com.whisk.docker.test +package com.whisk.docker.testkit.test -import com.whisk.docker.{ - ContainerGroup, - ContainerState, - DockerElasticsearchService, - DockerMongodbService -} +import com.whisk.docker.testkit._ import org.scalatest.FunSuite class MultiContainerTest diff --git a/tests/src/test/scala/com/whisk/docker/test/MysqlServiceTest.scala b/tests/src/test/scala/com/whisk/docker/testkit/test/MysqlServiceTest.scala similarity index 75% rename from tests/src/test/scala/com/whisk/docker/test/MysqlServiceTest.scala rename to tests/src/test/scala/com/whisk/docker/testkit/test/MysqlServiceTest.scala index 3bedb66..e45aad4 100644 --- a/tests/src/test/scala/com/whisk/docker/test/MysqlServiceTest.scala +++ b/tests/src/test/scala/com/whisk/docker/testkit/test/MysqlServiceTest.scala @@ -1,6 +1,6 @@ -package com.whisk.docker.test +package com.whisk.docker.testkit.test -import com.whisk.docker.{ContainerState, DockerMysqlService} +import com.whisk.docker.testkit.{ContainerState, DockerMysqlService} import org.scalatest.FunSuite class MysqlServiceTest extends FunSuite with DockerMysqlService { diff --git a/tests/src/test/scala/com/whisk/docker/test/PostgresServiceTest.scala b/tests/src/test/scala/com/whisk/docker/testkit/test/PostgresServiceTest.scala similarity index 77% rename from tests/src/test/scala/com/whisk/docker/test/PostgresServiceTest.scala rename to tests/src/test/scala/com/whisk/docker/testkit/test/PostgresServiceTest.scala index 782c63c..11154b2 100644 --- a/tests/src/test/scala/com/whisk/docker/test/PostgresServiceTest.scala +++ b/tests/src/test/scala/com/whisk/docker/testkit/test/PostgresServiceTest.scala @@ -1,6 +1,6 @@ -package com.whisk.docker.test +package com.whisk.docker.testkit.test -import com.whisk.docker.{ContainerState, DockerPostgresService} +import com.whisk.docker.testkit.{ContainerState, DockerPostgresService} import org.scalatest.FunSuite class PostgresServiceTest extends FunSuite with DockerPostgresService { From c050fff7d8ae04489446b715f9cfc0955518b143 Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Sat, 16 Sep 2017 00:45:41 +0100 Subject: [PATCH 10/36] simpler method to access mapped port --- .../main/scala/com/whisk/docker/testkit/Container.scala | 8 ++++++++ .../docker/testkit/test/ElasticsearchServiceTest.scala | 2 +- .../whisk/docker/testkit/test/MongodbServiceTest.scala | 2 +- .../whisk/docker/testkit/test/MultiContainerTest.scala | 4 ++-- .../com/whisk/docker/testkit/test/MysqlServiceTest.scala | 2 +- .../whisk/docker/testkit/test/PostgresServiceTest.scala | 5 ++--- 6 files changed, 15 insertions(+), 8 deletions(-) diff --git a/core/src/main/scala/com/whisk/docker/testkit/Container.scala b/core/src/main/scala/com/whisk/docker/testkit/Container.scala index 2273cdc..5c6a71f 100644 --- a/core/src/main/scala/com/whisk/docker/testkit/Container.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/Container.scala @@ -102,5 +102,13 @@ class Container(val spec: ContainerSpec) { } } + def mappedPort(port: Int): Int = { + mappedPorts().apply(port) + } + + def mappedPortOpt(port: Int): Option[Int] = { + mappedPorts().get(port) + } + def toManagedContainer: SingleContainer = SingleContainer(this) } diff --git a/tests/src/test/scala/com/whisk/docker/testkit/test/ElasticsearchServiceTest.scala b/tests/src/test/scala/com/whisk/docker/testkit/test/ElasticsearchServiceTest.scala index d464d0b..e107d78 100644 --- a/tests/src/test/scala/com/whisk/docker/testkit/test/ElasticsearchServiceTest.scala +++ b/tests/src/test/scala/com/whisk/docker/testkit/test/ElasticsearchServiceTest.scala @@ -8,6 +8,6 @@ class ElasticsearchServiceTest extends FunSuite with DockerElasticsearchService test("test container started") { assert(elasticsearchContainer.state().isInstanceOf[ContainerState.Ready], "elasticsearch container is ready") - assert(elasticsearchContainer.mappedPorts().get(9200).nonEmpty, "elasticsearch port is exposed") + assert(elasticsearchContainer.mappedPortOpt(9200).nonEmpty, "elasticsearch port is exposed") } } diff --git a/tests/src/test/scala/com/whisk/docker/testkit/test/MongodbServiceTest.scala b/tests/src/test/scala/com/whisk/docker/testkit/test/MongodbServiceTest.scala index 266c2a2..71b0556 100644 --- a/tests/src/test/scala/com/whisk/docker/testkit/test/MongodbServiceTest.scala +++ b/tests/src/test/scala/com/whisk/docker/testkit/test/MongodbServiceTest.scala @@ -7,6 +7,6 @@ class MongodbServiceTest extends FunSuite with DockerMongodbService { test("test container started") { assert(mongodbContainer.state().isInstanceOf[ContainerState.Ready], "mongodb is ready") - assert(mongodbContainer.mappedPorts().get(27017).nonEmpty, "port 2017 is exposed") + assert(mongodbContainer.mappedPortOpt(27017).nonEmpty, "port 2017 is exposed") } } diff --git a/tests/src/test/scala/com/whisk/docker/testkit/test/MultiContainerTest.scala b/tests/src/test/scala/com/whisk/docker/testkit/test/MultiContainerTest.scala index e404984..1d0bda2 100644 --- a/tests/src/test/scala/com/whisk/docker/testkit/test/MultiContainerTest.scala +++ b/tests/src/test/scala/com/whisk/docker/testkit/test/MultiContainerTest.scala @@ -13,9 +13,9 @@ class MultiContainerTest test("both containers should be ready") { assert(elasticsearchContainer.state().isInstanceOf[ContainerState.Ready], "elasticsearch container is ready") - assert(elasticsearchContainer.mappedPorts().get(9200).nonEmpty, "elasticsearch port is exposed") + assert(elasticsearchContainer.mappedPortOpt(9200).nonEmpty, "elasticsearch port is exposed") assert(mongodbContainer.state().isInstanceOf[ContainerState.Ready], "mongodb is ready") - assert(mongodbContainer.mappedPorts().get(27017).nonEmpty, "port 2017 is exposed") + assert(mongodbContainer.mappedPortOpt(27017).nonEmpty, "port 2017 is exposed") } } diff --git a/tests/src/test/scala/com/whisk/docker/testkit/test/MysqlServiceTest.scala b/tests/src/test/scala/com/whisk/docker/testkit/test/MysqlServiceTest.scala index e45aad4..fa02846 100644 --- a/tests/src/test/scala/com/whisk/docker/testkit/test/MysqlServiceTest.scala +++ b/tests/src/test/scala/com/whisk/docker/testkit/test/MysqlServiceTest.scala @@ -7,6 +7,6 @@ class MysqlServiceTest extends FunSuite with DockerMysqlService { test("test container started") { assert(mysqlContainer.state().isInstanceOf[ContainerState.Ready], "mysql is ready") - assert(mysqlContainer.mappedPorts().get(MysqlAdvertisedPort).nonEmpty, "mysql port exposed") + assert(mysqlContainer.mappedPortOpt(MysqlAdvertisedPort).nonEmpty, "mysql port exposed") } } diff --git a/tests/src/test/scala/com/whisk/docker/testkit/test/PostgresServiceTest.scala b/tests/src/test/scala/com/whisk/docker/testkit/test/PostgresServiceTest.scala index 11154b2..8449381 100644 --- a/tests/src/test/scala/com/whisk/docker/testkit/test/PostgresServiceTest.scala +++ b/tests/src/test/scala/com/whisk/docker/testkit/test/PostgresServiceTest.scala @@ -7,8 +7,7 @@ class PostgresServiceTest extends FunSuite with DockerPostgresService { test("test container started") { assert(postgresContainer.state().isInstanceOf[ContainerState.Ready], "postgres is ready") - assert( - postgresContainer.mappedPorts().get(PostgresAdvertisedPort) === Some(PostgresExposedPort), - "postgres port exposed") + assert(postgresContainer.mappedPortOpt(PostgresAdvertisedPort) === Some(PostgresExposedPort), + "postgres port exposed") } } From b6d747f87cc7b2e66b7d19a69af4e50997172e19 Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Sat, 16 Sep 2017 01:54:31 +0100 Subject: [PATCH 11/36] allow defining containers in separate classes --- .../whisk/docker/testkit/BaseContainer.scala | 116 ++++++++++++++++++ .../com/whisk/docker/testkit/Container.scala | 113 +---------------- .../testkit/DockerContainerManager.scala | 12 +- .../docker/testkit/DockerReadyChecker.scala | 52 +++++--- .../docker/testkit/DockerMysqlService.scala | 42 ++++--- .../testkit/DockerPostgresService.scala | 1 - .../testkit/test/MysqlServiceTest.scala | 3 +- 7 files changed, 181 insertions(+), 158 deletions(-) create mode 100644 core/src/main/scala/com/whisk/docker/testkit/BaseContainer.scala diff --git a/core/src/main/scala/com/whisk/docker/testkit/BaseContainer.scala b/core/src/main/scala/com/whisk/docker/testkit/BaseContainer.scala new file mode 100644 index 0000000..a482c57 --- /dev/null +++ b/core/src/main/scala/com/whisk/docker/testkit/BaseContainer.scala @@ -0,0 +1,116 @@ +package com.whisk.docker.testkit + +import java.util.concurrent.atomic.AtomicReference + +import com.spotify.docker.client.messages.ContainerInfo +import org.slf4j.LoggerFactory + +import scala.collection.JavaConverters._ + +sealed trait ContainerState + +object ContainerState { + + trait HasId extends ContainerState { + val id: String + } + + trait IsRunning extends HasId { + val info: ContainerInfo + override val id: String = info.id + } + + object NotStarted extends ContainerState + case class Created(id: String) extends ContainerState with HasId + case class Starting(id: String) extends ContainerState with HasId + case class Running(info: ContainerInfo) extends ContainerState with IsRunning + case class Ready(info: ContainerInfo) extends ContainerState with IsRunning + case class Failed(id: String) extends ContainerState + object Stopped extends ContainerState +} + +abstract class BaseContainer { + + def spec: ContainerSpec + + private lazy val log = LoggerFactory.getLogger(this.getClass) + + private val _state = new AtomicReference[ContainerState](ContainerState.NotStarted) + + def state(): ContainerState = { + _state.get() + } + + private def updateState(state: ContainerState): Unit = { + _state.set(state) + } + + private[docker] def created(id: String): Unit = { + updateState(ContainerState.Created(id)) + } + + private[docker] def starting(id: String): Unit = { + updateState(ContainerState.Starting(id)) + } + + private[docker] def running(info: ContainerInfo): Unit = { + updateState(ContainerState.Running(info)) + } + + private[docker] def ready(info: ContainerInfo): Unit = { + updateState(ContainerState.Ready(info)) + } + + private def addresses(info: ContainerInfo): Seq[String] = { + val addrs: Iterable[String] = for { + networks <- Option(info.networkSettings().networks()).map(_.asScala).toSeq + (key, network) <- networks + ip <- Option(network.ipAddress) + } yield { + ip + } + addrs.toList + } + + private def portsFrom(info: ContainerInfo): Map[Int, Int] = { + info + .networkSettings() + .ports() + .asScala + .collect { + case (portStr, bindings) if Option(bindings).exists(!_.isEmpty) => + val port = ContainerPort.parsed(portStr).port + val hostPort = bindings.get(0).hostPort().toInt + port -> hostPort + } + .toMap + } + + def ipAddresses(): Seq[String] = { + state() match { + case s: ContainerState.IsRunning => + addresses(s.info) + case _ => + throw new Exception("can't get addresses of not running container") + } + } + + def mappedPorts(): Map[Int, Int] = { + state() match { + case s: ContainerState.IsRunning => + portsFrom(s.info) + case _ => + throw new Exception("can't get ports of not running container") + } + } + + def mappedPort(port: Int): Int = { + mappedPorts().apply(port) + } + + def mappedPortOpt(port: Int): Option[Int] = { + mappedPorts().get(port) + } + + def toManagedContainer: SingleContainer = SingleContainer(this) +} \ No newline at end of file diff --git a/core/src/main/scala/com/whisk/docker/testkit/Container.scala b/core/src/main/scala/com/whisk/docker/testkit/Container.scala index 5c6a71f..a58e078 100644 --- a/core/src/main/scala/com/whisk/docker/testkit/Container.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/Container.scala @@ -1,114 +1,3 @@ package com.whisk.docker.testkit -import java.util.concurrent.atomic.AtomicReference - -import com.spotify.docker.client.messages.ContainerInfo -import org.slf4j.LoggerFactory - -import scala.collection.JavaConverters._ - -sealed trait ContainerState - -object ContainerState { - - trait HasId extends ContainerState { - val id: String - } - - trait IsRunning extends HasId { - val info: ContainerInfo - override val id: String = info.id - } - - object NotStarted extends ContainerState - case class Created(id: String) extends ContainerState with HasId - case class Starting(id: String) extends ContainerState with HasId - case class Running(info: ContainerInfo) extends ContainerState with IsRunning - case class Ready(info: ContainerInfo) extends ContainerState with IsRunning - case class Failed(id: String) extends ContainerState - object Stopped extends ContainerState -} - -class Container(val spec: ContainerSpec) { - - private lazy val log = LoggerFactory.getLogger(this.getClass) - - private val _state = new AtomicReference[ContainerState](ContainerState.NotStarted) - - def state(): ContainerState = { - _state.get() - } - - private def updateState(state: ContainerState): Unit = { - _state.set(state) - } - - private[docker] def created(id: String): Unit = { - updateState(ContainerState.Created(id)) - } - - private[docker] def starting(id: String): Unit = { - updateState(ContainerState.Starting(id)) - } - - private[docker] def running(info: ContainerInfo): Unit = { - updateState(ContainerState.Running(info)) - } - - private[docker] def ready(info: ContainerInfo): Unit = { - updateState(ContainerState.Ready(info)) - } - - private def addresses(info: ContainerInfo): Seq[String] = { - val addrs: Iterable[String] = for { - networks <- Option(info.networkSettings().networks()).map(_.asScala).toSeq - (key, network) <- networks - ip <- Option(network.ipAddress) - } yield { - ip - } - addrs.toList - } - - private def portsFrom(info: ContainerInfo): Map[Int, Int] = { - info - .networkSettings() - .ports() - .asScala - .collect { - case (portStr, bindings) if Option(bindings).exists(!_.isEmpty) => - val port = ContainerPort.parsed(portStr).port - val hostPort = bindings.get(0).hostPort().toInt - port -> hostPort - } - .toMap - } - - def ipAddresses(): Seq[String] = { - state() match { - case s: ContainerState.IsRunning => - addresses(s.info) - case _ => - throw new Exception("can't get addresses of not running container") - } - } - - def mappedPorts(): Map[Int, Int] = { - state() match { - case s: ContainerState.IsRunning => - portsFrom(s.info) - case _ => - throw new Exception("can't get ports of not running container") - } - } - - def mappedPort(port: Int): Int = { - mappedPorts().apply(port) - } - - def mappedPortOpt(port: Int): Option[Int] = { - mappedPorts().get(port) - } - - def toManagedContainer: SingleContainer = SingleContainer(this) -} +class Container(override val spec: ContainerSpec) extends BaseContainer diff --git a/core/src/main/scala/com/whisk/docker/testkit/DockerContainerManager.scala b/core/src/main/scala/com/whisk/docker/testkit/DockerContainerManager.scala index a4277ca..44a0bc4 100644 --- a/core/src/main/scala/com/whisk/docker/testkit/DockerContainerManager.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/DockerContainerManager.scala @@ -12,15 +12,15 @@ import scala.language.postfixOps trait ManagedContainers -case class SingleContainer(container: Container) extends ManagedContainers +case class SingleContainer(container: BaseContainer) extends ManagedContainers -case class ContainerGroup(containers: Seq[Container]) extends ManagedContainers { +case class ContainerGroup(containers: Seq[BaseContainer]) extends ManagedContainers { require(containers.nonEmpty, "container group should be non-empty") } object ContainerGroup { - def of(containers: Container*): ContainerGroup = ContainerGroup(containers) + def of(containers: BaseContainer*): ContainerGroup = ContainerGroup(containers) } class DockerContainerManager(managedContainers: ManagedContainers, @@ -34,7 +34,7 @@ class DockerContainerManager(managedContainers: ManagedContainers, private val registeredContainers = new ConcurrentHashMap[String, String]() - private def waitUntilReady(container: Container): Future[Unit] = { + private def waitUntilReady(container: BaseContainer): Future[Unit] = { container.spec.readyChecker match { case None => Future.successful(()) @@ -61,7 +61,7 @@ class DockerContainerManager(managedContainers: ManagedContainers, } //TODO log listeners - def startContainer(container: Container): Future[Unit] = { + def startContainer(container: BaseContainer): Future[Unit] = { val image = container.spec.image val startTime = System.nanoTime() log.debug("Starting container: {}", image) @@ -92,7 +92,7 @@ class DockerContainerManager(managedContainers: ManagedContainers, def start(): Unit = { log.debug("Starting containers") - val containers: Seq[Container] = managedContainers match { + val containers: Seq[BaseContainer] = managedContainers match { case SingleContainer(c) => Seq(c) case ContainerGroup(cs) => cs case _ => throw new Exception("unsupported type of managed containers") diff --git a/core/src/main/scala/com/whisk/docker/testkit/DockerReadyChecker.scala b/core/src/main/scala/com/whisk/docker/testkit/DockerReadyChecker.scala index b949237..c6b3085 100644 --- a/core/src/main/scala/com/whisk/docker/testkit/DockerReadyChecker.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/DockerReadyChecker.scala @@ -11,8 +11,8 @@ class FailFastCheckException(m: String) extends Exception(m) trait DockerReadyChecker { - def apply(container: Container)(implicit docker: ContainerCommandExecutor, - ec: ExecutionContext): Future[Unit] + def apply(container: BaseContainer)(implicit docker: ContainerCommandExecutor, + ec: ExecutionContext): Future[Unit] def and(other: DockerReadyChecker)(implicit docker: ContainerCommandExecutor, ec: ExecutionContext) = { @@ -81,8 +81,8 @@ object RetryUtils { object DockerReadyChecker { object Always extends DockerReadyChecker { - override def apply(container: Container)(implicit docker: ContainerCommandExecutor, - ec: ExecutionContext): Future[Unit] = + override def apply(container: BaseContainer)(implicit docker: ContainerCommandExecutor, + ec: ExecutionContext): Future[Unit] = Future.successful(()) } @@ -92,8 +92,8 @@ object DockerReadyChecker { code: Int = 200) extends DockerReadyChecker { - override def apply(container: Container)(implicit docker: ContainerCommandExecutor, - ec: ExecutionContext): Future[Unit] = { + override def apply(container: BaseContainer)(implicit docker: ContainerCommandExecutor, + ec: ExecutionContext): Future[Unit] = { val p = container.mappedPorts()(port) val url = new URL("http", host.getOrElse(docker.client.getHost), p, path) @@ -114,8 +114,8 @@ object DockerReadyChecker { case class LogLineContains(str: String) extends DockerReadyChecker { - override def apply(container: Container)(implicit docker: ContainerCommandExecutor, - ec: ExecutionContext): Future[Unit] = { + override def apply(container: BaseContainer)(implicit docker: ContainerCommandExecutor, + ec: ExecutionContext): Future[Unit] = { container.state() match { case ContainerState.Ready(_) => Future.successful(()) @@ -133,8 +133,8 @@ object DockerReadyChecker { private[docker] case class TimeLimited(underlying: DockerReadyChecker, duration: FiniteDuration) extends DockerReadyChecker { - override def apply(container: Container)(implicit docker: ContainerCommandExecutor, - ec: ExecutionContext): Future[Unit] = { + override def apply(container: BaseContainer)(implicit docker: ContainerCommandExecutor, + ec: ExecutionContext): Future[Unit] = { RetryUtils.runWithin(underlying(container), duration) } } @@ -144,8 +144,8 @@ object DockerReadyChecker { delay: FiniteDuration) extends DockerReadyChecker { - override def apply(container: Container)(implicit docker: ContainerCommandExecutor, - ec: ExecutionContext): Future[Unit] = { + override def apply(container: BaseContainer)(implicit docker: ContainerCommandExecutor, + ec: ExecutionContext): Future[Unit] = { def attempt(attemptsLeft: Int): Future[Unit] = { underlying(container) @@ -162,30 +162,42 @@ object DockerReadyChecker { } } - private[docker] case class F(f: Container => Future[Unit]) extends DockerReadyChecker { - override def apply(container: Container)(implicit docker: ContainerCommandExecutor, - ec: ExecutionContext): Future[Unit] = + private[docker] case class F(f: BaseContainer => Future[Unit]) extends DockerReadyChecker { + override def apply(container: BaseContainer)(implicit docker: ContainerCommandExecutor, + ec: ExecutionContext): Future[Unit] = f(container) } case class Jdbc(driverClass: String, - urlFunc: Int => String, user: String, password: String, + database: Option[String] = None, port: Option[Int] = None) extends DockerReadyChecker { - override def apply(container: Container)(implicit docker: ContainerCommandExecutor, - ec: ExecutionContext): Future[Unit] = { + private val driverLower = driverClass.toLowerCase + private[Jdbc] val dbms: String = if (driverLower.contains("mysql")) { + "mysql" + } else if (driverLower.contains("postgres")) { + "postgresql" + } else { + throw new IllegalArgumentException("unsupported database for ready check") + } + + override def apply(container: BaseContainer)(implicit docker: ContainerCommandExecutor, + ec: ExecutionContext): Future[Unit] = { Future(scala.concurrent.blocking { try { Class.forName(driverClass) val p = port match { - case Some(v) => container.mappedPorts().apply(v) + case Some(v) => container.mappedPort(v) case None => container.mappedPorts().head._2 } - val url = urlFunc(p) + + val url = "jdbc:" + dbms + "://" + docker.client.getHost + ":" + p + "/" + database + .getOrElse("") + val connection = Option(DriverManager.getConnection(url, user, password)) connection.foreach(_.close()) if (connection.isEmpty) { diff --git a/samples/src/main/scala/com/whisk/docker/testkit/DockerMysqlService.scala b/samples/src/main/scala/com/whisk/docker/testkit/DockerMysqlService.scala index eeb9ff9..c72a54a 100644 --- a/samples/src/main/scala/com/whisk/docker/testkit/DockerMysqlService.scala +++ b/samples/src/main/scala/com/whisk/docker/testkit/DockerMysqlService.scala @@ -5,26 +5,32 @@ import org.scalatest.Suite import scala.concurrent.duration._ +class MysqlContainer(image: String) extends BaseContainer { + + val AdvertisedPort = 3306 + val User = "root" + val Password = "test" + val Database = "test" + + override val spec: ContainerSpec = { + ContainerSpec(image) + .withExposedPorts(AdvertisedPort) + .withReadyChecker( + DockerReadyChecker + .Jdbc( + driverClass = "com.mysql.jdbc.Driver", + user = User, + password = Password, + database = Some(Database) + ) + .looped(25, 1.second) + ) + } +} + trait DockerMysqlService extends DockerTestKitForAll { self: Suite => - def MysqlAdvertisedPort = 3306 - val MysqlUser = "test" - val MysqlPassword = "test" - val MysqlDatabase = "test" - - val mysqlContainer = ContainerSpec("quay.io/whisk/fastboot-mysql:5.7.19") - .withExposedPorts(MysqlAdvertisedPort) - .withReadyChecker( - DockerReadyChecker - .Jdbc( - driverClass = "com.mysql.jdbc.Driver", - urlFunc = port => s"jdbc:mysql://${dockerClient.getHost}:$port/test", - user = MysqlUser, - password = MysqlPassword - ) - .looped(25, 1.second) - ) - .toContainer + val mysqlContainer = new MysqlContainer("quay.io/whisk/fastboot-mysql:5.7.19") override val managedContainers: ManagedContainers = mysqlContainer.toManagedContainer } diff --git a/samples/src/main/scala/com/whisk/docker/testkit/DockerPostgresService.scala b/samples/src/main/scala/com/whisk/docker/testkit/DockerPostgresService.scala index 3a998d8..d8866b9 100644 --- a/samples/src/main/scala/com/whisk/docker/testkit/DockerPostgresService.scala +++ b/samples/src/main/scala/com/whisk/docker/testkit/DockerPostgresService.scala @@ -20,7 +20,6 @@ trait DockerPostgresService extends DockerTestKitForAll { self: Suite => DockerReadyChecker .Jdbc( driverClass = "org.postgresql.Driver", - urlFunc = port => s"jdbc:postgresql://${dockerClient.getHost}:$port/", user = PostgresUser, password = PostgresPassword ) diff --git a/tests/src/test/scala/com/whisk/docker/testkit/test/MysqlServiceTest.scala b/tests/src/test/scala/com/whisk/docker/testkit/test/MysqlServiceTest.scala index fa02846..ac01a7d 100644 --- a/tests/src/test/scala/com/whisk/docker/testkit/test/MysqlServiceTest.scala +++ b/tests/src/test/scala/com/whisk/docker/testkit/test/MysqlServiceTest.scala @@ -7,6 +7,7 @@ class MysqlServiceTest extends FunSuite with DockerMysqlService { test("test container started") { assert(mysqlContainer.state().isInstanceOf[ContainerState.Ready], "mysql is ready") - assert(mysqlContainer.mappedPortOpt(MysqlAdvertisedPort).nonEmpty, "mysql port exposed") + assert(mysqlContainer.mappedPortOpt(mysqlContainer.AdvertisedPort).nonEmpty, + "mysql port exposed") } } From d5d758c6537eb81b190822f80549184131170004 Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Sat, 16 Sep 2017 01:56:37 +0100 Subject: [PATCH 12/36] improve docker ready checker And --- .../docker/testkit/DockerReadyChecker.scala | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/core/src/main/scala/com/whisk/docker/testkit/DockerReadyChecker.scala b/core/src/main/scala/com/whisk/docker/testkit/DockerReadyChecker.scala index c6b3085..53b67dc 100644 --- a/core/src/main/scala/com/whisk/docker/testkit/DockerReadyChecker.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/DockerReadyChecker.scala @@ -14,21 +14,6 @@ trait DockerReadyChecker { def apply(container: BaseContainer)(implicit docker: ContainerCommandExecutor, ec: ExecutionContext): Future[Unit] - def and(other: DockerReadyChecker)(implicit docker: ContainerCommandExecutor, - ec: ExecutionContext) = { - val s = this - DockerReadyChecker.F { container => - val aF = s(container) - val bF = other(container) - for { - a <- aF - b <- bF - } yield { - () - } - } - } - def within(duration: FiniteDuration): DockerReadyChecker = { DockerReadyChecker.TimeLimited(this, duration) } @@ -80,6 +65,21 @@ object RetryUtils { object DockerReadyChecker { + case class And(r1: DockerReadyChecker, r2: DockerReadyChecker) extends DockerReadyChecker { + + override def apply(container: BaseContainer)(implicit docker: ContainerCommandExecutor, + ec: ExecutionContext): Future[Unit] = { + val aF = r1(container) + val bF = r2(container) + for { + a <- aF + b <- bF + } yield { + () + } + } + } + object Always extends DockerReadyChecker { override def apply(container: BaseContainer)(implicit docker: ContainerCommandExecutor, ec: ExecutionContext): Future[Unit] = From 56724739e247b63522785bb07603f3bebacf64bf Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Sun, 12 Nov 2017 13:32:09 +0000 Subject: [PATCH 13/36] update docker client depdenency. make password optional in jdbc --- build.sbt | 6 +++--- .../scala/com/whisk/docker/testkit/DockerReadyChecker.scala | 4 ++-- .../scala/com/whisk/docker/testkit/DockerMysqlService.scala | 2 +- .../com/whisk/docker/testkit/DockerPostgresService.scala | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build.sbt b/build.sbt index e034b85..3d8eb14 100644 --- a/build.sbt +++ b/build.sbt @@ -1,8 +1,8 @@ lazy val commonSettings = Seq( organization := "com.whisk", version := "0.10.0-wip", - scalaVersion := "2.12.3", - crossScalaVersions := Seq("2.12.3", "2.11.11"), + scalaVersion := "2.12.4", + crossScalaVersions := Seq("2.12.4", "2.11.11"), scalacOptions ++= Seq("-feature", "-deprecation"), fork in Test := true, licenses += ("MIT", url("http://opensource.org/licenses/MIT")), @@ -45,7 +45,7 @@ lazy val core = name := "docker-testkit-core", libraryDependencies ++= Seq( "org.slf4j" % "slf4j-api" % "1.7.25", - "com.spotify" % "docker-client" % "8.9.0", + "com.spotify" % "docker-client" % "8.9.1", "com.google.code.findbugs" % "jsr305" % "3.0.1", ) ) diff --git a/core/src/main/scala/com/whisk/docker/testkit/DockerReadyChecker.scala b/core/src/main/scala/com/whisk/docker/testkit/DockerReadyChecker.scala index 53b67dc..95cfcbf 100644 --- a/core/src/main/scala/com/whisk/docker/testkit/DockerReadyChecker.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/DockerReadyChecker.scala @@ -170,7 +170,7 @@ object DockerReadyChecker { case class Jdbc(driverClass: String, user: String, - password: String, + password: Option[String], database: Option[String] = None, port: Option[Int] = None) extends DockerReadyChecker { @@ -198,7 +198,7 @@ object DockerReadyChecker { val url = "jdbc:" + dbms + "://" + docker.client.getHost + ":" + p + "/" + database .getOrElse("") - val connection = Option(DriverManager.getConnection(url, user, password)) + val connection = Option(DriverManager.getConnection(url, user, password.orNull)) connection.foreach(_.close()) if (connection.isEmpty) { throw new Exception(s"can't establish jdbc connection to $url") diff --git a/samples/src/main/scala/com/whisk/docker/testkit/DockerMysqlService.scala b/samples/src/main/scala/com/whisk/docker/testkit/DockerMysqlService.scala index c72a54a..c3e5188 100644 --- a/samples/src/main/scala/com/whisk/docker/testkit/DockerMysqlService.scala +++ b/samples/src/main/scala/com/whisk/docker/testkit/DockerMysqlService.scala @@ -20,7 +20,7 @@ class MysqlContainer(image: String) extends BaseContainer { .Jdbc( driverClass = "com.mysql.jdbc.Driver", user = User, - password = Password, + password = Some(Password), database = Some(Database) ) .looped(25, 1.second) diff --git a/samples/src/main/scala/com/whisk/docker/testkit/DockerPostgresService.scala b/samples/src/main/scala/com/whisk/docker/testkit/DockerPostgresService.scala index d8866b9..2eb0fb0 100644 --- a/samples/src/main/scala/com/whisk/docker/testkit/DockerPostgresService.scala +++ b/samples/src/main/scala/com/whisk/docker/testkit/DockerPostgresService.scala @@ -21,7 +21,7 @@ trait DockerPostgresService extends DockerTestKitForAll { self: Suite => .Jdbc( driverClass = "org.postgresql.Driver", user = PostgresUser, - password = PostgresPassword + password = Some(PostgresPassword) ) .looped(15, 1.second) ) From 72c7b5b3fb0937ca801eaaf14f2c77da9d41571d Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Fri, 17 Nov 2017 20:01:53 +0000 Subject: [PATCH 14/36] update deps --- build.sbt | 6 +++--- project/build.properties | 2 +- project/plugins.sbt | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build.sbt b/build.sbt index 3d8eb14..0a84a1d 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ lazy val commonSettings = Seq( organization := "com.whisk", - version := "0.10.0-wip", + version := "0.10.0-beta3", scalaVersion := "2.12.4", crossScalaVersions := Seq("2.12.4", "2.11.11"), scalacOptions ++= Seq("-feature", "-deprecation"), @@ -45,7 +45,7 @@ lazy val core = name := "docker-testkit-core", libraryDependencies ++= Seq( "org.slf4j" % "slf4j-api" % "1.7.25", - "com.spotify" % "docker-client" % "8.9.1", + "com.spotify" % "docker-client" % "8.9.2", "com.google.code.findbugs" % "jsr305" % "3.0.1", ) ) @@ -74,7 +74,7 @@ lazy val tests = .settings( name := "docker-testkit-tests", libraryDependencies ++= Seq( - "org.postgresql" % "postgresql" % "9.4.1210" % "test", + "org.postgresql" % "postgresql" % "42.1.4" % "test", "mysql" % "mysql-connector-java" % "5.1.44" % "test" ) ) diff --git a/project/build.properties b/project/build.properties index 7b6213b..9abea12 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.0.1 +sbt.version=1.0.3 diff --git a/project/plugins.sbt b/project/plugins.sbt index 771138f..7007dbc 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.0") -addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0-M1") +addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0") -addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.2.0") +addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.3.0") From 18ec662ee21dadeaef018b79e4da6cbeccbce54c Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Thu, 7 Dec 2017 19:59:45 +0000 Subject: [PATCH 15/36] update docker-client to 8.10.0 --- .travis.yml | 4 ++-- build.sbt | 2 +- project/build.properties | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7003a77..8efb38a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,12 +5,12 @@ services: before_install: - docker pull mongo:3.4.8 - docker pull elasticsearch:1.7.1 - - docker pull elasticsearch:1.7.1 + - docker pull postgres:9.6.5 language: scala scala: - - 2.12.3 + - 2.12.4 - 2.11.11 jdk: diff --git a/build.sbt b/build.sbt index 0a84a1d..0166738 100644 --- a/build.sbt +++ b/build.sbt @@ -45,7 +45,7 @@ lazy val core = name := "docker-testkit-core", libraryDependencies ++= Seq( "org.slf4j" % "slf4j-api" % "1.7.25", - "com.spotify" % "docker-client" % "8.9.2", + "com.spotify" % "docker-client" % "8.10.0", "com.google.code.findbugs" % "jsr305" % "3.0.1", ) ) diff --git a/project/build.properties b/project/build.properties index 9abea12..394cb75 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.0.3 +sbt.version=1.0.4 From ecb19fe9dc1a8c8c5579e8874ec6766894a1a664 Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Thu, 7 Dec 2017 20:00:24 +0000 Subject: [PATCH 16/36] v0.10.0-beta4 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 0166738..af39181 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ lazy val commonSettings = Seq( organization := "com.whisk", - version := "0.10.0-beta3", + version := "0.10.0-beta4", scalaVersion := "2.12.4", crossScalaVersions := Seq("2.12.4", "2.11.11"), scalacOptions ++= Seq("-feature", "-deprecation"), From 922e4f2b2450f7c6cbd196f0cc01ada831a20308 Mon Sep 17 00:00:00 2001 From: McKinley Olsen Date: Tue, 10 Apr 2018 14:36:07 -0600 Subject: [PATCH 17/36] WIP --- build.sbt | 2 +- .../com/whisk/docker/testkit/ContainerCommandExecutor.scala | 1 + .../main/scala/com/whisk/docker/testkit/ContainerSpec.scala | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index af39181..cd146f6 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ lazy val commonSettings = Seq( organization := "com.whisk", - version := "0.10.0-beta4", + version := "0.10.0-beta5-SNAPSHOT", scalaVersion := "2.12.4", crossScalaVersions := Seq("2.12.4", "2.11.11"), scalacOptions ++= Seq("-feature", "-deprecation"), diff --git a/core/src/main/scala/com/whisk/docker/testkit/ContainerCommandExecutor.scala b/core/src/main/scala/com/whisk/docker/testkit/ContainerCommandExecutor.scala index ac0234e..3525f46 100644 --- a/core/src/main/scala/com/whisk/docker/testkit/ContainerCommandExecutor.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/ContainerCommandExecutor.scala @@ -28,6 +28,7 @@ class ContainerCommandExecutor(val client: DockerClient) { HostConfig .builder() .portBindings(portBindings.asJava) + .binds(spec.volumeBindings:_*) .build() val containerConfig = ContainerConfig diff --git a/core/src/main/scala/com/whisk/docker/testkit/ContainerSpec.scala b/core/src/main/scala/com/whisk/docker/testkit/ContainerSpec.scala index 7886861..e7371fe 100644 --- a/core/src/main/scala/com/whisk/docker/testkit/ContainerSpec.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/ContainerSpec.scala @@ -1,11 +1,13 @@ package com.whisk.docker.testkit import com.spotify.docker.client.messages.PortBinding +import com.spotify.docker.client.messages.HostConfig.Bind case class ContainerSpec(image: String, name: Option[String] = None, command: Option[Seq[String]] = None, portBindings: Map[Int, PortBinding] = Map.empty, + volumeBindings: Seq[Bind] = Seq.empty, env: Seq[String] = Seq.empty, readyChecker: Option[DockerReadyChecker] = None) { @@ -19,6 +21,8 @@ case class ContainerSpec(image: String, def withPortBindings(ps: (Int, PortBinding)*): ContainerSpec = copy(portBindings = ps.toMap) + def withVolumeBindings(vs: Seq[Bind]*): ContainerSpec = copy(volumeBindings = vs.flatten) + def withReadyChecker(checker: DockerReadyChecker): ContainerSpec = copy(readyChecker = Some(checker)) From fa7022cd54610ea5ca087609e807c3ca4ef7cab6 Mon Sep 17 00:00:00 2001 From: McKinley Olsen Date: Tue, 10 Apr 2018 15:47:14 -0600 Subject: [PATCH 18/36] Remove 'SNAPSHOT' from version string --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index cd146f6..d9e81e4 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ lazy val commonSettings = Seq( organization := "com.whisk", - version := "0.10.0-beta5-SNAPSHOT", + version := "0.10.0-beta5", scalaVersion := "2.12.4", crossScalaVersions := Seq("2.12.4", "2.11.11"), scalacOptions ++= Seq("-feature", "-deprecation"), From feaef87759ca0eb415aeb3afcf43eeedfd4caff9 Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Tue, 29 May 2018 21:21:45 +0100 Subject: [PATCH 19/36] docker-client v8.11.5 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index d9e81e4..06c2268 100644 --- a/build.sbt +++ b/build.sbt @@ -45,7 +45,7 @@ lazy val core = name := "docker-testkit-core", libraryDependencies ++= Seq( "org.slf4j" % "slf4j-api" % "1.7.25", - "com.spotify" % "docker-client" % "8.10.0", + "com.spotify" % "docker-client" % "8.11.5", "com.google.code.findbugs" % "jsr305" % "3.0.1", ) ) From db11a158f8f4424b4c2fdc2e315180ac48ef40d2 Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Tue, 29 May 2018 21:23:53 +0100 Subject: [PATCH 20/36] updated sbt to 1.1.5 and plugins --- .../main/scala/com/whisk/docker/testkit/BaseContainer.scala | 2 +- .../com/whisk/docker/testkit/ContainerCommandExecutor.scala | 2 +- .../main/scala/com/whisk/docker/testkit/ContainerPort.scala | 2 +- project/build.properties | 2 +- project/plugins.sbt | 6 +++--- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/src/main/scala/com/whisk/docker/testkit/BaseContainer.scala b/core/src/main/scala/com/whisk/docker/testkit/BaseContainer.scala index a482c57..8590792 100644 --- a/core/src/main/scala/com/whisk/docker/testkit/BaseContainer.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/BaseContainer.scala @@ -113,4 +113,4 @@ abstract class BaseContainer { } def toManagedContainer: SingleContainer = SingleContainer(this) -} \ No newline at end of file +} diff --git a/core/src/main/scala/com/whisk/docker/testkit/ContainerCommandExecutor.scala b/core/src/main/scala/com/whisk/docker/testkit/ContainerCommandExecutor.scala index 3525f46..749f645 100644 --- a/core/src/main/scala/com/whisk/docker/testkit/ContainerCommandExecutor.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/ContainerCommandExecutor.scala @@ -28,7 +28,7 @@ class ContainerCommandExecutor(val client: DockerClient) { HostConfig .builder() .portBindings(portBindings.asJava) - .binds(spec.volumeBindings:_*) + .binds(spec.volumeBindings: _*) .build() val containerConfig = ContainerConfig diff --git a/core/src/main/scala/com/whisk/docker/testkit/ContainerPort.scala b/core/src/main/scala/com/whisk/docker/testkit/ContainerPort.scala index 8bc836f..b7d5d97 100644 --- a/core/src/main/scala/com/whisk/docker/testkit/ContainerPort.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/ContainerPort.scala @@ -14,4 +14,4 @@ object ContainerPort { .getOrElse(PortProtocol.TCP) ContainerPort(p.toInt, proto) } -} \ No newline at end of file +} diff --git a/project/build.properties b/project/build.properties index 394cb75..7c81737 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.0.4 +sbt.version=1.1.5 diff --git a/project/plugins.sbt b/project/plugins.sbt index 7007dbc..c0b5303 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ -addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.0") +addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.3") -addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0") +addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.1") -addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.3.0") +addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.5.1") From 9cb66bf4f3b4d46103d84131eae9482c8b57d6a0 Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Tue, 29 May 2018 21:27:52 +0100 Subject: [PATCH 21/36] update es spec example --- .travis.yml | 3 +- .../testkit/DockerElasticsearchService.scala | 30 +++++++++++++------ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8efb38a..e5debb1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,9 @@ services: before_install: - docker pull mongo:3.4.8 - - docker pull elasticsearch:1.7.1 + - docker pull docker.elastic.co/elasticsearch/elasticsearch:6.2.4 - docker pull postgres:9.6.5 + - docker pull quay.io/whisk/fastboot-mysql:5.7.19 language: scala diff --git a/samples/src/main/scala/com/whisk/docker/testkit/DockerElasticsearchService.scala b/samples/src/main/scala/com/whisk/docker/testkit/DockerElasticsearchService.scala index d6904eb..9c45e9d 100644 --- a/samples/src/main/scala/com/whisk/docker/testkit/DockerElasticsearchService.scala +++ b/samples/src/main/scala/com/whisk/docker/testkit/DockerElasticsearchService.scala @@ -1,5 +1,7 @@ package com.whisk.docker.testkit +import java.util.UUID + import com.whisk.docker.testkit.scalatest.DockerTestKitForAll import org.scalatest.Suite @@ -10,16 +12,26 @@ trait DockerElasticsearchService extends DockerTestKitForAll { val DefaultElasticsearchHttpPort = 9200 val DefaultElasticsearchClientPort = 9300 + val EsClusterName = UUID.randomUUID().toString - val elasticsearchContainer: Container = ContainerSpec("elasticsearch:1.7.1") - .withExposedPorts(DefaultElasticsearchHttpPort, DefaultElasticsearchClientPort) - .withReadyChecker( - DockerReadyChecker - .HttpResponseCode(DefaultElasticsearchHttpPort, "/") - .within(100.millis) - .looped(20, 1250.millis) - ) - .toContainer + protected val elasticsearchContainer = + ContainerSpec("docker.elastic.co/elasticsearch/elasticsearch:6.2.4") + .withExposedPorts(DefaultElasticsearchHttpPort, DefaultElasticsearchClientPort) + .withEnv( + "http.host=0.0.0.0", + "xpack.security.enabled=false", + "http.cors.enabled: true", + "http.cors.allow-origin: \"*\"", + s"cluster.name=$EsClusterName", + "discovery.type=single-node", + "ES_JAVA_OPTS=-Xms512m -Xmx512m" + ) + .withReadyChecker( + DockerReadyChecker + .HttpResponseCode(DefaultElasticsearchHttpPort, "/") + .within(100.millis) + .looped(20, 1250.millis)) + .toContainer override val managedContainers: ManagedContainers = elasticsearchContainer.toManagedContainer } From 7110e24621dcce4d5277c1e65ed5bb57959a0b57 Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Tue, 29 May 2018 22:17:58 +0100 Subject: [PATCH 22/36] enable access to hostbuilder and configbuilder --- .../testkit/ContainerCommandExecutor.scala | 28 +------ .../whisk/docker/testkit/ContainerSpec.scala | 81 +++++++++++++++---- .../testkit/DockerElasticsearchService.scala | 2 +- 3 files changed, 67 insertions(+), 44 deletions(-) diff --git a/core/src/main/scala/com/whisk/docker/testkit/ContainerCommandExecutor.scala b/core/src/main/scala/com/whisk/docker/testkit/ContainerCommandExecutor.scala index 749f645..043a32a 100644 --- a/core/src/main/scala/com/whisk/docker/testkit/ContainerCommandExecutor.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/ContainerCommandExecutor.scala @@ -1,16 +1,13 @@ package com.whisk.docker.testkit import java.nio.charset.StandardCharsets -import java.util.Collections import java.util.concurrent.TimeUnit import com.google.common.io.Closeables import com.spotify.docker.client.DockerClient.{AttachParameter, RemoveContainerParam} -import com.spotify.docker.client.exceptions.ImageNotFoundException import com.spotify.docker.client.messages._ import com.spotify.docker.client.{DockerClient, LogMessage, LogStream} -import scala.collection.JavaConverters._ import scala.concurrent.{ExecutionContext, Future, Promise} class StartFailedException(msg: String) extends Exception(msg) @@ -19,28 +16,7 @@ class ContainerCommandExecutor(val client: DockerClient) { def createContainer(spec: ContainerSpec)( implicit ec: ExecutionContext): Future[ContainerCreation] = { - - val portBindings: Map[String, java.util.List[PortBinding]] = spec.portBindings.map { - case (guestPort, binding) => - guestPort.toString -> Collections.singletonList(binding) - } - val hostConfig = - HostConfig - .builder() - .portBindings(portBindings.asJava) - .binds(spec.volumeBindings: _*) - .build() - - val containerConfig = ContainerConfig - .builder() - .image(spec.image) - .hostConfig(hostConfig) - .exposedPorts(spec.portBindings.keySet.map(_.toString).asJava) - .env(spec.env: _*) - .withOption(spec.command) { case (config, command) => config.cmd(command: _*) } - .build() - - Future(scala.concurrent.blocking(client.createContainer(containerConfig, spec.name.orNull))) + Future(scala.concurrent.blocking(client.createContainer(spec.containerConfig(), spec.name.orNull))) } def startContainer(id: String)(implicit ec: ExecutionContext): Future[Unit] = { @@ -97,7 +73,7 @@ class ContainerCommandExecutor(val client: DockerClient) { } } - def withLogStreamLinesRequirement(id: String, withErr: Boolean)(f: (String) => Boolean)( + def withLogStreamLinesRequirement(id: String, withErr: Boolean)(f: String => Boolean)( implicit ec: ExecutionContext): Future[Unit] = { logStreamFuture(id, withErr).flatMap { stream => diff --git a/core/src/main/scala/com/whisk/docker/testkit/ContainerSpec.scala b/core/src/main/scala/com/whisk/docker/testkit/ContainerSpec.scala index e7371fe..4187534 100644 --- a/core/src/main/scala/com/whisk/docker/testkit/ContainerSpec.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/ContainerSpec.scala @@ -1,32 +1,79 @@ package com.whisk.docker.testkit -import com.spotify.docker.client.messages.PortBinding +import java.util.Collections + +import com.spotify.docker.client.messages.{ContainerConfig, HostConfig, PortBinding} import com.spotify.docker.client.messages.HostConfig.Bind -case class ContainerSpec(image: String, - name: Option[String] = None, - command: Option[Seq[String]] = None, - portBindings: Map[Int, PortBinding] = Map.empty, - volumeBindings: Seq[Bind] = Seq.empty, - env: Seq[String] = Seq.empty, - readyChecker: Option[DockerReadyChecker] = None) { +import scala.collection.JavaConverters._ + +case class ContainerSpec(image: String) { + + private val builder: ContainerConfig.Builder = ContainerConfig.builder().image(image) + private val hostConfigBuilder: HostConfig.Builder = HostConfig.builder() - def withCommand(cmd: String*): ContainerSpec = copy(command = Some(cmd)) + private var _readyChecker: Option[DockerReadyChecker] = None + private var _name: Option[String] = None + + def withCommand(cmd: String*): ContainerSpec = { + builder.cmd(cmd: _*) + this + } def withExposedPorts(ports: Int*): ContainerSpec = { - val binds: Map[Int, PortBinding] = - ports.map(p => p -> PortBinding.randomPort("0.0.0.0"))(collection.breakOut) //TODO check - copy(portBindings = binds) + val binds: Seq[(Int, PortBinding)] = + ports.map(p => p -> PortBinding.randomPort("0.0.0.0"))(collection.breakOut) + withPortBindings(binds: _*) + } + + def withPortBindings(ps: (Int, PortBinding)*): ContainerSpec = { + val binds: Map[String, java.util.List[PortBinding]] = ps.map { + case (guestPort, binding) => + guestPort.toString -> Collections.singletonList(binding) + }(collection.breakOut) + + hostConfigBuilder.portBindings(binds.asJava) + builder.exposedPorts(binds.keySet.asJava) + this + } + + def withVolumeBindings(vs: Bind*): ContainerSpec = { + hostConfigBuilder.binds(vs: _*) + this } - def withPortBindings(ps: (Int, PortBinding)*): ContainerSpec = copy(portBindings = ps.toMap) + def withReadyChecker(checker: DockerReadyChecker): ContainerSpec = { + _readyChecker = Some(checker) + this + } + + def withName(name: String): ContainerSpec = { + _name = Some(name) + this + } + + def withEnv(env: String*): ContainerSpec = { + builder.env(env: _*) + this + } - def withVolumeBindings(vs: Seq[Bind]*): ContainerSpec = copy(volumeBindings = vs.flatten) + def withConfiguration(withBuilder: ContainerConfig.Builder => ContainerConfig.Builder): ContainerSpec = { + withBuilder(builder) + this + } + + def withHostConfiguration(withBuilder: HostConfig.Builder => HostConfig.Builder): ContainerSpec = { + withBuilder(hostConfigBuilder) + this + } - def withReadyChecker(checker: DockerReadyChecker): ContainerSpec = - copy(readyChecker = Some(checker)) + def name: Option[String] = _name - def withEnv(env: String*): ContainerSpec = copy(env = env) + def readyChecker: Option[DockerReadyChecker] = _readyChecker + + def containerConfig(): ContainerConfig = { + builder.hostConfig(hostConfigBuilder.build()).build() + } def toContainer: Container = new Container(this) diff --git a/samples/src/main/scala/com/whisk/docker/testkit/DockerElasticsearchService.scala b/samples/src/main/scala/com/whisk/docker/testkit/DockerElasticsearchService.scala index 9c45e9d..6f1b3c6 100644 --- a/samples/src/main/scala/com/whisk/docker/testkit/DockerElasticsearchService.scala +++ b/samples/src/main/scala/com/whisk/docker/testkit/DockerElasticsearchService.scala @@ -12,7 +12,7 @@ trait DockerElasticsearchService extends DockerTestKitForAll { val DefaultElasticsearchHttpPort = 9200 val DefaultElasticsearchClientPort = 9300 - val EsClusterName = UUID.randomUUID().toString + val EsClusterName: String = UUID.randomUUID().toString protected val elasticsearchContainer = ContainerSpec("docker.elastic.co/elasticsearch/elasticsearch:6.2.4") From 65b2229b6cb3da72b97a024bbbc21bbc2f70735b Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Tue, 29 May 2018 22:20:41 +0100 Subject: [PATCH 23/36] fmt. attempting to fix travis build --- build.sbt | 4 +- .../testkit/ContainerCommandExecutor.scala | 35 +++++--- .../whisk/docker/testkit/ContainerSpec.scala | 8 +- .../testkit/DockerContainerManager.scala | 10 ++- .../docker/testkit/DockerReadyChecker.scala | 90 +++++++++++-------- project/plugins.sbt | 2 +- .../testkit/DockerElasticsearchService.scala | 3 +- .../scalatest/DockerTestKitForAll.scala | 10 ++- 8 files changed, 99 insertions(+), 63 deletions(-) diff --git a/build.sbt b/build.sbt index 06c2268..6d13ec7 100644 --- a/build.sbt +++ b/build.sbt @@ -1,8 +1,8 @@ lazy val commonSettings = Seq( organization := "com.whisk", version := "0.10.0-beta5", - scalaVersion := "2.12.4", - crossScalaVersions := Seq("2.12.4", "2.11.11"), + scalaVersion := "2.12.6", + crossScalaVersions := Seq("2.12.4", "2.11.12"), scalacOptions ++= Seq("-feature", "-deprecation"), fork in Test := true, licenses += ("MIT", url("http://opensource.org/licenses/MIT")), diff --git a/core/src/main/scala/com/whisk/docker/testkit/ContainerCommandExecutor.scala b/core/src/main/scala/com/whisk/docker/testkit/ContainerCommandExecutor.scala index 043a32a..d6d9af5 100644 --- a/core/src/main/scala/com/whisk/docker/testkit/ContainerCommandExecutor.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/ContainerCommandExecutor.scala @@ -14,9 +14,12 @@ class StartFailedException(msg: String) extends Exception(msg) class ContainerCommandExecutor(val client: DockerClient) { - def createContainer(spec: ContainerSpec)( - implicit ec: ExecutionContext): Future[ContainerCreation] = { - Future(scala.concurrent.blocking(client.createContainer(spec.containerConfig(), spec.name.orNull))) + def createContainer( + spec: ContainerSpec + )(implicit ec: ExecutionContext): Future[ContainerCreation] = { + Future( + scala.concurrent.blocking(client.createContainer(spec.containerConfig(), spec.name.orNull)) + ) } def startContainer(id: String)(implicit ec: ExecutionContext): Future[Unit] = { @@ -53,14 +56,16 @@ class ContainerCommandExecutor(val client: DockerClient) { private def logStreamFuture(id: String, withErr: Boolean)( implicit - ec: ExecutionContext): Future[LogStream] = { + ec: ExecutionContext + ): Future[LogStream] = { val baseParams = List(AttachParameter.STDOUT, AttachParameter.STREAM, AttachParameter.LOGS) val logParams = if (withErr) AttachParameter.STDERR :: baseParams else baseParams Future(scala.concurrent.blocking(client.attachContainer(id, logParams: _*))) } - def withLogStreamLines(id: String, withErr: Boolean)(f: String => Unit)( - implicit ec: ExecutionContext): Unit = { + def withLogStreamLines(id: String, withErr: Boolean)( + f: String => Unit + )(implicit ec: ExecutionContext): Unit = { logStreamFuture(id, withErr).foreach { stream => stream.forEachRemaining(new java.util.function.Consumer[LogMessage] { @@ -73,8 +78,9 @@ class ContainerCommandExecutor(val client: DockerClient) { } } - def withLogStreamLinesRequirement(id: String, withErr: Boolean)(f: String => Boolean)( - implicit ec: ExecutionContext): Future[Unit] = { + def withLogStreamLinesRequirement(id: String, withErr: Boolean)( + f: String => Boolean + )(implicit ec: ExecutionContext): Future[Unit] = { logStreamFuture(id, withErr).flatMap { stream => val p = Promise[Unit]() @@ -95,12 +101,17 @@ class ContainerCommandExecutor(val client: DockerClient) { } def remove(id: String, force: Boolean, removeVolumes: Boolean)( - implicit ec: ExecutionContext): Future[Unit] = { + implicit ec: ExecutionContext + ): Future[Unit] = { Future( scala.concurrent.blocking( - client.removeContainer(id, - RemoveContainerParam.forceKill(force), - RemoveContainerParam.removeVolumes(removeVolumes)))) + client.removeContainer( + id, + RemoveContainerParam.forceKill(force), + RemoveContainerParam.removeVolumes(removeVolumes) + ) + ) + ) } def close(): Unit = { diff --git a/core/src/main/scala/com/whisk/docker/testkit/ContainerSpec.scala b/core/src/main/scala/com/whisk/docker/testkit/ContainerSpec.scala index 4187534..4b68713 100644 --- a/core/src/main/scala/com/whisk/docker/testkit/ContainerSpec.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/ContainerSpec.scala @@ -57,12 +57,16 @@ case class ContainerSpec(image: String) { this } - def withConfiguration(withBuilder: ContainerConfig.Builder => ContainerConfig.Builder): ContainerSpec = { + def withConfiguration( + withBuilder: ContainerConfig.Builder => ContainerConfig.Builder + ): ContainerSpec = { withBuilder(builder) this } - def withHostConfiguration(withBuilder: HostConfig.Builder => HostConfig.Builder): ContainerSpec = { + def withHostConfiguration( + withBuilder: HostConfig.Builder => HostConfig.Builder + ): ContainerSpec = { withBuilder(hostConfigBuilder) this } diff --git a/core/src/main/scala/com/whisk/docker/testkit/DockerContainerManager.scala b/core/src/main/scala/com/whisk/docker/testkit/DockerContainerManager.scala index 44a0bc4..acb2ddc 100644 --- a/core/src/main/scala/com/whisk/docker/testkit/DockerContainerManager.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/DockerContainerManager.scala @@ -23,10 +23,12 @@ object ContainerGroup { def of(containers: BaseContainer*): ContainerGroup = ContainerGroup(containers) } -class DockerContainerManager(managedContainers: ManagedContainers, - executor: ContainerCommandExecutor, - dockerTestTimeouts: DockerTestTimeouts, - executionContext: ExecutionContext) { +class DockerContainerManager( + managedContainers: ManagedContainers, + executor: ContainerCommandExecutor, + dockerTestTimeouts: DockerTestTimeouts, + executionContext: ExecutionContext +) { private implicit val ec: ExecutionContext = executionContext diff --git a/core/src/main/scala/com/whisk/docker/testkit/DockerReadyChecker.scala b/core/src/main/scala/com/whisk/docker/testkit/DockerReadyChecker.scala index 95cfcbf..fdc1a4e 100644 --- a/core/src/main/scala/com/whisk/docker/testkit/DockerReadyChecker.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/DockerReadyChecker.scala @@ -11,8 +11,9 @@ class FailFastCheckException(m: String) extends Exception(m) trait DockerReadyChecker { - def apply(container: BaseContainer)(implicit docker: ContainerCommandExecutor, - ec: ExecutionContext): Future[Unit] + def apply( + container: BaseContainer + )(implicit docker: ContainerCommandExecutor, ec: ExecutionContext): Future[Unit] def within(duration: FiniteDuration): DockerReadyChecker = { DockerReadyChecker.TimeLimited(this, duration) @@ -38,17 +39,20 @@ object RetryUtils { } def runWithin[T](future: => Future[T], deadline: FiniteDuration)( - implicit ec: ExecutionContext): Future[T] = { + implicit ec: ExecutionContext + ): Future[T] = { val bail = Promise[T]() withDelay(deadline.toMillis)( bail .tryCompleteWith(Future.failed(new TimeoutException(s"timed out after $deadline"))) - .future) + .future + ) Future.firstCompletedOf(future :: bail.future :: Nil) } def looped[T](future: => Future[T], attempts: Int, delay: FiniteDuration)( - implicit ec: ExecutionContext): Future[T] = { + implicit ec: ExecutionContext + ): Future[T] = { def attempt(rest: Int): Future[T] = { future.recoverWith { case e: FailFastCheckException => Future.failed(e) @@ -67,8 +71,9 @@ object DockerReadyChecker { case class And(r1: DockerReadyChecker, r2: DockerReadyChecker) extends DockerReadyChecker { - override def apply(container: BaseContainer)(implicit docker: ContainerCommandExecutor, - ec: ExecutionContext): Future[Unit] = { + override def apply( + container: BaseContainer + )(implicit docker: ContainerCommandExecutor, ec: ExecutionContext): Future[Unit] = { val aF = r1(container) val bF = r2(container) for { @@ -81,19 +86,22 @@ object DockerReadyChecker { } object Always extends DockerReadyChecker { - override def apply(container: BaseContainer)(implicit docker: ContainerCommandExecutor, - ec: ExecutionContext): Future[Unit] = + override def apply( + container: BaseContainer + )(implicit docker: ContainerCommandExecutor, ec: ExecutionContext): Future[Unit] = Future.successful(()) } - case class HttpResponseCode(port: Int, - path: String = "/", - host: Option[String] = None, - code: Int = 200) - extends DockerReadyChecker { + case class HttpResponseCode( + port: Int, + path: String = "/", + host: Option[String] = None, + code: Int = 200 + ) extends DockerReadyChecker { - override def apply(container: BaseContainer)(implicit docker: ContainerCommandExecutor, - ec: ExecutionContext): Future[Unit] = { + override def apply( + container: BaseContainer + )(implicit docker: ContainerCommandExecutor, ec: ExecutionContext): Future[Unit] = { val p = container.mappedPorts()(port) val url = new URL("http", host.getOrElse(docker.client.getHost), p, path) @@ -114,8 +122,9 @@ object DockerReadyChecker { case class LogLineContains(str: String) extends DockerReadyChecker { - override def apply(container: BaseContainer)(implicit docker: ContainerCommandExecutor, - ec: ExecutionContext): Future[Unit] = { + override def apply( + container: BaseContainer + )(implicit docker: ContainerCommandExecutor, ec: ExecutionContext): Future[Unit] = { container.state() match { case ContainerState.Ready(_) => Future.successful(()) @@ -125,7 +134,8 @@ object DockerReadyChecker { .map(_ => ()) case _ => Future.failed( - new FailFastCheckException("can't initialise LogStream to container without Id")) + new FailFastCheckException("can't initialise LogStream to container without Id") + ) } } } @@ -133,19 +143,22 @@ object DockerReadyChecker { private[docker] case class TimeLimited(underlying: DockerReadyChecker, duration: FiniteDuration) extends DockerReadyChecker { - override def apply(container: BaseContainer)(implicit docker: ContainerCommandExecutor, - ec: ExecutionContext): Future[Unit] = { + override def apply( + container: BaseContainer + )(implicit docker: ContainerCommandExecutor, ec: ExecutionContext): Future[Unit] = { RetryUtils.runWithin(underlying(container), duration) } } - private[docker] case class Looped(underlying: DockerReadyChecker, - attempts: Int, - delay: FiniteDuration) - extends DockerReadyChecker { + private[docker] case class Looped( + underlying: DockerReadyChecker, + attempts: Int, + delay: FiniteDuration + ) extends DockerReadyChecker { - override def apply(container: BaseContainer)(implicit docker: ContainerCommandExecutor, - ec: ExecutionContext): Future[Unit] = { + override def apply( + container: BaseContainer + )(implicit docker: ContainerCommandExecutor, ec: ExecutionContext): Future[Unit] = { def attempt(attemptsLeft: Int): Future[Unit] = { underlying(container) @@ -163,17 +176,19 @@ object DockerReadyChecker { } private[docker] case class F(f: BaseContainer => Future[Unit]) extends DockerReadyChecker { - override def apply(container: BaseContainer)(implicit docker: ContainerCommandExecutor, - ec: ExecutionContext): Future[Unit] = + override def apply( + container: BaseContainer + )(implicit docker: ContainerCommandExecutor, ec: ExecutionContext): Future[Unit] = f(container) } - case class Jdbc(driverClass: String, - user: String, - password: Option[String], - database: Option[String] = None, - port: Option[Int] = None) - extends DockerReadyChecker { + case class Jdbc( + driverClass: String, + user: String, + password: Option[String], + database: Option[String] = None, + port: Option[Int] = None + ) extends DockerReadyChecker { private val driverLower = driverClass.toLowerCase private[Jdbc] val dbms: String = if (driverLower.contains("mysql")) { @@ -184,8 +199,9 @@ object DockerReadyChecker { throw new IllegalArgumentException("unsupported database for ready check") } - override def apply(container: BaseContainer)(implicit docker: ContainerCommandExecutor, - ec: ExecutionContext): Future[Unit] = { + override def apply( + container: BaseContainer + )(implicit docker: ContainerCommandExecutor, ec: ExecutionContext): Future[Unit] = { Future(scala.concurrent.blocking { try { diff --git a/project/plugins.sbt b/project/plugins.sbt index c0b5303..b8ff9da 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,4 +2,4 @@ addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.3") addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.1") -addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.5.1") +addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.6.0-RC1") diff --git a/samples/src/main/scala/com/whisk/docker/testkit/DockerElasticsearchService.scala b/samples/src/main/scala/com/whisk/docker/testkit/DockerElasticsearchService.scala index 6f1b3c6..6c71e3c 100644 --- a/samples/src/main/scala/com/whisk/docker/testkit/DockerElasticsearchService.scala +++ b/samples/src/main/scala/com/whisk/docker/testkit/DockerElasticsearchService.scala @@ -30,7 +30,8 @@ trait DockerElasticsearchService extends DockerTestKitForAll { DockerReadyChecker .HttpResponseCode(DefaultElasticsearchHttpPort, "/") .within(100.millis) - .looped(20, 1250.millis)) + .looped(20, 1250.millis) + ) .toContainer override val managedContainers: ManagedContainers = elasticsearchContainer.toManagedContainer diff --git a/scalatest/src/main/scala/com/whisk/docker/testkit/scalatest/DockerTestKitForAll.scala b/scalatest/src/main/scala/com/whisk/docker/testkit/scalatest/DockerTestKitForAll.scala index 56a18c0..b13d5ec 100644 --- a/scalatest/src/main/scala/com/whisk/docker/testkit/scalatest/DockerTestKitForAll.scala +++ b/scalatest/src/main/scala/com/whisk/docker/testkit/scalatest/DockerTestKitForAll.scala @@ -22,10 +22,12 @@ trait DockerTestKitForAll extends SuiteMixin { self: Suite => implicit lazy val dockerExecutor: ContainerCommandExecutor = new ContainerCommandExecutor(dockerClient) - lazy val containerManager = new DockerContainerManager(managedContainers, - dockerExecutor, - dockerTestTimeouts, - dockerExecutionContext) + lazy val containerManager = new DockerContainerManager( + managedContainers, + dockerExecutor, + dockerTestTimeouts, + dockerExecutionContext + ) abstract override def run(testName: Option[String], args: Args): Status = { containerManager.start() From b8317ad34dc625279c4651cf0bd8c63847516c58 Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Tue, 29 May 2018 22:33:49 +0100 Subject: [PATCH 24/36] update travis scala versions --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index e5debb1..4236b4b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,8 +11,8 @@ before_install: language: scala scala: - - 2.12.4 - - 2.11.11 + - 2.12.6 + - 2.11.12 jdk: - oraclejdk8 From 2ea77f6bf0da6bbae0ae583992231bc0048f3297 Mon Sep 17 00:00:00 2001 From: Vova Date: Mon, 1 Apr 2019 13:19:28 +0300 Subject: [PATCH 25/36] 0.10.0-beta6 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 6d13ec7..659fa3b 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ lazy val commonSettings = Seq( organization := "com.whisk", - version := "0.10.0-beta5", + version := "0.10.0-beta6", scalaVersion := "2.12.6", crossScalaVersions := Seq("2.12.4", "2.11.12"), scalacOptions ++= Seq("-feature", "-deprecation"), From 6d0b7bee6c196ed64cb0e7851b205fc8342541c2 Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Sun, 5 May 2019 22:18:08 +0100 Subject: [PATCH 26/36] 0.10.0-beta7 --- .gitignore | 1 + .travis.yml | 2 +- build.sbt | 22 +++++++++++++++++----- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index c73cbe1..212ad60 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ target/ +target-shaded/ project/sbt-launch-*.jar .idea/ .DS_Store diff --git a/.travis.yml b/.travis.yml index 4236b4b..c97decc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ before_install: language: scala scala: - - 2.12.6 + - 2.12.8 - 2.11.12 jdk: diff --git a/build.sbt b/build.sbt index 659fa3b..a2b270c 100644 --- a/build.sbt +++ b/build.sbt @@ -1,8 +1,8 @@ lazy val commonSettings = Seq( organization := "com.whisk", - version := "0.10.0-beta6", - scalaVersion := "2.12.6", - crossScalaVersions := Seq("2.12.4", "2.11.12"), + version := "0.10.0-beta7", + scalaVersion := "2.12.8", + crossScalaVersions := Seq("2.12.8", "2.11.12"), scalacOptions ++= Seq("-feature", "-deprecation"), fork in Test := true, licenses += ("MIT", url("http://opensource.org/licenses/MIT")), @@ -36,7 +36,7 @@ lazy val root = .in(file(".")) .settings(commonSettings: _*) .settings(publish := {}, publishLocal := {}, packagedArtifacts := Map.empty) - .aggregate(core, scalatest, samples) + .aggregate(core, scalatest, samples, coreShaded) lazy val core = project @@ -45,7 +45,7 @@ lazy val core = name := "docker-testkit-core", libraryDependencies ++= Seq( "org.slf4j" % "slf4j-api" % "1.7.25", - "com.spotify" % "docker-client" % "8.11.5", + "com.spotify" % "docker-client" % "8.15.2", "com.google.code.findbugs" % "jsr305" % "3.0.1", ) ) @@ -79,3 +79,15 @@ lazy val tests = ) ) .dependsOn(core, scalatest, samples % "test") + + +lazy val coreShaded = + project + .in(file("core")) + .settings(commonSettings: _*) + .settings(name := "docker-testkit-core-shaded", + libraryDependencies ++= + Seq("com.spotify" % "docker-client" % "8.15.2" classifier "shaded", + "com.google.code.findbugs" % "jsr305" % "3.0.1"), + target := baseDirectory.value / "target-shaded" + ) \ No newline at end of file From 93ff7eea3e1700c44d5a259cdd96095af652120f Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Sat, 13 Jul 2019 18:53:36 +0100 Subject: [PATCH 27/36] build for scala 2.13 --- .travis.yml | 1 + build.sbt | 6 +++--- .../main/scala/com/whisk/docker/testkit/ContainerSpec.scala | 4 ++-- .../com/whisk/docker/testkit/DockerContainerManager.scala | 2 +- project/build.properties | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index c97decc..467d638 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ before_install: language: scala scala: + - 2.13.0 - 2.12.8 - 2.11.12 diff --git a/build.sbt b/build.sbt index a2b270c..e7113da 100644 --- a/build.sbt +++ b/build.sbt @@ -1,8 +1,8 @@ lazy val commonSettings = Seq( organization := "com.whisk", version := "0.10.0-beta7", - scalaVersion := "2.12.8", - crossScalaVersions := Seq("2.12.8", "2.11.12"), + scalaVersion := "2.13.0", + crossScalaVersions := Seq("2.13.0", "2.12.8", "2.11.12"), scalacOptions ++= Seq("-feature", "-deprecation"), fork in Test := true, licenses += ("MIT", url("http://opensource.org/licenses/MIT")), @@ -56,7 +56,7 @@ lazy val scalatest = .settings( name := "docker-testkit-scalatest", libraryDependencies ++= Seq( - "org.scalatest" %% "scalatest" % "3.0.4", + "org.scalatest" %% "scalatest" % "3.0.8", "ch.qos.logback" % "logback-classic" % "1.2.3" % "test" ) ) diff --git a/core/src/main/scala/com/whisk/docker/testkit/ContainerSpec.scala b/core/src/main/scala/com/whisk/docker/testkit/ContainerSpec.scala index 4b68713..a7a387c 100644 --- a/core/src/main/scala/com/whisk/docker/testkit/ContainerSpec.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/ContainerSpec.scala @@ -22,7 +22,7 @@ case class ContainerSpec(image: String) { def withExposedPorts(ports: Int*): ContainerSpec = { val binds: Seq[(Int, PortBinding)] = - ports.map(p => p -> PortBinding.randomPort("0.0.0.0"))(collection.breakOut) + ports.map(p => p -> PortBinding.randomPort("0.0.0.0")).toSeq withPortBindings(binds: _*) } @@ -30,7 +30,7 @@ case class ContainerSpec(image: String) { val binds: Map[String, java.util.List[PortBinding]] = ps.map { case (guestPort, binding) => guestPort.toString -> Collections.singletonList(binding) - }(collection.breakOut) + }.toMap hostConfigBuilder.portBindings(binds.asJava) builder.exposedPorts(binds.keySet.asJava) diff --git a/core/src/main/scala/com/whisk/docker/testkit/DockerContainerManager.scala b/core/src/main/scala/com/whisk/docker/testkit/DockerContainerManager.scala index acb2ddc..f410eb9 100644 --- a/core/src/main/scala/com/whisk/docker/testkit/DockerContainerManager.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/DockerContainerManager.scala @@ -129,7 +129,7 @@ class DockerContainerManager( } def stopRmAll(): Future[Unit] = { - val future = Future.traverse(registeredContainers.asScala) { + val future = Future.traverse(registeredContainers.asScala.toSeq) { case (cid, _) => executor.remove(cid, force = true, removeVolumes = true) } diff --git a/project/build.properties b/project/build.properties index 7c81737..c0bab04 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.1.5 +sbt.version=1.2.8 From 9a3440de9f5a90c7662eb7c2768b42c2b19e35aa Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Sat, 13 Jul 2019 18:54:11 +0100 Subject: [PATCH 28/36] v0.10.0-beta8 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index e7113da..d4ec5ec 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ lazy val commonSettings = Seq( organization := "com.whisk", - version := "0.10.0-beta7", + version := "0.10.0-beta8", scalaVersion := "2.13.0", crossScalaVersions := Seq("2.13.0", "2.12.8", "2.11.12"), scalacOptions ++= Seq("-feature", "-deprecation"), From 733feda25322181b88ca8b64eec3f91a52c5b006 Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Sat, 8 Aug 2020 12:22:54 +0100 Subject: [PATCH 29/36] sbt 1.3.13 --- .jvmopts | 12 ++++-------- project/build.properties | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/.jvmopts b/.jvmopts index 5e4a229..e66da10 100644 --- a/.jvmopts +++ b/.jvmopts @@ -1,8 +1,4 @@ --Dfile.encoding=UTF8 --Xms1G --Xmx2G --XX:ReservedCodeCacheSize=250M --XX:+TieredCompilation --XX:-UseGCOverheadLimit --XX:+CMSClassUnloadingEnabled --XX:+UseConcMarkSweepGC +-Xms512M +-Xmx4096M +-Xss2M +-XX:MaxMetaspaceSize=1024M \ No newline at end of file diff --git a/project/build.properties b/project/build.properties index c0bab04..0837f7a 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.2.8 +sbt.version=1.3.13 From a76de7a83a30fa73fb97148990abcd44d79b2719 Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Sat, 8 Aug 2020 12:25:09 +0100 Subject: [PATCH 30/36] scalafmt 2.6.4 --- .scalafmt.conf | 1 + .../testkit/ContainerCommandExecutor.scala | 7 +++--- .../docker/testkit/DockerReadyChecker.scala | 23 +++++++++++-------- .../com/whisk/docker/testkit/package.scala | 9 ++++---- project/plugins.sbt | 2 +- 5 files changed, 23 insertions(+), 19 deletions(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 1818d46..63f357d 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,2 +1,3 @@ +version = "2.6.4" style = default maxColumn = 100 diff --git a/core/src/main/scala/com/whisk/docker/testkit/ContainerCommandExecutor.scala b/core/src/main/scala/com/whisk/docker/testkit/ContainerCommandExecutor.scala index d6d9af5..a6307b4 100644 --- a/core/src/main/scala/com/whisk/docker/testkit/ContainerCommandExecutor.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/ContainerCommandExecutor.scala @@ -54,8 +54,7 @@ class ContainerCommandExecutor(val client: DockerClient) { attempt(10) } - private def logStreamFuture(id: String, withErr: Boolean)( - implicit + private def logStreamFuture(id: String, withErr: Boolean)(implicit ec: ExecutionContext ): Future[LogStream] = { val baseParams = List(AttachParameter.STDOUT, AttachParameter.STREAM, AttachParameter.LOGS) @@ -100,8 +99,8 @@ class ContainerCommandExecutor(val client: DockerClient) { } } - def remove(id: String, force: Boolean, removeVolumes: Boolean)( - implicit ec: ExecutionContext + def remove(id: String, force: Boolean, removeVolumes: Boolean)(implicit + ec: ExecutionContext ): Future[Unit] = { Future( scala.concurrent.blocking( diff --git a/core/src/main/scala/com/whisk/docker/testkit/DockerReadyChecker.scala b/core/src/main/scala/com/whisk/docker/testkit/DockerReadyChecker.scala index fdc1a4e..f8793da 100644 --- a/core/src/main/scala/com/whisk/docker/testkit/DockerReadyChecker.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/DockerReadyChecker.scala @@ -29,17 +29,20 @@ object RetryUtils { def withDelay[T](delay: Long)(f: => Future[T]): Future[T] = { val timer = new Timer() val promise = Promise[T]() - timer.schedule(new TimerTask { - override def run(): Unit = { - promise.completeWith(f) - timer.cancel() - } - }, delay) + timer.schedule( + new TimerTask { + override def run(): Unit = { + promise.completeWith(f) + timer.cancel() + } + }, + delay + ) promise.future } - def runWithin[T](future: => Future[T], deadline: FiniteDuration)( - implicit ec: ExecutionContext + def runWithin[T](future: => Future[T], deadline: FiniteDuration)(implicit + ec: ExecutionContext ): Future[T] = { val bail = Promise[T]() withDelay(deadline.toMillis)( @@ -50,8 +53,8 @@ object RetryUtils { Future.firstCompletedOf(future :: bail.future :: Nil) } - def looped[T](future: => Future[T], attempts: Int, delay: FiniteDuration)( - implicit ec: ExecutionContext + def looped[T](future: => Future[T], attempts: Int, delay: FiniteDuration)(implicit + ec: ExecutionContext ): Future[T] = { def attempt(rest: Int): Future[T] = { future.recoverWith { diff --git a/core/src/main/scala/com/whisk/docker/testkit/package.scala b/core/src/main/scala/com/whisk/docker/testkit/package.scala index 84ec042..34a1c7a 100644 --- a/core/src/main/scala/com/whisk/docker/testkit/package.scala +++ b/core/src/main/scala/com/whisk/docker/testkit/package.scala @@ -9,10 +9,11 @@ import scala.concurrent.{Future, Promise} */ package object testkit { implicit class OptionalOps[A](val content: A) extends AnyVal { - def withOption[B](optional: Option[B])(f: (A, B) => A): A = optional match { - case None => content - case Some(x) => f(content, x) - } + def withOption[B](optional: Option[B])(f: (A, B) => A): A = + optional match { + case None => content + case Some(x) => f(content, x) + } } private[docker] class SinglePromise[T] { diff --git a/project/plugins.sbt b/project/plugins.sbt index b8ff9da..5ad60ad 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,4 +2,4 @@ addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.3") addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.1") -addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.6.0-RC1") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") From cd61b010ce0869510dd8d838791f83fea225f55d Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Sat, 8 Aug 2020 12:28:01 +0100 Subject: [PATCH 31/36] update scala version --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index d4ec5ec..25edd4a 100644 --- a/build.sbt +++ b/build.sbt @@ -1,8 +1,8 @@ lazy val commonSettings = Seq( organization := "com.whisk", version := "0.10.0-beta8", - scalaVersion := "2.13.0", - crossScalaVersions := Seq("2.13.0", "2.12.8", "2.11.12"), + scalaVersion := "2.13.3", + crossScalaVersions := Seq("2.13.3", "2.12.12", "2.11.12"), scalacOptions ++= Seq("-feature", "-deprecation"), fork in Test := true, licenses += ("MIT", url("http://opensource.org/licenses/MIT")), From ca032e99348e392d9f7fa7db3a0121f397e40285 Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Sat, 8 Aug 2020 12:32:55 +0100 Subject: [PATCH 32/36] update docker-client and scalatest libs --- build.sbt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index 25edd4a..e533ab1 100644 --- a/build.sbt +++ b/build.sbt @@ -45,7 +45,7 @@ lazy val core = name := "docker-testkit-core", libraryDependencies ++= Seq( "org.slf4j" % "slf4j-api" % "1.7.25", - "com.spotify" % "docker-client" % "8.15.2", + "com.spotify" % "docker-client" % "8.16.0", "com.google.code.findbugs" % "jsr305" % "3.0.1", ) ) @@ -56,7 +56,7 @@ lazy val scalatest = .settings( name := "docker-testkit-scalatest", libraryDependencies ++= Seq( - "org.scalatest" %% "scalatest" % "3.0.8", + "org.scalatest" %% "scalatest" % "3.1.2", "ch.qos.logback" % "logback-classic" % "1.2.3" % "test" ) ) @@ -87,7 +87,7 @@ lazy val coreShaded = .settings(commonSettings: _*) .settings(name := "docker-testkit-core-shaded", libraryDependencies ++= - Seq("com.spotify" % "docker-client" % "8.15.2" classifier "shaded", + Seq("com.spotify" % "docker-client" % "8.16.0" classifier "shaded", "com.google.code.findbugs" % "jsr305" % "3.0.1"), target := baseDirectory.value / "target-shaded" ) \ No newline at end of file From 4fc22447822531fe010190afe83ef45b064538d7 Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Sat, 8 Aug 2020 12:34:32 +0100 Subject: [PATCH 33/36] fix scalatest deprecations --- .../whisk/docker/testkit/test/ElasticsearchServiceTest.scala | 4 ++-- .../com/whisk/docker/testkit/test/MongodbServiceTest.scala | 4 ++-- .../com/whisk/docker/testkit/test/MultiContainerTest.scala | 4 ++-- .../com/whisk/docker/testkit/test/MysqlServiceTest.scala | 4 ++-- .../com/whisk/docker/testkit/test/PostgresServiceTest.scala | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/src/test/scala/com/whisk/docker/testkit/test/ElasticsearchServiceTest.scala b/tests/src/test/scala/com/whisk/docker/testkit/test/ElasticsearchServiceTest.scala index e107d78..e42b2cb 100644 --- a/tests/src/test/scala/com/whisk/docker/testkit/test/ElasticsearchServiceTest.scala +++ b/tests/src/test/scala/com/whisk/docker/testkit/test/ElasticsearchServiceTest.scala @@ -1,9 +1,9 @@ package com.whisk.docker.testkit.test import com.whisk.docker.testkit.{ContainerState, DockerElasticsearchService} -import org.scalatest.FunSuite +import org.scalatest.funsuite.AnyFunSuite -class ElasticsearchServiceTest extends FunSuite with DockerElasticsearchService { +class ElasticsearchServiceTest extends AnyFunSuite with DockerElasticsearchService { test("test container started") { assert(elasticsearchContainer.state().isInstanceOf[ContainerState.Ready], diff --git a/tests/src/test/scala/com/whisk/docker/testkit/test/MongodbServiceTest.scala b/tests/src/test/scala/com/whisk/docker/testkit/test/MongodbServiceTest.scala index 71b0556..716e03e 100644 --- a/tests/src/test/scala/com/whisk/docker/testkit/test/MongodbServiceTest.scala +++ b/tests/src/test/scala/com/whisk/docker/testkit/test/MongodbServiceTest.scala @@ -1,9 +1,9 @@ package com.whisk.docker.testkit.test import com.whisk.docker.testkit.{ContainerState, DockerMongodbService} -import org.scalatest.FunSuite +import org.scalatest.funsuite.AnyFunSuite -class MongodbServiceTest extends FunSuite with DockerMongodbService { +class MongodbServiceTest extends AnyFunSuite with DockerMongodbService { test("test container started") { assert(mongodbContainer.state().isInstanceOf[ContainerState.Ready], "mongodb is ready") diff --git a/tests/src/test/scala/com/whisk/docker/testkit/test/MultiContainerTest.scala b/tests/src/test/scala/com/whisk/docker/testkit/test/MultiContainerTest.scala index 1d0bda2..4d4af1d 100644 --- a/tests/src/test/scala/com/whisk/docker/testkit/test/MultiContainerTest.scala +++ b/tests/src/test/scala/com/whisk/docker/testkit/test/MultiContainerTest.scala @@ -1,10 +1,10 @@ package com.whisk.docker.testkit.test import com.whisk.docker.testkit._ -import org.scalatest.FunSuite +import org.scalatest.funsuite.AnyFunSuite class MultiContainerTest - extends FunSuite + extends AnyFunSuite with DockerElasticsearchService with DockerMongodbService { diff --git a/tests/src/test/scala/com/whisk/docker/testkit/test/MysqlServiceTest.scala b/tests/src/test/scala/com/whisk/docker/testkit/test/MysqlServiceTest.scala index ac01a7d..61b438a 100644 --- a/tests/src/test/scala/com/whisk/docker/testkit/test/MysqlServiceTest.scala +++ b/tests/src/test/scala/com/whisk/docker/testkit/test/MysqlServiceTest.scala @@ -1,9 +1,9 @@ package com.whisk.docker.testkit.test import com.whisk.docker.testkit.{ContainerState, DockerMysqlService} -import org.scalatest.FunSuite +import org.scalatest.funsuite.AnyFunSuite -class MysqlServiceTest extends FunSuite with DockerMysqlService { +class MysqlServiceTest extends AnyFunSuite with DockerMysqlService { test("test container started") { assert(mysqlContainer.state().isInstanceOf[ContainerState.Ready], "mysql is ready") diff --git a/tests/src/test/scala/com/whisk/docker/testkit/test/PostgresServiceTest.scala b/tests/src/test/scala/com/whisk/docker/testkit/test/PostgresServiceTest.scala index 8449381..f1ce660 100644 --- a/tests/src/test/scala/com/whisk/docker/testkit/test/PostgresServiceTest.scala +++ b/tests/src/test/scala/com/whisk/docker/testkit/test/PostgresServiceTest.scala @@ -1,9 +1,9 @@ package com.whisk.docker.testkit.test import com.whisk.docker.testkit.{ContainerState, DockerPostgresService} -import org.scalatest.FunSuite +import org.scalatest.funsuite.AnyFunSuite -class PostgresServiceTest extends FunSuite with DockerPostgresService { +class PostgresServiceTest extends AnyFunSuite with DockerPostgresService { test("test container started") { assert(postgresContainer.state().isInstanceOf[ContainerState.Ready], "postgres is ready") From d5441dcbe9a498cd66c7ecfa1d40a4a5a98e3f42 Mon Sep 17 00:00:00 2001 From: Viktor Taranenko Date: Sat, 8 Aug 2020 12:35:35 +0100 Subject: [PATCH 34/36] v0.10.0-beta9 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index e533ab1..69a2988 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ lazy val commonSettings = Seq( organization := "com.whisk", - version := "0.10.0-beta8", + version := "0.10.0-beta9", scalaVersion := "2.13.3", crossScalaVersions := Seq("2.13.3", "2.12.12", "2.11.12"), scalacOptions ++= Seq("-feature", "-deprecation"), From 8cac4d2d15d65585c27288473cd125c84e18e86f Mon Sep 17 00:00:00 2001 From: "C.Solovev" Date: Wed, 8 Dec 2021 13:57:42 +0400 Subject: [PATCH 35/36] Release 0.10.0-RC --- build.sbt | 2 +- notes/0.10.0.markdown | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 notes/0.10.0.markdown diff --git a/build.sbt b/build.sbt index be2ae8c..f5353c1 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ lazy val commonSettings = Seq( organization := "com.whisk", - version := "0.10.0-beta9", + version := "0.10.0-RC", scalaVersion := "2.13.6", crossScalaVersions := Seq("2.13.6", "2.12.15", "2.11.12", "3.0.2"), scalacOptions ++= Seq("-feature", "-deprecation"), diff --git a/notes/0.10.0.markdown b/notes/0.10.0.markdown new file mode 100644 index 0000000..ff1852e --- /dev/null +++ b/notes/0.10.0.markdown @@ -0,0 +1,11 @@ +#scala #docker #integration-testing + +## Highlights + +* global inner refactoring, packages were changed +* removed publishing of next artifacts: + - docker-testkit-config + - docker-testkit-impl-spotify + - docker-testkit-impl-spotify-shaded + - docker-testkit-impl-docker-java +* `"com.spotify" % "docker-client" % "8.16.0"` used as default backend From f9ebb0aed91db138b6a7ecdaabe6494555bd9e23 Mon Sep 17 00:00:00 2001 From: "C.Solovev" Date: Wed, 8 Dec 2021 14:01:20 +0400 Subject: [PATCH 36/36] Temporary remove supports scala 3 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index f5353c1..725b7c8 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ lazy val commonSettings = Seq( organization := "com.whisk", version := "0.10.0-RC", scalaVersion := "2.13.6", - crossScalaVersions := Seq("2.13.6", "2.12.15", "2.11.12", "3.0.2"), + crossScalaVersions := Seq("2.13.6", "2.12.15", "2.11.12"), scalacOptions ++= Seq("-feature", "-deprecation"), Test / fork := true, licenses += ("MIT", url("http://opensource.org/licenses/MIT")),