Skip to content

Commit

Permalink
Remove StoragesStatistics from Files
Browse files Browse the repository at this point in the history
  • Loading branch information
olivergrabinski committed Apr 18, 2024
1 parent 2972cb2 commit b16f700
Show file tree
Hide file tree
Showing 8 changed files with 34 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,6 @@ class StoragePluginModule(priority: Int) extends ModuleDef {
aclCheck: AclCheck,
fetchContext: FetchContext,
storages: Storages,
storagesStatistics: StoragesStatistics,
xas: Transactors,
clock: Clock[IO],
uuidF: UUIDF,
Expand All @@ -191,7 +190,6 @@ class StoragePluginModule(priority: Int) extends ModuleDef {
fetchContext,
aclCheck,
storages,
storagesStatistics,
xas,
cfg.files,
fileOps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.model.StorageRejec
import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.model.{DigestAlgorithm, Storage, StorageRejection, StorageType}
import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.operations.StorageFileRejection.{FetchAttributeRejection, FetchFileRejection, SaveFileRejection}
import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.operations._
import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.{FetchStorage, Storages, StoragesStatistics}
import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.{FetchStorage, Storages}
import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.ContextValue
import ch.epfl.bluebrain.nexus.delta.sdk.AkkaSource
Expand Down Expand Up @@ -52,7 +52,6 @@ final class Files(
aclCheck: AclCheck,
fetchContext: FetchContext,
storages: FetchStorage,
storagesStatistics: StoragesStatistics,
fileOperations: FileOperations
)(implicit uuidF: UUIDF)
extends FetchFileStorage
Expand All @@ -77,6 +76,8 @@ final class Files(
* the http FormData entity
* @param tag
* the optional tag this file is being created with, attached to the current revision
* @param metadata
* the optional custom metadata provided by the user
*/
def create(
storageId: Option[IdSegment],
Expand All @@ -102,12 +103,12 @@ final class Files(
* the file identifier to expand as the iri of the file
* @param storageId
* the optional storage identifier to expand as the id of the storage. When None, the default storage is used
* @param projectRef
* the project where the file will belong
* @param entity
* the http FormData entity
* @param tag
* the optional tag this file is being created with, attached to the current revision
* @param metadata
* the optional custom metadata provided by the user
*/
def create(
id: FileId,
Expand All @@ -132,10 +133,6 @@ final class Files(
* the optional storage identifier to expand as the id of the storage. When None, the default storage is used
* @param projectRef
* the project where the file will belong
* @param filename
* the optional filename to use
* @param mediaType
* the optional media type to use
* @param path
* the path where the file is located inside the storage
* @param tag
Expand All @@ -162,12 +159,6 @@ final class Files(
* the file identifier to expand as the iri of the file
* @param storageId
* the optional storage identifier to expand as the id of the storage. When None, the default storage is used
* @param projectRef
* the project where the file will belong
* @param filename
* the optional filename to use
* @param mediaType
* the optional media type to use
* @param path
* the path where the file is located inside the storage
* @param tag
Expand All @@ -193,8 +184,6 @@ final class Files(
* the file identifier to expand as the iri of the file
* @param storageId
* the optional storage identifier to expand as the id of the storage. When None, the default storage is used
* @param projectRef
* the project where the file will belong
* @param rev
* the current revision of the file
* @param entity
Expand Down Expand Up @@ -236,14 +225,8 @@ final class Files(
* the file identifier to expand as the iri of the file
* @param storageId
* the optional storage identifier to expand as the id of the storage. When None, the default storage is used
* @param projectRef
* the project where the file will belong
* @param rev
* the current revision of the file
* @param filename
* the optional filename to use
* @param mediaType
* the optional media type to use
* @param path
* the path where the file is located inside the storage
*/
Expand Down Expand Up @@ -283,8 +266,6 @@ final class Files(
*
* @param id
* the file identifier to expand as the iri of the storage
* @param projectRef
* the project where the file belongs
* @param tag
* the tag name
* @param tagRev
Expand All @@ -309,8 +290,6 @@ final class Files(
*
* @param id
* the identifier that will be expanded to the Iri of the file
* @param projectRef
* the project reference where the file belongs
* @param tag
* the tag name
* @param rev
Expand All @@ -332,8 +311,6 @@ final class Files(
*
* @param id
* the file identifier to expand as the iri of the file
* @param projectRef
* the project where the file belongs
* @param rev
* the current revision of the file
*/
Expand All @@ -352,8 +329,6 @@ final class Files(
*
* @param id
* the file identifier to expand as the iri of the file
* @param projectRef
* the project where the file belongs
* @param rev
* the current revision of the file
*/
Expand All @@ -372,8 +347,6 @@ final class Files(
*
* @param id
* the identifier that will be expanded to the Iri of the file with its optional rev/tag
* @param project
* the project where the storage belongs
*/
def fetchContent(id: FileId)(implicit caller: Caller): IO[FileResponse] = {
for {
Expand Down Expand Up @@ -480,17 +453,11 @@ final class Files(
fileMetadata: Option[FileCustomMetadata]
): IO[FileAttributes] =
for {
info <- extractFormData(iri, storage, entity)
info <- formDataExtractor(iri, entity, storage.storageValue.maxFileSize)
description = FileDescription.from(info, fileMetadata)
storageMetadata <- saveFile(iri, storage, description, info.contents)
} yield FileAttributes.from(description, storageMetadata)

private def extractFormData(iri: Iri, storage: Storage, entity: HttpEntity): IO[UploadedFileInformation] =
for {
storageAvailableSpace <- storagesStatistics.getStorageAvailableSpace(storage)
fi <- formDataExtractor(iri, entity, storage.storageValue.maxFileSize, storageAvailableSpace)
} yield fi

private def saveFile(
iri: Iri,
storage: Storage,
Expand Down Expand Up @@ -754,7 +721,6 @@ object Files {
fetchContext: FetchContext,
aclCheck: AclCheck,
storages: FetchStorage,
storagesStatistics: StoragesStatistics,
xas: Transactors,
config: FilesConfig,
fileOps: FileOperations,
Expand All @@ -770,7 +736,6 @@ object Files {
aclCheck,
fetchContext,
storages,
storagesStatistics,
fileOps
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,16 @@ sealed trait FormDataExtractor {
* @param id
* the file id
* @param entity
* the Miltipart/FormData payload
* the Multipart/FormData payload
* @param maxFileSize
* the file size limit to be uploaded, provided by the storage
* @param storageAvailableSpace
* the remaining available space on the storage
* @return
* the file metadata. plus the entity with the file content
*/
def apply(
id: Iri,
entity: HttpEntity,
maxFileSize: Long,
storageAvailableSpace: Option[Long]
maxFileSize: Long
): IO[UploadedFileInformation]
}

Expand Down Expand Up @@ -79,13 +76,11 @@ object FormDataExtractor {
override def apply(
id: Iri,
entity: HttpEntity,
maxFileSize: Long,
storageAvailableSpace: Option[Long]
maxFileSize: Long
): IO[UploadedFileInformation] = {
val sizeLimit = Math.min(storageAvailableSpace.getOrElse(Long.MaxValue), maxFileSize)
for {
formData <- unmarshall(entity, sizeLimit)
fileOpt <- extractFile(formData, maxFileSize, storageAvailableSpace)
formData <- unmarshall(entity, maxFileSize)
fileOpt <- extractFile(formData, maxFileSize)
file <- IO.fromOption(fileOpt)(InvalidMultipartFieldName(id))
} yield file
}
Expand All @@ -110,8 +105,7 @@ object FormDataExtractor {

private def extractFile(
formData: FormData,
maxFileSize: Long,
storageAvailableSpace: Option[Long]
maxFileSize: Long
): IO[Option[UploadedFileInformation]] = IO
.fromFuture(
IO(
Expand All @@ -124,7 +118,7 @@ object FormDataExtractor {
)
.adaptError {
case _: EntityStreamSizeException =>
FileTooLarge(maxFileSize, storageAvailableSpace)
FileTooLarge(maxFileSize)
case NotARejection(th) =>
WrappedAkkaRejection(MalformedRequestContentRejection(th.getMessage, th))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,8 @@ object FileRejection {
* Rejection returned when attempting to create/update a file with a Multipart/Form-Data payload that does not
* contain a ''file'' fieldName
*/
final case class FileTooLarge(maxFileSize: Long, storageAvailableSpace: Option[Long])
extends FileRejection(
s"File size exceeds the max file size for the storage ($maxFileSize bytes)${storageAvailableSpace
.fold("") { r => s" or its remaining available space ($r bytes)" }}."
)
final case class FileTooLarge(maxFileSize: Long)
extends FileRejection(s"File size exceeds the max file size for the storage ($maxFileSize bytes).")

/**
* Rejection returned when attempting to create/update a file and the unmarshaller fails
Expand Down Expand Up @@ -277,7 +274,7 @@ object FileRejection {
case FileNotFound(_, _) => (StatusCodes.NotFound, Seq.empty)
case ResourceAlreadyExists(_, _) => (StatusCodes.Conflict, Seq.empty)
case IncorrectRev(_, _) => (StatusCodes.Conflict, Seq.empty)
case FileTooLarge(_, _) => (StatusCodes.PayloadTooLarge, Seq.empty)
case FileTooLarge(_) => (StatusCodes.PayloadTooLarge, Seq.empty)
case WrappedAkkaRejection(rej) => (rej.status, rej.headers)
case WrappedStorageRejection(rej) => (rej.status, rej.headers)
// If this happens it signifies a system problem rather than the user having made a mistake
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"@context": "https://bluebrain.github.io/nexus/contexts/error.json",
"@type": "FileTooLarge",
"reason": "File size exceeds the max file size for the storage (1000 bytes) or its remaining available space (5000 bytes)."
"reason": "File size exceeds the max file size for the storage (1000 bytes)."
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.model.FileRejection._
import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.model.{FileAttributes, FileCustomMetadata, FileDescription, FileId}
import ch.epfl.bluebrain.nexus.delta.plugins.storage.remotestorage.RemoteStorageClientFixtures
import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.model.StorageRejection.StorageNotFound
import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.model.StorageType
import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.model.StorageType.{RemoteDiskStorage => RemoteStorageType}
import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.model.{StorageStatEntry, StorageType}
import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.operations.{AkkaSourceHelpers, FileOperations}
import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.{StorageFixtures, Storages, StoragesConfig, StoragesStatistics}
import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.{StorageFixtures, Storages, StoragesConfig}
import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri
import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary.nxv
import ch.epfl.bluebrain.nexus.delta.sdk.ConfigFixtures
Expand Down Expand Up @@ -119,14 +119,13 @@ class FilesSpec(fixture: RemoteStorageClientFixtures)
(alice, AclAddress.Project(projectRef), Set(otherRead, otherWrite))
).accepted

val maxFileSize = 300L

val cfg = config.copy(
disk = config.disk.copy(defaultMaxFileSize = 500, allowedVolumes = config.disk.allowedVolumes + path),
remoteDisk = Some(config.remoteDisk.value.copy(defaultMaxFileSize = 500))
disk = config.disk.copy(defaultMaxFileSize = maxFileSize, allowedVolumes = config.disk.allowedVolumes + path),
remoteDisk = Some(config.remoteDisk.value.copy(defaultMaxFileSize = maxFileSize))
)

val storageStatistics: StoragesStatistics =
(_, _) => IO.pure { StorageStatEntry(10L, 100L) }

lazy val storages: Storages = Storages(
fetchContext,
ResolverContextResolution(rcr),
Expand All @@ -144,7 +143,6 @@ class FilesSpec(fixture: RemoteStorageClientFixtures)
fetchContext,
aclCheck,
storages,
storageStatistics,
xas,
FilesConfig(eventLogConfig, MediaTypeDetectorConfig.Empty),
fileOps,
Expand Down Expand Up @@ -262,15 +260,10 @@ class FilesSpec(fixture: RemoteStorageClientFixtures)
val aliceCaller = Caller(alice, Set(alice, Group("mygroup", realm), Authenticated(realm)))

"reject if the file exceeds max file size for the storage" in {
val fileTooLarge = randomEntity("large_file", (maxFileSize + 1).toInt)
files
.create(fileId("file-too-long"), Some(remoteId), randomEntity("large_file", 280), None, None)(aliceCaller)
.rejected shouldEqual FileTooLarge(300L, None)
}

"reject if the file exceeds the remaining available space on the storage" in {
files
.create(fileId("file-too-long"), Some(diskId), randomEntity("large_file", 250), None, None)
.rejected shouldEqual FileTooLarge(300L, Some(220))
.create(fileId("file-too-long"), Some(remoteId), fileTooLarge, None, None)(aliceCaller)
.rejected shouldEqual FileTooLarge(maxFileSize)
}

"reject if storage does not exist" in {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class FormDataExtractorSpec
val entity = createEntity("file", NoContentType, Some("filename"))

val UploadedFileInformation(filename, contentType, contents) =
extractor(iri, entity, 250, None).accepted
extractor(iri, entity, 250).accepted

filename shouldEqual "filename"
contentType shouldEqual `application/octet-stream`
Expand All @@ -79,7 +79,7 @@ class FormDataExtractorSpec
"be extracted with the custom media type from the config" in {
val entity = createEntity("file", NoContentType, Some("file.custom"))
val UploadedFileInformation(filename, contentType, contents) =
extractor(iri, entity, 2000, None).accepted
extractor(iri, entity, 2000).accepted

filename shouldEqual "file.custom"
contentType shouldEqual customContentType
Expand All @@ -90,7 +90,7 @@ class FormDataExtractorSpec
val entity = createEntity("file", NoContentType, Some("file.txt"))

val UploadedFileInformation(filename, contentType, contents) =
extractor(iri, entity, 250, None).accepted
extractor(iri, entity, 250).accepted
filename shouldEqual "file.txt"
contentType shouldEqual `text/plain(UTF-8)`
consume(contents.dataBytes) shouldEqual content
Expand All @@ -99,20 +99,20 @@ class FormDataExtractorSpec
"be extracted with the provided content type header" in {
val entity = createEntity("file", `text/plain(UTF-8)`, Some("file.custom"))
val UploadedFileInformation(filename, contentType, contents) =
extractor(iri, entity, 2000, None).accepted
extractor(iri, entity, 2000).accepted
filename shouldEqual "file.custom"
contentType shouldEqual `text/plain(UTF-8)`
consume(contents.dataBytes) shouldEqual content
}

"fail to be extracted if no file part exists found" in {
val entity = createEntity("other", NoContentType, None)
extractor(iri, entity, 250, None).rejectedWith[InvalidMultipartFieldName]
extractor(iri, entity, 250).rejectedWith[InvalidMultipartFieldName]
}

"fail to be extracted if payload size is too large" in {
val entity = createEntity("other", `text/plain(UTF-8)`, None)
extractor(iri, entity, 10, None).rejected shouldEqual FileTooLarge(10L, None)
extractor(iri, entity, 10).rejected shouldEqual FileTooLarge(10L)
}
}
}
Loading

0 comments on commit b16f700

Please sign in to comment.