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

io.buoyant.admin.Admin.scala Maven / Gradle / Ivy

There is a newer version: 1.7.5
Show newest version
package io.buoyant.admin

import com.twitter.app.{App => TApp}
import com.twitter.concurrent.NamedPoolThreadFactory
import com.twitter.finagle._
import com.twitter.finagle.buoyant._
import com.twitter.finagle.http.filter.HeadFilter
import com.twitter.finagle.http.{HttpMuxer, Request, Response}
import com.twitter.finagle.netty4.param.WorkerPool
import com.twitter.finagle.netty4.ssl.server.Netty4ServerEngineFactory
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.finagle.tracing.NullTracer
import com.twitter.logging.Logger
import com.twitter.server.handler.{SummaryHandler => _, _}
import com.twitter.server.view.{NotFoundView, TextBlockView}
import com.twitter.util.Monitor
import java.net.InetSocketAddress
import java.util.concurrent.Executors

object Admin {
  val label = "adminhttp"
  private val log = Logger.get(label)

  case class Handler(url: String, service: Service[Request, Response], css: Seq[String] = Nil)

  /**
   * A type for modules that expose admin handlers.
   */
  trait WithHandlers {
    def adminHandlers: Seq[Handler]
  }

  def getHandlers(obj: AnyRef): Seq[Handler] =
    obj match {
      case wh: WithHandlers => wh.adminHandlers
      case _ => Nil
    }

  def extractHandlers(objs: Seq[AnyRef]): Seq[Handler] =
    objs.flatMap(getHandlers(_))

  case class NavItem(name: String, url: String)
  trait WithNavItems {
    def navItems: Seq[NavItem]
  }

  def getNavItems(obj: AnyRef): Seq[NavItem] = obj match {
    case withNav: WithNavItems => withNav.navItems
    case _ => Nil
  }

  def extractNavItems(objs: Seq[AnyRef]): Seq[NavItem] =
    objs.flatMap(getNavItems)

  private val loggingMonitor = new Monitor {
    def handle(exc: Throwable): Boolean = {
      log.error(exc, label)
      false
    }
  }

  private def makeServer(
    tls: Option[TlsServerConfig],
    workers: Int,
    socketOptionsParams: Option[SocketOptionsConfig],
    stats: StatsReceiver
  ) = {

    val workerPool = new WorkerPool(Executors.newCachedThreadPool(
      new NamedPoolThreadFactory("admin", makeDaemons = true)
    ), workers)

    Http.server
      .withLabel(label)
      .withMonitor(loggingMonitor)
      .withStatsReceiver(stats)
      .withTracer(NullTracer)
      .configured(workerPool)
      .maybeWith(socketOptionsParams.map(_.params))
      .maybeWith(tls.map(_.params(None, Netty4ServerEngineFactory())))
  }

  val threadsJs = ""

  def appHandlers(app: TApp): Seq[Handler] = Seq(
    Handler("/admin/server_info", new TextBlockView().andThen(new ServerInfoHandler(app))),
    Handler("/admin/shutdown", new ShutdownHandler(app))
  )

  def baseHandlers: Seq[Handler] = Seq(
    Handler("/admin/contention", new TextBlockView andThen new ContentionHandler),
    Handler("/admin/lint", new AdminAssetsFilter andThen new LintHandler),
    Handler("/admin/lint.json", new LintHandler),
    Handler("/admin/threads", new AdminAssetsFilter(threadsJs) andThen new ThreadsHandler),
    Handler("/admin/threads.json", new ThreadsHandler),
    Handler("/admin/announcer", new TextBlockView andThen new AnnouncerHandler),
    Handler("/admin/pprof/heap", new HeapResourceHandler),
    Handler("/admin/pprof/profile", new ProfileResourceHandler(Thread.State.RUNNABLE)),
    Handler("/admin/pprof/contention", new ProfileResourceHandler(Thread.State.BLOCKED)),
    Handler("/admin/ping", new ReplyHandler("pong")),
    Handler("/admin/tracing", new TracingHandler),
    Handler("/admin/registry.json", new RegistryHandler),
    Handler("/admin/files/", StaticFilter.andThen(ResourceHandler.fromDirectoryOrJar( // for Admin handlers
      baseRequestPath = "/admin/files/",
      baseResourcePath = "io/buoyant/admin/twitter-server",
      localFilePath = "admin/src/main/resources/io/buoyant/admin"
    ))),
    Handler("/favicon.ico", StaticFilter.andThen(ResourceHandler.fromDirectoryOrJar(
      baseRequestPath = "/",
      baseResourcePath = "io/buoyant/admin/twitter-server/images",
      localFilePath = "admin/src/main/resources/io/buoyant/admin/twitter-server/images"
    )))
  )

  /** Generate an index of the provided handlers */
  private def indexHandlers(handlers: Seq[Handler]): Seq[Handler] = {
    val paths = handlers.map(_.url).sorted.distinct
    val index = new IndexTxtHandler(paths)
    Seq(
      Handler("/admin/index.txt", index),
      Handler("/admin", index)
    )
  }
}

class Admin(
  val address: InetSocketAddress,
  tlsCfg: Option[TlsServerConfig],
  workers: Int,
  stats: StatsReceiver,
  securityConfig: Option[AdminSecurityConfig],
  socketOptionsConfig: Option[SocketOptionsConfig]
) {

  import Admin._

  private[this] val notFoundView = new NotFoundView()
  private[this] val server = makeServer(tlsCfg, workers, socketOptionsConfig, stats)

  /**
   * Whether or not this admin service was configured to serve over TLS
   */
  val isTls: Boolean = tlsCfg.isDefined

  /**
   * the name of the scheme the admin page is served over
   */
  val scheme: String = if (this.isTls) "https" else "http"

  def mkService(app: TApp, extHandlers: Seq[Handler]): Service[Request, Response] = {
    val handlers = baseHandlers ++ appHandlers(app) ++ extHandlers
    val muxer = (handlers ++ indexHandlers(handlers)).foldLeft(new HttpMuxer) {
      case (muxer, Handler(url, service, _)) =>
        log.debug("admin: %s => %s", url, service.getClass.getName)
        muxer.withHandler(url, service)
    }
    val adminService = HeadFilter andThen notFoundView andThen muxer
    securityConfig match {
      case Some(cfg) => cfg.mkFilter andThen adminService
      case None => adminService
    }
  }

  def serve(app: TApp, extHandlers: Seq[Handler]): ListeningServer =
    server.serve(address, mkService(app, extHandlers))

  def serveHandler(port: Int, handler: Handler): ListeningServer = {
    val addrWithPort = new InetSocketAddress(address.getAddress, port)
    val muxer = new HttpMuxer().withHandler(handler.url, handler.service)
    makeServer(tlsCfg, workers, socketOptionsConfig, stats).serve(addrWithPort, muxer)
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy