Skip to content

Commit 496e24c

Browse files
committed
feat: server environments for server runner
fix: static servers deleted when reattaching
1 parent c732672 commit 496e24c

File tree

5 files changed

+158
-56
lines changed

5 files changed

+158
-56
lines changed

serverhost-runtime/src/main/kotlin/app/simplecloud/droplet/serverhost/runtime/ServerHostRuntime.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import app.simplecloud.droplet.serverhost.runtime.config.environment.Environment
66
import app.simplecloud.droplet.serverhost.runtime.files.FileSystemSnapshotCache
77
import app.simplecloud.droplet.serverhost.runtime.host.ServerHostService
88
import app.simplecloud.droplet.serverhost.runtime.launcher.ServerHostStartCommand
9+
import app.simplecloud.droplet.serverhost.runtime.runner.GroupRuntimeDirectory
910
import app.simplecloud.droplet.serverhost.runtime.runner.MetricsTracker
10-
import app.simplecloud.droplet.serverhost.runtime.runner.ServerRunner
11+
import app.simplecloud.droplet.serverhost.runtime.runner.ServerEnvironments
1112
import app.simplecloud.droplet.serverhost.runtime.template.ActionProvider
1213
import app.simplecloud.droplet.serverhost.runtime.template.TemplateProvider
1314
import app.simplecloud.droplet.serverhost.shared.controller.Attacher
@@ -53,13 +54,14 @@ class ServerHostRuntime(
5354
.withCallCredentials(authCallCredentials)
5455
private val pubSubClient =
5556
PubSubClient(serverHostStartCommand.pubSubGrpcHost, serverHostStartCommand.pubSubGrpcPort, authCallCredentials)
56-
private val runner = ServerRunner(
57+
private val environments = ServerEnvironments(
5758
templateProvider,
5859
serverHost,
5960
serverHostStartCommand,
6061
controllerStub,
6162
MetricsTracker(pubSubClient),
62-
environmentsRepository
63+
environmentsRepository,
64+
GroupRuntimeDirectory()
6365
)
6466
private val server = createGrpcServer()
6567
private val resourceCopier = ResourceCopier()
@@ -69,11 +71,10 @@ class ServerHostRuntime(
6971
startGrpcServer()
7072
attach()
7173
resourceCopier.copyAll("copy")
72-
runner.startServerStateChecker()
7374
actionProvider.load()
7475
templateProvider.load()
7576
startFileSystemWatcher()
76-
77+
environments.getAll().forEach { env -> env.startServerStateChecker() }
7778
suspendCancellableCoroutine<Unit> { continuation ->
7879
Runtime.getRuntime().addShutdownHook(Thread {
7980
server.shutdown()
@@ -117,7 +118,7 @@ class ServerHostRuntime(
117118
serverHostStartCommand.grpcHost,
118119
serverHostStartCommand.authorizationPort
119120
)
120-
.addService(ServerHostService(serverHost, runner, templateSnapshotCache, serverHostStartCommand))
121+
.addService(ServerHostService(serverHost, environments, templateSnapshotCache, serverHostStartCommand))
121122
.build()
122123
}
123124

serverhost-runtime/src/main/kotlin/app/simplecloud/droplet/serverhost/runtime/host/ServerHostService.kt

Lines changed: 19 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,17 @@ package app.simplecloud.droplet.serverhost.runtime.host
33
import app.simplecloud.controller.shared.group.Group
44
import app.simplecloud.controller.shared.host.ServerHost
55
import app.simplecloud.controller.shared.server.Server
6-
import app.simplecloud.droplet.serverhost.runtime.ServerHostRuntime
76
import app.simplecloud.droplet.serverhost.runtime.files.FileSystemSnapshotCache
87
import app.simplecloud.droplet.serverhost.runtime.launcher.ServerHostStartCommand
9-
import app.simplecloud.droplet.serverhost.runtime.runner.ServerRunner
8+
import app.simplecloud.droplet.serverhost.runtime.runner.ServerEnvironments
109
import app.simplecloud.droplet.serverhost.shared.hack.PortProcessHandle
11-
import app.simplecloud.droplet.serverhost.shared.logs.DefaultLogStreamer
12-
import app.simplecloud.droplet.serverhost.shared.logs.ScreenCommandExecutor
13-
import app.simplecloud.droplet.serverhost.shared.logs.ScreenConfigurer
1410
import build.buf.gen.simplecloud.controller.v1.*
1511
import com.google.protobuf.ByteString
1612
import io.grpc.Status
1713
import io.grpc.StatusException
1814
import kotlinx.coroutines.Dispatchers
1915
import kotlinx.coroutines.flow.Flow
20-
import kotlinx.coroutines.flow.onCompletion
2116
import kotlinx.coroutines.withContext
22-
import org.apache.logging.log4j.LogManager
2317
import java.io.FileInputStream
2418
import java.io.FileWriter
2519
import java.nio.file.Files
@@ -28,13 +22,11 @@ import kotlin.io.path.*
2822

2923
class ServerHostService(
3024
private val serverHost: ServerHost,
31-
private val runner: ServerRunner,
25+
private val envs: ServerEnvironments,
3226
private val cache: FileSystemSnapshotCache,
3327
private val args: ServerHostStartCommand,
3428
) : ServerHostServiceGrpcKt.ServerHostServiceCoroutineImplBase() {
3529

36-
private val logger = LogManager.getLogger(ServerHostRuntime::class.java)
37-
3830
override suspend fun startServer(request: ServerHostStartServerRequest): ServerDefinition {
3931
val group = Group.fromDefinition(request.group)
4032
val port = PortProcessHandle.findNextFreePort(group.startPort.toInt(), request.server)
@@ -45,7 +37,8 @@ class ServerHostService(
4537
this.serverIp = serverHost.host
4638
})
4739
try {
48-
val started = runner.startServer(server)
40+
val env = envs.firstFor(server)
41+
val started = env.startServer(server)
4942
if (!started) {
5043
throw StatusException(Status.INVALID_ARGUMENT.withDescription("Group not supported by this ServerHost."))
5144
}
@@ -56,7 +49,9 @@ class ServerHostService(
5649
}
5750

5851
override suspend fun stopServer(request: ServerDefinition): ServerDefinition {
59-
val stopped = runner.stopServer(Server.fromDefinition(request))
52+
val env =
53+
envs.of(request.uniqueId) ?: throw StatusException(Status.NOT_FOUND.withDescription("Server not found"))
54+
val stopped = env.stopServer(Server.fromDefinition(request))
6055
if (!stopped) {
6156
throw StatusException(Status.INTERNAL.withDescription("Could not stop server"))
6257
}
@@ -65,39 +60,30 @@ class ServerHostService(
6560

6661
override suspend fun reattachServer(request: ServerDefinition): ServerDefinition {
6762
val server = Server.fromDefinition(request)
68-
val success = runner.reattachServer(server)
63+
val env =
64+
envs.firstFor(server)
65+
val success = env.reattachServer(server)
6966
if (!success) {
7067
throw StatusException(Status.INTERNAL.withDescription("Could not reattach server"))
7168
}
72-
return server.toDefinition()
69+
return request
7370
}
7471

7572
override suspend fun executeCommand(request: ServerHostServerExecuteCommandRequest): ServerHostServerExecuteCommandResponse {
76-
val process = runner.getProcess(request.serverId)
77-
?: throw StatusException(Status.NOT_FOUND.withDescription("Server not found"))
78-
val streamer = ScreenCommandExecutor(process.pid())
79-
if (!streamer.isScreen()) throw StatusException(Status.UNAVAILABLE.withDescription("Only servers started with screen have access to logs."))
80-
streamer.sendCommand(request.command)
73+
val env =
74+
envs.of(request.serverId) ?: throw StatusException(Status.NOT_FOUND.withDescription("Server not found"))
75+
val success = env.executeCommand(env.getServer(request.serverId)!!, request.command)
76+
if (!success) throw StatusException(Status.INTERNAL.withDescription("Could not send command."))
8177
return serverHostServerExecuteCommandResponse {}
8278
}
8379

8480
override fun streamServerLogs(request: ServerHostStreamServerLogsRequest): Flow<ServerHostStreamServerLogsResponse> {
85-
var configurer: ScreenConfigurer? = null
81+
val env =
82+
envs.of(request.serverId) ?: throw StatusException(Status.NOT_FOUND.withDescription("Server not found"))
8683
try {
87-
val server = runner.getServer(request.serverId)
88-
?: throw StatusException(Status.NOT_FOUND.withDescription("Server not found"))
89-
val process = runner.getProcess(request.serverId)
90-
if (process != null) {
91-
configurer = ScreenConfigurer(process.pid())
92-
configurer.setLogsFlush(0)
93-
logger.warn("Screen streaming for server ${server.group}-${server.numericalId} (${request.serverId}) not available, log stream will be slower.")
94-
}
95-
val fileStreamer = DefaultLogStreamer(runner.getServerLogFile(server))
96-
return fileStreamer.readScreenLogs().onCompletion { configurer?.setLogsFlush(10) }
84+
return env.streamLogs(env.getServer(request.serverId)!!)
9785
} catch (e: Exception) {
98-
configurer?.setLogsFlush(10)
99-
logger.error("Failed to stream server logs", e)
100-
throw StatusException(Status.INTERNAL.withDescription("Failed to stream server logs: ${e.message}"))
86+
throw StatusException(Status.INTERNAL.withDescription("Could not stream logs: ${e.message}"))
10187
}
10288
}
10389

serverhost-runtime/src/main/kotlin/app/simplecloud/droplet/serverhost/runtime/runner/ServerRunner.kt renamed to serverhost-runtime/src/main/kotlin/app/simplecloud/droplet/serverhost/runtime/runner/DefaultServerEnvironment.kt

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package app.simplecloud.droplet.serverhost.runtime.runner
33
import app.simplecloud.controller.shared.host.ServerHost
44
import app.simplecloud.controller.shared.server.Server
55
import app.simplecloud.droplet.serverhost.runtime.ServerHostRuntime
6+
import app.simplecloud.droplet.serverhost.runtime.config.environment.EnvironmentConfig
67
import app.simplecloud.droplet.serverhost.runtime.config.environment.EnvironmentConfigRepository
78
import app.simplecloud.droplet.serverhost.runtime.host.ServerVersionLoader
89
import app.simplecloud.droplet.serverhost.runtime.launcher.ServerHostStartCommand
@@ -15,8 +16,13 @@ import app.simplecloud.droplet.serverhost.shared.actions.YamlActionPlaceholderCo
1516
import app.simplecloud.droplet.serverhost.shared.actions.YamlActionTriggerTypes
1617
import app.simplecloud.droplet.serverhost.shared.hack.PortProcessHandle
1718
import app.simplecloud.droplet.serverhost.shared.hack.ServerPinger
19+
import app.simplecloud.droplet.serverhost.shared.logs.DefaultLogStreamer
20+
import app.simplecloud.droplet.serverhost.shared.logs.ScreenCommandExecutor
21+
import app.simplecloud.droplet.serverhost.shared.logs.ScreenConfigurer
1822
import build.buf.gen.simplecloud.controller.v1.*
1923
import kotlinx.coroutines.*
24+
import kotlinx.coroutines.flow.Flow
25+
import kotlinx.coroutines.flow.onCompletion
2026
import kotlinx.coroutines.future.await
2127
import kotlinx.coroutines.sync.Mutex
2228
import kotlinx.coroutines.sync.withLock
@@ -30,17 +36,17 @@ import java.nio.file.Paths
3036
import kotlin.io.path.absolutePathString
3137
import kotlin.io.path.exists
3238

33-
class ServerRunner(
39+
class DefaultServerEnvironment(
3440
private val templateProvider: TemplateProvider,
3541
private val serverHost: ServerHost,
3642
private val args: ServerHostStartCommand,
3743
private val controllerStub: ControllerServerServiceGrpcKt.ControllerServerServiceCoroutineStub,
3844
private val metricsTracker: MetricsTracker,
3945
private val environmentsRepository: EnvironmentConfigRepository,
40-
) {
46+
override val runtimeRepository: GroupRuntimeDirectory,
47+
) : ServerEnvironment(runtimeRepository, environmentsRepository) {
4148

4249
private val copyTemplateMutex = Mutex()
43-
private val runtimeRepository = GroupRuntimeDirectory()
4450

4551
private val stopTries = mutableMapOf<String, Int>()
4652
private val maxGracefulTries = 3
@@ -57,7 +63,7 @@ class ServerRunner(
5763
return serverToProcessHandle.any { it.key.uniqueId == uniqueId }
5864
}
5965

60-
fun getServer(uniqueId: String): Server? {
66+
override fun getServer(uniqueId: String): Server? {
6167
return serverToProcessHandle.keys.find { it.uniqueId == uniqueId }
6268
}
6369

@@ -79,7 +85,7 @@ class ServerRunner(
7985

8086
// Retrieving this before the ping makes it possible to stop servers way sooner (port is registered in system nearly instantly, it takes longer for the
8187
// server to respond to pings though)
82-
val executable = environmentsRepository.get(runtimeRepository.get(server.group))
88+
val executable = getEnvironment(server)
8389
val handle = PortProcessHandle.of(server.port.toInt()).orElse(null)?.let {
8490
val realProcess = executable?.getRealExecutable()?.let { exe ->
8591
ProcessFinder.findHighestProcessWorkingDir(
@@ -164,14 +170,14 @@ class ServerRunner(
164170
return File(basicUrl)
165171
}
166172

167-
fun getServerLogFile(server: Server): Path {
173+
private fun getServerLogFile(server: Server): Path {
168174
return Paths.get(
169175
args.logsPath.absolutePathString(),
170176
"${server.group}-${server.numericalId}-${server.uniqueId}.log"
171177
)
172178
}
173179

174-
suspend fun startServer(server: Server): Boolean {
180+
override suspend fun startServer(server: Server): Boolean {
175181
logger.info("Starting server ${server.uniqueId} of group ${server.group} (#${server.numericalId})")
176182

177183
if (containsServer(server)) {
@@ -235,7 +241,7 @@ class ServerRunner(
235241
else
236242
logger.error("Template ${server.properties["template-id"] ?: ""} of group ${server.group} was not found!")
237243
}
238-
return null;
244+
return null
239245
}
240246

241247
private fun getServerDir(ctx: YamlActionContext): File? {
@@ -244,7 +250,7 @@ class ServerRunner(
244250
return Paths.get(placeholders.parse(dir)).toFile()
245251
}
246252

247-
suspend fun stopServer(server: Server): Boolean {
253+
override suspend fun stopServer(server: Server): Boolean {
248254
logger.info("Stopping server ${server.uniqueId} of group ${server.group} (#${server.numericalId})")
249255
val stopped = stopServer(server.uniqueId, stopTries.getOrDefault(server.uniqueId, 0) >= maxGracefulTries)
250256
if (!stopped) return false
@@ -271,9 +277,7 @@ class ServerRunner(
271277
return false
272278
}
273279

274-
val load = runtimeRepository.get(server.group)
275-
276-
val env = environmentsRepository.get(load)
280+
val env = getEnvironment(server)
277281
if (env != null && env.useScreenStop) {
278282
terminateScreenSession(process.pid())
279283
} else {
@@ -295,19 +299,20 @@ class ServerRunner(
295299
}
296300
}
297301

298-
fun getProcess(uniqueId: String): ProcessHandle? {
302+
private fun getProcess(uniqueId: String): ProcessHandle? {
299303
return serverToProcessHandle.getOrDefault(
300304
serverToProcessHandle.keys.firstOrNull { it.uniqueId == uniqueId },
301305
null
302306
)
303307
}
304308

305-
fun reattachServer(server: Server): Boolean {
309+
310+
override fun reattachServer(server: Server): Boolean {
306311
if (containsServer(server.uniqueId)) {
307312
logger.error("Server ${server.uniqueId} of group ${server.group} is already running.")
308313
return true
309314
}
310-
val executable = environmentsRepository.get(runtimeRepository.get(server.group))?.getRealExecutable()
315+
val executable = getEnvironment(server)?.getRealExecutable()
311316
val handle = PortProcessHandle.of(server.port.toInt()).orElse(null)
312317
?.let {
313318
executable?.let { exe ->
@@ -322,7 +327,6 @@ class ServerRunner(
322327
if (handle == null) {
323328
logger.error("Server ${server.uniqueId} of group ${server.group} not found running on port ${server.port}. Is it down?")
324329
executeTemplate(getServerDir(server).toPath(), server, YamlActionTriggerTypes.STOP)
325-
FileUtils.deleteDirectory(getServerDir(server))
326330
PortProcessHandle.removePreBind(server.port.toInt(), true)
327331
return false
328332
}
@@ -332,6 +336,37 @@ class ServerRunner(
332336
return true
333337
}
334338

339+
override fun executeCommand(server: Server, command: String): Boolean {
340+
if (getEnvironment(server)?.isScreen != true) return false
341+
val process = getProcess(server.uniqueId)
342+
?: return false
343+
val streamer = ScreenCommandExecutor(process.pid())
344+
streamer.sendCommand(command)
345+
return true
346+
}
347+
348+
override fun streamLogs(server: Server): Flow<ServerHostStreamServerLogsResponse> {
349+
var configurer: ScreenConfigurer? = null
350+
try {
351+
val process = getProcess(server.uniqueId)
352+
if (process != null) {
353+
configurer = ScreenConfigurer(process.pid())
354+
configurer.setLogsFlush(0)
355+
logger.warn("Screen streaming for server ${server.group}-${server.numericalId} (${server.uniqueId}) not available, log stream will be slower.")
356+
}
357+
val fileStreamer = DefaultLogStreamer(getServerLogFile(server))
358+
return fileStreamer.readScreenLogs().onCompletion { configurer?.setLogsFlush(10) }
359+
} catch (e: Exception) {
360+
configurer?.setLogsFlush(10)
361+
logger.error("Failed to stream server logs", e)
362+
throw e
363+
}
364+
}
365+
366+
override fun appliesFor(env: EnvironmentConfig): Boolean {
367+
return env.enabled && env.start?.command != null
368+
}
369+
335370
private fun createRuntimePlaceholders(server: Server): MutableMap<String, String> {
336371
val placeholders = mutableMapOf(
337372
"%MIN_MEMORY%" to server.minMemory.toString(),
@@ -419,7 +454,7 @@ class ServerRunner(
419454
}
420455

421456

422-
fun startServerStateChecker(): Job {
457+
override fun startServerStateChecker(): Job {
423458
return CoroutineScope(Dispatchers.IO).launch {
424459
while (isActive) {
425460
serverToProcessHandle.keys.toList().forEach {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package app.simplecloud.droplet.serverhost.runtime.runner
2+
3+
import app.simplecloud.controller.shared.server.Server
4+
import app.simplecloud.droplet.serverhost.runtime.config.environment.EnvironmentConfig
5+
import app.simplecloud.droplet.serverhost.runtime.config.environment.EnvironmentConfigRepository
6+
import build.buf.gen.simplecloud.controller.v1.ServerHostStreamServerLogsResponse
7+
import kotlinx.coroutines.Job
8+
import kotlinx.coroutines.flow.Flow
9+
10+
abstract class ServerEnvironment(
11+
protected open val runtimeRepository: GroupRuntimeDirectory,
12+
private val environmentRepository: EnvironmentConfigRepository
13+
) {
14+
abstract suspend fun startServer(server: Server): Boolean
15+
abstract suspend fun stopServer(server: Server): Boolean
16+
abstract fun getServer(uniqueId: String): Server?
17+
abstract fun reattachServer(server: Server): Boolean
18+
abstract fun executeCommand(server: Server, command: String): Boolean
19+
abstract fun streamLogs(server: Server): Flow<ServerHostStreamServerLogsResponse>
20+
abstract fun appliesFor(env: EnvironmentConfig): Boolean
21+
abstract fun startServerStateChecker(): Job
22+
23+
fun getEnvironment(server: Server): EnvironmentConfig? {
24+
return environmentRepository.get(runtimeRepository.get(server.group))
25+
}
26+
}

0 commit comments

Comments
 (0)