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

com.twitter.finagle.Resolver.scala Maven / Gradle / Ivy

The newest version!
package com.twitter.finagle

import com.twitter.conversions.time._
import com.twitter.finagle.util._
import com.twitter.util.{Return, Throw, Try, Var, FuturePool}
import java.net.SocketAddress
import java.security.{PrivilegedAction, Security}
import java.util.WeakHashMap
import java.util.logging.{Level, Logger}

/**
 * A resolver binds a name, represented by a string, to a
 * variable address. Resolvers have an associated scheme
 * which is used for lookup so that names may be resolved
 * in a global context.
 *
 * These are loaded by Finagle through the
 * [[com.twitter.finagle.util.LoadService service loading mechanism]]. Thus, in
 * order to implement a new resolver, a class implementing `Resolver` with a
 * 0-arg constructor must be registered in a file named
 * `META-INF/services/com.twitter.finagle.Resolver` included in the classpath; see
 * Oracle's
 * [[http://docs.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html ServiceLoader]]
 * documentation for further details.
 */
trait Resolver {
  val scheme: String
  def bind(arg: String): Var[Addr]

  @deprecated("Use Resolver.bind", "6.7.x")
  final def resolve(name: String): Try[Group[SocketAddress]] =
    bind(name) match {
      case Var.Sampled(Addr.Failed(e)) => Throw(e)
      case va => Return(Group.fromVarAddr(va))
    }
}

/**
 * An abstract class version of Resolver for java compatibility.
 */
abstract class AbstractResolver extends Resolver

/**
 * Resolver for inet scheme. This Resolver resolves DNS after each TTL timeout.
 * The TTL is gotten from "networkaddress.cache.ttl", a Java Security Property.
 * If "networkaddress.cache.ttl" is not set or set to a non-positive value, DNS
 * cache refresh will be turned off.
 */
object InetResolver extends Resolver {
  val scheme = "inet"
  private val log = Logger.getLogger(getClass.getName)
  private val ttlOption = {
    val t = Try(Option(java.security.AccessController.doPrivileged(
      new PrivilegedAction[String] {
        override def run(): String = Security.getProperty("networkaddress.cache.ttl")
      }
    )) map { s => s.toInt })

    t match {
      case Return(Some(value)) =>
        if (value <= 0) {
          log.log(Level.INFO,
            "networkaddress.cache.ttl is set as non-positive value, DNS cache refresh turned off")
          None
        } else {
          val duration = value.seconds
          log.log(Level.CONFIG, "networkaddress.cache.ttl found to be %s".format(duration) +
            " will refresh DNS every %s.".format(duration))
          Some(duration)
        }
      case Return(None) =>
        log.log(Level.INFO, "networkaddress.cache.ttl is not set, DNS cache refresh turned off")
        None
      case Throw(exc: NumberFormatException) =>
        log.log(Level.WARNING,
          "networkaddress.cache.ttl is set as non-number, DNS cache refresh turned off", exc)
        None
      case Throw(exc) =>
        log.log(Level.WARNING, "Unexpected Exception is thrown when getting " +
          "networkaddress.cache.ttl, DNS cache refresh turned off", exc)
        None
    }
  }
  private val timer = DefaultTimer.twitter
  private val futurePool = FuturePool.unboundedPool

  /**
   * Binds to the specified hostnames, and refreshes the DNS information periodically.
   */
  def bind(hosts: String): Var[Addr] = {
    import InetSocketAddressUtil._

    val hostPorts = parseHostPorts(hosts)
    val init = Addr.Bound(resolveHostPorts(hostPorts))
    ttlOption match {
      case Some(ttl) =>
        Var.async(init) { u =>
          implicit val intPri = new Prioritized[Int] { def apply(i: Int) = i }
          val updater = Updater { _: Int =>
            val addr = resolveHostPorts(hostPorts)
            u() = Addr.Bound(addr)
          }
          timer.schedule(ttl.fromNow, ttl) {
            futurePool {
              updater(0)
            } onFailure { ex =>
              log.log(Level.WARNING, "failed to resolve hosts ", ex)
            }
          }
        }
      case None =>
        Var.value(init)
    }
  }
}

object NegResolver extends Resolver {
  val scheme = "neg"
  def bind(arg: String) = Var.value(Addr.Neg)
}

object NilResolver extends Resolver {
  val scheme = "nil"
  def bind(arg: String) = Var.value(Addr.Bound())
}

object FailResolver extends Resolver {
  val scheme = "fail"
  def bind(arg: String) = Var.value(Addr.Failed(new Exception(arg)))
}

class ResolverNotFoundException(scheme: String)
  extends Exception(
    "Resolver not found for scheme \"%s\". Please add the jar containing this resolver to your classpath".format(scheme))

class ResolverAddressInvalid(addr: String)
  extends Exception("Resolver address \"%s\" is not valid".format(addr))

class MultipleResolversPerSchemeException(resolvers: Map[String, Seq[Resolver]])
  extends NoStacktrace
{
  override def getMessage = {
    val msgs = resolvers map { case (scheme, rs) =>
      "%s=(%s)".format(scheme, rs.map(_.getClass.getName).mkString(", "))
    } mkString(" ")
    "Multiple resolvers defined: %s".format(msgs)
  }
}

