Skip to content

Commit fcbaf27

Browse files
author
Johannes Duesing
committed
The DAO is now able to dump its data to a recovery file and re-read it
Tests now cover the whole DynamicInstanceDAO
1 parent bc764a6 commit fcbaf27

File tree

4 files changed

+172
-10
lines changed

4 files changed

+172
-10
lines changed

src/main/scala/de/upb/cs/swt/delphi/instanceregistry/Configuration.scala

+1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ package de.upb.cs.swt.delphi.instanceregistry
33
class Configuration( //Server configuration
44
val bindHost: String = "0.0.0.0",
55
val bindPort: Int = 8087,
6+
val recoveryFileName : String = "dump.temp",
67
)

src/main/scala/de/upb/cs/swt/delphi/instanceregistry/daos/DynamicInstanceDAO.scala

+73-4
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,33 @@
11
package de.upb.cs.swt.delphi.instanceregistry.daos
22

3+
import java.io.{File, IOException, PrintWriter}
4+
35
import akka.actor.ActorSystem
4-
import de.upb.cs.swt.delphi.instanceregistry.{AppLogging, Server}
5-
import de.upb.cs.swt.delphi.instanceregistry.io.swagger.client.model.Instance
6+
import akka.stream.ActorMaterializer
7+
import de.upb.cs.swt.delphi.instanceregistry.{AppLogging, Configuration, Server}
8+
import de.upb.cs.swt.delphi.instanceregistry.io.swagger.client.model.{Instance, JsonSupport}
69
import de.upb.cs.swt.delphi.instanceregistry.io.swagger.client.model.InstanceEnums.ComponentType
710

811
import scala.collection.mutable
12+
import scala.concurrent.ExecutionContext
913
import scala.util.{Failure, Success, Try}
14+
import spray.json._
15+
import spray.json.DefaultJsonProtocol._
16+
17+
import scala.io.Source
1018

1119
/**
1220
* Implementation of the instance data access object that keeps its data in memory
1321
* instead of using a persistent storage.
1422
*/
15-
class DynamicInstanceDAO extends InstanceDAO with AppLogging {
23+
class DynamicInstanceDAO (configuration : Configuration) extends InstanceDAO with AppLogging with JsonSupport {
1624

1725
private val instances : mutable.Set[Instance] = new mutable.HashSet[Instance]()
26+
1827
implicit val system : ActorSystem = Server.system
28+
implicit val materializer : ActorMaterializer = ActorMaterializer()
29+
implicit val ec : ExecutionContext = system.dispatcher
30+
1931

2032
override def addInstance(instance: Instance): Try[Unit] = {
2133
//Verify ID is present in instance
@@ -27,6 +39,7 @@ class DynamicInstanceDAO extends InstanceDAO with AppLogging {
2739
//Verify id is not already present in instances!
2840
if(!hasInstance(instance.id.get)){
2941
instances.add(instance)
42+
dumpToRecoveryFile()
3043
Success(log.info(s"Added instance ${instance.name} with id ${instance.id} to database."))
3144
} else {
3245
val msg = s"Cannot add instance ${instance.name}, id ${instance.id} already present."
@@ -46,6 +59,7 @@ class DynamicInstanceDAO extends InstanceDAO with AppLogging {
4659
if(hasInstance(id)){
4760
//AddInstance verifies that id is always present, hasInstance verifies that find will return an instance
4861
instances.remove(instances.find(i => i.id.get == id).get)
62+
dumpToRecoveryFile()
4963
Success(log.info(s"Successfully removed instance with id $id."))
5064
} else {
5165
val msg = s"Cannot remove instance with id $id, that id is not present."
@@ -72,8 +86,63 @@ class DynamicInstanceDAO extends InstanceDAO with AppLogging {
7286
List() ++ instances
7387
}
7488

75-
override def clearAll() : Unit = {
89+
override def removeAll() : Unit = {
90+
instances.clear()
91+
dumpToRecoveryFile()
92+
}
93+
94+
private[daos] def clearData() : Unit = {
7695
instances.clear()
7796
}
7897

98+
private[daos] def dumpToRecoveryFile() : Unit = {
99+
log.info(s"Dumping data to recovery file ${configuration.recoveryFileName} ...")
100+
val writer = new PrintWriter(new File(configuration.recoveryFileName))
101+
writer.write(getAllInstances().toJson(listFormat(instanceFormat)).toString())
102+
writer.flush()
103+
writer.close()
104+
log.info(s"Successfully wrote to recovery file.")
105+
}
106+
107+
private[daos] def deleteRecoveryFile() : Unit = {
108+
log.info("Deleting data recovery file...")
109+
if(new File(configuration.recoveryFileName).delete()){
110+
log.info(s"Successfully deleted data recovery file ${configuration.recoveryFileName}.")
111+
} else {
112+
log.warning(s"Failed to delete data recovery file ${configuration.recoveryFileName}.")
113+
}
114+
}
115+
116+
private[daos] def tryInitFromRecoveryFile() : Unit = {
117+
try {
118+
log.info(s"Attempting to load data from recovery file ${configuration.recoveryFileName} ...")
119+
val recoveryFileContent = Source.fromFile(configuration.recoveryFileName).getLines()
120+
121+
if(!recoveryFileContent.hasNext){
122+
log.warning(s"Recovery file invalid, more than one line found.")
123+
throw new IOException("Recovery file invalid.")
124+
}
125+
126+
val jsonString : String = recoveryFileContent.next()
127+
128+
val instanceList = jsonString.parseJson.convertTo[List[Instance]](listFormat(instanceFormat))
129+
130+
log.info(s"Successfully loaded ${instanceList.size} instance from recovery file. Initializing...")
131+
132+
clearData()
133+
for(instance <- instanceList){
134+
addInstance(instance)
135+
}
136+
137+
log.info(s"Successfully initialized from recovery file.")
138+
139+
} catch {
140+
case iox : IOException =>
141+
log.error(iox, s"An error occurred while reading the recovery file at ${configuration.recoveryFileName}.")
142+
case x : Exception =>
143+
log.error(x, "An error occurred while deserializing the contents of the recovery file.")
144+
}
145+
146+
}
147+
79148
}

src/main/scala/de/upb/cs/swt/delphi/instanceregistry/daos/InstanceDAO.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,6 @@ trait InstanceDAO {
5454
/**
5555
* Removes all instances from the DAO
5656
*/
57-
def clearAll() : Unit
57+
def removeAll() : Unit
5858

5959
}
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
package de.upb.cs.swt.delphi.instanceregistry.daos
22

3+
import de.upb.cs.swt.delphi.instanceregistry.Configuration
34
import de.upb.cs.swt.delphi.instanceregistry.io.swagger.client.model.Instance
45
import de.upb.cs.swt.delphi.instanceregistry.io.swagger.client.model.InstanceEnums.ComponentType
5-
import org.scalatest.{BeforeAndAfterAll, FlatSpec, Matchers}
6+
import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach, FlatSpec, Matchers}
67

7-
class DynamicInstanceDAOTest extends FlatSpec with Matchers with BeforeAndAfterAll{
8+
class DynamicInstanceDAOTest extends FlatSpec with Matchers with BeforeAndAfterEach{
89

9-
val dao : InstanceDAO = new DynamicInstanceDAO()
10+
val dao : DynamicInstanceDAO = new DynamicInstanceDAO(new Configuration())
1011

1112
private def buildInstance(id : Int) : Instance = {
1213
Instance(Some(id), "https://localhost", 12345, "TestInstance", ComponentType.Crawler)
1314
}
1415

15-
override protected def beforeAll() : Unit = {
16-
dao.clearAll()
16+
override protected def beforeEach() : Unit = {
17+
dao.deleteRecoveryFile()
1718
for(i <- 1 to 3){
1819
dao.addInstance(buildInstance(i))
1920
}
@@ -26,4 +27,95 @@ class DynamicInstanceDAOTest extends FlatSpec with Matchers with BeforeAndAfterA
2627
assert(dao.removeInstance(4).isSuccess)
2728
}
2829

30+
it must "not allow the addition of any id twice" in {
31+
assert(dao.addInstance(buildInstance(3)).isFailure)
32+
assert(dao.getAllInstances().size == 3)
33+
}
34+
35+
it must "return true on hasInstance for any present id" in {
36+
for(i <- 1 to 3){
37+
assert(dao.hasInstance(i))
38+
}
39+
}
40+
41+
it must "return false on hasInstance for any id not present" in {
42+
assert(!dao.hasInstance(-1))
43+
assert(!dao.hasInstance(Long.MaxValue))
44+
assert(!dao.hasInstance(4))
45+
}
46+
47+
it must "return instances with the correct id on getInstance" in {
48+
for(i <- 1 to 3){
49+
val instance = dao.getInstance(i)
50+
assert(instance.isDefined)
51+
assert(instance.get.id.isDefined)
52+
assert(instance.get.id.get == i)
53+
}
54+
}
55+
56+
it must "return instance with the correct type on getInstanceOfType" in {
57+
val compTypeInstances = dao.getInstancesOfType(ComponentType.Crawler)
58+
assert(compTypeInstances.size == 3)
59+
60+
for(instance <- compTypeInstances){
61+
assert(instance.componentType == ComponentType.Crawler)
62+
}
63+
}
64+
65+
it must "remove instances that are present in the DAO" in {
66+
for(i <- 1 to 3){
67+
assert(dao.removeInstance(i).isSuccess)
68+
assert(!dao.hasInstance(i))
69+
}
70+
assert(dao.getAllInstances().isEmpty)
71+
}
72+
73+
it must "not change the data on removing invalid IDs" in {
74+
assert(dao.removeInstance(-1).isFailure)
75+
assert(dao.removeInstance(Long.MaxValue).isFailure)
76+
assert(dao.removeInstance(4).isFailure)
77+
}
78+
79+
it must "remove all instance on removeAll" in {
80+
dao.removeAll()
81+
assert(dao.getAllInstances().isEmpty)
82+
}
83+
84+
"The DAO" must "be able to read multiple instances from the recovery file" in {
85+
dao.dumpToRecoveryFile()
86+
dao.clearData()
87+
assert(dao.getAllInstances().isEmpty)
88+
dao.tryInitFromRecoveryFile()
89+
assert(dao.getAllInstances().size == 3)
90+
}
91+
92+
it must "fail to load from recovery file if it is not present" in {
93+
dao.dumpToRecoveryFile()
94+
assert(dao.getAllInstances().size == 3)
95+
dao.deleteRecoveryFile()
96+
dao.clearData()
97+
assert(dao.getAllInstances().isEmpty)
98+
dao.tryInitFromRecoveryFile()
99+
assert(dao.getAllInstances().isEmpty)
100+
}
101+
102+
it must "contain the correct instance data after loading from recovery file" in {
103+
assert(dao.addInstance(buildInstance(4)).isSuccess)
104+
dao.dumpToRecoveryFile()
105+
assert(dao.getAllInstances().size == 4)
106+
dao.clearData()
107+
assert(dao.getAllInstances().isEmpty)
108+
dao.tryInitFromRecoveryFile()
109+
assert(dao.getAllInstances().size == 4)
110+
val instance = dao.getInstance(4)
111+
assert(instance.isDefined)
112+
assert(instance.get.id.get == 4)
113+
}
114+
115+
116+
override protected def afterEach() : Unit = {
117+
dao.removeAll()
118+
dao.deleteRecoveryFile()
119+
}
120+
29121
}

0 commit comments

Comments
 (0)