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

pl.touk.nussknacker.engine.management.FlinkSlotsChecker.scala Maven / Gradle / Ivy

The newest version!
package pl.touk.nussknacker.engine.management

import cats.data.OptionT
import cats.implicits._
import com.typesafe.scalalogging.LazyLogging
import org.apache.flink.configuration.CoreOptions
import pl.touk.nussknacker.engine.api.StreamMetaData
import pl.touk.nussknacker.engine.api.process.ProcessName
import pl.touk.nussknacker.engine.canonicalgraph.CanonicalProcess
import pl.touk.nussknacker.engine.deployment.ExternalDeploymentId
import pl.touk.nussknacker.engine.management.FlinkSlotsChecker.{NotEnoughSlotsException, SlotsBalance}
import pl.touk.nussknacker.engine.management.rest.FlinkClient
import pl.touk.nussknacker.engine.management.rest.flinkRestModel.ClusterOverview

import scala.concurrent.{ExecutionContext, Future}
import scala.util.control.NonFatal

class FlinkSlotsChecker(client: FlinkClient)(implicit ec: ExecutionContext) extends LazyLogging {

  def checkRequiredSlotsExceedAvailableSlots(
      canonicalProcess: CanonicalProcess,
      currentlyDeployedJobsIds: List[ExternalDeploymentId]
  ): Future[Unit] = {
    val collectedSlotsCheckInputs = for {
      slotsBalance    <- determineSlotsBalance(canonicalProcess, currentlyDeployedJobsIds)
      clusterOverview <- OptionT(client.getClusterOverview.map(Option(_)))
    } yield (slotsBalance, clusterOverview)

    val checkResult = for {
      collectedInputs <- OptionT(collectedSlotsCheckInputs.value.recover { case NonFatal(ex) =>
        logger.warn(
          "Error during collecting inputs needed for available slots checking. Slots checking will be omitted",
          ex
        )
        None
      })
      (slotsBalance, clusterOverview) = collectedInputs
      _ <- OptionT(
        if (slotsBalance.value > clusterOverview.`slots-available`)
          Future.failed(NotEnoughSlotsException(clusterOverview, slotsBalance))
        else
          Future.successful(Option(()))
      )
    } yield ()
    checkResult.value.map(_ => ())
  }

  private def determineSlotsBalance(
      canonicalProcess: CanonicalProcess,
      currentlyDeployedJobsIds: List[ExternalDeploymentId]
  ): OptionT[Future, SlotsBalance] = {
    canonicalProcess.metaData.typeSpecificData match {
      case stream: StreamMetaData =>
        val requiredSlotsFuture = for {
          releasedSlots  <- slotsThatWillBeReleasedAfterJobCancel(currentlyDeployedJobsIds)
          allocatedSlots <- slotsAllocatedByProcessThatWilBeDeployed(stream, canonicalProcess.metaData.name)
        } yield Option(SlotsBalance(releasedSlots, allocatedSlots))
        OptionT(requiredSlotsFuture)
      case _ => OptionT.none
    }
  }

  private def slotsThatWillBeReleasedAfterJobCancel(
      currentlyDeployedJobsIds: List[ExternalDeploymentId]
  ): Future[Int] = {
    Future
      .sequence(
        currentlyDeployedJobsIds
          .map(deploymentId => client.getJobConfig(deploymentId.value).map(_.`job-parallelism`))
      )
      .map(_.sum)
  }

  private def slotsAllocatedByProcessThatWilBeDeployed(
      stream: StreamMetaData,
      processName: ProcessName
  ): Future[Int] = {
    stream.parallelism
      .map(definedParallelism => Future.successful(definedParallelism))
      .getOrElse(client.getJobManagerConfig.map { config =>
        val defaultParallelism = config.get(CoreOptions.DEFAULT_PARALLELISM)
        logger.debug(
          s"Not specified parallelism for process: $processName, will be used default configured on jobmanager: $defaultParallelism"
        )
        defaultParallelism
      })
  }

}

object FlinkSlotsChecker {

  case class NotEnoughSlotsException(availableSlots: Int, totalSlots: Int, slotsBalance: SlotsBalance)
      extends IllegalArgumentException(
        s"Not enough free slots on Flink cluster. Available slots: $availableSlots, requested: ${Math
            .max(0, slotsBalance.value)}. ${if (slotsBalance.allocated > 1)
            "Decrease scenario's parallelism or extend Flink cluster resources"
          else
            "Extend resources of Flink cluster resources"}"
      )

  object NotEnoughSlotsException {

    def apply(clusterOverview: ClusterOverview, slotsBalance: SlotsBalance): NotEnoughSlotsException =
      NotEnoughSlotsException(
        availableSlots = clusterOverview.`slots-available`,
        totalSlots = clusterOverview.`slots-total`,
        slotsBalance = slotsBalance
      )

  }

  case class SlotsBalance(released: Int, allocated: Int) {
    def value: Int = allocated - released
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy