Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #21539: Make campaign test work #4416

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ import java.sql.Timestamp
trait CampaignEventRepository {
def get(campaignEventId: CampaignEventId) : IOResult[CampaignEvent]
def saveCampaignEvent(c : CampaignEvent) : IOResult[CampaignEvent]

/*
* Semantic is:
* - if Nil or None, clause is ignored
* - if a value is provided, then it is use to filter things accordingly
*/
def getWithCriteria(states : List[CampaignEventState], campaignType: Option[CampaignType], campaignId : Option[CampaignId]) : IOResult[List[CampaignEvent]]
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ import scala.concurrent.duration.Duration


trait Campaign {
def info : CampaignInfo
def details : CampaignDetails
def campaignType : CampaignType
def info : CampaignInfo
def details : CampaignDetails
def campaignType: CampaignType
def copyWithId(newId : CampaignId) : Campaign
}

Expand Down Expand Up @@ -144,11 +144,17 @@ case class WeeklySchedule(
case class OneShot(start : DateTime) extends CampaignSchedule

trait CampaignDetails
trait CampaignType {
def value : String
}

case class CampaignEvent(id : CampaignEventId, campaignId : CampaignId, state : CampaignEventState, start : DateTime, end : DateTime, campaignType : CampaignType )
case class CampaignType(value : String)

case class CampaignEvent(
id : CampaignEventId
, campaignId : CampaignId
, state : CampaignEventState
, start : DateTime
, end : DateTime
, campaignType: CampaignType
)
case class CampaignEventId(value : String)

sealed trait CampaignEventState{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,7 @@ class CampaignSerializer {
}

val campaignTypeBase : PartialFunction[String, CampaignType]= {
case c: String =>
new CampaignType {
def value: String = c
}
case c: String => CampaignType(c)
}


Expand All @@ -95,7 +92,9 @@ class CampaignSerializer {
def parse(string : String) : IOResult[Campaign] = tranlaters.map(_.read()).fold(readBase) { case (a, b) => b orElse a }(string)
def campaignType (string : String) : CampaignType = tranlaters.map(_.campaignType()).fold(campaignTypeBase) { case (a, b) => b orElse a }(string)

}

object CampaignSerializer {
implicit val idEncoder : JsonEncoder[CampaignId]= JsonEncoder[String].contramap(_.value)
implicit val dateTime : JsonEncoder[DateTime] = JsonEncoder[String].contramap(DateFormaterService.serialize)
implicit val dayOfWeek : JsonEncoder[DayOfWeek] = JsonEncoder[Int].contramap(_.value)
Expand All @@ -114,7 +113,6 @@ class CampaignSerializer {
implicit val statusInfoEncoder : JsonEncoder[CampaignStatus] = DeriveJsonEncoder.gen
implicit val scheduleEncoder : JsonEncoder[CampaignSchedule]= DeriveJsonEncoder.gen
implicit val campaignInfoEncoder : JsonEncoder[CampaignInfo]= DeriveJsonEncoder.gen
implicit val typeEncoder : JsonEncoder[CampaignType] = JsonEncoder[String].contramap(_.value)

implicit val idDecoder : JsonDecoder[CampaignId] = JsonDecoder[String].map(s => CampaignId(s))

Expand Down Expand Up @@ -148,19 +146,20 @@ class CampaignSerializer {
}
)

implicit val durationDecoder : JsonDecoder[Duration] = JsonDecoder[Long].map(_.millis)
implicit val statusInfoDecoder : JsonDecoder[CampaignStatus] = DeriveJsonDecoder.gen
implicit val scheduleDecoder : JsonDecoder[CampaignSchedule]= DeriveJsonDecoder.gen
implicit val typeDecoder : JsonDecoder[CampaignType] = JsonDecoder[String].map(campaignType)
implicit val campaignInfoDecoder : JsonDecoder[CampaignInfo]= DeriveJsonDecoder.gen
implicit val durationDecoder : JsonDecoder[Duration] = JsonDecoder[Long].map(_.millis)
implicit val statusInfoDecoder : JsonDecoder[CampaignStatus] = DeriveJsonDecoder.gen
implicit val scheduleDecoder : JsonDecoder[CampaignSchedule]= DeriveJsonDecoder.gen
implicit val campaignTypeDecoder: JsonDecoder[CampaignType] = JsonDecoder[String].map(CampaignType)
implicit val campaignInfoDecoder: JsonDecoder[CampaignInfo]= DeriveJsonDecoder.gen

implicit val campaignEventIdDecoder : JsonDecoder[CampaignEventId] = JsonDecoder[String].map(CampaignEventId)
implicit val campaignEventStateDecoder : JsonDecoder[CampaignEventState] = JsonDecoder[String].mapOrFail(CampaignEventState.parse)
implicit val campaignEventDecoder : JsonDecoder[CampaignEvent] = DeriveJsonDecoder.gen
implicit val campaignEventIdDecoder : JsonDecoder[CampaignEventId] = JsonDecoder[String].map(CampaignEventId)
implicit val campaignEventStateDecoder: JsonDecoder[CampaignEventState] = JsonDecoder[String].mapOrFail(CampaignEventState.parse)
implicit val campaignEventDecoder : JsonDecoder[CampaignEvent] = DeriveJsonDecoder.gen


implicit val campaignEventIdEncoder : JsonEncoder[CampaignEventId] = JsonEncoder[String].contramap(_.value)
implicit val campaignEventStateEncoder : JsonEncoder[CampaignEventState] = JsonEncoder[String].contramap( _.value)
implicit val campaignEventEncoder : JsonEncoder[CampaignEvent] = DeriveJsonEncoder.gen
implicit val campaignTypeEncoder : JsonEncoder[CampaignType] = JsonEncoder[String].contramap(_.value)
implicit val campaignEventIdEncoder : JsonEncoder[CampaignEventId] = JsonEncoder[String].contramap(_.value)
implicit val campaignEventStateEncoder: JsonEncoder[CampaignEventState] = JsonEncoder[String].contramap( _.value)
implicit val campaignEventEncoder : JsonEncoder[CampaignEvent] = DeriveJsonEncoder.gen
}

Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,15 @@
package com.normation.rudder.campaigns

import cats.implicits._

import com.normation.errors.IOResult
import com.normation.errors.Inconsistency
import com.normation.rudder.campaigns.CampaignEventState._
import com.normation.utils.StringUuidGenerator

import com.normation.zio.ZioRuntime
import org.joda.time.DateTime

import zio.Queue
import zio.ZIO
import zio.clock.Clock
Expand Down Expand Up @@ -286,10 +289,10 @@ class MainCampaignService(repo: CampaignEventRepository, campaignRepo: CampaignR
alreadyScheduled <- repo.getWithCriteria(Running :: Scheduled :: Nil, None, None)
campaigns <- campaignRepo.getAll()
_ <- CampaignLogger.debug(s"Got ${campaigns.size} campaigns, check all started")
newEvents <- ZIO.foreach(campaigns.filterNot(c => alreadyScheduled.exists(_.campaignId == c.info.id))) {
c =>
scheduleCampaignEvent(c)
}
toStart = campaigns.filterNot(c => alreadyScheduled.exists(_.campaignId == c.info.id))
newEvents <- ZIO.foreach(toStart) { c =>
scheduleCampaignEvent(c)
}
_ <- CampaignLogger.debug(s"Scheduled ${newEvents.size} new events, queue them")
_ <- ZIO.foreach(newEvents) { ev => s.queueCampaign(ev) }
} yield {
Expand All @@ -304,7 +307,6 @@ class MainCampaignService(repo: CampaignEventRepository, campaignRepo: CampaignR
for {
_ <- CampaignLogger.debug("Starting campaign scheduler")
_ <- s.start().forkDaemon

_ <- CampaignLogger.debug("Starting campaign forked, now getting already created events")
alreadyScheduled <- repo.getWithCriteria(Running :: Scheduled :: Nil, None, None)
_ <- CampaignLogger.debug("Got events, queue them")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
package com.normation.rudder.rest.lift
import com.normation.errors.Unexpected
import com.normation.rudder.api.ApiVersion
import com.normation.rudder.apidata.ZioJsonExtractor
import com.normation.rudder.campaigns.CampaignEvent
Expand All @@ -10,12 +9,13 @@ import com.normation.rudder.campaigns.CampaignId
import com.normation.rudder.campaigns.CampaignLogger
import com.normation.rudder.campaigns.CampaignRepository
import com.normation.rudder.campaigns.CampaignSerializer
import com.normation.rudder.campaigns.CampaignSerializer._
import com.normation.rudder.campaigns.MainCampaignService
import com.normation.rudder.rest.ApiPath
import com.normation.rudder.rest.AuthzToken
import com.normation.rudder.rest.RestExtractorService
import com.normation.rudder.rest.implicits.*
import com.normation.rudder.rest.CampaignApi as API
import com.normation.rudder.rest.implicits._
import com.normation.rudder.rest.{CampaignApi => API}
import com.normation.utils.StringUuidGenerator

import net.liftweb.common.EmptyBox
Expand All @@ -24,7 +24,8 @@ import net.liftweb.http.LiftResponse
import net.liftweb.http.Req

import zio.ZIO
import zio.syntax.*
import zio.syntax._
import com.normation.errors.Unexpected

class CampaignApi (
campaignRepository: CampaignRepository
Expand All @@ -34,7 +35,6 @@ class CampaignApi (
, restExtractorService: RestExtractorService
, stringUuidGenerator: StringUuidGenerator
) extends LiftApiModuleProvider[API] {
import campaignSerializer._

def schemas = API

Expand Down Expand Up @@ -149,7 +149,6 @@ class CampaignApi (
val campaignType = req.params.get("campaignType").flatMap(_.headOption).map(campaignSerializer.campaignType)
val campaignId = req.params.get("campaignId").flatMap(_.headOption).map(i => CampaignId(i))
campaignEventRepository.getWithCriteria(states,campaignType,campaignId).toLiftResponseList(params,schema )

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ import scala.concurrent.duration.Duration
import scala.util.control.NonFatal
import scala.xml.Elem
import zio.syntax._
import zio.{Tag as _, *}
import zio.{Tag => _, _}
import com.normation.box._
import com.normation.errors.IOResult
import com.normation.errors._
Expand Down Expand Up @@ -193,7 +193,7 @@ object TestActor {
}

object revisionRepo {
import GitVersion._
import com.normation.GitVersion._

val revisionsMap = RefM.make(Map[Revision, RevisionInfo]()).runNow

Expand Down Expand Up @@ -2346,12 +2346,13 @@ class MockSettings(wfservice: WorkflowLevelService, asyncWF: AsyncWorkflowInfo)

// It would be much simpler if the root classes were concrete, parameterized with a A type:
// case class Campaign[A](info: CampaignInfo, details: A) // or even info inlined
case object DumbCampaignType extends CampaignType {
val value = "dumb-campaign"
}
final object DumbCampaignType extends CampaignType("dumb-campaign")

final case class DumbCampaignDetails(name: String) extends CampaignDetails

@jsonDiscriminator("campaignType")
sealed trait DumbCampaignTrait extends Campaign

@jsonHint(DumbCampaignType.value)
final case class DumbCampaign(info: CampaignInfo, details: DumbCampaignDetails) extends DumbCampaignTrait {
val campaignType = DumbCampaignType
Expand Down Expand Up @@ -2390,7 +2391,7 @@ class MockCampaign() {

object dumbCampaignTranslator extends JSONTranslateCampaign {
import zio.json._
import campaignSerializer._
import com.normation.rudder.campaigns.CampaignSerializer._
implicit val dumbCampaignDetailsDecoder : JsonDecoder[DumbCampaignDetails] = DeriveJsonDecoder.gen
implicit val dumbCampaignDecoder : JsonDecoder[DumbCampaignTrait] = DeriveJsonDecoder.gen
implicit val dumbCampaignDetailsEncoder : JsonEncoder[DumbCampaignDetails] = DeriveJsonEncoder.gen
Expand Down Expand Up @@ -2426,7 +2427,9 @@ class MockCampaign() {
items.get.map(_.get(id)).notOptional(s"Campaign event not found: ${id.value}")
}
def saveCampaignEvent(c : CampaignEvent) : IOResult[CampaignEvent] = {
items.update(_ + (c.id -> c)) *> c.succeed
for {
_ <- items.update(map => map + ((c.id, c)))
} yield c
}

def getWithCriteria(states: List[CampaignEventState], campaignType: Option[CampaignType], campaignId: Option[CampaignId]): IOResult[List[CampaignEvent]] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@

package com.normation.rudder.rest

import com.normation.rudder.DumbCampaignType
import com.normation.rudder.campaigns.CampaignEvent
import com.normation.rudder.campaigns.CampaignEventId
import com.normation.rudder.campaigns.CampaignEventState
import com.normation.rudder.campaigns.CampaignId
import com.normation.rudder.campaigns.MainCampaignService
import com.normation.rudder.rest.RudderJsonResponse.JsonRudderApiResponse
import com.normation.rudder.rest.RudderJsonResponse.LiftJsonResponse
Expand All @@ -52,8 +57,11 @@ import org.specs2.mutable.Specification
import org.specs2.runner.JUnitRunner
import org.specs2.specification.AfterAll

import zio.json._
import com.normation.zio._

@RunWith(classOf[JUnitRunner])
class CampaignApiTests extends Specification with AfterAll with Loggable {
class CampaignApiTest extends Specification with AfterAll with Loggable {

val restTestSetUp = RestTestSetUp.newEnv
val restTest = new RestTest(restTestSetUp.liftRules)
Expand All @@ -72,7 +80,7 @@ class CampaignApiTests extends Specification with AfterAll with Loggable {

def children(f: File) = f.children.toList.map(_.name)

// org.slf4j.LoggerFactory.getLogger("api-processing").asInstanceOf[ch.qos.logback.classic.Logger].setLevel(ch.qos.logback.classic.Level.TRACE)
org.slf4j.LoggerFactory.getLogger("campaign").asInstanceOf[ch.qos.logback.classic.Logger].setLevel(ch.qos.logback.classic.Level.TRACE)

sequential

Expand All @@ -83,32 +91,59 @@ class CampaignApiTests extends Specification with AfterAll with Loggable {
|"name":"first campaign",
|"description":"a test campaign present when rudder boot",
|"status":{"value":"enabled"},
|"schedule":{"day":1,"startHour":3,"type":"weekly"},
|"schedule":{"day":1,"startHour":3,"startMinute":42,"type":"weekly"},
|"duration":3600000},
|"details":{"name":"campaign #0"}
|"details":{"name":"campaign #0"},
|"campaignType":"dumb-campaign"
|}""".stripMargin.replaceAll("""\n""","")

// init in mock
val ce0 = CampaignEvent(
CampaignEventId("e0")
, CampaignId("c0")
, CampaignEventState.Finished
, new DateTime(0)
, new DateTime(1)
, DumbCampaignType
)

"have one campaign" in {
val resp = s"""[$c0json]"""

restTest.testGETResponse("/secure/api/campaigns/models") {
restTest.testGETResponse("/secure/api/campaigns") {
case Full(LiftJsonResponse(JsonRudderApiResponse(_, _, _, Some(map), _), _, _)) =>
map.asInstanceOf[Map[String, List[zio.json.ast.Json]]]("campaigns").toJson must beEqualTo(resp)
case err => ko(s"I got an error in test: ${err}")
}
}

// THIS TEST IS BROKEN FOR NOW
"and its json format is stable" in {
restTest.testGETResponse("/secure/api/campaigns/events") {
case Full(LiftJsonResponse(JsonRudderApiResponse(_, _, _, Some(map), _), _, _)) =>
map.asInstanceOf[Map[String, List[CampaignEvent]]]("campaignEvents") must beEqualTo(List(ce0))
case err => ko(s"I got an error in test: ${err}")
}
}

"have a second one when we trigger a scheduling check" in {
MainCampaignService.start(restTestSetUp.mockCampaign.mainCampaignService)

// the second one won't be C0, but for now, it is not even generated
val resp = s"""[$c0json,$c0json]"""
"have one more campaign event when we trigger a scheduling check" in {
ZioRuntime.unsafeRun(MainCampaignService.start(restTestSetUp.mockCampaign.mainCampaignService))

restTest.testGETResponse("/secure/api/campaigns/models") {
Thread.sleep(50) // this is needed because the start is async and takes a bit of time

restTest.testGETResponse("/secure/api/campaigns/events") {
case Full(LiftJsonResponse(JsonRudderApiResponse(_, _, _, Some(map), _), _, _)) =>
map.asInstanceOf[Map[String, List[zio.json.ast.Json]]]("campaigns").toJson must beEqualTo(resp)
val events = map.asInstanceOf[Map[String, List[CampaignEvent]]]("campaignEvents")

(events.size must beEqualTo(2)) and
{
val next = events.collectFirst { case x if x.id != ce0.id => x }.getOrElse(throw new IllegalArgumentException(s"Missing test value"))
// it's in the future
(next.start.getMillis must be_>(System.currentTimeMillis())) and
(next.state must beEqualTo(CampaignEventState.Scheduled)) and
(next.campaignId must beEqualTo(ce0.campaignId))
}

case err => ko(s"I got an error in test: ${err}")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ import java.nio.file.Files
import java.util.zip.ZipFile
import com.normation.rudder.rest.RestUtils.toJsonResponse
import com.normation.rudder.rest.v1.RestStatus
import net.liftweb.common.{Full, Loggable}
import net.liftweb.http.{InMemoryResponse, Req}
import net.liftweb.json.JsonAST.{JArray, JField, JObject}
import net.liftweb.common._
import net.liftweb.http.InMemoryResponse
import net.liftweb.http.Req
import net.liftweb.json.JsonAST._
import net.liftweb.json.JsonDSL._
import org.apache.commons.io.FileUtils
import org.junit.runner.RunWith
Expand Down Expand Up @@ -524,7 +525,8 @@ class SystemApiTest extends Specification with AfterAll with Loggable {
import net.liftweb.http.js.JsExp._
(code must beEqualTo(500)) and
(json.toJsCmd must beMatching(".*Error when trying to get archive as a Zip: SystemError: Error when retrieving commit revision.*"))
case _ => ko
case x =>
ko
}
}

Expand Down