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

tech.ytsaurus.spyt.wrapper.discovery.CypressDiscoveryService.scala Maven / Gradle / Ivy

package tech.ytsaurus.spyt.wrapper.discovery

import org.slf4j.LoggerFactory
import tech.ytsaurus.spyt.HostAndPort
import tech.ytsaurus.spyt.wrapper.YtWrapper
import tech.ytsaurus.spyt.wrapper.operation.OperationStatus
import tech.ytsaurus.client.CompoundClient
import tech.ytsaurus.client.request.GetOperation
import tech.ytsaurus.core.GUID
import tech.ytsaurus.ysontree.YTreeNode

import java.util.Optional
import scala.concurrent.duration._
import scala.language.{implicitConversions, postfixOps}
import scala.util.{Failure, Success, Try}

class CypressDiscoveryService(baseDiscoveryPath: String)(implicit yt: CompoundClient) extends DiscoveryService {
  private val log = LoggerFactory.getLogger(getClass)

  private val discoveryPath: String = s"$baseDiscoveryPath/discovery"

  private def addressPath: String = s"$discoveryPath/spark_address"

  private def webUiPath: String = s"$discoveryPath/webui"

  private def restPath: String = s"$discoveryPath/rest"

  private def operationPath: String = s"$discoveryPath/operation"

  private def childrenOperationsPath: String = s"$discoveryPath/children_operations"

  private def shsPath: String = s"$discoveryPath/shs"

  private def livyPath: String = s"$discoveryPath/livy"

  private def clusterVersionPath: String = s"$discoveryPath/version"

  private def confPath: String = s"$discoveryPath/conf"

  private def masterWrapperPath: String = s"$discoveryPath/master_wrapper"

  override def operationInfo: Option[OperationInfo] = operation.flatMap(oid => {
    val id = GUID.valueOf(oid)
    val r = yt.getOperation(new GetOperation(id)).join()
    if (r.getAttribute("state").isPresent) {
      Try(OperationStatus.getByName(r.getAttribute("state").get().stringValue()))
        .toOption
        .map(s => OperationInfo(id, s))
    } else None
  })

  override def registerMaster(operationId: String,
                              address: Address,
                              clusterVersion: String,
                              masterWrapperEndpoint: HostAndPort,
                              clusterConf: SparkConfYsonable): Unit = {
    val clearDir = discoverAddress() match {
      case Success(_) if operation.exists(_ != operationId) && operationInfo.exists(!_.state.isFinished) =>
        throw new IllegalStateException(s"Spark instance with path $discoveryPath already exists")
      case Success(_) =>
        log.info(s"Spark instance with path $discoveryPath registered, but is not alive, rewriting id")
        true
      case Failure(EmptyDirectoryException(_)) =>
        log.info(s"Spark instance with path $discoveryPath doesn't exist, registering new one")
        false
      case Failure(ex) =>
        throw ex
    }

    val transaction = YtWrapper.createTransaction(None, 1 minute)
    val tr = Some(transaction.getId.toString)
    try {
      if (clearDir) removeAddress(tr)
      YtWrapper.createDir(s"$addressPath/${YtWrapper.escape(address.hostAndPort.toString)}", tr)
      Map(
        webUiPath -> YtWrapper.escape(address.webUiHostAndPort.toString),
        restPath -> YtWrapper.escape(address.restHostAndPort.toString),
        operationPath -> operationId,
        clusterVersionPath -> clusterVersion,
        masterWrapperPath -> YtWrapper.escape(masterWrapperEndpoint.toString)
      ).foreach { case (path, value) =>
        YtWrapper.createDir(s"$path/$value", tr)
      }
      YtWrapper.createDocumentFromProduct(confPath, clusterConf, tr)
    } catch {
      case e: Throwable =>
        transaction.abort().join()
        throw e
    }
    transaction.commit().join()
  }

  override def registerWorker(operationId: String): Unit = {
    log.info(s"Registering worker operation $operationId")
    if (!operation.contains(operationId) && operationId != null && !operationId.isBlank) {
      log.info(s"Registering worker operation $operationId: started")
      val tr = YtWrapper.createTransaction(None, 1 minute)
      YtWrapper.createDir(s"$childrenOperationsPath/$operationId", Some(tr.getId.toString), ignoreExisting = true)
      tr.commit().join()
      log.info(s"Registering worker operation $operationId: completed")
    }
  }

  private def registerSimpleService(dirPath: String, address: HostAndPort): Unit = {
    val transaction = YtWrapper.createTransaction(None, 1 minute)
    val tr = Some(transaction.getId.toString)
    val addr = YtWrapper.escape(address.toString)
    YtWrapper.removeDirIfExists(dirPath, recursive = true, transaction = tr)
    YtWrapper.createDir(s"$dirPath/$addr", tr)
    transaction.commit().join()
  }

  override def registerSHS(address: HostAndPort): Unit = {
    registerSimpleService(shsPath, address)
  }

  override def registerLivy(address: HostAndPort, livyVersion: String): Unit = {
    registerSimpleService(livyPath, address)
  }

  private def cypressHostAndPort(path: String): Try[HostAndPort] = {
    getPath(path).map(HostAndPort.fromString)
  }

  private def getPath(path: String): Try[String] =
    if (YtWrapper.exists(path))
      Try(YtWrapper.listDir(path)).map(_.head)
    else
      Failure(EmptyDirectoryException(s"Path not found: $path"))


  override def discoverAddress(): Try[Address] =
    for {
      _ <- getPath(confPath).recover { case InvalidCatalogException(msg) => EmptyDirectoryException(msg) }
      hostAndPort <- cypressHostAndPort(addressPath)
      webUiHostAndPort <- cypressHostAndPort(webUiPath)
      restHostAndPort <- cypressHostAndPort(restPath)
    } yield Address(hostAndPort, webUiHostAndPort, restHostAndPort)

  def clusterVersion: Try[String] = getPath(clusterVersionPath)


  override def masterWrapperEndpoint(): Option[HostAndPort] = cypressHostAndPort(masterWrapperPath).toOption

  private def operation: Option[String] = getPath(operationPath).toOption

  override def operations(): Option[OperationSet] = {
    def isDriverOp: String => Boolean = opId => {
      import CypressDiscoveryService._
      yt.getOperation(new GetOperation(GUID.valueOf(opId)))
        .join()
        .path("full_spec", "tasks", "drivers")
        .isDefined
    }
    operation.map(masterId => {
      val allChildren = if (YtWrapper.exists(childrenOperationsPath)) {
        YtWrapper.listDir(childrenOperationsPath).toSet
      } else {
        Set[String]()
      }
      val children = allChildren.filterNot(isDriverOp)
      val driverOp = allChildren.find(isDriverOp)
      OperationSet(masterId, children, driverOp)
    })
  }

  override def toString: String = s"CypressDiscovery[$baseDiscoveryPath]"

  private def removeAddress(transaction: Option[String]): Unit = {
    YtWrapper.listDir(discoveryPath)
      .map(name => s"$discoveryPath/$name")
      .filter(_ != shsPath)
      .foreach(YtWrapper.removeDirIfExists(_, recursive = true, transaction = transaction))
  }
}

object CypressDiscoveryService {
  def eventLogPath(discoveryBasePath: String): String = {
    s"$discoveryBasePath/logs/event_log_table"
  }

  implicit def convertOptional[T](opt: Optional[T]): Option[T] = {
    if (opt.isPresent)
      Some(opt.get())
    else
      None
  }

  implicit class YTreeNodeExt(n: YTreeNode) {
    def path(path: String*): Option[YTreeNode] =
      path.foldLeft(Option(n)) { case (no, p) =>
        no.flatMap(v => Option(v.asMap().get(p)))
      }

    def longAttribute(path: String*): Option[Long] = n.path(path:_*).map(_.longValue())
  }
}

case class InvalidCatalogException(message: String) extends RuntimeException(message)
case class EmptyDirectoryException(message: String) extends RuntimeException(message)





© 2015 - 2025 Weber Informatics LLC | Privacy Policy