Skip to content

Commit 4b90591

Browse files
committed
PMM-347 coordinator and monitoring support
1 parent cf2eaac commit 4b90591

File tree

9 files changed

+137
-248
lines changed

9 files changed

+137
-248
lines changed

http/src/main/scala/ru/itclover/tsp/http/HttpService.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ trait HttpService extends RoutesProtocols {
2525
implicit val system: ActorSystem
2626
implicit val materializer: ActorMaterializer
2727
implicit val executionContext: ExecutionContextExecutor
28+
implicit val queueManagerService: QueueManagerService
2829

2930
val blockingExecutorContext: ExecutionContextExecutor
3031

@@ -39,8 +40,9 @@ trait HttpService extends RoutesProtocols {
3940

4041
val res = for {
4142
jobs <- JobsRoutes.fromExecutionContext(blockingExecutorContext)
43+
monitoring <- MonitoringRoutes.fromExecutionContext(queueManagerService)
4244
validation <- ValidationRoutes.fromExecutionContext()
43-
} yield jobs ~ validation
45+
} yield jobs ~ monitoring ~ validation
4446

4547
log.debug("composeRoutes finished")
4648
res

http/src/main/scala/ru/itclover/tsp/http/Launcher.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,5 +153,5 @@ object Launcher extends App with HttpService {
153153
}
154154

155155

156-
val queueManager = QueueManagerService.getOrCreate("mgr", blockingExecutorContext)
156+
implicit val queueManagerService = QueueManagerService.getOrCreate("mgr", blockingExecutorContext)
157157
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package ru.itclover.tsp.http.routes
2+
3+
import akka.actor.ActorSystem
4+
import akka.http.scaladsl.model.StatusCodes.{BadRequest, InternalServerError}
5+
import akka.http.scaladsl.model.{HttpResponse, StatusCodes, Uri}
6+
import akka.http.scaladsl.server.Directives._
7+
import akka.http.scaladsl.server.Route
8+
import akka.stream.ActorMaterializer
9+
import cats.data.Reader
10+
import com.typesafe.scalalogging.Logger
11+
import ru.itclover.tsp.BuildInfo
12+
import ru.itclover.tsp.http.domain.output.{FailureResponse, SuccessfulResponse}
13+
import ru.itclover.tsp.http.protocols.RoutesProtocols
14+
import ru.itclover.tsp.http.services.queuing.QueueManagerService
15+
import ru.itclover.tsp.streaming.checkpointing.CheckpointingService
16+
17+
import scala.concurrent.ExecutionContextExecutor
18+
import scala.util.Success
19+
20+
object MonitoringRoutes {
21+
22+
private val log = Logger[MonitoringRoutes]
23+
24+
def fromExecutionContext(
25+
queueManagerService: QueueManagerService
26+
)(implicit as: ActorSystem, am: ActorMaterializer): Reader[ExecutionContextExecutor, Route] = {
27+
28+
log.debug("fromExecutionContext started")
29+
30+
Reader { execContext =>
31+
new MonitoringRoutes {
32+
implicit override val executionContext = execContext
33+
implicit override val actors = as
34+
implicit override val materializer = am
35+
implicit override val qm = queueManagerService
36+
}.route
37+
}
38+
39+
}
40+
log.debug("fromExecutionContext finished")
41+
}
42+
43+
trait MonitoringRoutes extends RoutesProtocols {
44+
implicit val qm: QueueManagerService
45+
46+
implicit val executionContext: ExecutionContextExecutor
47+
implicit val actors: ActorSystem
48+
implicit val materializer: ActorMaterializer
49+
50+
val route: Route = path("job" / Segment / "status") { uuid =>
51+
CheckpointingService.getCheckpoint(uuid) match {
52+
case Some(details) => complete(Map("rowsRead" -> details.readRows, "rowsWritten" -> details.writtenRows))
53+
case None => complete((BadRequest, FailureResponse(4006, "No such job.", Seq.empty)))
54+
//case Failure(err) => complete((InternalServerError, FailureResponse(5005, err)))
55+
}
56+
} ~ path("jobs" / "overview") {
57+
complete(qm.getRunningJobsIds)
58+
} ~
59+
path("metainfo" / "getVersion") {
60+
complete(
61+
SuccessfulResponse(
62+
Map(
63+
"tsp" -> BuildInfo.version,
64+
"scala" -> BuildInfo.scalaVersion,
65+
)
66+
)
67+
)
68+
}
69+
70+
}

http/src/main/scala/ru/itclover/tsp/http/services/coordinator/CoordinatorService.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ case class CoordinatorService (coordUri: String)
6767
method = HttpMethods.POST,
6868
uri = uri,
6969
entity = HttpEntity(ContentTypes.`application/json`,
70-
s"""{"jobId": "$jobId", "success": $success, "error": "$error"}""")
70+
s"""{"jobId": "$jobId", "success": $success, "error": "Exception occurred"}""")
7171
)
7272
)
7373

