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

com.metamx.tranquility.druid.TaskClient.scala Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to Metamarkets Group Inc. (Metamarkets) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  Metamarkets licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.metamx.tranquility.druid

import com.github.nscala_time.time.Imports._
import com.metamx.common.Backoff
import com.metamx.common.scala.Logging
import com.metamx.common.scala.event.WARN
import com.metamx.common.scala.event.emit.emitAlert
import com.metamx.common.scala.untyped.Dict
import com.metamx.common.scala.untyped.str
import com.metamx.emitter.service.ServiceEmitter
import com.metamx.tranquility.druid.TaskClient.timer
import com.metamx.tranquility.finagle._
import com.twitter.finagle.util.DefaultTimer
import com.twitter.finagle.Service
import com.twitter.finagle.http
import com.twitter.util.Closable
import com.twitter.util.Future
import com.twitter.util.Time
import com.twitter.util.Timer
import java.io.IOException

/**
 * Client for a single Druid task.
 */
class TaskClient(
  val task: TaskPointer,
  client: Service[http.Request, http.Response],
  dataSource: String,
  quietPeriod: Period,
  retryPeriod: Period,
  indexService: IndexService,
  emitter: ServiceEmitter
) extends Logging with Closable
{
  // Assume tasks start running
  @volatile private[this] var _status: IndexStatus = TaskRunning

  def status_=(newStatus: IndexStatus) = synchronized {
    // Don't allow transitions out of inactive states
    if (active && _status != newStatus) {
      log.info("Task %s status changed from %s -> %s", task.id, _status, newStatus)
      _status = newStatus
    }
  }

  def status = _status

  def active = Seq(TaskRunning, TaskNotFound) contains _status

  def apply(request: http.Request): Future[Option[http.Response]] = {
    val retryable = IndexService.isTransient(retryPeriod)
    FutureRetry.onErrors(Seq(retryable), Backoff.standard(), DateTime.now + quietPeriod) {
      client(request) map {
        response =>
          val code = response.statusCode
          val reason = response.status.reason
          if (code / 100 == 2) {
            if (log.isTraceEnabled) {
              log.trace(
                "Sent request to task[%s], serviceKey[%s], got response: %s",
                task.id,
                task.serviceKey,
                response.contentString
              )
            }
            Some(response)
          } else {
            throw new IOException(
              "Failed to send request to task[%s]: %s %s" format(task.id, code, reason)
            )
          }
      } rescue {
        case e: Exception if retryable(e) =>
          // This is a retryable exception.
          (if (!active) {
            // Possibly give up by returning None if the task has completed.
            Future(status)
          } else {
            indexService.status(task.id)
          }) map {
            newStatus =>
              status = newStatus
              if (!active) {
                // Task inactive, let's give up
                emitAlert(
                  log, emitter, WARN, "Loss of Druid redundancy: %s" format dataSource, Dict(
                    "dataSource" -> dataSource,
                    "task" -> task.id,
                    "status" -> newStatus.toString
                  )
                )
                None
              } else {
                // Task still active, allow retry
                throw new IOException("Failed to send request to task[%s] (status = %s)" format(task.id, status), e)
              }
          }
      }
    }
  }

  override def close(deadline: Time): Future[Unit] = {
    client.close(deadline)
  }
}

object TaskClient
{
  implicit val timer: Timer = DefaultTimer.twitter
}

case class TaskPointer(id: String, serviceKey: String)
{
  def toMap = Dict(
    "id" -> id,
    "serviceKey" -> serviceKey
  )
}

object TaskPointer
{
  def fromMap(d: Dict): TaskPointer = {
    TaskPointer(
      str(d("id")),
      str(d("serviceKey"))
    )
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy