From 35f8db60a0335f6eb6c1d7b99caaa35ef52989e1 Mon Sep 17 00:00:00 2001 From: To-om Date: Thu, 26 Aug 2021 14:03:06 +0200 Subject: [PATCH] #2182 Reload last case number from database periodically --- ScalliGraph | 2 +- .../thehive/cloner/IntegrityCheckApp.scala | 3 +- .../thp/thehive/migration/th4/Output.scala | 4 +- .../org/thp/thehive/services/CaseNumber.scala | 49 ++++++++++--------- thehive/conf/reference.conf | 2 + .../test/org/thp/thehive/TestAppBuilder.scala | 4 +- 6 files changed, 37 insertions(+), 27 deletions(-) diff --git a/ScalliGraph b/ScalliGraph index ff5911920a..3069fcc626 160000 --- a/ScalliGraph +++ b/ScalliGraph @@ -1 +1 @@ -Subproject commit ff5911920aca12e491878970e1a1061fc9998453 +Subproject commit 3069fcc62619fe5063d354acde42e5e999f22317 diff --git a/migration/src/main/scala/org/thp/thehive/cloner/IntegrityCheckApp.scala b/migration/src/main/scala/org/thp/thehive/cloner/IntegrityCheckApp.scala index 61b4bcfca1..a73f85f580 100644 --- a/migration/src/main/scala/org/thp/thehive/cloner/IntegrityCheckApp.scala +++ b/migration/src/main/scala/org/thp/thehive/cloner/IntegrityCheckApp.scala @@ -1,6 +1,7 @@ package org.thp.thehive.cloner import akka.actor.ActorSystem +import akka.actor.typed.ActorRef import akka.stream.Materializer import com.google.inject.{Guice, Injector => GInjector} import net.codingwell.scalaguice.{ScalaModule, ScalaMultibinder} @@ -40,7 +41,7 @@ trait IntegrityCheckApp { bindActor[DummyActor]("config-actor") bindActor[DummyActor]("cortex-actor") bindActor[DummyActor]("integrity-check-actor") - bindTypedActor(CaseNumberActor.behavior, "case-number-actor") + bind[ActorRef[CaseNumberActor.Request]].toProvider[CaseNumberActorProvider] val integrityCheckOpsBindings = ScalaMultibinder.newSetBinder[GenIntegrityCheckOps](binder) integrityCheckOpsBindings.addBinding.to[ProfileIntegrityCheckOps] diff --git a/migration/src/main/scala/org/thp/thehive/migration/th4/Output.scala b/migration/src/main/scala/org/thp/thehive/migration/th4/Output.scala index 2585c8e22c..89a97b32a3 100644 --- a/migration/src/main/scala/org/thp/thehive/migration/th4/Output.scala +++ b/migration/src/main/scala/org/thp/thehive/migration/th4/Output.scala @@ -1,7 +1,7 @@ package org.thp.thehive.migration.th4 import akka.actor.ActorSystem -import akka.actor.typed.Scheduler +import akka.actor.typed.{ActorRef, Scheduler} import akka.stream.Materializer import com.google.inject.{Guice, Injector => GInjector} import net.codingwell.scalaguice.{ScalaModule, ScalaMultibinder} @@ -52,7 +52,7 @@ object Output { bindActor[DummyActor]("config-actor") bindActor[DummyActor]("cortex-actor") bindActor[DummyActor]("integrity-check-actor") - bindTypedActor(CaseNumberActor.behavior, "case-number-actor") + bind[ActorRef[CaseNumberActor.Request]].toProvider[CaseNumberActorProvider] val schemaBindings = ScalaMultibinder.newSetBinder[UpdatableSchema](binder) schemaBindings.addBinding.to[TheHiveSchemaDefinition] diff --git a/thehive/app/org/thp/thehive/services/CaseNumber.scala b/thehive/app/org/thp/thehive/services/CaseNumber.scala index f25e242bb6..497d15e443 100644 --- a/thehive/app/org/thp/thehive/services/CaseNumber.scala +++ b/thehive/app/org/thp/thehive/services/CaseNumber.scala @@ -1,19 +1,21 @@ package org.thp.thehive.services -import akka.actor.typed.scaladsl.{ActorContext, Behaviors} +import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.scaladsl.adapter.ClassicActorSystemOps import akka.actor.typed.{ActorRefResolver, Behavior, ActorRef => TypedActorRef} import akka.actor.{ActorSystem, ExtendedActorSystem} import akka.cluster.typed.{ClusterSingleton, SingletonActor} import akka.serialization.Serializer import org.thp.scalligraph.models.Database +import org.thp.scalligraph.services.config.ApplicationConfig +import org.thp.scalligraph.services.config.ApplicationConfig.finiteDurationFormat import org.thp.scalligraph.traversal.TraversalOps._ -import org.thp.thehive.GuiceAkkaExtension import org.thp.thehive.services.CaseOps._ import java.io.NotSerializableException import java.nio.ByteBuffer import javax.inject.{Inject, Provider, Singleton} +import scala.concurrent.duration.FiniteDuration object CaseNumberActor { sealed trait Message @@ -21,39 +23,39 @@ object CaseNumberActor { sealed trait Response extends Message case class GetNextNumber(replyTo: TypedActorRef[Response]) extends Request case class NextNumber(number: Int) extends Response + case object ReloadFromDatabase extends Request - val behavior: Behavior[Request] = Behaviors.setup[Request](context => waitFirstRequest(context)) - - def getNextCaseNumber(context: ActorContext[Request]): Int = { - val injector = GuiceAkkaExtension(context.system).injector - val db = injector.getInstance(classOf[Database]) - val caseSrv = injector.getInstance(classOf[CaseSrv]) - db.roTransaction { implicit graph => - caseSrv.startTraversal.getLast.headOption.fold(0)(_.number) + 1 + def behavior(db: Database, appConfig: ApplicationConfig, caseSrvProvider: Provider[CaseSrv]): Behavior[Request] = { + lazy val caseSrv: CaseSrv = caseSrvProvider.get + val caseNumberReloadIntervalConfig = + appConfig.item[FiniteDuration]("caseNumber.reload.interval", "Reload last case number from database interval") + Behaviors.withTimers { timers => + val reloadTimer: () => Unit = () => timers.startSingleTimer(ReloadFromDatabase, caseNumberReloadIntervalConfig.get) + val getNextNumber: () => Int = () => + db.roTransaction { implicit graph => + caseSrv.startTraversal.getLast.headOption.fold(0)(_.number) + 1 + } + caseNumberProvider(getNextNumber, reloadTimer, getNextNumber()) } } - def waitFirstRequest(context: ActorContext[Request]): Behaviors.Receive[Request] = - Behaviors.receiveMessage { - case GetNextNumber(replyTo) => - val nextNumber = getNextCaseNumber(context) - replyTo ! NextNumber(nextNumber) - caseNumberProvider(nextNumber + 1) - } - - def caseNumberProvider(nextNumber: Int): Behavior[Request] = + def caseNumberProvider(getNextNumber: () => Int, reloadTimer: () => Unit, nextNumber: Int): Behavior[Request] = Behaviors.receiveMessage { case GetNextNumber(replyTo) => replyTo ! NextNumber(nextNumber) - caseNumberProvider(nextNumber + 1) + caseNumberProvider(getNextNumber, reloadTimer, nextNumber + 1) + case ReloadFromDatabase => + reloadTimer() + caseNumberProvider(getNextNumber, reloadTimer, Math.max(getNextNumber(), nextNumber)) } } @Singleton -class CaseNumberActorProvider @Inject() (system: ActorSystem) extends Provider[TypedActorRef[CaseNumberActor.Request]] { +class CaseNumberActorProvider @Inject() (system: ActorSystem, db: Database, appConfig: ApplicationConfig, caseSrvProvider: Provider[CaseSrv]) + extends Provider[TypedActorRef[CaseNumberActor.Request]] { override lazy val get: TypedActorRef[CaseNumberActor.Request] = ClusterSingleton(system.toTyped) - .init(SingletonActor(CaseNumberActor.behavior, "CaseNumberLeader")) + .init(SingletonActor(CaseNumberActor.behavior(db, appConfig, caseSrvProvider), "CaseNumberLeader")) } class CaseNumberSerializer(system: ExtendedActorSystem) extends Serializer { @@ -67,6 +69,7 @@ class CaseNumberSerializer(system: ExtendedActorSystem) extends Serializer { o match { case GetNextNumber(replyTo) => 0.toByte +: actorRefResolver.toSerializationFormat(replyTo).getBytes case NextNumber(number) => ByteBuffer.allocate(5).put(1.toByte).putInt(number).array() + case ReloadFromDatabase => Array(2) case _ => throw new NotSerializableException } @@ -76,5 +79,7 @@ class CaseNumberSerializer(system: ExtendedActorSystem) extends Serializer { bytes(0) match { case 0 => GetNextNumber(actorRefResolver.resolveActorRef(new String(bytes.tail))) case 1 => NextNumber(ByteBuffer.wrap(bytes).getInt(1)) + case 2 => ReloadFromDatabase + case _ => throw new NotSerializableException } } diff --git a/thehive/conf/reference.conf b/thehive/conf/reference.conf index d2e6743f45..103027f1e9 100644 --- a/thehive/conf/reference.conf +++ b/thehive/conf/reference.conf @@ -63,6 +63,8 @@ stream { } } +caseNumber.reload.interval: 10 minutes + tags.freeTagColour: "#000000" user { diff --git a/thehive/test/org/thp/thehive/TestAppBuilder.scala b/thehive/test/org/thp/thehive/TestAppBuilder.scala index 322c1a11d1..0b9c442333 100644 --- a/thehive/test/org/thp/thehive/TestAppBuilder.scala +++ b/thehive/test/org/thp/thehive/TestAppBuilder.scala @@ -131,5 +131,7 @@ class BasicDatabaseProvider @Inject() (database: Database) extends Provider[Data class TestNumberActorProvider @Inject() (actorSystem: ActorSystem) extends Provider[TypedActorRef[CaseNumberActor.Request]] { override def get: TypedActorRef[CaseNumberActor.Request] = - actorSystem.toTyped.systemActorOf(CaseNumberActor.caseNumberProvider(36), "case-number") + actorSystem + .toTyped + .systemActorOf(CaseNumberActor.caseNumberProvider(getNextNumber = () => 36, reloadTimer = () => (), nextNumber = 36), "case-number") }