http/src/main/scala/ru/itclover/tsp/http/services/queuing/QueueManagerService.scala

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
66
import akka.http.scaladsl.model.{HttpRequest, Uri}
77
import akka.http.scaladsl.unmarshalling.Unmarshal
88
import akka.stream.ActorMaterializer
9-
import cats.effect.IO
9+
import cats.effect.{Deferred, IO}
10+
import cats.effect.syntax.async
1011
import cats.effect.unsafe.implicits.global
12+
import cats.implicits.toTraverseOps
1113
import com.typesafe.scalalogging.Logger
14+
import fs2.concurrent.SignallingRef
1215
import ru.itclover.tsp.StreamSource.Row
1316
import ru.itclover.tsp.core.{Incident, RawPattern}
1417
import ru.itclover.tsp.{JdbcSource, KafkaSource, RowWithIdx, StreamSource}
@@ -17,7 +20,6 @@ import ru.itclover.tsp.dsl.PatternFieldExtractor
1720
import ru.itclover.tsp.http.domain.input.{FindPatternsRequest, QueueableRequest}
1821
import ru.itclover.tsp.http.protocols.RoutesProtocols
1922
import ru.itclover.tsp.http.services.coordinator.CoordinatorService
20-
import ru.itclover.tsp.http.services.streaming.MonitoringServiceModel.{JobDetails, Vertex, VertexMetrics}
2123
import ru.itclover.tsp.streaming.io.{InputConf, JDBCInputConf, KafkaInputConf}
2224
import ru.itclover.tsp.streaming.io.{JDBCOutputConf, KafkaOutputConf, OutputConf}
2325
import ru.itclover.tsp.streaming.mappers.PatternsToRowMapper
@@ -35,7 +37,7 @@ import scala.concurrent.{Await, ExecutionContextExecutor, Future}
3537
import scala.reflect.ClassTag
3638
import scala.util.{Failure, Success, Try}
3739
import collection.JavaConverters._
38-
40+
import scala.collection.mutable.ListBuffer
3941