object Resolver {
  private[this] lazy val resolvers = {
    val rs = LoadService[Resolver]()
    val log = Logger.getLogger(getClass.getName)
    val resolvers = Seq(InetResolver, NegResolver, NilResolver, FailResolver) ++ rs

    val dups = resolvers groupBy(_.scheme) filter { case (_, rs) => rs.size > 1 }
    if (dups.size > 0) throw new MultipleResolversPerSchemeException(dups)

    for (r <- resolvers)
      log.info("Resolver[%s] = %s(%s)".format(r.scheme, r.getClass.getName, r))
    resolvers
  }

  def get[T <: Resolver](clazz: Class[T]): Option[T] =
    resolvers find { _.getClass isAssignableFrom clazz } map { _.asInstanceOf[T] }

  private[this] sealed trait Token
  private[this] case class El(e: String) extends Token
  private[this] object Eq extends Token
  private[this] object Bang extends Token

  private[this] def delex(ts: Seq[Token]) =
    ts map {
      case El(e) => e
      case Bang => "!"
      case Eq => "="
    } mkString ""

  private[this] def lex(s: String) = {
    s.foldLeft(List[Token]()) {
      case (ts, '=') => Eq :: ts
      case (ts, '!') => Bang :: ts
      case (El(s) :: ts, c) => El(s+c) :: ts
      case (ts, c) => El(""+c) :: ts
    }
  }.reverse

  /**
   * Resolve a group from an address, a string. Resolve uses
   * `Resolver`s to do this. These are loaded via the Java
   * [[http://docs.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html ServiceLoader]]
   * mechanism. The default resolver is "inet", resolving DNS
   * name/port pairs.
   *
   * Target names have a simple grammar: The name of the resolver
   * precedes the name of the address to be resolved, separated by
   * an exclamation mark ("bang"). For example: inet!twitter.com:80
   * resolves the name "twitter.com:80" using the "inet" resolver. If no
   * resolver name is present, the inet resolver is used.
   *
   * Names resolved by this mechanism are also a
   * [[com.twitter.finagle.LabelledGroup]]. By default, this name is
   * simply the `addr` string, but it can be overriden by prefixing
   * a name separated by an equals sign from the rest of the addr.
   * For example, the addr "www=inet!google.com:80" resolves
   * "google.com:80" with the inet resolver, but the returned group's
   * [[com.twitter.finagle.LabelledGroup]] name is "www".
   */
  @deprecated("Use Resolver.eval", "6.7.x")
  def resolve(addr: String): Try[Group[SocketAddress]] =
    Try { eval(addr) } flatMap {
        case Name.Path(_) =>
          Throw(new IllegalArgumentException("Resolver.resolve does not support logical names"))
        case [email protected](_) =>
          Return(NameGroup(bound))
      }

  /**
   * Parse and evaluate the argument into a Name. Eval parses
   * a simple grammar: a scheme is followed by a bang, followed
   * by an argument:
   * 	name := scheme ! arg
   * The scheme is looked up from registered Resolvers, and the
   * argument is passed in.
   *
   * When `name` begins with the character '/' it is intepreted to be
   * a logical name whose interpetation is subject to a
   * [[com.twitter.finagle.Dtab Dtab]].
   *
   * Eval throws exceptions upon failure to parse the name, or
   * on failure to scheme lookup. Since names are late bound,
   * binding failures are deferred.
   */
  def eval(name: String): Name =
    if (name startsWith "/") Name(name)
    else {
      val (resolver, arg) = lex(name) match {
        case (Eq :: _) | (Bang :: _) =>
          throw new ResolverAddressInvalid(name)

        case El(scheme) :: Bang :: name =>
          resolvers.find(_.scheme == scheme) match {
            case Some(resolver) =>  (resolver, delex(name))
            case None => throw new ResolverNotFoundException(scheme)
          }

        case ts => (InetResolver, delex(ts))
      }

      Name.Bound(resolver.bind(arg), name)
    }

  /**
   * Parse and evaluate the argument into a (Name, label: String) tuple.
   * Arguments are parsed with the same grammar as in `eval`. If a label is not
   * provided (i.e. no "label="), then the empty string is returned.
   */
  private[finagle] def evalLabeled(addr: String): (Name, String) = {
    val (label, rest) = lex(addr) match {
      case El(n) :: Eq :: rest => (n, rest)
      case rest => ("", rest)
    }

    (eval(delex(rest)), label)
  }
}

private object ServerRegistry {
  private val addrNames = new WeakHashMap[SocketAddress, String]

  // This is a terrible hack until we have a better
  // way of labelling addresses.

  def register(addr: String): SocketAddress =
    addr.split("=", 2) match {
      case Array(addr) =>
        val Seq(ia) = InetSocketAddressUtil.parseHosts(addr)
        ia
      case Array(name, addr) =>
        val Seq(ia) = InetSocketAddressUtil.parseHosts(addr)
        addrNames.put(ia, name)
        ia
    }

  def nameOf(addr: SocketAddress): Option[String] =
    Option(addrNames.get(addr))
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy