Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,15 @@
</arguments>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>9</source>
<target>9</target>
</configuration>
</plugin>
</plugins>
</build>

Expand Down
4 changes: 3 additions & 1 deletion src/main/kotlin/com/ex_dock/ex_dock/database/JDBCStarter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.ex_dock.ex_dock.database.home.HomeJdbcVerticle
import com.ex_dock.ex_dock.database.product.*
import com.ex_dock.ex_dock.database.scope.ScopeJdbcVerticle
import com.ex_dock.ex_dock.database.server.ServerJDBCVerticle
import com.ex_dock.ex_dock.database.service.DatabaseBackupVerticle
import com.ex_dock.ex_dock.database.text_pages.TextPagesJdbcVerticle
import com.ex_dock.ex_dock.database.url.UrlJdbcVerticle
import com.ex_dock.ex_dock.helper.deployWorkerVerticleHelper
Expand All @@ -24,7 +25,7 @@ class JDBCStarter: AbstractVerticle() {
Future.all(verticles)
.onComplete {
println("All JDBC verticles deployed")
TODO("Add all codecs to the eventbus")
// TODO("Add all codecs to the eventbus")
}
.onFailure { error ->
println("Failed to deploy JDBC verticles: $error")
Expand All @@ -47,6 +48,7 @@ class JDBCStarter: AbstractVerticle() {
verticles.add(deployWorkerVerticleHelper(vertx, ProductStoreViewEavJdbcVerticle::class.qualifiedName.toString(), 5, 5))
verticles.add(deployWorkerVerticleHelper(vertx, ProductWebsiteEavJdbcVerticle::class.qualifiedName.toString(), 5, 5))
verticles.add(deployWorkerVerticleHelper(vertx, ProductCustomAttributesJdbcVerticle::class.qualifiedName.toString(), 5, 5))
verticles.add(deployWorkerVerticleHelper(vertx, DatabaseBackupVerticle::class.qualifiedName.toString(), 1, 1))
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
package com.ex_dock.ex_dock.database.service

import com.ex_dock.ex_dock.database.connection.Connection
import io.vertx.core.AbstractVerticle
import io.vertx.core.eventbus.EventBus
import io.vertx.sqlclient.Pool
import java.io.BufferedReader
import java.io.File
import java.io.IOException
import java.io.InputStreamReader
import java.time.LocalTime
import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoField
import java.time.temporal.ChronoUnit
import java.util.*

/**
* Verticle for all events connected to database backups
*/
class DatabaseBackupVerticle: AbstractVerticle() {
private lateinit var client: Pool
private lateinit var eventBus: EventBus
private lateinit var frequency: String
private lateinit var time: String
private lateinit var dbUser: String
private lateinit var dbPassword: String
private var savedBackups = 1

/**
* Initialize variables from properties and start the eventbus and scheduler
*/
override fun start() {
client = Connection().getConnection(vertx)
eventBus = vertx.eventBus()

//Read the variables from the properties file
try {
val props: Properties = javaClass.classLoader.getResourceAsStream("secret.properties").use {
Properties().apply { load(it) }
}

frequency = props.getProperty("BACKUP_FREQUENCY")
time = props.getProperty("BACKUP_TIME")
savedBackups = props.getProperty("SAVED_BACKUPS").toInt()
dbUser = props.getProperty("DATABASE_USERNAME")
dbPassword = props.getProperty("DATABASE_PASSWORD")

} catch (e: Exception) {
println(e.message)
try {
// Try to load the docker values when run in GitHub actions
val isDocker: Boolean = !System.getenv("GITHUB_RUN_NUMBER").isNullOrEmpty()
if (isDocker) {
frequency = "daily"
time = "00:00"
savedBackups = 1
} else {
error("Could not load the Properties file!")
}
} catch (e: Exception) {
println(e.message)
error("Could not read the Properties file!")
}
}

backupWithEventBusRequest()
scheduler()
}

/**
* Makes a full backup of the current state of the database
*/
private fun backupDatabaseData() {
organizeBackups()
val zdt: ZonedDateTime = ZonedDateTime.now(ZoneId.systemDefault())
val zdtTruncatedHour: ZonedDateTime = zdt.truncatedTo(ChronoUnit.HOURS)
val f: DateTimeFormatter = DateTimeFormatter.ofPattern("MM/dd/yyyy HH", Locale.getDefault())
val timeString = zdtTruncatedHour.format(f)
.replace(" ", "_")
.replace("/", "_") +
".sql"

val backupDirectory = System.getProperty("user.dir") + "\\database\\backup\\backup_$timeString"
val processBuilder = ProcessBuilder(
"pg_dump",
"--host", "localhost",
"--port", "8890",
"--file", backupDirectory,
"--blobs",
"--verbose",
"--username", dbUser,
"--no-password",
"ex-dock"
)

try {
val env = processBuilder.environment()
env["PGPASSWORD"] = dbPassword
val p: Process = processBuilder.start()
val bufferedReader = BufferedReader(InputStreamReader(p.errorStream))
var line = bufferedReader.readLine()
while (line!= null) {
println(line)
line = bufferedReader.readLine()
}
} catch (e: IOException) {
e.printStackTrace()
}
}

/**
* Gets the request for a backup from the eventbus
*/
private fun backupWithEventBusRequest() {
eventBus.consumer<String>("process.service.backup").handler { message ->
backupDatabaseData()
message.reply("Completed backup!")
}
}

/**
* Creates a scheduler for making backups at specified intervals
*/
private fun scheduler() {
val frequencyInt: Long = getFrequencyTimeNumber(frequency)
val timeInt: Long = getTimeNumber(time)
val currentTime: Long = LocalTime.now(ZoneId.systemDefault())[ChronoField.MILLI_OF_DAY].toLong()
var waitingTime: Long = timeInt - currentTime

if (waitingTime < 0) {
val tempTimeInt = getTimeNumber("23:59")
waitingTime = (tempTimeInt - currentTime) + timeInt
}

vertx.setTimer(waitingTime) {
backupDatabaseData()

// Sets the given interval
vertx.setPeriodic(frequencyInt*1000L) {
backupDatabaseData()
}
}
}

/**
* Converts the given frequency to a usable number for the scheduler
*/
private fun getFrequencyTimeNumber(frequency: String): Long {
return when (frequency) {
"daily" -> 1*24*60*60
"weekly" -> 7*24*60*60
"monthly" -> 30*24*60*60
"hourly" -> 60*60
else -> 1
}
}

/**
* Gets the time in milliseconds for the wanted backup time
*/
private fun getTimeNumber(time: String): Long {
val parts = time.split(":")
return (parts[0].toInt() * 60 * 60 + parts[1].toInt() * 60) * 1000L
}

/**
* Removes the oldest backup if the number of backups is greater than the maximum number of backups allowed
*/
private fun organizeBackups() {
val backupDirectory = System.getProperty("user.dir") + "\\database\\backup"
val folder = File(backupDirectory)
val listOfFiles = folder.listFiles()
val fileDateList: MutableList<String> = emptyList<String>().toMutableList()
var oldestDate = ""

// Skip organizing when the number of backups is lower than the maximum number of backups allowed
if (listOfFiles!!.size >= savedBackups) {
for (f in listOfFiles) {
val fileDateString = f.name
.replace("backup_", "")
.replace(".sql", "")
fileDateList.add(fileDateString)
}

for (dateString in fileDateList) {
oldestDate = if (oldestDate != "") {
val dateParts = dateString.split("_")
val oldestDateParts = oldestDate.split("_")

checkIfOlderFile(
dateParts,
oldestDateParts,
dateString,
oldestDate)
} else {
dateString
}
}

val oldestBackup = File("$backupDirectory\\backup_$oldestDate.sql")

if (oldestBackup.delete()) {
println("Deleted oldest backup from: $oldestDate")
} else {
println("Could not delete oldest backup from: $oldestDate")
}
}
}

/**
* Checks the backup name and returns the oldest backup date
*/
private fun checkIfOlderFile(dateParts: List<String>,
oldestDateParts: List<String>,
dateString: String,
oldestDate: String): String {
// Check if the Year is older
if (dateParts[2].toInt() < oldestDateParts[2].toInt()) {
return dateString

// Check if the Year is the same
} else if (dateParts[2].toInt() == oldestDateParts[2].toInt()) {

// Check if the Month is older
if (dateParts[1].toInt() < oldestDateParts[1].toInt()) {
return dateString

// Check if the Month is the same
} else if (dateParts[1].toInt() == oldestDateParts[1].toInt()) {

// Check if the Day is older
if (dateParts[0].toInt() < oldestDateParts[0].toInt()) {
return dateString

// Check if the Day is the same
} else if (dateParts[0].toInt() == oldestDateParts[0].toInt()) {

// Check if the Hour is older
if (dateParts[3].toInt() < oldestDateParts[3].toInt()) {
return dateString

// Check if the Hour is the same
} else if (dateParts[3].toInt() == oldestDateParts[3].toInt()) {
return dateString
}
}
}
}

return oldestDate
}
}
3 changes: 3 additions & 0 deletions src/main/resources/default.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ JDBC_URL=127.0.0.1
DATABASE_URL="test_url"
DATABASE_PASSWORD="test_password"
DATABASE_USERNAME=test_username
BACKUP_FREQUENCY="daily"
BACKUP_TIME="23:00"
SAVED_BACKUPS=1