Initial work on scheduled backups.

This commit is contained in:
Logan Gorence 2021-12-24 08:38:57 +00:00
parent b2851d13b9
commit c1f621aa7b
No known key found for this signature in database
GPG Key ID: 9743CEF10935949A
12 changed files with 119 additions and 8 deletions

View File

@ -2,4 +2,5 @@ dependencies {
// TODO: might be able to ship all dependencies in core? are we duplicating classes in JARs? // TODO: might be able to ship all dependencies in core? are we duplicating classes in JARs?
implementation("software.amazon.awssdk:s3:2.17.102") implementation("software.amazon.awssdk:s3:2.17.102")
implementation("org.quartz-scheduler:quartz:2.3.2")
} }

View File

@ -8,6 +8,7 @@ import cloud.kubelet.foundation.core.features.stats.StatsFeature
import cloud.kubelet.foundation.core.features.update.UpdateFeature import cloud.kubelet.foundation.core.features.update.UpdateFeature
import cloud.kubelet.foundation.core.features.world.WorldFeature import cloud.kubelet.foundation.core.features.world.WorldFeature
import cloud.kubelet.foundation.core.features.persist.PersistenceFeature import cloud.kubelet.foundation.core.features.persist.PersistenceFeature
import cloud.kubelet.foundation.core.features.scheduler.SchedulerFeature
import org.koin.dsl.module import org.koin.dsl.module
import java.nio.file.Path import java.nio.file.Path
@ -38,6 +39,7 @@ class FoundationCorePlugin : FoundationPlugin() {
} }
override fun createFeatures() = listOf( override fun createFeatures() = listOf(
SchedulerFeature(),
PersistenceFeature(), PersistenceFeature(),
BackupFeature(), BackupFeature(),
DevFeature(), DevFeature(),

View File

@ -0,0 +1,7 @@
package cloud.kubelet.foundation.core.abstraction
interface CoreFeature {
fun enable()
fun disable()
fun module() = org.koin.dsl.module {}
}

View File

@ -7,13 +7,15 @@ import org.bukkit.event.Listener
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
import org.koin.dsl.module import org.koin.dsl.module
import org.quartz.Scheduler
abstract class Feature : KoinComponent, Listener { abstract class Feature : CoreFeature, KoinComponent, Listener {
private val plugin by inject<FoundationCorePlugin>() private val plugin by inject<FoundationCorePlugin>()
protected val scheduler by inject<Scheduler>()
open fun enable() {} override fun enable() {}
open fun disable() {} override fun disable() {}
open fun module() = module {} override fun module() = module {}
protected fun registerCommandExecutor(name: String, executor: CommandExecutor) { protected fun registerCommandExecutor(name: String, executor: CommandExecutor) {
registerCommandExecutor(listOf(name), executor) registerCommandExecutor(listOf(name), executor)

View File

@ -10,7 +10,7 @@ import org.koin.dsl.module
abstract class FoundationPlugin : JavaPlugin() { abstract class FoundationPlugin : JavaPlugin() {
private lateinit var pluginModule: Module private lateinit var pluginModule: Module
private lateinit var pluginApplication: KoinApplication private lateinit var pluginApplication: KoinApplication
private lateinit var features: List<Feature> private lateinit var features: List<CoreFeature>
private lateinit var module: Module private lateinit var module: Module
override fun onEnable() { override fun onEnable() {
@ -45,7 +45,10 @@ abstract class FoundationPlugin : JavaPlugin() {
try { try {
slF4JLogger.info("Enabling feature: ${it.javaClass.simpleName}") slF4JLogger.info("Enabling feature: ${it.javaClass.simpleName}")
it.enable() it.enable()
// TODO: May replace this check with a method in the interface, CoreFeature would no-op.
if (it is Feature) {
server.pluginManager.registerEvents(it, this) server.pluginManager.registerEvents(it, this)
}
} catch (e: Exception) { } catch (e: Exception) {
slF4JLogger.error("Failed to enable feature: ${it.javaClass.simpleName}", e) slF4JLogger.error("Failed to enable feature: ${it.javaClass.simpleName}", e)
} }
@ -61,5 +64,5 @@ abstract class FoundationPlugin : JavaPlugin() {
} }
protected open fun createModule() = module {} protected open fun createModule() = module {}
protected abstract fun createFeatures(): List<Feature> protected abstract fun createFeatures(): List<CoreFeature>
} }

View File

@ -4,9 +4,15 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
data class BackupConfig( data class BackupConfig(
val schedule: ScheduleConfig = ScheduleConfig(),
val s3: S3Config = S3Config(), val s3: S3Config = S3Config(),
) )
@Serializable
data class ScheduleConfig(
val cron: String = "",
)
@Serializable @Serializable
data class S3Config( data class S3Config(
val accessKeyId: String = "", val accessKeyId: String = "",

View File

@ -3,8 +3,9 @@ package cloud.kubelet.foundation.core.features.backup
import cloud.kubelet.foundation.core.FoundationCorePlugin import cloud.kubelet.foundation.core.FoundationCorePlugin
import cloud.kubelet.foundation.core.Util import cloud.kubelet.foundation.core.Util
import cloud.kubelet.foundation.core.abstraction.Feature import cloud.kubelet.foundation.core.abstraction.Feature
import cloud.kubelet.foundation.core.features.scheduler.cancel
import cloud.kubelet.foundation.core.features.scheduler.cron
import com.charleskorn.kaml.Yaml import com.charleskorn.kaml.Yaml
import org.koin.core.KoinApplication
import org.koin.core.component.inject import org.koin.core.component.inject
import org.koin.dsl.module import org.koin.dsl.module
import software.amazon.awssdk.auth.credentials.AwsSessionCredentials import software.amazon.awssdk.auth.credentials.AwsSessionCredentials
@ -18,6 +19,7 @@ class BackupFeature : Feature() {
private val plugin by inject<FoundationCorePlugin>() private val plugin by inject<FoundationCorePlugin>()
private val s3Client by inject<S3Client>() private val s3Client by inject<S3Client>()
private val config by inject<BackupConfig>() private val config by inject<BackupConfig>()
private lateinit var scheduleId: String
override fun enable() { override fun enable() {
// Create backup directory. // Create backup directory.
@ -25,6 +27,20 @@ class BackupFeature : Feature() {
backupPath.toFile().mkdir() backupPath.toFile().mkdir()
registerCommandExecutor("fbackup", BackupCommand(plugin, backupPath, config, s3Client)) registerCommandExecutor("fbackup", BackupCommand(plugin, backupPath, config, s3Client))
if (config.schedule.cron.isNotEmpty()) {
scheduleId = scheduler.cron("${config.schedule.cron} ?") {
plugin.server.scheduler.runTask(plugin) { ->
plugin.server.dispatchCommand(plugin.server.consoleSender, "fbackup")
}
}
}
}
override fun disable() {
if (::scheduleId.isInitialized) {
scheduler.cancel(scheduleId)
}
} }
override fun module() = module { override fun module() = module {

View File

@ -0,0 +1,30 @@
package cloud.kubelet.foundation.core.features.scheduler
import org.quartz.CronScheduleBuilder.cronSchedule
import org.quartz.JobBuilder.newJob
import org.quartz.JobDataMap
import org.quartz.Scheduler
import org.quartz.TriggerBuilder.newTrigger
import org.quartz.TriggerKey.triggerKey
import java.util.UUID
fun Scheduler.cron(cronExpression: String, f: () -> Unit): String {
val id = UUID.randomUUID().toString()
val job = newJob(SchedulerRunner::class.java).apply {
setJobData(JobDataMap().apply {
set("function", f)
})
}.build()
val trigger = newTrigger()
.withIdentity(triggerKey(id))
.withSchedule(cronSchedule(cronExpression))
.build()
scheduleJob(job, trigger)
return id
}
fun Scheduler.cancel(id: String) {
unscheduleJob(triggerKey(id))
}

View File

@ -0,0 +1,22 @@
package cloud.kubelet.foundation.core.features.scheduler
import cloud.kubelet.foundation.core.abstraction.CoreFeature
import org.koin.dsl.module
import org.quartz.Scheduler
import org.quartz.impl.StdSchedulerFactory
class SchedulerFeature : CoreFeature {
private val scheduler: Scheduler = StdSchedulerFactory.getDefaultScheduler()
override fun enable() {
scheduler.start()
}
override fun disable() {
scheduler.shutdown(true)
}
override fun module() = module {
single { scheduler }
}
}

View File

@ -0,0 +1,11 @@
package cloud.kubelet.foundation.core.features.scheduler
import org.quartz.Job
import org.quartz.JobExecutionContext
class SchedulerRunner : Job {
override fun execute(context: JobExecutionContext) {
val f = context.jobDetail.jobDataMap["function"] as () -> Unit
f()
}
}

View File

@ -1,3 +1,11 @@
# Configuration of backup scheduling, expressed by cron expressions.
schedule:
# Cron expression to use for the backup schedule.
# Examples:
# "0 3 * * *" -> every day at 3 AM
# "0 3 * * SUN" -> every Sunday at 3 AM
cron: ""
# Configuration of S3 service to upload back-ups to. # Configuration of S3 service to upload back-ups to.
s3: s3:
# The access key ID from your S3-compliant storage provider. # The access key ID from your S3-compliant storage provider.

View File

@ -0,0 +1,3 @@
org.quartz.scheduler.instanceName = Foundation
org.quartz.threadPool.threadCount = 2
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore