All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.logicovercode.wdocker.DockerReadyChecker.scala Maven / Gradle / Ivy

The newest version!
package com.logicovercode.wdocker

import java.net.{HttpURLConnection, URL}
import java.util.{Timer, TimerTask}

import scala.concurrent.duration.FiniteDuration
import scala.concurrent.{ExecutionContext, Future, Promise, TimeoutException}

trait DockerReadyChecker {

  def apply(container: DockerContainerState)(implicit docker: DockerCommandExecutor,
                                             ec: ExecutionContext): Future[Boolean]

  @deprecated("this method will be removed. Use DockerReadyChecker.And(a, b)", "0.9.6")
  def and(other: DockerReadyChecker): DockerReadyChecker = {
    DockerReadyChecker.And(this, other)
  }

  @deprecated("this method will be removed. Use DockerReadyChecker.Or(a, b)", "0.9.6")
  def or(other: DockerReadyChecker): DockerReadyChecker = {
    DockerReadyChecker.Or(this, other)
  }

  def within(duration: FiniteDuration): DockerReadyChecker = {
    DockerReadyChecker.TimeLimited(this, duration)
  }

  def looped(attempts: Int, delay: FiniteDuration): DockerReadyChecker = {
    DockerReadyChecker.Looped(this, attempts, delay)
  }
}

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)
    promise.future
  }

  def runWithin[T](future: => Future[T], deadline: FiniteDuration)(
      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.firstCompletedOf(future :: bail.future :: Nil)
  }

  def looped[T](future: => Future[T], attempts: Int, delay: FiniteDuration)(
      implicit ec: ExecutionContext): Future[T] = {
    def attempt(rest: Int): Future[T] = {
      future.recoverWith {
        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))
          }
      }
    }

    attempt(attempts)
  }
}

object DockerReadyChecker {

  case class And(r1: DockerReadyChecker, r2: DockerReadyChecker) extends DockerReadyChecker {
    override def apply(container: DockerContainerState)(implicit docker: DockerCommandExecutor,
                                                        ec: ExecutionContext): Future[Boolean] = {
      val aF = r1(container)
      val bF = r2(container)
      for {
        a <- aF
        b <- bF
      } yield a && b
    }
  }

  case class Or(r1: DockerReadyChecker, r2: DockerReadyChecker) extends DockerReadyChecker {
    override def apply(container: DockerContainerState)(implicit docker: DockerCommandExecutor,
                                                        ec: ExecutionContext): Future[Boolean] = {
      val aF = r1(container)
      val bF = r2(container)
      val p = Promise[Boolean]()
      aF.map {
        case true => p.trySuccess(true)
        case _    =>
      }
      bF.map {
        case true => p.trySuccess(true)
        case _    =>
      }
      p.future
    }
  }

  object Always extends DockerReadyChecker {
    override def apply(container: DockerContainerState)(implicit docker: DockerCommandExecutor,
                                                        ec: ExecutionContext): Future[Boolean] =
      Future.successful(true)
  }

  case class HttpResponseCode(port: Int,
                              path: String = "/",
                              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 {
          val con = url.openConnection().asInstanceOf[HttpURLConnection]
          try {
            con.getResponseCode == code
          } catch {
            case e: java.net.ConnectException =>
              false
          }
        }
      }
    }
  }

  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
      }
    }
  }

  private[wdocker] case class TimeLimited(underlying: DockerReadyChecker, duration: FiniteDuration)
      extends DockerReadyChecker {

    override def apply(container: DockerContainerState)(implicit docker: DockerCommandExecutor,
                                                        ec: ExecutionContext): Future[Boolean] = {
      RetryUtils.runWithin(underlying(container), duration).recover {
        case _: TimeoutException =>
          false
      }
    }
  }

  private[wdocker] case class Looped(underlying: DockerReadyChecker,
                                    attempts: Int,
                                    delay: FiniteDuration)
      extends DockerReadyChecker {

    override def apply(container: DockerContainerState)(implicit docker: DockerCommandExecutor,
                                                        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] =
      f(container)
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy