diff --git a/src/main/scala/com/advancedtelematic/director/Settings.scala b/src/main/scala/com/advancedtelematic/director/Settings.scala index d489a03..9cd41f9 100644 --- a/src/main/scala/com/advancedtelematic/director/Settings.scala +++ b/src/main/scala/com/advancedtelematic/director/Settings.scala @@ -1,10 +1,7 @@ package com.advancedtelematic.director -import com.advancedtelematic.libtuf.data.TufDataType.KeyType import com.typesafe.config.ConfigFactory -import scala.util.Try - trait Settings { import Util._ @@ -14,11 +11,4 @@ trait Settings { val port = _config.getInt("server.port") val tufUri = mkUri(_config, "keyserver.uri") - val tufBinaryUri = mkUri(_config, "tuf.binary.uri") - - val defaultKeyType: Try[KeyType] = { - Try(_config.getString("daemon.defaultKeyType")).map { defaultKeyTypeName => - namedType[KeyType](defaultKeyTypeName) - } - } } diff --git a/src/main/scala/com/advancedtelematic/director/db/Repository.scala b/src/main/scala/com/advancedtelematic/director/db/Repository.scala index 26b930b..4c09a62 100644 --- a/src/main/scala/com/advancedtelematic/director/db/Repository.scala +++ b/src/main/scala/com/advancedtelematic/director/db/Repository.scala @@ -39,7 +39,21 @@ trait DeviceRepositorySupport extends DatabaseSupport { protected class DeviceRepository()(implicit val db: Database, val ec: ExecutionContext) { def create(ns: Namespace, deviceId: DeviceId, primaryEcuId: EcuIdentifier, ecus: Seq[Ecu]): Future[Unit] = { + val ecusDeleteIO = + Schema.ecus + .filter(_.namespace === ns) + .filter(_.deviceId === deviceId) + .filter(_.ecuSerial.inSet(ecus.map(_.ecuSerial))) + .delete + + val deviceDeleteIo = + Schema.devices + .filter(_.namespace === ns) + .filter(_.id === deviceId) + .delete + val io = for { + _ <- ecusDeleteIO.andThen(deviceDeleteIo) // This is a bad idea and will fail if device has assignments, see DeviceResourceSpec _ <- Schema.ecus ++= ecus _ <- Schema.devices += Device(ns, deviceId, primaryEcuId) } yield () @@ -233,7 +247,15 @@ protected class EcuRepository()(implicit val db: Database, val ec: ExecutionCont .filter(_.namespace === ns) .map(_.hardwareId) .distinct - .paginateAndSortResult(identity, offset = offset, limit = limit) + .paginateResult(offset = offset, limit = limit) + } + + def findAllDeviceIds(ns: Namespace, offset: Long, limit: Long): Future[PaginationResult[DeviceId]] = db.run { + Schema.devices + .filter(_.namespace === ns) + .map(d => (d.id, d.createdAt)) + .paginateAndSortResult(_._2, offset = offset, limit = limit) + .map(_.map(_._1)) } def findFor(deviceId: DeviceId): Future[Map[EcuIdentifier, Ecu]] = db.run { diff --git a/src/main/scala/com/advancedtelematic/director/db/Schema.scala b/src/main/scala/com/advancedtelematic/director/db/Schema.scala index 08d1c2e..a45e280 100644 --- a/src/main/scala/com/advancedtelematic/director/db/Schema.scala +++ b/src/main/scala/com/advancedtelematic/director/db/Schema.scala @@ -26,6 +26,7 @@ object Schema { def namespace = column[Namespace]("namespace") def id = column[DeviceId]("id") def primaryEcu = column[EcuIdentifier]("primary_ecu_id") + def createdAt = column[Instant]("created_at") def pk = primaryKey("devices_pk", id) diff --git a/src/main/scala/com/advancedtelematic/director/http/AdminResource.scala b/src/main/scala/com/advancedtelematic/director/http/AdminResource.scala index e04ad9d..5dbb208 100644 --- a/src/main/scala/com/advancedtelematic/director/http/AdminResource.scala +++ b/src/main/scala/com/advancedtelematic/director/http/AdminResource.scala @@ -6,34 +6,24 @@ import akka.http.scaladsl.server._ import cats.syntax.option._ import com.advancedtelematic.director.data.AdminDataType.{FindImageCount, RegisterDevice} import com.advancedtelematic.director.data.Codecs._ -import com.advancedtelematic.director.db.{AutoUpdateDefinitionRepositorySupport, DeviceRegistration, DeviceRepositorySupport, EcuRepositorySupport, RepoNamespaceRepositorySupport} +import com.advancedtelematic.director.db.{AutoUpdateDefinitionRepositorySupport, DeviceRegistration, EcuRepositorySupport, RepoNamespaceRepositorySupport} +import com.advancedtelematic.libats.codecs.CirceCodecs._ import com.advancedtelematic.libats.data.DataType.Namespace import com.advancedtelematic.libats.data.EcuIdentifier import com.advancedtelematic.libats.http.UUIDKeyAkka._ import com.advancedtelematic.libats.messaging.MessageBusPublisher -import com.advancedtelematic.libats.messaging_datatype.DataType.{DeviceId, UpdateId} +import com.advancedtelematic.libats.messaging_datatype.DataType.DeviceId import com.advancedtelematic.libtuf.data.ClientCodecs._ import com.advancedtelematic.libtuf.data.TufCodecs._ -import com.advancedtelematic.libtuf.data.TufDataType.{Ed25519KeyType, RepoId, TargetName} +import com.advancedtelematic.libtuf.data.TufDataType.{RepoId, TargetName} import com.advancedtelematic.libtuf_server.keyserver.KeyserverClient import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport._ -import com.advancedtelematic.libats.codecs.CirceCodecs._ import slick.jdbc.MySQLProfile.api._ +import PaginationParametersDirectives._ +import com.advancedtelematic.director.repo.DeviceRoleGeneration -import scala.concurrent.{ExecutionContext, Future} - -class RepositoryCreation(keyserverClient: KeyserverClient)(implicit val db: Database, val ec: ExecutionContext) - extends DeviceRepositorySupport with RepoNamespaceRepositorySupport { +import scala.concurrent.ExecutionContext - def create(ns: Namespace): Future[Unit] = { - val repoId = RepoId.generate() - - for { - _ <- keyserverClient.createRoot(repoId, Ed25519KeyType, forceSync = true) - _ <- repoNamespaceRepo.persist(repoId, ns) - } yield () - } -} class AdminResource(extractNamespace: Directive1[Namespace], val keyserverClient: KeyserverClient) (implicit val db: Database, val ec: ExecutionContext, messageBusPublisher: MessageBusPublisher) @@ -48,13 +38,7 @@ class AdminResource(extractNamespace: Directive1[Namespace], val keyserverClient val deviceRegistration = new DeviceRegistration(keyserverClient) val repositoryCreation = new RepositoryCreation(keyserverClient) - - val paginationParameters: Directive[(Long, Long)] = - (parameters('limit.as[Long].?) & parameters('offset.as[Long].?)).tmap { case (mLimit, mOffset) => - val limit = mLimit.getOrElse(50L).min(1000) - val offset = mOffset.getOrElse(0L) - (limit, offset) - } + val deviceRoleGeneration = new DeviceRoleGeneration(keyserverClient) def repoRoute(ns: Namespace): Route = pathPrefix("repo") { @@ -72,7 +56,7 @@ class AdminResource(extractNamespace: Directive1[Namespace], val keyserverClient } } - def devicePath(ns: Namespace): Route = + def devicePath(ns: Namespace, repoId: RepoId): Route = pathPrefix(DeviceId.Path) { device => pathPrefix("ecus") { pathPrefix(EcuIdPath) { ecuId => @@ -95,10 +79,13 @@ class AdminResource(extractNamespace: Directive1[Namespace], val keyserverClient } } } ~ - (pathEnd & get) { + get { val f = deviceRegistration.findDeviceEcuInfo(ns, device) complete(f) - } + } ~ + (path("targets.json") & put) { + complete(deviceRoleGeneration.forceTargetsRefresh(ns, repoId, device).map(StatusCodes.Created -> _)) + } } val route: Route = extractNamespace { ns => @@ -125,12 +112,12 @@ class AdminResource(extractNamespace: Directive1[Namespace], val keyserverClient } } ~ (get & path("hardware_identifiers")) { - paginationParameters { (limit, offset) => + PaginationParameters { (limit, offset) => val f = ecuRepository.findAllHardwareIdentifiers(ns, offset, limit) complete(f) } } ~ - devicePath(ns) + devicePath(ns, repoId) } } } diff --git a/src/main/scala/com/advancedtelematic/director/http/AssignmentsResource.scala b/src/main/scala/com/advancedtelematic/director/http/AssignmentsResource.scala index 3a20304..4d7385b 100644 --- a/src/main/scala/com/advancedtelematic/director/http/AssignmentsResource.scala +++ b/src/main/scala/com/advancedtelematic/director/http/AssignmentsResource.scala @@ -5,19 +5,17 @@ import java.time.Instant import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.server._ import akka.http.scaladsl.unmarshalling.PredefinedFromStringUnmarshallers.CsvSeq -import akka.http.scaladsl.util.FastFuture import com.advancedtelematic.director.data.AdminDataType.AssignUpdateRequest import com.advancedtelematic.director.data.AssignmentDataType.CancelAssignments import com.advancedtelematic.director.data.Codecs._ -import com.advancedtelematic.director.data.DbDataType import com.advancedtelematic.libats.data.DataType.Namespace import com.advancedtelematic.libats.http.UUIDKeyAkka._ import com.advancedtelematic.libats.messaging.MessageBusPublisher import com.advancedtelematic.libats.messaging_datatype.DataType.{DeviceId, UpdateId} -import com.advancedtelematic.libats.messaging_datatype.Messages.{DeviceUpdateAssigned, DeviceUpdateCanceled, DeviceUpdateEvent} +import com.advancedtelematic.libats.messaging_datatype.MessageCodecs.deviceUpdateCanceledEncoder +import com.advancedtelematic.libats.messaging_datatype.Messages.{DeviceUpdateAssigned, DeviceUpdateEvent} import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport._ import slick.jdbc.MySQLProfile.api.Database -import com.advancedtelematic.libats.messaging_datatype.MessageCodecs.deviceUpdateCanceledEncoder import scala.concurrent.{ExecutionContext, Future} @@ -63,7 +61,7 @@ class AssignmentsResource(extractNamespace: Directive1[Namespace]) } } } ~ - pathPrefix(DeviceId.Path) { deviceId => + path(DeviceId.Path) { deviceId => get { // This should be replacing /queue in /admin val f = deviceAssignments.findDeviceAssignments(ns, deviceId) complete(f) diff --git a/src/main/scala/com/advancedtelematic/director/http/DeviceAssignments.scala b/src/main/scala/com/advancedtelematic/director/http/DeviceAssignments.scala index 0c83382..e15e786 100644 --- a/src/main/scala/com/advancedtelematic/director/http/DeviceAssignments.scala +++ b/src/main/scala/com/advancedtelematic/director/http/DeviceAssignments.scala @@ -2,6 +2,7 @@ package com.advancedtelematic.director.http import java.time.Instant +import akka.http.scaladsl.util.FastFuture import cats.implicits._ import com.advancedtelematic.director.data.AdminDataType.QueueResponse import com.advancedtelematic.director.data.DbDataType.Assignment @@ -65,6 +66,10 @@ class DeviceAssignments(implicit val db: Database, val ec: ExecutionContext) ext } } + def createForDevice(ns: Namespace, correlationId: CorrelationId, deviceId: DeviceId, mtuId: UpdateId): Future[Assignment] = { + createForDevices(ns, correlationId, List(deviceId), mtuId).map(_.head) + } + def createForDevices(ns: Namespace, correlationId: CorrelationId, devices: Seq[DeviceId], mtuId: UpdateId): Future[Seq[Assignment]] = async { val ecus = await(findAffectedEcus(ns, devices, mtuId)) diff --git a/src/main/scala/com/advancedtelematic/director/http/DirectorRoutes.scala b/src/main/scala/com/advancedtelematic/director/http/DirectorRoutes.scala index f8acf9b..fb664c4 100644 --- a/src/main/scala/com/advancedtelematic/director/http/DirectorRoutes.scala +++ b/src/main/scala/com/advancedtelematic/director/http/DirectorRoutes.scala @@ -1,10 +1,12 @@ package com.advancedtelematic.director.http +import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.server.{Directives, _} import com.advancedtelematic.libats.auth.NamespaceDirectives import com.advancedtelematic.libats.http.DefaultRejectionHandler.rejectionHandler import com.advancedtelematic.libats.http.ErrorHandler import com.advancedtelematic.libats.messaging.MessageBusPublisher +import com.advancedtelematic.libats.messaging_datatype.DataType.{DeviceId, UpdateId} import com.advancedtelematic.libtuf_server.keyserver.KeyserverClient import slick.jdbc.MySQLProfile.api._ @@ -24,7 +26,8 @@ class DirectorRoutes(keyserverClient: KeyserverClient) new AdminResource(extractNamespace, keyserverClient).route ~ new AssignmentsResource(extractNamespace).route ~ new DeviceResource(extractNamespace, keyserverClient).route ~ - new MultiTargetUpdatesResource(extractNamespace).route + new MultiTargetUpdatesResource(extractNamespace).route ~ + new LegacyRoutes(extractNamespace).route } } } diff --git a/src/main/scala/com/advancedtelematic/director/http/LegacyRoutes.scala b/src/main/scala/com/advancedtelematic/director/http/LegacyRoutes.scala new file mode 100644 index 0000000..5d91810 --- /dev/null +++ b/src/main/scala/com/advancedtelematic/director/http/LegacyRoutes.scala @@ -0,0 +1,56 @@ +package com.advancedtelematic.director.http + +import java.time.Instant + +import akka.http.scaladsl.model.StatusCodes +import akka.http.scaladsl.server.Directives.{complete, delete, path, put} +import akka.http.scaladsl.server.{Directive1, Route} +import com.advancedtelematic.libats.data.DataType.{MultiTargetUpdateId, Namespace} +import com.advancedtelematic.libats.messaging.MessageBusPublisher +import com.advancedtelematic.libats.messaging_datatype.DataType.{DeviceId, UpdateId} +import com.advancedtelematic.libats.messaging_datatype.Messages.{DeviceUpdateAssigned, DeviceUpdateEvent} +import slick.jdbc.MySQLProfile.api._ +import com.advancedtelematic.libats.http.UUIDKeyAkka._ +import akka.http.scaladsl.server.Directives._ + +import scala.concurrent.{ExecutionContext, Future} +import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport._ +import PaginationParametersDirectives._ +import com.advancedtelematic.director.db.EcuRepositorySupport + +// Implements routes provided by old director that ota-web-app still uses +class LegacyRoutes(extractNamespace: Directive1[Namespace])(implicit val db: Database, val ec: ExecutionContext, messageBusPublisher: MessageBusPublisher) + extends EcuRepositorySupport { + private val deviceAssignments = new DeviceAssignments() + + private def createDeviceAssignment(ns: Namespace, deviceId: DeviceId, mtuId: UpdateId): Future[Unit] = { + val correlationId = MultiTargetUpdateId(mtuId.uuid) + val assignment = deviceAssignments.createForDevice(ns, correlationId, deviceId, mtuId) + + assignment.map { a => + val msg: DeviceUpdateEvent = DeviceUpdateAssigned(ns, Instant.now(), correlationId, a.deviceId) + messageBusPublisher.publishSafe(msg) + } + } + + val route: Route = + extractNamespace { ns => + path("admin" / "devices" / DeviceId.Path / "multi_target_update" / UpdateId.Path) { (deviceId, updateId) => + put { + val f = createDeviceAssignment(ns, deviceId, updateId).map(_ => StatusCodes.Created) + complete(f) + } + } ~ + path("assignments" / DeviceId.Path) { deviceId => + delete { + val a = deviceAssignments.cancel(ns, List(deviceId)) + complete(a.map(_.map(_.deviceId))) + } + } ~ + (path("admin" / "devices") & PaginationParameters) { (limit, offset) => + get { + complete(ecuRepository.findAllDeviceIds(ns, offset, limit)) + } + } + } +} diff --git a/src/main/scala/com/advancedtelematic/director/http/MultiTargetUpdatesResource.scala b/src/main/scala/com/advancedtelematic/director/http/MultiTargetUpdatesResource.scala index 5097085..8165d93 100644 --- a/src/main/scala/com/advancedtelematic/director/http/MultiTargetUpdatesResource.scala +++ b/src/main/scala/com/advancedtelematic/director/http/MultiTargetUpdatesResource.scala @@ -10,10 +10,8 @@ import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport._ import com.advancedtelematic.director.data.Codecs._ import com.advancedtelematic.director.db.MultiTargetUpdates import com.advancedtelematic.libats.data.DataType.Namespace - -import scala.concurrent.{ExecutionContext, Future} - - +import scala.concurrent.ExecutionContext +import com.advancedtelematic.libats.codecs.CirceCodecs._ class MultiTargetUpdatesResource(extractNamespace: Directive1[Namespace])(implicit val db: Database, val ec: ExecutionContext) { import Directives._ @@ -23,7 +21,10 @@ class MultiTargetUpdatesResource(extractNamespace: Directive1[Namespace])(implic val route = extractNamespace { ns => pathPrefix("multi_target_updates") { (get & pathPrefix(UpdateId.Path)) { uid => - complete(multiTargetUpdates.find(ns, uid)) + // For some reason director-v1 accepts `{targets: ...}` but returns `{...}` + // To make app compatible with director-v2, for now we do the same, but we should be returning what we accept: + // complete(multiTargetUpdates.find(ns, uid)) + complete(multiTargetUpdates.find(ns, uid).map(_.targets)) } ~ (post & pathEnd) { entity(as[MultiTargetUpdate]) { mtuRequest => diff --git a/src/main/scala/com/advancedtelematic/director/http/PaginationParametersDirectives.scala b/src/main/scala/com/advancedtelematic/director/http/PaginationParametersDirectives.scala new file mode 100644 index 0000000..e51e8e1 --- /dev/null +++ b/src/main/scala/com/advancedtelematic/director/http/PaginationParametersDirectives.scala @@ -0,0 +1,14 @@ +package com.advancedtelematic.director.http + + +import akka.http.scaladsl.server.Directive +import akka.http.scaladsl.server.Directives._ + +object PaginationParametersDirectives { + val PaginationParameters: Directive[(Long, Long)] = + (parameters('limit.as[Long].?) & parameters('offset.as[Long].?)).tmap { case (mLimit, mOffset) => + val limit = mLimit.getOrElse(50L).min(1000) + val offset = mOffset.getOrElse(0L) + (limit, offset) + } +} diff --git a/src/main/scala/com/advancedtelematic/director/http/RepositoryCreation.scala b/src/main/scala/com/advancedtelematic/director/http/RepositoryCreation.scala new file mode 100644 index 0000000..489dbb1 --- /dev/null +++ b/src/main/scala/com/advancedtelematic/director/http/RepositoryCreation.scala @@ -0,0 +1,23 @@ +package com.advancedtelematic.director.http + +import com.advancedtelematic.director.db.{DeviceRepositorySupport, RepoNamespaceRepositorySupport} +import com.advancedtelematic.libats.data.DataType.Namespace +import com.advancedtelematic.libtuf.data.TufDataType.{Ed25519KeyType, RepoId} +import com.advancedtelematic.libtuf_server.keyserver.KeyserverClient +import slick.jdbc.MySQLProfile.api._ +import com.advancedtelematic.libats.http.UUIDKeyAkka._ + +import scala.concurrent.{ExecutionContext, Future} + +class RepositoryCreation(keyserverClient: KeyserverClient)(implicit val db: Database, val ec: ExecutionContext) + extends DeviceRepositorySupport with RepoNamespaceRepositorySupport { + + def create(ns: Namespace): Future[Unit] = { + val repoId = RepoId.generate() + + for { + _ <- keyserverClient.createRoot(repoId, Ed25519KeyType, forceSync = true) + _ <- repoNamespaceRepo.persist(repoId, ns) + } yield () + } +} diff --git a/src/main/scala/com/advancedtelematic/director/repo/DeviceRoleGeneration.scala b/src/main/scala/com/advancedtelematic/director/repo/DeviceRoleGeneration.scala index 861e2f0..2bfa844 100644 --- a/src/main/scala/com/advancedtelematic/director/repo/DeviceRoleGeneration.scala +++ b/src/main/scala/com/advancedtelematic/director/repo/DeviceRoleGeneration.scala @@ -55,6 +55,11 @@ class DeviceRoleGeneration(keyserverClient: KeyserverClient)(implicit val db: Da } } + def forceTargetsRefresh(ns: Namespace, repoId: RepoId, deviceId: DeviceId): Future[JsonSignedPayload] = { + val refresher = roleRefresher(ns, deviceId) + refresher.refreshTargets(repoId).map(_.content) + } + def findFreshDeviceRole[T : TufRole](ns: Namespace, repoId: RepoId, deviceId: DeviceId): Future[JsonSignedPayload] = { implicit val refresher = roleRefresher(ns, deviceId) roleGeneration(ns, deviceId).findRole[T](repoId).map(_.content) diff --git a/src/test/scala/com/advancedtelematic/director/http/AdminResourceSpec.scala b/src/test/scala/com/advancedtelematic/director/http/AdminResourceSpec.scala index 23259ac..1e0438f 100644 --- a/src/test/scala/com/advancedtelematic/director/http/AdminResourceSpec.scala +++ b/src/test/scala/com/advancedtelematic/director/http/AdminResourceSpec.scala @@ -1,29 +1,27 @@ package com.advancedtelematic.director.http import akka.http.scaladsl.model.StatusCodes -import com.advancedtelematic.director.util._ -import com.advancedtelematic.libats.data.DataType.Namespace -import com.advancedtelematic.libats.messaging_datatype.DataType.{DeviceId, UpdateId} -import com.advancedtelematic.director.data.Generators._ -import com.advancedtelematic.director.data.GeneratorOps._ -import com.advancedtelematic.director.data.Codecs._ -import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport._ -import org.scalatest.Assertion import cats.syntax.option._ +import cats.syntax.show._ +import com.advancedtelematic.director.data.AdminDataType.{EcuInfoResponse, FindImageCount, RegisterDevice} +import com.advancedtelematic.director.data.Codecs._ import com.advancedtelematic.director.data.DbDataType.Ecu +import com.advancedtelematic.director.data.GeneratorOps._ +import com.advancedtelematic.director.data.Generators._ import com.advancedtelematic.director.db.{DbSignedRoleRepositorySupport, RepoNamespaceRepositorySupport} import com.advancedtelematic.director.http.AdminResources.RegisterDeviceResult +import com.advancedtelematic.director.util._ +import com.advancedtelematic.libats.codecs.CirceCodecs._ +import com.advancedtelematic.libats.data.DataType.Namespace import com.advancedtelematic.libats.data.{EcuIdentifier, PaginationResult} -import com.advancedtelematic.libtuf.data.ClientDataType.RootRole -import com.advancedtelematic.libtuf.data.TufDataType.{HardwareIdentifier, SignedPayload, TargetFilename, TufKey, TufKeyPair} +import com.advancedtelematic.libats.messaging_datatype.DataType.{DeviceId, UpdateId} import com.advancedtelematic.libtuf.data.ClientCodecs._ +import com.advancedtelematic.libtuf.data.ClientDataType.{RootRole, TargetsRole} import com.advancedtelematic.libtuf.data.TufCodecs._ -import cats.syntax.show._ -import com.advancedtelematic.director.data.AdminDataType.{EcuInfoResponse, FindImageCount, RegisterDevice} +import com.advancedtelematic.libtuf.data.TufDataType.{HardwareIdentifier, SignedPayload, TargetFilename, TufKey, TufKeyPair} +import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport._ import org.scalactic.source.Position -import com.advancedtelematic.director.data.Codecs._ -import com.advancedtelematic.director.data.DeviceRequest.{DeviceManifest, InstallationReportEntity} -import com.advancedtelematic.libats.codecs.CirceCodecs._ +import org.scalatest.Assertion object AdminResources { case class RegisterDeviceResult(deviceId: DeviceId, @@ -161,4 +159,23 @@ class AdminResourceSpec extends DirectorSpec resp.head.image.filepath shouldBe targetUpdate.target } } + + testWithRepo("PUT devices/id/targets.json forces refresh of devices targets.json") { implicit ns => + val dev = registerAdminDeviceOk() + + Get(apiUri(s"device/${dev.deviceId.show}/targets.json")).namespaced ~> routes ~> check { + status shouldBe StatusCodes.OK + responseAs[SignedPayload[TargetsRole]].signed.version shouldBe 1 + } + + Put(apiUri(s"admin/devices/${dev.deviceId.show}/targets.json")).namespaced ~> routes ~> check { + status shouldBe StatusCodes.Created + responseAs[SignedPayload[TargetsRole]].signed.version shouldBe 2 + } + + Get(apiUri(s"device/${dev.deviceId.show}/targets.json")).namespaced ~> routes ~> check { + status shouldBe StatusCodes.OK + responseAs[SignedPayload[TargetsRole]].signed.version shouldBe 2 + } + } } diff --git a/src/test/scala/com/advancedtelematic/director/http/AssignmentsResourceSpec.scala b/src/test/scala/com/advancedtelematic/director/http/AssignmentsResourceSpec.scala index fb50ca9..b8b6cb8 100644 --- a/src/test/scala/com/advancedtelematic/director/http/AssignmentsResourceSpec.scala +++ b/src/test/scala/com/advancedtelematic/director/http/AssignmentsResourceSpec.scala @@ -139,11 +139,9 @@ class AssignmentsResourceSpec extends DirectorSpec createAssignmentOk(regDev0.deviceId, regDev0.primary.hardwareId) val queue0 = getDeviceAssignmentOk(regDev0.deviceId) - queue0 shouldNot be(empty) val queue1 = getDeviceAssignmentOk(regDev1.deviceId) - queue1 shouldBe empty } @@ -184,6 +182,9 @@ class AssignmentsResourceSpec extends DirectorSpec cancelAssignmentsOk(Seq(regDev.deviceId)) shouldBe Seq(regDev.deviceId) + val queue = getDeviceAssignmentOk(regDev.deviceId) + queue shouldBe empty + val msg = msgPub.wasReceived[DeviceUpdateEvent] { msg: DeviceUpdateEvent => msg.deviceUuid == regDev.deviceId } @@ -192,7 +193,7 @@ class AssignmentsResourceSpec extends DirectorSpec msg.get shouldBe a [DeviceUpdateCanceled] } - testWithRepo("DELETE assignments can only cancel if update is not in-flight") { implicit ns => + testWithRepo("PATCH assignments can only cancel if update is not in-flight") { implicit ns => val regDev = registerAdminDeviceOk() createAssignmentOk(regDev.deviceId, regDev.primary.hardwareId) diff --git a/src/test/scala/com/advancedtelematic/director/http/AutoUpdateResourceSpec.scala b/src/test/scala/com/advancedtelematic/director/http/AutoUpdateResourceSpec.scala index da43b83..37ca064 100644 --- a/src/test/scala/com/advancedtelematic/director/http/AutoUpdateResourceSpec.scala +++ b/src/test/scala/com/advancedtelematic/director/http/AutoUpdateResourceSpec.scala @@ -1,12 +1,11 @@ package com.advancedtelematic.director.http import akka.http.scaladsl.model.StatusCodes -import com.advancedtelematic.director.db.{DbSignedRoleRepositorySupport, RepoNamespaceRepositorySupport} -import com.advancedtelematic.director.util.{DeviceManifestSpec, DirectorSpec, RepositorySpec, RouteResourceSpec} -import com.advancedtelematic.libtuf.data.TufDataType.TargetName import cats.syntax.show._ -import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport._ +import com.advancedtelematic.director.util.{DefaultPatience, DirectorSpec, RepositorySpec, RouteResourceSpec} import com.advancedtelematic.libtuf.data.ClientCodecs._ +import com.advancedtelematic.libtuf.data.TufDataType.TargetName +import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport._ class AutoUpdateResourceSpec extends DirectorSpec with RouteResourceSpec diff --git a/src/test/scala/com/advancedtelematic/director/http/DeviceResourceSpec.scala b/src/test/scala/com/advancedtelematic/director/http/DeviceResourceSpec.scala index b22c995..7edf5dd 100644 --- a/src/test/scala/com/advancedtelematic/director/http/DeviceResourceSpec.scala +++ b/src/test/scala/com/advancedtelematic/director/http/DeviceResourceSpec.scala @@ -91,6 +91,30 @@ class DeviceResourceSpec extends DirectorSpec registerDeviceOk() } + // TODO: Legacy, this should not be possible + // https://saeljira.it.here.com/browse/OTA-441 + // https://saeljira.it.here.com/browse/OTA-2517 + testWithNamespace("registering the same device id with different ecus works") { implicit ns => + createRepoOk() + + val deviceId = DeviceId.generate() + val ecus = GenRegisterEcu.generate + val primaryEcu = ecus.ecu_serial + val req = RegisterDevice(deviceId.some, primaryEcu, Seq(ecus)) + + val ecus2 = GenRegisterEcu.generate + val primaryEcu2 = ecus2.ecu_serial + val req2 = RegisterDevice(deviceId.some, primaryEcu2, Seq(ecus2)) + + Post(apiUri(s"device/${deviceId.show}/ecus"), req).namespaced ~> routes ~> check { + status shouldBe StatusCodes.Created + } + + Post(apiUri(s"device/${deviceId.show}/ecus"), req2).namespaced ~> routes ~> check { + status shouldBe StatusCodes.Created + } + } + testWithRepo("fails when primary ecu is not defined in ecus") { implicit ns => val ecus = GenRegisterEcu.generate val primaryEcu = GenEcuIdentifier.generate @@ -105,7 +129,7 @@ class DeviceResourceSpec extends DirectorSpec deviceId } - testWithRepo("targets.json is after after register") { implicit ns => + testWithRepo("targets.json is empty after register") { implicit ns => val deviceId = registerDeviceOk() Get(apiUri(s"device/${deviceId.show}/targets.json")).namespaced ~> routes ~> check { @@ -265,6 +289,26 @@ class DeviceResourceSpec extends DirectorSpec secondTargets.signed.version shouldBe 2 } + testWithRepo("a refreshed targets returns the same assignments as before, even if they were completed") { implicit ns => + val regDev = registerAdminDeviceOk() + + val targetUpdate = GenTargetUpdateRequest.generate + createAssignmentOk(regDev.deviceId, regDev.primary.hardwareId, targetUpdate.some) + + val firstTargets = fetchRoleOk[TargetsRole](regDev.deviceId) + + val deviceManifest = buildPrimaryManifest(regDev.primary, regDev.primaryKey, targetUpdate.to) + + putManifestOk(regDev.deviceId, deviceManifest) + + forceRoleExpire[TargetsRole](regDev.deviceId) + + val secondTargets = fetchRoleOk[TargetsRole](regDev.deviceId) + + secondTargets.signed.expires.isAfter(firstTargets.signed.expires) + secondTargets.signed.targets shouldBe firstTargets.signed.targets + } + testWithRepo("returns a refreshed version of snapshots if it expires") { implicit ns => val regDev = registerAdminDeviceOk() diff --git a/src/test/scala/com/advancedtelematic/director/http/LegacyApiResourceSpec.scala b/src/test/scala/com/advancedtelematic/director/http/LegacyApiResourceSpec.scala new file mode 100644 index 0000000..5b98722 --- /dev/null +++ b/src/test/scala/com/advancedtelematic/director/http/LegacyApiResourceSpec.scala @@ -0,0 +1,93 @@ +package com.advancedtelematic.director.http + +import akka.http.scaladsl.model.StatusCodes +import com.advancedtelematic.director.data.AdminDataType.{MultiTargetUpdate, QueueResponse} +import com.advancedtelematic.director.util.{DirectorSpec, MockMessageBus, RepositorySpec, RouteResourceSpec} +import com.advancedtelematic.libats.messaging_datatype.DataType.{DeviceId, UpdateId} +import com.advancedtelematic.director.data.Generators._ +import com.advancedtelematic.libats.data.DataType.MultiTargetUpdateId +import com.advancedtelematic.director.data.GeneratorOps._ +import com.advancedtelematic.director.data.Codecs._ +import com.advancedtelematic.libats.codecs.CirceCodecs._ +import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport._ +import cats.syntax.show._ +import com.advancedtelematic.libats.data.PaginationResult +import org.scalatest.OptionValues._ +import com.advancedtelematic.libats.messaging_datatype.Messages._ +import org.scalatest.LoneElement._ + +class LegacyApiResourceSpec extends DirectorSpec + with RouteResourceSpec + with AdminResources + with RepositorySpec + with AssignmentResources { + + override implicit val msgPub = new MockMessageBus + + testWithRepo("creates an assignment for the given update id for the specified device") { implicit ns => + val regDev = registerAdminDeviceWithSecondariesOk() + + val targetUpdate = GenTargetUpdateRequest.generate + val mtu = MultiTargetUpdate(Map(regDev.primary.hardwareId -> targetUpdate)) + + val mtuId = Post(apiUri("multi_target_updates"), mtu).namespaced ~> routes ~> check { + status shouldBe StatusCodes.Created + responseAs[UpdateId] + } + + Put(apiUri(s"admin/devices/${regDev.deviceId.show}/multi_target_update/${mtuId.show}")).namespaced ~> routes ~> check { + status shouldBe StatusCodes.Created + } + + val queue = Get(apiUri(s"assignments/${regDev.deviceId.show}")).namespaced ~> routes ~> check { + status shouldBe StatusCodes.OK + responseAs[List[QueueResponse]] + } + + queue.head.correlationId shouldBe MultiTargetUpdateId(mtuId.uuid) + queue.head.targets.get(regDev.primary.ecuSerial).value.image.filepath shouldBe targetUpdate.to.target + queue.head.targets.get(regDev.secondaries.keys.head) shouldBe empty + + val msg = msgPub.wasReceived[DeviceUpdateEvent] { msg: DeviceUpdateEvent => + msg.deviceUuid == regDev.deviceId + } + + msg.value shouldBe a [DeviceUpdateAssigned] + } + + testWithRepo("DELETE assignments cancels assigned updates") { implicit ns => + val regDev = registerAdminDeviceOk() + createAssignmentOk(regDev.deviceId, regDev.primary.hardwareId) + + val queue0 = getDeviceAssignmentOk(regDev.deviceId) + queue0 shouldNot be(empty) + + Delete(apiUri("assignments/" + regDev.deviceId.show)).namespaced ~> routes ~> check { + status shouldBe StatusCodes.OK + responseAs[Seq[DeviceId]] + } + + val queue = getDeviceAssignmentOk(regDev.deviceId) + queue shouldBe empty + + val msg = msgPub.wasReceived[DeviceUpdateEvent] { msg: DeviceUpdateEvent => + msg.deviceUuid == regDev.deviceId + } + + msg shouldBe defined + msg.get shouldBe a [DeviceUpdateCanceled] + } + + testWithRepo("get admin devices") { implicit ns => + val regDev = registerAdminDeviceOk() + + Get(apiUri("admin/devices")).namespaced ~> routes ~> check { + status shouldBe StatusCodes.OK + val devices = responseAs[PaginationResult[DeviceId]] + devices.total shouldBe 1 + devices.offset shouldBe 0 + devices.limit shouldBe 50 + devices.values.loneElement shouldBe regDev.deviceId + } + } +} diff --git a/src/test/scala/com/advancedtelematic/director/http/MultiTargetUpdatesResourceSpec.scala b/src/test/scala/com/advancedtelematic/director/http/MultiTargetUpdatesResourceSpec.scala index c019b0e..39809cb 100644 --- a/src/test/scala/com/advancedtelematic/director/http/MultiTargetUpdatesResourceSpec.scala +++ b/src/test/scala/com/advancedtelematic/director/http/MultiTargetUpdatesResourceSpec.scala @@ -1,16 +1,16 @@ package com.advancedtelematic.director.http -import cats.syntax.show._ import akka.http.scaladsl.model.StatusCodes -import com.advancedtelematic.director.data.AdminDataType.MultiTargetUpdate +import cats.syntax.show._ +import com.advancedtelematic.director.data.AdminDataType.{MultiTargetUpdate, TargetUpdateRequest} +import com.advancedtelematic.director.data.Codecs._ import com.advancedtelematic.director.data.Generators import com.advancedtelematic.director.util.{DefaultPatience, DirectorSpec, RouteResourceSpec} +import com.advancedtelematic.libats.codecs.CirceCodecs._ +import com.advancedtelematic.libats.data.ErrorCodes.MissingEntity import com.advancedtelematic.libats.data.ErrorRepresentation import com.advancedtelematic.libats.messaging_datatype.DataType.UpdateId -import com.advancedtelematic.libats.data.ErrorCodes.MissingEntity +import com.advancedtelematic.libtuf.data.TufDataType.HardwareIdentifier import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport._ -import com.advancedtelematic.director.data.GeneratorOps._ -import com.advancedtelematic.director.data.Codecs._ -import com.advancedtelematic.director.http.AdminResources class MultiTargetUpdatesResourceSpec extends DirectorSpec with Generators with DefaultPatience with RouteResourceSpec with AdminResources { @@ -24,7 +24,18 @@ class MultiTargetUpdatesResourceSpec extends DirectorSpec } } + testWithNamespace("Legacy API: can GET multi-target updates") { implicit ns => + val mtu = createMtuOk() + + Get(apiUri(s"multi_target_updates/${mtu.show}")).namespaced ~> routes ~> check { + status shouldBe StatusCodes.OK + responseAs[Map[HardwareIdentifier, TargetUpdateRequest]] // This should be responseAs[MultiTargetUpdate], see comments on resource + } + } + testWithNamespace("can GET multi-target updates") { implicit ns => + pending // due to legacy api support + val mtu = createMtuOk() Get(apiUri(s"multi_target_updates/${mtu.show}")).namespaced ~> routes ~> check { @@ -33,6 +44,7 @@ class MultiTargetUpdatesResourceSpec extends DirectorSpec } } + testWithNamespace("accepts mtu with an update") { implicit ns => createMtuOk() } diff --git a/src/test/scala/com/advancedtelematic/director/util/DirectorSpec.scala b/src/test/scala/com/advancedtelematic/director/util/DirectorSpec.scala index 5fb5e31..30edf48 100644 --- a/src/test/scala/com/advancedtelematic/director/util/DirectorSpec.scala +++ b/src/test/scala/com/advancedtelematic/director/util/DirectorSpec.scala @@ -30,7 +30,8 @@ object NamespacedTests extends NamespacedTests abstract class DirectorSpec extends FunSuite with Matchers with ScalaFutures - with NamespacedTests { + with NamespacedTests + with DefaultPatience { Security.addProvider(new BouncyCastleProvider())