Skip to content

Commit b5bf704

Browse files
committed
Merge branch 'feat/OTA-799/delete-objects' into 'master'
Resolve OTA-799 "Feat//delete objects" Closes OTA-799 See merge request rd/platform/core-services/treehub!101
2 parents c60c41c + 6458f2e commit b5bf704

File tree

9 files changed

+119
-5
lines changed

9 files changed

+119
-5
lines changed

src/main/scala/com/advancedtelematic/treehub/http/ObjectResource.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ class ObjectResource(namespace: Directive1[Namespace],
6969

7070
complete(f)
7171
} ~
72+
(delete & hintNamespaceStorage(ns)) {
73+
complete(objectStore.deleteObject(ns, objectId).map(_ => StatusCodes.NoContent))
74+
} ~
7275
(put & hintNamespaceStorage(ns)) {
7376
complete(objectStore.completeClientUpload(ns, objectId).map(_ => StatusCodes.NoContent))
7477
} ~

src/main/scala/com/advancedtelematic/treehub/object_store/BlobStore.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.advancedtelematic.treehub.object_store
22

3+
import akka.Done
34
import akka.http.scaladsl.model._
45
import akka.stream.scaladsl.Source
56
import akka.util.ByteString
@@ -16,6 +17,8 @@ object BlobStore {
1617
}
1718

1819
trait BlobStore {
20+
def deleteObject(ns: Namespace, objectId: ObjectId): Future[Done]
21+
1922
def storeStream(namespace: Namespace, id: ObjectId, size: Long, blob: Source[ByteString, _]): Future[Long]
2023

2124
val supportsOutOfBandStorage: Boolean

src/main/scala/com/advancedtelematic/treehub/object_store/LocalFsBlobStore.scala

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package com.advancedtelematic.treehub.object_store
22

3+
import akka.Done
4+
35
import java.nio.file.StandardOpenOption.{CREATE, READ, WRITE}
46
import java.nio.file.{Files, Path}
5-
67
import akka.http.scaladsl.model.HttpResponse
78
import akka.http.scaladsl.util.FastFuture
89
import akka.stream.Materializer
@@ -16,7 +17,6 @@ import org.slf4j.LoggerFactory
1617
import scala.concurrent.{ExecutionContext, Future}
1718
import scala.util.Try
1819

19-
2020
object LocalFsBlobStore {
2121
private val _log = LoggerFactory.getLogger(this.getClass)
2222

@@ -92,4 +92,13 @@ class LocalFsBlobStore(root: Path)(implicit ec: ExecutionContext, mat: Materiali
9292
}
9393

9494
override val supportsOutOfBandStorage: Boolean = false
95+
96+
override def deleteObject(ns: Namespace, objectId: ObjectId): Future[Done] = {
97+
val f = Try {
98+
Files.delete(objectId.path(namespacePath(ns)))
99+
Done
100+
}
101+
102+
Future.fromTry(f)
103+
}
95104
}

src/main/scala/com/advancedtelematic/treehub/object_store/ObjectStore.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package com.advancedtelematic.treehub.object_store
22

3-
import java.io.File
3+
import akka.Done
44

5+
import java.io.File
56
import akka.http.scaladsl.model.HttpResponse
67
import akka.http.scaladsl.util.FastFuture
78
import akka.stream.scaladsl.{FileIO, Source}
@@ -16,6 +17,10 @@ import slick.jdbc.MySQLProfile.api._
1617
import scala.concurrent.{ExecutionContext, Future}
1718

1819
class ObjectStore(blobStore: BlobStore)(implicit ec: ExecutionContext, db: Database) extends ObjectRepositorySupport {
20+
def deleteObject(ns: Namespace, objectId: ObjectId): Future[Done] = for {
21+
deleted <- objectRepository.delete(ns, objectId)
22+
_ <- if(deleted > 0) blobStore.deleteObject(ns, objectId) else FastFuture.failed(Errors.ObjectNotFound)
23+
} yield Done
1924

2025
import scala.async.Async._
2126

src/main/scala/com/advancedtelematic/treehub/object_store/S3BlobStore.scala

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package com.advancedtelematic.treehub.object_store
22

3+
import akka.Done
4+
35
import java.io.File
46
import java.nio.file.Paths
57
import java.time.temporal.ChronoUnit
68
import java.time.{Duration, Instant}
79
import java.util.Date
8-
910
import akka.http.scaladsl.model.headers.Location
1011
import akka.http.scaladsl.model.{HttpResponse, StatusCodes, Uri}
1112
import akka.stream.Materializer
@@ -144,6 +145,14 @@ class S3BlobStore(s3Credentials: S3Credentials, s3client: AmazonS3, allowRedirec
144145
objectId.path(Paths.get(namespaceDir(namespace))).toString
145146

146147
override val supportsOutOfBandStorage: Boolean = true
148+
149+
override def deleteObject(ns: Namespace, objectId: ObjectId): Future[Done] =
150+
Future {
151+
blocking {
152+
s3client.deleteObject(bucketId, objectFilename(ns, objectId))
153+
Done
154+
}
155+
}
147156
}
148157

149158
object S3Client {

src/test/scala/com/advancedtelematic/treehub/http/ObjectResourceSpec.scala

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package com.advancedtelematic.treehub.http
77
import akka.http.scaladsl.model._
88
import akka.http.scaladsl.unmarshalling.PredefinedFromEntityUnmarshallers.byteArrayUnmarshaller
99
import akka.pattern.ask
10+
import com.advancedtelematic.libats.http.Errors.MissingEntity
1011
import com.advancedtelematic.treehub.db.ObjectRepositorySupport
1112
import com.advancedtelematic.util.FakeUsageUpdate.{CurrentBandwith, CurrentStorage}
1213
import com.advancedtelematic.util.ResourceSpec.ClientTObject
@@ -43,6 +44,71 @@ class ObjectResourceSpec extends TreeHubSpec with ResourceSpec with ObjectReposi
4344
}
4445
}
4546

47+
test("DELETE removes object") {
48+
val obj = new ClientTObject()
49+
50+
Post(apiUri(s"objects/${obj.prefixedObjectId}"), obj.blob) ~> routes ~> check {
51+
status shouldBe StatusCodes.NoContent
52+
}
53+
54+
Delete(apiUri(s"objects/${obj.prefixedObjectId}")) ~> routes ~> check {
55+
status shouldBe StatusCodes.NoContent
56+
}
57+
58+
Get(apiUri(s"objects/${obj.prefixedObjectId}")) ~> routes ~> check {
59+
status shouldBe StatusCodes.NotFound
60+
}
61+
}
62+
63+
test("DELETE removes object from database and underlying storage") {
64+
val obj = new ClientTObject()
65+
66+
Post(apiUri(s"objects/${obj.prefixedObjectId}"), obj.blob) ~> routes ~> check {
67+
status shouldBe StatusCodes.NoContent
68+
}
69+
70+
Delete(apiUri(s"objects/${obj.prefixedObjectId}")) ~> routes ~> check {
71+
status shouldBe StatusCodes.NoContent
72+
}
73+
74+
objectRepository.find(defaultNs, obj.objectId).failed.futureValue shouldBe Errors.ObjectNotFound
75+
objectStore.exists(defaultNs, obj.objectId).futureValue shouldBe false
76+
localFsBlobStore.exists(defaultNs, obj.objectId).futureValue shouldBe false
77+
}
78+
79+
test("DELETE keeps other objects intact") {
80+
val obj = new ClientTObject()
81+
val obj2 = new ClientTObject()
82+
83+
Post(apiUri(s"objects/${obj.prefixedObjectId}"), obj.blob) ~> routes ~> check {
84+
status shouldBe StatusCodes.NoContent
85+
}
86+
87+
Post(apiUri(s"objects/${obj2.prefixedObjectId}"), obj.blob) ~> routes ~> check {
88+
status shouldBe StatusCodes.NoContent
89+
}
90+
91+
Delete(apiUri(s"objects/${obj.prefixedObjectId}")) ~> routes ~> check {
92+
status shouldBe StatusCodes.NoContent
93+
}
94+
95+
Get(apiUri(s"objects/${obj.prefixedObjectId}")) ~> routes ~> check {
96+
status shouldBe StatusCodes.NotFound
97+
}
98+
99+
Get(apiUri(s"objects/${obj2.prefixedObjectId}")) ~> routes ~> check {
100+
status shouldBe StatusCodes.OK
101+
}
102+
}
103+
104+
test("DELETE returns 404 when object does not exist") {
105+
val obj = new ClientTObject()
106+
107+
Delete(apiUri(s"objects/${obj.prefixedObjectId}")) ~> routes ~> check {
108+
status shouldBe StatusCodes.NotFound
109+
}
110+
}
111+
46112
test("POST fails if object is empty and server does not support out of band upload") {
47113
val obj = new ClientTObject()
48114

src/test/scala/com/advancedtelematic/treehub/http/TreehubRoutesSpec.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.advancedtelematic.treehub.http
22

3+
import akka.Done
34
import akka.http.scaladsl.model.{HttpResponse, StatusCodes}
45
import akka.http.scaladsl.server.Directives
56
import akka.http.scaladsl.util.FastFuture
@@ -28,6 +29,8 @@ class TreehubRoutesSpec extends TreeHubSpec with ResourceSpec {
2829
override def readFull(namespace: DataType.Namespace, id: ObjectId): Future[ByteString] = ???
2930

3031
override def exists(namespace: DataType.Namespace, id: ObjectId): Future[Boolean] = FastFuture.failed(new SdkClientException("Timeout on waiting"))
32+
33+
override def deleteObject(ns: DataType.Namespace, objectId: ObjectId): Future[Done] = FastFuture.failed(new RuntimeException("[test] delete failed"))
3134
}
3235

3336
val errorObjectStore = new ObjectStore(errorBlobStore)

src/test/scala/com/advancedtelematic/treehub/object_store/S3BlobStoreIntegrationSpec.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,20 @@ class S3BlobStoreIntegrationSpec extends TreeHubSpec {
5656
s3BlobStore.exists(ns, obj.objectId).futureValue shouldBe true
5757
}
5858

59+
test("can delete object") {
60+
val obj = new ClientTObject()
61+
62+
val f = async {
63+
await(s3BlobStore.storeStream(ns, obj.objectId, obj.blob.size, obj.byteSource))
64+
}
65+
66+
s3BlobStore.exists(ns, obj.objectId).futureValue shouldBe true
67+
68+
s3BlobStore.deleteObject(ns, obj.objectId).futureValue
69+
70+
s3BlobStore.exists(ns, obj.objectId).futureValue shouldBe false
71+
}
72+
5973
test("build response builds a redirect") {
6074
val redirectS3BlobStore = S3BlobStore(s3Credentials, allowRedirects = true)
6175

src/test/scala/com/advancedtelematic/util/ResourceSpec.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ trait ResourceSpec extends ScalatestRouteTest with DatabaseSpec with Settings {
8989

9090
lazy val namespaceExtractor = NamespaceDirectives.defaultNamespaceExtractor.map(_.namespace)
9191

92-
val objectStore = new ObjectStore(new LocalFsBlobStore(Files.createTempDirectory("treehub-obj")))
92+
val localFsBlobStore = new LocalFsBlobStore(Files.createTempDirectory("treehub-obj"))
93+
94+
val objectStore = new ObjectStore(localFsBlobStore)
9395

9496
val deltaStore = new LocalDeltaStorage(Files.createTempDirectory("treehub-deltas"))
9597

0 commit comments

Comments
 (0)