From 1e163baf584c3288da6a0c34d81c78051d9a375c Mon Sep 17 00:00:00 2001 From: Oliver <20188437+olivergrabinski@users.noreply.github.com> Date: Mon, 22 Apr 2024 15:55:25 +0200 Subject: [PATCH] Add FileProcessor --- .../storage/StorageScopeInitialization.scala | 4 +- .../storage/files/FormDataExtractor.scala | 2 +- .../operations/s3/S3FileOperations.scala | 93 ++++++++------ .../s3/client/S3StorageClient.scala | 35 +++++- .../s3/LocalStackS3StorageClient.scala | 13 +- ship/src/main/resources/ship-default.conf | 1 + .../epfl/bluebrain/nexus/ship/InitShip.scala | 39 +++--- .../ch/epfl/bluebrain/nexus/ship/Main.scala | 4 +- .../epfl/bluebrain/nexus/ship/RunShip.scala | 13 +- .../nexus/ship/config/InputConfig.scala | 1 + .../nexus/ship/files/FileCopier.scala | 38 ++++++ .../nexus/ship/files/FileProcessor.scala | 119 ++++++++++++++++++ .../nexus/ship/files/FileWiring.scala | 24 ++++ .../nexus/ship/storages/StorageWiring.scala | 54 +++++++- ship/src/test/resources/gpfs/cat_scream.gif | Bin 0 -> 80030 bytes .../resources/import/file-events-import.json | 2 + .../bluebrain/nexus/ship/RunShipSuite.scala | 48 ++++++- .../bluebrain/nexus/ship/S3RunShipSuite.scala | 37 +++++- .../ship/config/ShipConfigFixtures.scala | 2 + .../nexus/ship/config/ShipConfigSuite.scala | 7 ++ 20 files changed, 456 insertions(+), 80 deletions(-) create mode 100644 ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/files/FileCopier.scala create mode 100644 ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/files/FileProcessor.scala create mode 100644 ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/files/FileWiring.scala create mode 100644 ship/src/test/resources/gpfs/cat_scream.gif create mode 100644 ship/src/test/resources/import/file-events-import.json diff --git a/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/StorageScopeInitialization.scala b/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/StorageScopeInitialization.scala index a53c1644b6..2c2f1fd68c 100644 --- a/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/StorageScopeInitialization.scala +++ b/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/StorageScopeInitialization.scala @@ -83,10 +83,10 @@ object StorageScopeInitialization { * Creates a [[StorageScopeInitialization]] that creates a default S3Storage with the provided default fields */ def s3( - storage: Storages, + storages: Storages, serviceAccount: ServiceAccount, defaultFields: S3StorageFields ): StorageScopeInitialization = - new StorageScopeInitialization(storage, serviceAccount, defaultS3StorageId, defaultFields) + new StorageScopeInitialization(storages, serviceAccount, defaultS3StorageId, defaultFields) } diff --git a/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/FormDataExtractor.scala b/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/FormDataExtractor.scala index 6d431e3074..b8be837c76 100644 --- a/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/FormDataExtractor.scala +++ b/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/FormDataExtractor.scala @@ -19,7 +19,7 @@ import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri import scala.concurrent.{ExecutionContext, Future} import scala.util.Try -sealed trait FormDataExtractor { +trait FormDataExtractor { /** * Extracts the part with fieldName ''file'' from the passed ''entity'' MultiPart/FormData. Any other part is diff --git a/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/operations/s3/S3FileOperations.scala b/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/operations/s3/S3FileOperations.scala index bea15744ab..e0e1138ac6 100644 --- a/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/operations/s3/S3FileOperations.scala +++ b/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/operations/s3/S3FileOperations.scala @@ -45,9 +45,10 @@ trait S3FileOperations { object S3FileOperations { final case class S3FileMetadata(contentType: ContentType, metadata: FileStorageMetadata) + private val log = Logger[S3FileOperations] + def mk(client: S3StorageClient)(implicit as: ActorSystem, uuidf: UUIDF): S3FileOperations = new S3FileOperations { - private val log = Logger[S3FileOperations] private lazy val saveFile = new S3StorageSaveFile(client) override def checkBucketExists(bucket: String): IO[Unit] = @@ -76,47 +77,61 @@ object S3FileOperations { override def save(storage: S3Storage, filename: String, entity: BodyPartEntity): IO[FileStorageMetadata] = saveFile.apply(storage, filename, entity) - override def register(bucket: String, path: Uri.Path): IO[S3FileMetadata] = { - for { - _ <- log.info(s"Fetching attributes for S3 file. Bucket $bucket at path $path") - resp <- client.headObject(bucket, path.toString()) - contentType <- parseContentType(resp.contentType()) - metadata <- mkS3Metadata(bucket, path, resp, contentType) - } yield metadata - } - .onError { e => - log.error(e)(s"Failed fetching required attributes for S3 file registration. Bucket $bucket and path $path") - } + override def register(bucket: String, path: Uri.Path): IO[S3FileMetadata] = + registerInternal(client, bucket, path) - private def parseContentType(raw: String): IO[ContentType] = - ContentType.parse(raw).map(_.pure[IO]).getOrElse(IO.raiseError(InvalidContentType(raw))) - - private def mkS3Metadata(bucket: String, path: Uri.Path, resp: HeadObjectResponse, ct: ContentType) = { - for { - uuid <- uuidf() - checksum <- checksumFrom(resp) - } yield S3FileMetadata( - ct, - FileStorageMetadata( - uuid, - resp.contentLength(), - checksum, - FileAttributesOrigin.External, - client.baseEndpoint / bucket / path, - path - ) - ) + } + + def registerInternal(client: S3StorageClient, bucket: String, path: Uri.Path)(implicit + uuidF: UUIDF + ): IO[S3FileMetadata] = { + for { + _ <- log.info(s"Fetching attributes for S3 file. Bucket $bucket at path $path") + resp <- client.headObject(bucket, path.toString()) + contentType <- parseContentType(resp.contentType()) + metadata <- mkS3Metadata(client, bucket, path, resp, contentType) + } yield metadata + } + .onError { e => + log.error(e)(s"Failed fetching required attributes for S3 file registration. Bucket $bucket and path $path") } - private def checksumFrom(response: HeadObjectResponse) = IO.fromOption { - Option(response.checksumSHA256()) - .map { checksum => - Digest.ComputedDigest( - DigestAlgorithm.default, - Hex.encodeHexString(Base64.getDecoder.decode(checksum)) - ) - } - }(new IllegalArgumentException("Missing checksum")) + private def parseContentType(raw: String): IO[ContentType] = + ContentType.parse(raw).map(_.pure[IO]).getOrElse(IO.raiseError(InvalidContentType(raw))) + + private def mkS3Metadata( + client: S3StorageClient, + bucket: String, + path: Uri.Path, + resp: HeadObjectResponse, + ct: ContentType + )(implicit + uuidf: UUIDF + ) = { + for { + uuid <- uuidf() + checksum <- checksumFrom(resp) + } yield S3FileMetadata( + ct, + FileStorageMetadata( + uuid, + resp.contentLength(), + checksum, + FileAttributesOrigin.External, + client.baseEndpoint / bucket / path, + path + ) + ) } + private def checksumFrom(response: HeadObjectResponse) = IO.fromOption { + Option(response.checksumSHA256()) + .map { checksum => + Digest.ComputedDigest( + DigestAlgorithm.default, + Hex.encodeHexString(Base64.getDecoder.decode(checksum)) + ) + } + }(new IllegalArgumentException("Missing checksum")) + } diff --git a/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/operations/s3/client/S3StorageClient.scala b/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/operations/s3/client/S3StorageClient.scala index 35d68afd4a..83315c06bb 100644 --- a/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/operations/s3/client/S3StorageClient.scala +++ b/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/operations/s3/client/S3StorageClient.scala @@ -8,16 +8,16 @@ import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.model.DigestAlgori import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.operations.s3.client.S3StorageClient.UploadMetadata import ch.epfl.bluebrain.nexus.delta.rdf.syntax.uriSyntax import ch.epfl.bluebrain.nexus.delta.sdk.error.ServiceError.FeatureDisabled -import fs2.{Chunk, Pipe, Stream} import fs2.aws.s3.S3 import fs2.aws.s3.models.Models.{BucketName, FileKey} +import fs2.{Chunk, Pipe, Stream} import io.laserdisc.pure.s3.tagless.{Interpreter, S3AsyncClientOp} import org.apache.commons.codec.binary.Hex import software.amazon.awssdk.auth.credentials.{AwsBasicCredentials, AwsCredentialsProvider, StaticCredentialsProvider} import software.amazon.awssdk.core.async.AsyncRequestBody import software.amazon.awssdk.regions.Region import software.amazon.awssdk.services.s3.S3AsyncClient -import software.amazon.awssdk.services.s3.model.{ChecksumAlgorithm, ChecksumMode, HeadObjectRequest, HeadObjectResponse, ListObjectsV2Request, ListObjectsV2Response, NoSuchKeyException, PutObjectRequest, PutObjectResponse} +import software.amazon.awssdk.services.s3.model._ import java.net.URI import java.util.Base64 @@ -34,6 +34,13 @@ trait S3StorageClient { def headObject(bucket: String, key: String): IO[HeadObjectResponse] + def copyObject( + sourceBucket: BucketName, + sourceKey: FileKey, + destinationBucket: BucketName, + destinationKey: FileKey + ): IO[CopyObjectResponse] + def uploadFile( fileData: Stream[IO, Byte], bucket: String, @@ -88,6 +95,23 @@ object S3StorageClient { override def headObject(bucket: String, key: String): IO[HeadObjectResponse] = client.headObject(HeadObjectRequest.builder().bucket(bucket).key(key).checksumMode(ChecksumMode.ENABLED).build) + override def copyObject( + sourceBucket: BucketName, + sourceKey: FileKey, + destinationBucket: BucketName, + destinationKey: FileKey + ): IO[CopyObjectResponse] = + client.copyObject( + CopyObjectRequest + .builder() + .sourceBucket(sourceBucket.value.value) + .sourceKey(sourceKey.value.value) + .destinationBucket(destinationBucket.value.value) + .destinationKey(destinationKey.value.value) + .checksumAlgorithm(ChecksumAlgorithm.SHA256) // TODO: See what to do with this + .build() + ) + override def objectExists(bucket: String, key: String): IO[Boolean] = { headObject(bucket, key) .redeemWith( @@ -173,6 +197,13 @@ object S3StorageClient { override def baseEndpoint: Uri = throw disabledErr + override def copyObject( + sourceBucket: BucketName, + sourceKey: FileKey, + destinationBucket: BucketName, + destinationKey: FileKey + ): IO[CopyObjectResponse] = raiseDisabledErr + override def objectExists(bucket: String, key: String): IO[Boolean] = raiseDisabledErr override def uploadFile( diff --git a/delta/plugins/storage/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/operations/s3/LocalStackS3StorageClient.scala b/delta/plugins/storage/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/operations/s3/LocalStackS3StorageClient.scala index 41778c02c3..200c631b34 100644 --- a/delta/plugins/storage/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/operations/s3/LocalStackS3StorageClient.scala +++ b/delta/plugins/storage/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/operations/s3/LocalStackS3StorageClient.scala @@ -21,11 +21,18 @@ import java.nio.file.Paths object LocalStackS3StorageClient { val ServiceType = Service.S3 + def createBucket(s3Client: S3AsyncClientOp[IO], bucket: BucketName) = + s3Client.createBucket(CreateBucketRequest.builder().bucket(bucket.value.value).build) + def uploadFileToS3(s3Client: S3AsyncClientOp[IO], bucket: BucketName, path: Path): IO[PutObjectResponse] = { - s3Client.createBucket(CreateBucketRequest.builder().bucket(bucket.value.value).build) >> + val absoluteResourcePath = if (path.isAbsolute) path else Path("/" + path.toString) + createBucket(s3Client, bucket) >> s3Client.putObject( - PutObjectRequest.builder.bucket(bucket.value.value).key(path.toString).build, - Paths.get(getClass.getResource(path.toString).toURI) + PutObjectRequest.builder + .bucket(bucket.value.value) + .key(path.toString) + .build, + Paths.get(getClass.getResource(absoluteResourcePath.toString).toURI) ) } diff --git a/ship/src/main/resources/ship-default.conf b/ship/src/main/resources/ship-default.conf index 2c6404e7b6..df23965ea4 100644 --- a/ship/src/main/resources/ship-default.conf +++ b/ship/src/main/resources/ship-default.conf @@ -74,6 +74,7 @@ ship { realm: "internal" } + import-bucket = ${ship.s-3.import-bucket} # The bucket to which the files will be copied by the Nexus Ship target-bucket = "nexus-delta-production" diff --git a/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/InitShip.scala b/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/InitShip.scala index c40b1c1fb4..490a42b29e 100644 --- a/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/InitShip.scala +++ b/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/InitShip.scala @@ -10,31 +10,30 @@ import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider object InitShip { - def apply(run: RunCommand): Resource[IO, (ShipConfig, fs2.Stream[IO, RowEvent], Transactors)] = - Resource.eval(configAndStream(run)).flatMap { case (config, eventStream) => + def apply(run: RunCommand): Resource[IO, (ShipConfig, fs2.Stream[IO, RowEvent], S3StorageClient, Transactors)] = + Resource.eval(configAndStream(run)).flatMap { case (config, eventStream, s3Client) => Transactors .init(config.database) - .map { xas => (config, eventStream, xas) } + .map { xas => (config, eventStream, s3Client, xas) } } - private def configAndStream(run: RunCommand): IO[(ShipConfig, fs2.Stream[IO, RowEvent])] = run.mode match { - case RunMode.Local => - val eventsStream = EventStreamer.localStreamer.stream(run.path, run.offset) - ShipConfig.load(run.config).map(_ -> eventsStream) - case RunMode.S3 => - for { - localConfig <- ShipConfig.load(None) - s3Config = localConfig.s3 - (config, eventsStream) <- - S3StorageClient.resource(s3Config.endpoint, DefaultCredentialsProvider.create()).use { client => - val eventsStream = EventStreamer.s3eventStreamer(client, s3Config.importBucket).stream(run.path, run.offset) + private def configAndStream(run: RunCommand): IO[(ShipConfig, fs2.Stream[IO, RowEvent], S3StorageClient)] = { + ShipConfig.load(run.config).flatMap { shipConfig => + S3StorageClient.resource(shipConfig.s3.endpoint, DefaultCredentialsProvider.create()).use { s3Client => + run.mode match { + case RunMode.Local => + val eventsStream = EventStreamer.localStreamer.stream(run.path, run.offset) + ShipConfig.load(run.config).map((_, eventsStream, s3Client)) + case RunMode.S3 => + val eventsStream = + EventStreamer.s3eventStreamer(s3Client, shipConfig.s3.importBucket).stream(run.path, run.offset) val config = run.config match { - case Some(configPath) => ShipConfig.loadFromS3(client, s3Config.importBucket, configPath) - case None => IO.pure(localConfig) + case Some(configPath) => ShipConfig.loadFromS3(s3Client, shipConfig.s3.importBucket, configPath) + case None => IO.pure(shipConfig) } - config.map(_ -> eventsStream) - } - } yield (config, eventsStream) + config.map((_, eventsStream, s3Client)) + } + } + } } - } diff --git a/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/Main.scala b/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/Main.scala index 45607738a1..3d8fc9fe55 100644 --- a/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/Main.scala +++ b/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/Main.scala @@ -57,10 +57,10 @@ object Main private[ship] def run(r: RunCommand): IO[Unit] = { val clock = Clock[IO] - InitShip(r).use { case (config, eventsStream, xas) => + InitShip(r).use { case (config, eventsStream, s3Client, xas) => for { start <- clock.realTimeInstant - reportOrError <- RunShip(eventsStream, config.input, xas).attempt + reportOrError <- RunShip(eventsStream, s3Client, config.input, xas).attempt end <- clock.realTimeInstant _ <- ShipSummaryStore.save(xas, start, end, r, reportOrError) _ <- IO.fromEither(reportOrError) diff --git a/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/RunShip.scala b/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/RunShip.scala index 1324a30e48..8ab1736bd8 100644 --- a/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/RunShip.scala +++ b/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/RunShip.scala @@ -2,6 +2,7 @@ package ch.epfl.bluebrain.nexus.ship import cats.effect.{Clock, IO} import ch.epfl.bluebrain.nexus.delta.kernel.utils.UUIDF +import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.operations.s3.client.S3StorageClient import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.api.{JsonLdApi, JsonLdJavaApi} import ch.epfl.bluebrain.nexus.delta.rdf.shacl.ValidateShacl import ch.epfl.bluebrain.nexus.delta.sdk.organizations.FetchActiveOrganization @@ -11,6 +12,7 @@ import ch.epfl.bluebrain.nexus.delta.sdk.quotas.Quotas import ch.epfl.bluebrain.nexus.delta.sourcing.Transactors import ch.epfl.bluebrain.nexus.delta.sourcing.exporter.RowEvent import ch.epfl.bluebrain.nexus.ship.config.InputConfig +import ch.epfl.bluebrain.nexus.ship.files.FileProcessor import ch.epfl.bluebrain.nexus.ship.organizations.OrganizationProvider import ch.epfl.bluebrain.nexus.ship.projects.ProjectProcessor import ch.epfl.bluebrain.nexus.ship.resolvers.ResolverProcessor @@ -21,7 +23,12 @@ import fs2.Stream object RunShip { - def apply(eventsStream: Stream[IO, RowEvent], config: InputConfig, xas: Transactors): IO[ImportReport] = { + def apply( + eventsStream: Stream[IO, RowEvent], + s3Client: S3StorageClient, + config: InputConfig, + xas: Transactors + ): IO[ImportReport] = { val clock = Clock[IO] val uuidF = UUIDF.random // Resources may have been created with different configurations so we adopt the lenient one for the import @@ -55,6 +62,7 @@ object RunShip { esViewsProcessor = ElasticSearchViewProcessor(fetchContext, rcr, projectMapper, eventLogConfig, eventClock, xas) bgViewsProcessor = BlazegraphViewProcessor(fetchContext, rcr, projectMapper, eventLogConfig, eventClock, xas) compositeViewsProcessor = CompositeViewProcessor(fetchContext, rcr, projectMapper, eventLogConfig, eventClock, xas) + fileProcessor = FileProcessor(fetchContext, s3Client, projectMapper, rcr, config, eventClock, xas) // format: on report <- EventProcessor .run( @@ -65,7 +73,8 @@ object RunShip { resourceProcessor, esViewsProcessor, bgViewsProcessor, - compositeViewsProcessor + compositeViewsProcessor, + fileProcessor ) } yield report } diff --git a/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/config/InputConfig.scala b/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/config/InputConfig.scala index 96113adf16..66b93e8c3b 100644 --- a/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/config/InputConfig.scala +++ b/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/config/InputConfig.scala @@ -22,6 +22,7 @@ final case class InputConfig( viewDefaults: ViewDefaults, serviceAccount: ServiceAccountConfig, storages: StoragesConfig, + importBucket: BucketName, targetBucket: BucketName ) diff --git a/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/files/FileCopier.scala b/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/files/FileCopier.scala new file mode 100644 index 0000000000..81e4724c34 --- /dev/null +++ b/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/files/FileCopier.scala @@ -0,0 +1,38 @@ +package ch.epfl.bluebrain.nexus.ship.files + +import akka.http.scaladsl.model.Uri +import cats.effect.IO +import cats.implicits._ +import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.operations.s3.client.S3StorageClient +import eu.timepit.refined.collection.NonEmpty +import eu.timepit.refined.refineV +import fs2.aws.s3.models.Models.{BucketName, FileKey} + +trait FileCopier { + + def copyFile(path: Uri.Path): IO[Unit] + +} + +object FileCopier { + + def apply( + s3StorageClient: S3StorageClient, + importBucket: BucketName, + targetBucket: BucketName + ): FileCopier = + (path: Uri.Path) => { + def refineString(str: String) = + refineV[NonEmpty](str).leftMap(e => new IllegalArgumentException(e)) + + val fileKey = IO.fromEither(refineString(path.toString).map(FileKey)) + + fileKey.flatMap { key => + s3StorageClient.copyObject(importBucket, key, targetBucket, key) + }.void + } + + def apply(): FileCopier = + (_: Uri.Path) => IO.unit + +} diff --git a/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/files/FileProcessor.scala b/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/files/FileProcessor.scala new file mode 100644 index 0000000000..7e458fd4ed --- /dev/null +++ b/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/files/FileProcessor.scala @@ -0,0 +1,119 @@ +package ch.epfl.bluebrain.nexus.ship.files + +import cats.effect.IO +import ch.epfl.bluebrain.nexus.delta.kernel.Logger +import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.Files +import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.Files.definition +import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.model.FileEvent._ +import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.model.FileRejection.{IncorrectRev, ResourceAlreadyExists} +import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.model.{FileEvent, FileId} +import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.operations.s3.client.S3StorageClient +import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.{FetchStorage, StorageResource} +import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.api.JsonLdApi +import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.Caller +import ch.epfl.bluebrain.nexus.delta.sdk.model.IdSegmentRef +import ch.epfl.bluebrain.nexus.delta.sdk.projects.FetchContext +import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.ResolverContextResolution +import ch.epfl.bluebrain.nexus.delta.sourcing.model.Identity.Subject +import ch.epfl.bluebrain.nexus.delta.sourcing.model.{EntityType, ProjectRef} +import ch.epfl.bluebrain.nexus.delta.sourcing.{ScopedEventLog, Transactors} +import ch.epfl.bluebrain.nexus.ship._ +import ch.epfl.bluebrain.nexus.ship.acls.AclWiring.alwaysAuthorize +import ch.epfl.bluebrain.nexus.ship.config.InputConfig +import ch.epfl.bluebrain.nexus.ship.files.FileProcessor.logger +import ch.epfl.bluebrain.nexus.ship.files.FileWiring._ +import ch.epfl.bluebrain.nexus.ship.storages.StorageWiring +import io.circe.Decoder + +class FileProcessor private ( + files: Files, + projectMapper: ProjectMapper, + fileCopier: FileCopier, + clock: EventClock +) extends EventProcessor[FileEvent] { + + override def resourceType: EntityType = Files.entityType + + override def decoder: Decoder[FileEvent] = FileEvent.serializer.codec + + override def evaluate(event: FileEvent): IO[ImportStatus] = + for { + _ <- clock.setInstant(event.instant) + result <- evaluateInternal(event) + } yield result + + private def evaluateInternal(event: FileEvent): IO[ImportStatus] = { + implicit val s: Subject = event.subject + implicit val c: Caller = Caller(s, Set.empty) + val cRev = event.rev - 1 + val project = projectMapper.map(event.project) + + event match { + case e: FileCreated => + fileCopier.copyFile(e.attributes.path) >> + files.registerFile(FileId(e.id, project), None, None, e.attributes.path, e.tag).flatMap(IO.println) + case e: FileUpdated => + fileCopier.copyFile(e.attributes.path) >> IO.unit + case e: FileCustomMetadataUpdated => + files.updateMetadata(FileId(e.id, project), cRev, e.metadata, e.tag) + case _: FileAttributesUpdated => IO.unit + case e: FileTagAdded => + files.tag(FileId(e.id, project), e.tag, e.targetRev, cRev) + case e: FileTagDeleted => + files.deleteTag(FileId(e.id, project), e.tag, cRev) + case e: FileDeprecated => + files.deprecate(FileId(e.id, project), cRev) + case e: FileUndeprecated => + files.undeprecate(FileId(e.id, project), cRev) + } + }.redeemWith( + { + case a: ResourceAlreadyExists => logger.warn(a)("The resource already exists").as(ImportStatus.Dropped) + case i: IncorrectRev => logger.warn(i)("An incorrect revision has been provided").as(ImportStatus.Dropped) + case other => IO.raiseError(other) + }, + _ => IO.pure(ImportStatus.Success) + ) + +} + +object FileProcessor { + + private val logger = Logger[FileProcessor] + + def apply( + fetchContext: FetchContext, + s3Client: S3StorageClient, + projectMapper: ProjectMapper, + rcr: ResolverContextResolution, + config: InputConfig, + clock: EventClock, + xas: Transactors + )(implicit jsonLdApi: JsonLdApi): FileProcessor = { + + val storages = StorageWiring.storages(fetchContext, rcr, config, clock, xas) + + val fe = new FetchStorage { + override def fetch(id: IdSegmentRef, project: ProjectRef): IO[StorageResource] = + storages.flatMap(_.fetch(id, project)) + + override def fetchDefault(project: ProjectRef): IO[StorageResource] = + storages.flatMap(_.fetchDefault(project)) + } + + val fileCopier = FileCopier(s3Client, config.importBucket, config.targetBucket) + + val files = + new Files( + failingFormDataExtractor, + ScopedEventLog(definition(clock), config.eventLog, xas), + alwaysAuthorize, + fetchContext, + fe, + registerOperationOnly(s3Client) + )(FailingUUID) + + new FileProcessor(files, projectMapper, fileCopier, clock) + } + +} diff --git a/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/files/FileWiring.scala b/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/files/FileWiring.scala new file mode 100644 index 0000000000..9454ffa880 --- /dev/null +++ b/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/files/FileWiring.scala @@ -0,0 +1,24 @@ +package ch.epfl.bluebrain.nexus.ship.files + +import akka.http.scaladsl.model.HttpEntity +import cats.effect.IO +import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.FormDataExtractor +import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.operations.FileOperations +import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.operations.s3.client.S3StorageClient +import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode +import ch.epfl.bluebrain.nexus.ship.storages.StorageWiring.{failingDiskFileOperations, failingRemoteDiskFileOperations, registerS3FileOperationOnly} + +object FileWiring { + + def registerOperationOnly(s3StorageClient: S3StorageClient): FileOperations = + FileOperations.mk( + failingDiskFileOperations, + failingRemoteDiskFileOperations, + registerS3FileOperationOnly(s3StorageClient) + ) + + def failingFormDataExtractor: FormDataExtractor = + (_: IriOrBNode.Iri, _: HttpEntity, _: Long) => + IO.raiseError(new IllegalArgumentException("FormDataExtractor should not be called")) + +} diff --git a/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/storages/StorageWiring.scala b/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/storages/StorageWiring.scala index c12c057769..2da32bd655 100644 --- a/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/storages/StorageWiring.scala +++ b/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/storages/StorageWiring.scala @@ -1,18 +1,26 @@ package ch.epfl.bluebrain.nexus.ship.storages +import akka.http.scaladsl.model.{BodyPartEntity, Uri} import cats.effect.IO import ch.epfl.bluebrain.nexus.delta.kernel.utils.UUIDF import ch.epfl.bluebrain.nexus.delta.plugins.storage.StorageScopeInitialization +import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.model.{ComputedFileAttributes, FileStorageMetadata} import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.Storages import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.StoragesConfig.S3StorageConfig +import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.model.Storage.{DiskStorage, RemoteDiskStorage, S3Storage} import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.model.StorageFields.S3StorageFields -import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.model.StorageValue +import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.model.{AbsolutePath, StorageValue} import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.operations.StorageAccess +import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.operations.disk.DiskFileOperations +import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.operations.remote.RemoteDiskFileOperations +import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.operations.s3.S3FileOperations +import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.operations.s3.client.S3StorageClient import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.api.JsonLdApi -import ch.epfl.bluebrain.nexus.delta.sdk.Defaults import ch.epfl.bluebrain.nexus.delta.sdk.projects.FetchContext import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.ResolverContextResolution +import ch.epfl.bluebrain.nexus.delta.sdk.{AkkaSource, Defaults} import ch.epfl.bluebrain.nexus.delta.sourcing.Transactors +import ch.epfl.bluebrain.nexus.delta.sourcing.model.Label import ch.epfl.bluebrain.nexus.ship.EventClock import ch.epfl.bluebrain.nexus.ship.config.InputConfig import fs2.aws.s3.models.Models.BucketName @@ -75,4 +83,46 @@ object StorageWiring { ) } + def failingDiskFileOperations: DiskFileOperations = new DiskFileOperations { + override def checkVolumeExists(path: AbsolutePath): IO[Unit] = + IO.raiseError(new IllegalArgumentException("DiskFileOperations should not be called")) + + override def fetch(path: Uri.Path): IO[AkkaSource] = + IO.raiseError(new IllegalArgumentException("DiskFileOperations should not be called")) + + override def save(storage: DiskStorage, filename: String, entity: BodyPartEntity): IO[FileStorageMetadata] = + IO.raiseError(new IllegalArgumentException("DiskFileOperations should not be called")) + } + + def failingRemoteDiskFileOperations: RemoteDiskFileOperations = new RemoteDiskFileOperations { + override def checkFolderExists(folder: Label): IO[Unit] = + IO.raiseError(new IllegalArgumentException("RemoteDiskFileOperations should not be called")) + + override def fetch(folder: Label, path: Uri.Path): IO[AkkaSource] = + IO.raiseError(new IllegalArgumentException("RemoteDiskFileOperations should not be called")) + + override def save(storage: RemoteDiskStorage, filename: String, entity: BodyPartEntity): IO[FileStorageMetadata] = + IO.raiseError(new IllegalArgumentException("RemoteDiskFileOperations should not be called")) + + override def link(storage: RemoteDiskStorage, sourcePath: Uri.Path, filename: String): IO[FileStorageMetadata] = + IO.raiseError(new IllegalArgumentException("RemoteDiskFileOperations should not be called")) + + override def fetchAttributes(folder: Label, path: Uri.Path): IO[ComputedFileAttributes] = + IO.raiseError(new IllegalArgumentException("RemoteDiskFileOperations should not be called")) + } + + def registerS3FileOperationOnly(s3Client: S3StorageClient): S3FileOperations = new S3FileOperations { + override def checkBucketExists(bucket: String): IO[Unit] = + IO.raiseError(new IllegalArgumentException("S3FileOperations should not be called")) + + override def fetch(bucket: String, path: Uri.Path): IO[AkkaSource] = + IO.raiseError(new IllegalArgumentException("S3FileOperations should not be called")) + + override def save(storage: S3Storage, filename: String, entity: BodyPartEntity): IO[FileStorageMetadata] = + IO.raiseError(new IllegalArgumentException("S3FileOperations should not be called")) + + override def register(bucket: String, path: Uri.Path): IO[S3FileOperations.S3FileMetadata] = + S3FileOperations.registerInternal(s3Client, bucket, path)(UUIDF.random) + } + } diff --git a/ship/src/test/resources/gpfs/cat_scream.gif b/ship/src/test/resources/gpfs/cat_scream.gif new file mode 100644 index 0000000000000000000000000000000000000000..cc5a14fd5681e649e6bbedb2ea5afd5424d6b073 GIT binary patch literal 80030 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�>Zh>bSzdb2eD0NY$E#jS{gT7=;X*N66?Cd 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

j`P+r8)pmaspW^KtE`?3 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 + _ <- RunShip(events, s3Client, inputConfig, xas).map { report => assert(report.offset == Offset.at(2L)) assert(thereIsOneProjectEventIn(report)) } @@ -68,7 +108,7 @@ class RunShipSuite extends NexusSuite with Doobie.Fixture with ShipConfigFixture ) for { events <- eventsStream("import/import.json") - _ <- RunShip(events, configWithProjectMapping, xas) + _ <- RunShip(events, s3Client, configWithProjectMapping, xas) _ <- getDistinctOrgProjects(xas).map { project => assertEquals(project, target) } diff --git a/ship/src/test/scala/ch/epfl/bluebrain/nexus/ship/S3RunShipSuite.scala b/ship/src/test/scala/ch/epfl/bluebrain/nexus/ship/S3RunShipSuite.scala index 3341fc5f9a..78c8fe9e58 100644 --- a/ship/src/test/scala/ch/epfl/bluebrain/nexus/ship/S3RunShipSuite.scala +++ b/ship/src/test/scala/ch/epfl/bluebrain/nexus/ship/S3RunShipSuite.scala @@ -1,7 +1,8 @@ package ch.epfl.bluebrain.nexus.ship import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.operations.s3.LocalStackS3StorageClient -import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.operations.s3.LocalStackS3StorageClient.uploadFileToS3 +import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.operations.s3.LocalStackS3StorageClient.{createBucket, uploadFileToS3} +import ch.epfl.bluebrain.nexus.delta.sourcing.model.EntityType import ch.epfl.bluebrain.nexus.delta.sourcing.offset.Offset import ch.epfl.bluebrain.nexus.delta.sourcing.postgres.Doobie import ch.epfl.bluebrain.nexus.ship.RunShipSuite.expectedImportReport @@ -11,6 +12,7 @@ import eu.timepit.refined.types.string.NonEmptyString import fs2.aws.s3.models.Models.BucketName import fs2.io.file.Path import munit.AnyFixture +import software.amazon.awssdk.services.s3.model.GetObjectAttributesRequest import scala.concurrent.duration.{Duration, DurationInt} @@ -33,10 +35,39 @@ class S3RunShipSuite for { _ <- uploadFileToS3(fs2S3client, bucket, importFilePath) events = EventStreamer.s3eventStreamer(s3Client, bucket).stream(importFilePath, Offset.start) - _ <- RunShip(events, inputConfig, xas).assertEquals(expectedImportReport) + _ <- RunShip(events, s3Client, inputConfig, xas).assertEquals(expectedImportReport) } yield () } + test("Run import with file events") { + val importFilePath = Path("/import/file-events-import.json") + val gif = Path("gpfs/cat_scream.gif") + + val importBucket = BucketName(NonEmptyString.unsafeFrom("nexus-ship-production")) + val targetBucket = BucketName(NonEmptyString.unsafeFrom("nexus-delta-production")) + val shipConfig = inputConfig.copy(importBucket = importBucket, targetBucket = targetBucket) + + { + for { + _ <- uploadFileToS3(fs2S3client, importBucket, importFilePath) + _ <- uploadFileToS3(fs2S3client, importBucket, gif) + _ <- createBucket(fs2S3client, targetBucket) + events = EventStreamer.s3eventStreamer(s3Client, importBucket).stream(importFilePath, Offset.start) + _ <- RunShip(events, s3Client, shipConfig, xas).map(_.progress(EntityType("file")).success == 1L) + _ <- fs2S3client.getObjectAttributes( + GetObjectAttributesRequest + .builder() + .bucket(targetBucket.value.value) + .key(gif.toString) + .objectAttributesWithStrings(java.util.List.of("Checksum")) + .build() + ) + } yield () + }.accepted + + println(123) + } + test("Run import from S3 providing a directory") { val directoryPath = Path("/import/multi-part-import") for { @@ -44,7 +75,7 @@ class S3RunShipSuite _ <- uploadFileToS3(fs2S3client, bucket, Path("/import/multi-part-import/2024-04-05T14:38:31.165389Z.success")) _ <- uploadFileToS3(fs2S3client, bucket, Path("/import/multi-part-import/2024-04-06T11:34:31.165389Z.json")) events = EventStreamer.s3eventStreamer(s3Client, bucket).stream(directoryPath, Offset.start) - _ <- RunShip(events, inputConfig, xas).assertEquals(expectedImportReport) + _ <- RunShip(events, s3Client, inputConfig, xas).assertEquals(expectedImportReport) } yield () } diff --git a/ship/src/test/scala/ch/epfl/bluebrain/nexus/ship/config/ShipConfigFixtures.scala b/ship/src/test/scala/ch/epfl/bluebrain/nexus/ship/config/ShipConfigFixtures.scala index 1589ab58e3..893368699e 100644 --- a/ship/src/test/scala/ch/epfl/bluebrain/nexus/ship/config/ShipConfigFixtures.scala +++ b/ship/src/test/scala/ch/epfl/bluebrain/nexus/ship/config/ShipConfigFixtures.scala @@ -43,6 +43,7 @@ trait ShipConfigFixtures extends ConfigFixtures with StorageFixtures with Classp 10737418240L ) + private val importBucket = BucketName(NonEmptyString.unsafeFrom("nexus-ship-production")) private val targetBucket = BucketName(NonEmptyString.unsafeFrom("nexus-delta-production")) def inputConfig: InputConfig = @@ -54,6 +55,7 @@ trait ShipConfigFixtures extends ConfigFixtures with StorageFixtures with Classp viewDefaults, serviceAccount, StoragesConfig(eventLogConfig, pagination, config.copy(amazon = Some(amazonConfig))), + importBucket, targetBucket ) diff --git a/ship/src/test/scala/ch/epfl/bluebrain/nexus/ship/config/ShipConfigSuite.scala b/ship/src/test/scala/ch/epfl/bluebrain/nexus/ship/config/ShipConfigSuite.scala index 1b13254017..4e972ca80f 100644 --- a/ship/src/test/scala/ch/epfl/bluebrain/nexus/ship/config/ShipConfigSuite.scala +++ b/ship/src/test/scala/ch/epfl/bluebrain/nexus/ship/config/ShipConfigSuite.scala @@ -66,6 +66,13 @@ class ShipConfigSuite extends NexusSuite with ShipConfigFixtures with LocalStack } yield () } + test("Should read the import bucket") { + for { + config <- ShipConfig.load(None).map(_.input) + _ = assertEquals(config.importBucket, inputConfig.importBucket) + } yield () + } + test("Should read the target bucket") { for { config <- ShipConfig.load(None).map(_.input)