4042
class QueueManagerService(id: String, blockingExecutionContext: ExecutionContextExecutor)(
4143
implicit executionContext: ExecutionContextExecutor,
@@ -49,7 +51,6 @@ class QueueManagerService(id: String, blockingExecutionContext: ExecutionContext
4951
type TypedRequest = (QueueableRequest, String)
5052
type Request = FindPatternsRequest[RowWithIdx, Symbol, Any, Row]
5153

52-
5354
case class Metric(id: String, value: String)
5455

5556
implicit val metricFmt = jsonFormat2(Metric.apply)
@@ -60,6 +61,8 @@ class QueueManagerService(id: String, blockingExecutionContext: ExecutionContext
6061
//log.warn(s"Recovering job queue: ${jobQueue.count} entries found")
6162
val jobQueue = mutable.Queue[TypedRequest]()
6263

64+
val runningStreams = mutable.Map[String, SignallingRef[IO, Boolean]]()
65+
6366
val isLocalhost: Boolean = true
6467

6568
val ex = new ScheduledThreadPoolExecutor(1)
@@ -72,17 +75,15 @@ class QueueManagerService(id: String, blockingExecutionContext: ExecutionContext
7275

7376
def enqueue(r: Request): Unit = {
7477
jobQueue.enqueue(
75-
(r,
76-
confClassTagToString(ClassTag(r.inputConf.getClass))
77-
)
78-
)
78+
(r, confClassTagToString(ClassTag(r.inputConf.getClass)))
79+
)
7980
log.info(s"Job ${r.uuid} enqueued.")
8081
}
8182

8283
def confClassTagToString(ct: ClassTag[_]): String = ct.runtimeClass match {
83-
case c if c.isAssignableFrom(classOf[JDBCInputConf]) => "from-jdbc"
84+
case c if c.isAssignableFrom(classOf[JDBCInputConf]) => "from-jdbc"
8485
case c if c.isAssignableFrom(classOf[KafkaInputConf]) => "from-kafka"
85-
case _ => "unknown"
86+
case _ => "unknown"
8687
}
8788

8889
def getQueuedJobs: Seq[QueueableRequest] = jobQueue.map(_._1).toSeq
@@ -101,7 +102,9 @@ class QueueManagerService(id: String, blockingExecutionContext: ExecutionContext
101102
_ = log.info("JDBC-to-JDBC: stream started")
102103
} yield result
103104
resultOrErr match {
104-
case Left(error) => log.error(s"Cannot run request. Reason: $error")
105+
case Left(error) =>
106+
log.error(s"Cannot run request. Reason: $error")
107+
CoordinatorService.notifyJobCompleted(uuid, Some(new Exception(error.toString)))
105108
case Right(_) => log.info(s"Stream successfully started!")
106109
}
107110
}
@@ -124,7 +127,6 @@ class QueueManagerService(id: String, blockingExecutionContext: ExecutionContext
124127
}
125128
}
126129

127-
128130
/*def dequeueAndRun(slots: Int): Unit = {
129131
// TODO: Functional style
130132
var slotsRemaining = slots
@@ -204,24 +206,39 @@ class QueueManagerService(id: String, blockingExecutionContext: ExecutionContext
204206
CoordinatorService.notifyJobStarted(uuid)
205207

206208
// Run the streams (multiple sinks)
207-
streams.foreach { stream =>
208-
stream.compile.drain.unsafeRunAsync {
209-
case Left(throwable) =>
210-
log.error(s"Job $uuid failed: $throwable")
211-
CoordinatorService.notifyJobCompleted(uuid, Some(throwable))
212-
case Right(_) =>
213-
// success
214-
log.info(s"Job $uuid finished")
215-
CoordinatorService.notifyJobCompleted(uuid, None)
216-
}
217-
}
209+
SignallingRef[IO, Boolean](false)
210+
.flatMap { signal =>
211+
runningStreams(uuid) = signal
212+
streams
213+
.sequence
214+
.interruptWhen(signal)
215+
.compile
216+
.drain
217+
}
218+
.unsafeRunAsync {
219+
case Left(throwable) =>
220+
log.error(s"Job $uuid failed: $throwable")
221+
CoordinatorService.notifyJobCompleted(uuid, Some(throwable))
222+
runningStreams.remove(uuid)
223+
case Right(_) =>
224+
// success
225+
log.info(s"Job $uuid finished")
226+
CoordinatorService.notifyJobCompleted(uuid, None)
227+
runningStreams.remove(uuid)
228+
}
218229

219230
log.debug("runStream finished")
220231
Right(None)
221232
}
222233

223-
def availableSlots: Future[Int] = Future(32)
234+
def stopStream(uuid: String): Unit = runningStreams.get(uuid).map { signal =>
235+
log.info(s"Job $uuid stopped")
236+
signal.set(true)
237+
}
238+
239+
def getRunningJobsIds: Seq[String] = runningStreams.keys.toSeq
224240

241+
def availableSlots: Future[Int] = Future(32)
225242

226243
def onTimer(): Unit = {
227244
availableSlots.onComplete {

http/src/main/scala/ru/itclover/tsp/http/services/streaming/MonitoringServiceModel.scala

Lines changed: 0 additions & 107 deletions
This file was deleted.

integration/correctness/src/test/scala/ru/itclover/tsp/http/SimpleCasesTest.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import cats.effect.IO
77
import cats.effect.unsafe.implicits.global
88
import com.dimafeng.testcontainers._
99
import fs2.kafka.{Acks, KafkaProducer, ProducerRecord, ProducerRecords, ProducerSettings, Serializer}
10+
import ru.itclover.tsp.http.services.queuing.QueueManagerService
1011
import ru.itclover.tsp.streaming.io.{IntESValue, StringESValue}
1112

1213
import scala.concurrent.duration.FiniteDuration
@@ -48,6 +49,8 @@ class SimpleCasesTest
4849
with RoutesProtocols {
4950
implicit override val executionContext: ExecutionContextExecutor = scala.concurrent.ExecutionContext.global
5051

52+
override val queueManagerService: QueueManagerService = QueueManagerService.getOrCreate("mgr", executionContext)
53+
5154
// to run blocking tasks.
5255
val blockingExecutorContext: ExecutionContextExecutor =
5356
ExecutionContext.fromExecutor(

0 commit comments

Comments
 (0)