From c462c71e9830edeb1b9ce85cca13c83a64acf913 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Sun, 14 Jul 2024 23:57:50 +0200 Subject: [PATCH 01/16] refactor: add toEnv method to server, improve error logging --- .../runtime/server/ServerService.kt | 9 ++++++-- .../controller/shared/server/Server.kt | 21 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerService.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerService.kt index c0b6dfd..0987612 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerService.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerService.kt @@ -224,6 +224,8 @@ class ServerService( } else { startServer(host, group) } + }.exceptionally { + logger.error("Error whilst starting server:", it) } } } @@ -249,6 +251,7 @@ class ServerService( serverRepository.delete(server) numericalIdRepository.removeNumericalId(group.name, server.numericalId) channel.shutdown() + logger.error("Error whilst starting server:", it) throw it } } @@ -285,10 +288,9 @@ class ServerService( stopServer(server.toDefinition()).thenApply { responseObserver.onNext(it) responseObserver.onCompleted() - }.exceptionally { - responseObserver.onError(it) }.get() }.exceptionally { + logger.error("Error whilst stopping server:", it) responseObserver.onError(it) } } @@ -305,6 +307,9 @@ class ServerService( serverRepository.delete(Server.fromDefinition(server)) channel.shutdown() return@thenApply it + }.exceptionally { + logger.error("Server stop error occured:", it) + throw it } } diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/Server.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/Server.kt index 94c5398..bab9c24 100644 --- a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/Server.kt +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/Server.kt @@ -42,6 +42,27 @@ data class Server( .build() } + fun toEnv(): MutableMap { + val map = mutableMapOf() + map["SIMPLECLOUD_GROUP"] = this.group + map["SIMPLECLOUD_HOST"] = this.host ?: "unknown" + map["SIMPLECLOUD_IP"] = this.ip + map["SIMPLECLOUD_PORT"] = this.port.toString() + map["SIMPLECLOUD_UNIQUE_ID"] = this.uniqueId + map["SIMPLECLOUD_CREATED_AT"] = this.createdAt.toString() + map["SIMPLECLOUD_MAX_PLAYERS"] = this.maxPlayers.toString() + map["SIMPLECLOUD_NUMERICAL_ID"] = this.numericalId.toString() + map["SIMPLECLOUD_TYPE"] = this.type.toString() + map["SIMPLECLOUD_MAX_MEMORY"] = this.maxMemory.toString() + map["SIMPLECLOUD_MIN_MEMORY"] = this.minMemory.toString() + map.putAll(this.properties.map { + "SIMPLECLOUD_${ + it.key.uppercase().replace(" ", "_").replace("-", "_") + }" to it.value + }) + return map + } + companion object { @JvmStatic fun fromDefinition(serverDefinition: ServerDefinition): Server { From 1374d18a4ca1b61c98bf60bcd2eb3e8ab514b89b Mon Sep 17 00:00:00 2001 From: Philipp Date: Sat, 17 Aug 2024 11:42:01 +0200 Subject: [PATCH 02/16] feat: pub sub --- .../controller/api/ControllerApi.kt | 6 ++++++ .../controller/api/impl/ControllerApiImpl.kt | 16 ++++++++++++++++ .../controller/runtime/ControllerRuntime.kt | 18 ++++++++++++++++++ .../runtime/launcher/ControllerStartCommand.kt | 2 ++ controller-shared/build.gradle.kts | 1 + gradle/libs.versions.toml | 4 +++- 6 files changed, 46 insertions(+), 1 deletion(-) diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/ControllerApi.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/ControllerApi.kt index d1a9f91..2eae9f0 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/ControllerApi.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/ControllerApi.kt @@ -1,6 +1,7 @@ package app.simplecloud.controller.api import app.simplecloud.controller.api.impl.ControllerApiImpl +import app.simplecloud.pubsub.PubSubClient interface ControllerApi { @@ -14,6 +15,11 @@ interface ControllerApi { */ fun getServers(): ServerApi + /** + * @return the [PubSubClient] to subscribe to Controller events and send messages + */ + fun getPubSubClient(): PubSubClient + companion object { /** diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ControllerApiImpl.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ControllerApiImpl.kt index 2550e78..137cd6b 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ControllerApiImpl.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ControllerApiImpl.kt @@ -4,6 +4,7 @@ import app.simplecloud.controller.api.ControllerApi import app.simplecloud.controller.api.GroupApi import app.simplecloud.controller.api.ServerApi import app.simplecloud.controller.shared.auth.AuthCallCredentials +import app.simplecloud.pubsub.PubSubClient import io.grpc.ManagedChannel import io.grpc.ManagedChannelBuilder @@ -17,6 +18,13 @@ class ControllerApiImpl( private val groups: GroupApi = GroupApiImpl(managedChannel, authCallCredentials) private val servers: ServerApi = ServerApiImpl(managedChannel, authCallCredentials) + + private val pubSubClient = PubSubClient( + System.getenv("CONTROLLER_PUBSUB_HOST") ?: "localhost", + System.getenv("CONTROLLER_PUBSUB_PORT")?.toInt() ?: 5817, + authCallCredentials, + ) + /** * @return The controllers [GroupApi] */ @@ -31,10 +39,18 @@ class ControllerApiImpl( return servers } + /** + * @return The [PubSubClient] to subscribe to Controller events and send messages + */ + override fun getPubSubClient(): PubSubClient { + return pubSubClient + } + private fun createManagedChannelFromEnv(): ManagedChannel { val host = System.getenv("CONTROLLER_HOST") ?: "127.0.0.1" val port = System.getenv("CONTROLLER_PORT")?.toInt() ?: 5816 return ManagedChannelBuilder.forAddress(host, port).usePlaintext().build() } + } \ No newline at end of file diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/ControllerRuntime.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/ControllerRuntime.kt index c5abe3e..94f553f 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/ControllerRuntime.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/ControllerRuntime.kt @@ -11,6 +11,7 @@ import app.simplecloud.controller.runtime.server.ServerRepository import app.simplecloud.controller.runtime.server.ServerService import app.simplecloud.controller.shared.auth.AuthCallCredentials import app.simplecloud.controller.shared.auth.AuthSecretInterceptor +import app.simplecloud.pubsub.PubSubService import io.grpc.ManagedChannel import io.grpc.ManagedChannelBuilder import io.grpc.Server @@ -40,10 +41,12 @@ class ControllerRuntime( authCallCredentials ) private val server = createGrpcServer() + private val pubSubServer = createPubSubGrpcServer() fun start() { setupDatabase() startGrpcServer() + startPubSubGrpcServer() startReconciler() loadGroups() loadServers() @@ -72,6 +75,14 @@ class ControllerRuntime( } } + private fun startPubSubGrpcServer() { + logger.info("Starting pubsub gRPC server...") + thread { + pubSubServer.start() + pubSubServer.awaitTermination() + } + } + private fun startReconciler() { logger.info("Starting Reconciler...") startReconcilerJob() @@ -105,6 +116,13 @@ class ControllerRuntime( .build() } + private fun createPubSubGrpcServer(): Server { + return ServerBuilder.forPort(controllerStartCommand.pubSubGrpcPort) + .addService(PubSubService()) + .intercept(AuthSecretInterceptor(controllerStartCommand.authSecret)) + .build() + } + private fun createManagedChannel(): ManagedChannel { return ManagedChannelBuilder.forAddress(controllerStartCommand.grpcHost, controllerStartCommand.grpcPort) .usePlaintext() diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/ControllerStartCommand.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/ControllerStartCommand.kt index 85f5558..918760d 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/ControllerStartCommand.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/ControllerStartCommand.kt @@ -23,6 +23,8 @@ class ControllerStartCommand : CliktCommand() { val grpcHost: String by option(help = "Grpc host (default: localhost)", envvar = "GRPC_HOST").default("localhost") val grpcPort: Int by option(help = "Grpc port (default: 5816)", envvar = "GRPC_PORT").int().default(5816) + val pubSubGrpcPort: Int by option(help = "PubSub Grpc port (default: 5817)", envvar = "PUBSUB_GRPC_PORT").int().default(5817) + private val authSecretPath: Path by option( help = "Path to auth secret file (default: .auth.secret)", envvar = "AUTH_SECRET_PATH" diff --git a/controller-shared/build.gradle.kts b/controller-shared/build.gradle.kts index 56a56b8..17decfa 100644 --- a/controller-shared/build.gradle.kts +++ b/controller-shared/build.gradle.kts @@ -1,4 +1,5 @@ dependencies { api(rootProject.libs.bundles.proto) + api(rootProject.libs.simpleCloudPubSub) api(rootProject.libs.bundles.configurate) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c6e6e36..c405676 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,8 @@ log4j = "2.20.0" protobuf = "3.25.2" grpc = "1.61.0" grpcKotlin = "1.4.1" -simpleCloudProtoSpecs = "1.4.1.1.20240504121939.09af2f3cc691" +simpleCloudProtoSpecs = "1.4.1.1.20240606064605.c07118735783" +simpleCloudPubSub = "1.0.3" jooq = "3.19.3" configurate = "4.1.2" sqliteJdbc = "3.44.1.0" @@ -31,6 +32,7 @@ grpcProtobuf = { module = "io.grpc:grpc-protobuf", version.ref = "grpc" } grpcNettyShaded = { module = "io.grpc:grpc-netty-shaded", version.ref = "grpc" } simpleCloudProtoSpecs = { module = "build.buf.gen:simplecloud_proto-specs_grpc_kotlin", version.ref = "simpleCloudProtoSpecs" } +simpleCloudPubSub = { module = "app.simplecloud:simplecloud-pubsub", version.ref = "simpleCloudPubSub" } qooq = { module = "org.jooq:jooq", version.ref = "jooq" } qooqMeta = { module = "org.jooq:jooq-meta", version.ref = "jooq" } From d931fbfc8eaf66708a73de415cb5d417f856ac6b Mon Sep 17 00:00:00 2001 From: David Date: Tue, 1 Oct 2024 18:35:10 +0200 Subject: [PATCH 03/16] refactor: upgrade to kotlin 2.0.20, java 22, new grpc defs --- build.gradle.kts | 13 ++-- .../simplecloud/controller/api/ServerApi.kt | 8 ++- .../controller/api/impl/GroupApiImpl.kt | 25 ++++++-- .../controller/api/impl/ServerApiImpl.kt | 43 +++++++------ .../controller/runtime/group/GroupService.kt | 16 ++--- .../runtime/reconciler/GroupReconciler.kt | 13 ++-- .../runtime/server/ServerService.kt | 61 ++++++++++--------- .../controller/shared/group/Group.kt | 6 +- .../controller/shared/host/ServerHost.kt | 12 ++-- .../controller/shared/server/Server.kt | 31 +++++----- .../shared/time/ProtoBufTimestamp.kt | 21 +++++++ gradle/libs.versions.toml | 4 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 13 files changed, 148 insertions(+), 107 deletions(-) create mode 100644 controller-shared/src/main/kotlin/app/simplecloud/controller/shared/time/ProtoBufTimestamp.kt diff --git a/build.gradle.kts b/build.gradle.kts index 3ed64a9..92cb3e0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "0.0.27-EXPERIMENTAL" + version = "0.0.28-EXPERIMENTAL" repositories { mavenCentral() @@ -38,15 +38,14 @@ subprojects { } java { - toolchain.languageVersion.set(JavaLanguageVersion.of(21)) + toolchain.languageVersion.set(JavaLanguageVersion.of(22)) } kotlin { - jvmToolchain(17) - } - - tasks.withType { - kotlinOptions.jvmTarget = "17" + jvmToolchain(22) + compilerOptions { + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) + } } tasks.named("shadowJar", ShadowJar::class) { diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/ServerApi.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/ServerApi.kt index af0bb38..3a71938 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/ServerApi.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/ServerApi.kt @@ -3,7 +3,9 @@ package app.simplecloud.controller.api import app.simplecloud.controller.shared.group.Group import build.buf.gen.simplecloud.controller.v1.ServerType import app.simplecloud.controller.shared.server.Server +import build.buf.gen.simplecloud.controller.v1.ServerStartCause import build.buf.gen.simplecloud.controller.v1.ServerState +import build.buf.gen.simplecloud.controller.v1.ServerStopCause import java.util.concurrent.CompletableFuture interface ServerApi { @@ -41,20 +43,20 @@ interface ServerApi { * @param groupName the group name of the group the new server should be of. * @return a [CompletableFuture] with a [Server] or null. */ - fun startServer(groupName: String): CompletableFuture + fun startServer(groupName: String, startCause: ServerStartCause = ServerStartCause.API_START): CompletableFuture /** * @param groupName the group name of the servers group. * @param numericalId the numerical id of the server. * @return a [CompletableFuture] with the stopped [Server]. */ - fun stopServer(groupName: String, numericalId: Long): CompletableFuture + fun stopServer(groupName: String, numericalId: Long, stopCause: ServerStopCause = ServerStopCause.API_STOP): CompletableFuture /** * @param id the id of the server. * @return a [CompletableFuture] with the stopped [Server]. */ - fun stopServer(id: String): CompletableFuture + fun stopServer(id: String, stopCause: ServerStopCause = ServerStopCause.API_STOP): CompletableFuture /** * @param id the id of the server. diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/GroupApiImpl.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/GroupApiImpl.kt index 25b4fc6..8464445 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/GroupApiImpl.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/GroupApiImpl.kt @@ -5,10 +5,13 @@ import app.simplecloud.controller.shared.auth.AuthCallCredentials import app.simplecloud.controller.shared.future.toCompletable import app.simplecloud.controller.shared.group.Group import build.buf.gen.simplecloud.controller.v1.ControllerGroupServiceGrpc +import build.buf.gen.simplecloud.controller.v1.CreateGroupRequest +import build.buf.gen.simplecloud.controller.v1.DeleteGroupByNameRequest import build.buf.gen.simplecloud.controller.v1.GetGroupByNameRequest import build.buf.gen.simplecloud.controller.v1.GetAllGroupsRequest import build.buf.gen.simplecloud.controller.v1.GetGroupsByTypeRequest import build.buf.gen.simplecloud.controller.v1.ServerType +import build.buf.gen.simplecloud.controller.v1.UpdateGroupRequest import io.grpc.ManagedChannel import java.util.concurrent.CompletableFuture @@ -24,7 +27,7 @@ class GroupApiImpl( override fun getGroupByName(name: String): CompletableFuture { return groupServiceStub.getGroupByName( GetGroupByNameRequest.newBuilder() - .setName(name) + .setGroupName(name) .build() ).toCompletable() .thenApply { @@ -34,8 +37,8 @@ class GroupApiImpl( override fun deleteGroup(name: String): CompletableFuture { return groupServiceStub.deleteGroupByName( - GetGroupByNameRequest.newBuilder() - .setName(name) + DeleteGroupByNameRequest.newBuilder() + .setGroupName(name) .build() ).toCompletable() .thenApply { @@ -45,7 +48,9 @@ class GroupApiImpl( override fun createGroup(group: Group): CompletableFuture { return groupServiceStub.createGroup( - group.toDefinition() + CreateGroupRequest.newBuilder() + .setGroup(group.toDefinition()) + .build() ).toCompletable() .thenApply { Group.fromDefinition(it) @@ -53,7 +58,11 @@ class GroupApiImpl( } override fun updateGroup(group: Group): CompletableFuture { - return groupServiceStub.updateGroup(group.toDefinition()).toCompletable().thenApply { + return groupServiceStub.updateGroup( + UpdateGroupRequest.newBuilder() + .setGroup(group.toDefinition()) + .build() + ).toCompletable().thenApply { return@thenApply Group.fromDefinition(it) } } @@ -65,7 +74,11 @@ class GroupApiImpl( } override fun getGroupsByType(type: ServerType): CompletableFuture> { - return groupServiceStub.getGroupsByType(GetGroupsByTypeRequest.newBuilder().setType(type).build()).toCompletable().thenApply { + return groupServiceStub.getGroupsByType( + GetGroupsByTypeRequest.newBuilder() + .setServerType(type) + .build() + ).toCompletable().thenApply { return@thenApply it.groupsList.map { group -> Group.fromDefinition(group) } } } diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ServerApiImpl.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ServerApiImpl.kt index 189429c..77a7632 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ServerApiImpl.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ServerApiImpl.kt @@ -25,8 +25,8 @@ class ServerApiImpl( override fun getServerById(id: String): CompletableFuture { return serverServiceStub.getServerById( - ServerIdRequest.newBuilder() - .setId(id) + GetServerByIdRequest.newBuilder() + .setServerId(id) .build() ).toCompletable().thenApply { Server.fromDefinition(it) @@ -35,8 +35,8 @@ class ServerApiImpl( override fun getServersByGroup(groupName: String): CompletableFuture> { return serverServiceStub.getServersByGroup( - GroupNameRequest.newBuilder() - .setName(groupName) + GetServersByGroupRequest.newBuilder() + .setGroupName(groupName) .build() ).toCompletable().thenApply { Server.fromDefinition(it.serversList) @@ -50,38 +50,41 @@ class ServerApiImpl( override fun getServersByType(type: ServerType): CompletableFuture> { return serverServiceStub.getServersByType( ServerTypeRequest.newBuilder() - .setType(type) + .setServerType(type) .build() ).toCompletable().thenApply { Server.fromDefinition(it.serversList) } } - override fun startServer(groupName: String): CompletableFuture { + override fun startServer(groupName: String, startCause: ServerStartCause): CompletableFuture { return serverServiceStub.startServer( - GroupNameRequest.newBuilder() - .setName(groupName) + ControllerStartServerRequest.newBuilder() + .setGroupName(groupName) + .setStartCause(startCause) .build() ).toCompletable().thenApply { Server.fromDefinition(it) } } - override fun stopServer(groupName: String, numericalId: Long): CompletableFuture { + override fun stopServer(groupName: String, numericalId: Long, stopCause: ServerStopCause): CompletableFuture { return serverServiceStub.stopServerByNumerical( StopServerByNumericalRequest.newBuilder() - .setGroup(groupName) + .setServerGroup(groupName) .setNumericalId(numericalId) + .setStopCause(stopCause) .build() ).toCompletable().thenApply { Server.fromDefinition(it) } } - override fun stopServer(id: String): CompletableFuture { + override fun stopServer(id: String, stopCause: ServerStopCause): CompletableFuture { return serverServiceStub.stopServer( - ServerIdRequest.newBuilder() - .setId(id) + StopServerRequest.newBuilder() + .setServerId(id) + .setCause(stopCause) .build() ).toCompletable().thenApply { Server.fromDefinition(it) @@ -90,9 +93,9 @@ class ServerApiImpl( override fun updateServerState(id: String, state: ServerState): CompletableFuture { return serverServiceStub.updateServerState( - ServerUpdateStateRequest.newBuilder() - .setState(state) - .setId(id) + UpdateServerStateRequest.newBuilder() + .setServerState(state) + .setServerId(id) .build() ).toCompletable().thenApply { return@thenApply Server.fromDefinition(it) @@ -101,10 +104,10 @@ class ServerApiImpl( override fun updateServerProperty(id: String, key: String, value: Any): CompletableFuture { return serverServiceStub.updateServerProperty( - ServerUpdatePropertyRequest.newBuilder() - .setKey(key) - .setValue(value.toString()) - .setId(id) + UpdateServerPropertyRequest.newBuilder() + .setPropertyKey(key) + .setPropertyValue(value.toString()) + .setServerId(id) .build() ).toCompletable().thenApply { return@thenApply Server.fromDefinition(it) diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/group/GroupService.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/group/GroupService.kt index 5fd259a..d57945b 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/group/GroupService.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/group/GroupService.kt @@ -13,7 +13,7 @@ class GroupService( request: GetGroupByNameRequest, responseObserver: StreamObserver ) { - groupRepository.find(request.name).thenApply { group -> + groupRepository.find(request.groupName).thenApply { group -> if (group == null) { responseObserver.onError( Status.NOT_FOUND @@ -48,7 +48,7 @@ class GroupService( request: GetGroupsByTypeRequest, responseObserver: StreamObserver ) { - val type = request.type + val type = request.serverType groupRepository.getAll().thenApply { groups -> val response = GetGroupsByTypeResponse.newBuilder() .addAllGroups(groups.filter { it.type == type }.map { it.toDefinition() }) @@ -59,8 +59,8 @@ class GroupService( } - override fun updateGroup(request: GroupDefinition, responseObserver: StreamObserver) { - val group = Group.fromDefinition(request) + override fun updateGroup(request: UpdateGroupRequest, responseObserver: StreamObserver) { + val group = Group.fromDefinition(request.group) try { groupRepository.save(group) } catch (e: Exception) { @@ -77,8 +77,8 @@ class GroupService( responseObserver.onCompleted() } - override fun createGroup(request: GroupDefinition, responseObserver: StreamObserver) { - val group = Group.fromDefinition(request) + override fun createGroup(request: CreateGroupRequest, responseObserver: StreamObserver) { + val group = Group.fromDefinition(request.group) try { groupRepository.save(group) } catch (e: Exception) { @@ -95,8 +95,8 @@ class GroupService( responseObserver.onCompleted() } - override fun deleteGroupByName(request: GetGroupByNameRequest, responseObserver: StreamObserver) { - groupRepository.find(request.name).thenApply { group -> + override fun deleteGroupByName(request: DeleteGroupByNameRequest, responseObserver: StreamObserver) { + groupRepository.find(request.groupName).thenApply { group -> if (group == null) { responseObserver.onError( Status.NOT_FOUND diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/reconciler/GroupReconciler.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/reconciler/GroupReconciler.kt index 26de957..3b08b2e 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/reconciler/GroupReconciler.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/reconciler/GroupReconciler.kt @@ -6,10 +6,8 @@ import app.simplecloud.controller.runtime.server.ServerRepository import app.simplecloud.controller.shared.future.toCompletable import app.simplecloud.controller.shared.group.Group import app.simplecloud.controller.shared.server.Server +import build.buf.gen.simplecloud.controller.v1.* import build.buf.gen.simplecloud.controller.v1.ControllerServerServiceGrpc.ControllerServerServiceFutureStub -import build.buf.gen.simplecloud.controller.v1.GroupNameRequest -import build.buf.gen.simplecloud.controller.v1.ServerIdRequest -import build.buf.gen.simplecloud.controller.v1.ServerState import org.apache.logging.log4j.LogManager import java.time.LocalDateTime import kotlin.math.min @@ -69,8 +67,9 @@ class GroupReconciler( private fun stopServer(server: Server) { logger.info("Stopping server ${server.uniqueId} of group ${server.group}") serverStub.stopServer( - ServerIdRequest.newBuilder() - .setId(server.uniqueId) + StopServerRequest.newBuilder() + .setServerId(server.uniqueId) + .setStopCause(ServerStopCause.RECONCILE_STOP) .build() ).toCompletable() .thenApply { @@ -107,9 +106,9 @@ class GroupReconciler( private fun startServer() { logger.info("Starting new instance of group ${this.group.name}") - serverStub.startServer(GroupNameRequest.newBuilder().setName(this.group.name).build()).toCompletable() + serverStub.startServer(ControllerStartServerRequest.newBuilder().setGroupName(this.group.name).setStartCause(ServerStartCause.RECONCILER_START).build()).toCompletable() .thenApply { - logger.info("Started new instance ${it.groupName}-${it.numericalId}/${it.uniqueId} of group ${this.group.name} on ${it.ip}:${it.port}") + logger.info("Started new instance ${it.groupName}-${it.numericalId}/${it.uniqueId} of group ${this.group.name} on ${it.serverIp}:${it.serverPort}") }.exceptionally { it.printStackTrace() logger.error("Could not start a new instance of group ${this.group.name}: ${it.message}") diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerService.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerService.kt index 0987612..daa41b9 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerService.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerService.kt @@ -8,6 +8,7 @@ import app.simplecloud.controller.shared.future.toCompletable import app.simplecloud.controller.shared.group.Group import app.simplecloud.controller.shared.host.ServerHost import app.simplecloud.controller.shared.server.Server +import app.simplecloud.controller.shared.time.ProtoBufTimestamp import build.buf.gen.simplecloud.controller.v1.* import io.grpc.Context import io.grpc.Status @@ -28,8 +29,8 @@ class ServerService( private val logger = LogManager.getLogger(ServerService::class.java) - override fun attachServerHost(request: ServerHostDefinition, responseObserver: StreamObserver) { - val serverHost = ServerHost.fromDefinition(request) + override fun attachServerHost(request: AttachServerHostRequest, responseObserver: StreamObserver) { + val serverHost = ServerHost.fromDefinition(request.serverHost) try { hostRepository.delete(serverHost) hostRepository.save(serverHost) @@ -82,7 +83,7 @@ class ServerService( request: GetServerByNumericalRequest, responseObserver: StreamObserver ) { - serverRepository.findServerByNumerical(request.group, request.numericalId.toInt()).thenApply { server -> + serverRepository.findServerByNumerical(request.groupName, request.numericalId.toInt()).thenApply { server -> if (server == null) { responseObserver.onError( Status.NOT_FOUND @@ -101,7 +102,7 @@ class ServerService( responseObserver: StreamObserver ) { - serverRepository.findServerByNumerical(request.group, request.numericalId.toInt()).thenApply { server -> + serverRepository.findServerByNumerical(request.groupName, request.numericalId.toInt()).thenApply { server -> if (server == null) { responseObserver.onError( Status.NOT_FOUND @@ -120,7 +121,7 @@ class ServerService( } - override fun updateServer(request: ServerUpdateRequest, responseObserver: StreamObserver) { + override fun updateServer(request: UpdateServerRequest, responseObserver: StreamObserver) { val deleted = request.deleted val server = Server.fromDefinition(request.server) if (!deleted) { @@ -162,8 +163,8 @@ class ServerService( } } - override fun getServerById(request: ServerIdRequest, responseObserver: StreamObserver) { - serverRepository.find(request.id).thenApply { server -> + override fun getServerById(request: GetServerByIdRequest, responseObserver: StreamObserver) { + serverRepository.find(request.serverId).thenApply { server -> if (server == null) { responseObserver.onError( Status.NOT_FOUND @@ -179,10 +180,10 @@ class ServerService( } override fun getServersByGroup( - request: GroupNameRequest, + request: GetServersByGroupRequest, responseObserver: StreamObserver ) { - serverRepository.findServersByGroup(request.name).thenApply { servers -> + serverRepository.findServersByGroup(request.groupName).thenApply { servers -> val response = GetServersByGroupResponse.newBuilder() .addAllServers(servers.map { it.toDefinition() }) .build() @@ -193,10 +194,10 @@ class ServerService( override fun getServersByType( request: ServerTypeRequest, - responseObserver: StreamObserver + responseObserver: StreamObserver ) { - serverRepository.findServersByType(request.type).thenApply { servers -> - val response = GetServersByGroupResponse.newBuilder() + serverRepository.findServersByType(request.serverType).thenApply { servers -> + val response = GetServersByTypeResponse.newBuilder() .addAllServers(servers.map { it.toDefinition() }) .build() responseObserver.onNext(response) @@ -204,7 +205,7 @@ class ServerService( } } - override fun startServer(request: GroupNameRequest, responseObserver: StreamObserver) { + override fun startServer(request: ControllerStartServerRequest, responseObserver: StreamObserver) { hostRepository.find(serverRepository).thenApply { host -> if (host == null) { responseObserver.onError( @@ -214,7 +215,7 @@ class ServerService( ) return@thenApply } - groupRepository.find(request.name).thenApply { group -> + groupRepository.find(request.groupName).thenApply { group -> if (group == null) { responseObserver.onError( Status.NOT_FOUND @@ -239,7 +240,7 @@ class ServerService( .withCallCredentials(authCallCredentials) serverRepository.save(server) return stub.startServer( - StartServerRequest.newBuilder() + ServerHostStartServerRequest.newBuilder() .setGroup(group.toDefinition()) .setServer(server.toDefinition()) .build() @@ -260,16 +261,16 @@ class ServerService( return Server.fromDefinition( ServerDefinition.newBuilder() .setNumericalId(numericalId) - .setType(group.type) + .setServerType(group.type) .setGroupName(group.name) .setMinimumMemory(group.minMemory) .setMaximumMemory(group.maxMemory) - .setState(ServerState.PREPARING) + .setServerState(ServerState.PREPARING) .setMaxPlayers(group.maxPlayers) - .setCreatedAt(LocalDateTime.now().toString()) - .setUpdatedAt(LocalDateTime.now().toString()) + .setCreatedAt(ProtoBufTimestamp.fromLocalDateTime(LocalDateTime.now())) + .setUpdatedAt(ProtoBufTimestamp.fromLocalDateTime(LocalDateTime.now())) .setPlayerCount(0) - .setUniqueId(UUID.randomUUID().toString().replace("-", "")).putAllProperties( + .setUniqueId(UUID.randomUUID().toString().replace("-", "")).putAllCloudProperties( mapOf( *group.properties.entries.map { it.key to it.value }.toTypedArray(), "forwarding-secret" to forwardingSecret, @@ -278,8 +279,8 @@ class ServerService( ) } - override fun stopServer(request: ServerIdRequest, responseObserver: StreamObserver) { - serverRepository.find(request.id).thenApply { server -> + override fun stopServer(request: StopServerRequest, responseObserver: StreamObserver) { + serverRepository.find(request.serverId).thenApply { server -> if (server == null) { throw Status.NOT_FOUND .withDescription("No server was found matching this id.") @@ -314,16 +315,16 @@ class ServerService( } override fun updateServerProperty( - request: ServerUpdatePropertyRequest, + request: UpdateServerPropertyRequest, responseObserver: StreamObserver ) { - serverRepository.find(request.id).thenApply { server -> + serverRepository.find(request.serverId).thenApply { server -> if (server == null) { throw Status.NOT_FOUND - .withDescription("Server with id ${request.id} does not exist.") + .withDescription("Server with id ${request.serverId} does not exist.") .asRuntimeException() } - server.properties[request.key] = request.value + server.properties[request.propertyKey] = request.propertyValue serverRepository.save(server) responseObserver.onNext(server.toDefinition()) responseObserver.onCompleted() @@ -333,16 +334,16 @@ class ServerService( } override fun updateServerState( - request: ServerUpdateStateRequest, + request: UpdateServerStateRequest, responseObserver: StreamObserver ) { - serverRepository.find(request.id).thenApply { server -> + serverRepository.find(request.serverId).thenApply { server -> if (server == null) { throw Status.NOT_FOUND - .withDescription("Server with id ${request.id} does not exist.") + .withDescription("Server with id ${request.serverState} does not exist.") .asRuntimeException() } - server.state = request.state + server.state = request.serverState serverRepository.save(server) responseObserver.onNext(server.toDefinition()) responseObserver.onCompleted() diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/group/Group.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/group/Group.kt index 014f507..034784a 100644 --- a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/group/Group.kt +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/group/Group.kt @@ -7,7 +7,7 @@ import org.spongepowered.configurate.objectmapping.ConfigSerializable @ConfigSerializable data class Group( val name: String = "", - val type: ServerType = ServerType.OTHER, + val type: ServerType = ServerType.UNKNOWN_SERVER, val minMemory: Long = 0, val maxMemory: Long = 0, val startPort: Long = 0, @@ -29,7 +29,7 @@ data class Group( .setMaximumOnlineCount(maxOnlineCount) .setMaxPlayers(maxPlayers) .setNewServerPlayerRatio(newServerPlayerRatio) - .putAllProperties(properties) + .putAllCloudProperties(properties) .build() } @@ -46,7 +46,7 @@ data class Group( groupDefinition.maximumOnlineCount, groupDefinition.maxPlayers, groupDefinition.newServerPlayerRatio, - groupDefinition.propertiesMap + groupDefinition.cloudPropertiesMap ) } } diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/host/ServerHost.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/host/ServerHost.kt index c712a22..c9f9c9c 100644 --- a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/host/ServerHost.kt +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/host/ServerHost.kt @@ -14,9 +14,9 @@ data class ServerHost( fun toDefinition(): ServerHostDefinition { return ServerHostDefinition.newBuilder() - .setHost(host) - .setPort(port) - .setUniqueId(id) + .setHostHost(host) + .setHostPort(port) + .setHostId(id) .build() } @@ -24,9 +24,9 @@ data class ServerHost( @JvmStatic fun fromDefinition(serverHostDefinition: ServerHostDefinition): ServerHost { return ServerHost( - serverHostDefinition.uniqueId, - serverHostDefinition.host, - serverHostDefinition.port + serverHostDefinition.hostId, + serverHostDefinition.hostHost, + serverHostDefinition.hostPort ) } } diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/Server.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/Server.kt index bab9c24..58f70cf 100644 --- a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/Server.kt +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/Server.kt @@ -1,9 +1,12 @@ package app.simplecloud.controller.shared.server +import app.simplecloud.controller.shared.time.ProtoBufTimestamp import build.buf.gen.simplecloud.controller.v1.ServerDefinition import build.buf.gen.simplecloud.controller.v1.ServerState import build.buf.gen.simplecloud.controller.v1.ServerType +import java.time.Instant import java.time.LocalDateTime +import java.time.ZoneId data class Server( val uniqueId: String, @@ -25,20 +28,20 @@ data class Server( fun toDefinition(): ServerDefinition { return ServerDefinition.newBuilder() .setUniqueId(uniqueId) - .setType(type) + .setServerType(type) .setGroupName(group) .setHostId(host) - .setIp(ip) - .setPort(port) - .setState(state) + .setServerIp(ip) + .setServerPort(port) + .setServerState(state) .setMinimumMemory(minMemory) .setMaximumMemory(maxMemory) .setPlayerCount(playerCount) .setMaxPlayers(maxPlayers) - .putAllProperties(properties) + .putAllCloudProperties(properties) .setNumericalId(numericalId) - .setCreatedAt(createdAt.toString()) - .setUpdatedAt(updatedAt.toString()) + .setCreatedAt(ProtoBufTimestamp.fromLocalDateTime(createdAt)) + .setUpdatedAt(ProtoBufTimestamp.fromLocalDateTime(updatedAt)) .build() } @@ -68,20 +71,20 @@ data class Server( fun fromDefinition(serverDefinition: ServerDefinition): Server { return Server( serverDefinition.uniqueId, - serverDefinition.type, + serverDefinition.serverType, serverDefinition.groupName, serverDefinition.hostId, serverDefinition.numericalId, - serverDefinition.ip, - serverDefinition.port, + serverDefinition.serverIp, + serverDefinition.serverPort, serverDefinition.minimumMemory, serverDefinition.maximumMemory, serverDefinition.maxPlayers, serverDefinition.playerCount, - serverDefinition.propertiesMap, - serverDefinition.state, - LocalDateTime.parse(serverDefinition.createdAt), - LocalDateTime.parse(serverDefinition.updatedAt) + serverDefinition.cloudPropertiesMap, + serverDefinition.serverState, + ProtoBufTimestamp.toLocalDateTime(serverDefinition.createdAt), + ProtoBufTimestamp.toLocalDateTime(serverDefinition.updatedAt), ) } diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/time/ProtoBufTimestamp.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/time/ProtoBufTimestamp.kt new file mode 100644 index 0000000..f2c5d39 --- /dev/null +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/time/ProtoBufTimestamp.kt @@ -0,0 +1,21 @@ +package app.simplecloud.controller.shared.time + +import com.google.protobuf.Timestamp +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneId + +object ProtoBufTimestamp { + fun toLocalDateTime(timestamp: Timestamp): LocalDateTime { + return LocalDateTime.ofInstant(Instant.ofEpochSecond(timestamp.seconds, timestamp.nanos.toLong()), ZoneId.systemDefault()) + } + + fun fromLocalDateTime(localDateTime: LocalDateTime): Timestamp { + val instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant() + + return Timestamp.newBuilder() + .setSeconds(instant.epochSecond) + .setNanos(instant.nano) + .build() + } +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c405676..f7ffc5b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,12 +1,12 @@ [versions] -kotlin = "1.8.0" +kotlin = "2.0.20" kotlinCoroutines = "1.4.2" shadow = "8.1.1" log4j = "2.20.0" protobuf = "3.25.2" grpc = "1.61.0" grpcKotlin = "1.4.1" -simpleCloudProtoSpecs = "1.4.1.1.20240606064605.c07118735783" +simpleCloudProtoSpecs = "1.4.1.1.20241001163139.58018cb317ed" simpleCloudPubSub = "1.0.3" jooq = "3.19.3" configurate = "4.1.2" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2f2ebf5..32f9838 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Thu Jan 18 09:50:39 CET 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From d56405b3c98eee77381688a225dc0902f1700454 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 1 Oct 2024 18:46:17 +0200 Subject: [PATCH 04/16] refactor: rename fields to match proto defs --- .../app/simplecloud/controller/api/impl/ServerApiImpl.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ServerApiImpl.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ServerApiImpl.kt index 77a7632..9eef408 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ServerApiImpl.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ServerApiImpl.kt @@ -71,7 +71,7 @@ class ServerApiImpl( override fun stopServer(groupName: String, numericalId: Long, stopCause: ServerStopCause): CompletableFuture { return serverServiceStub.stopServerByNumerical( StopServerByNumericalRequest.newBuilder() - .setServerGroup(groupName) + .setGroupName(groupName) .setNumericalId(numericalId) .setStopCause(stopCause) .build() @@ -84,7 +84,7 @@ class ServerApiImpl( return serverServiceStub.stopServer( StopServerRequest.newBuilder() .setServerId(id) - .setCause(stopCause) + .setStopCause(stopCause) .build() ).toCompletable().thenApply { Server.fromDefinition(it) From be2adad8cabb3f814252569254b045265e6d05ef Mon Sep 17 00:00:00 2001 From: David Date: Wed, 2 Oct 2024 13:00:23 +0200 Subject: [PATCH 05/16] impl: pubsub events for server start, stop and update --- .../controller/runtime/ControllerRuntime.kt | 9 ++- .../runtime/server/ServerService.kt | 67 ++++++++++++++++--- 2 files changed, 64 insertions(+), 12 deletions(-) diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/ControllerRuntime.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/ControllerRuntime.kt index 94f553f..36ad827 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/ControllerRuntime.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/ControllerRuntime.kt @@ -11,6 +11,7 @@ import app.simplecloud.controller.runtime.server.ServerRepository import app.simplecloud.controller.runtime.server.ServerService import app.simplecloud.controller.shared.auth.AuthCallCredentials import app.simplecloud.controller.shared.auth.AuthSecretInterceptor +import app.simplecloud.pubsub.PubSubClient import app.simplecloud.pubsub.PubSubService import io.grpc.ManagedChannel import io.grpc.ManagedChannelBuilder @@ -32,6 +33,7 @@ class ControllerRuntime( private val numericalIdRepository = ServerNumericalIdRepository() private val serverRepository = ServerRepository(database, numericalIdRepository) private val hostRepository = ServerHostRepository() + private val pubSubService = PubSubService() private val reconciler = Reconciler( groupRepository, serverRepository, @@ -45,8 +47,8 @@ class ControllerRuntime( fun start() { setupDatabase() - startGrpcServer() startPubSubGrpcServer() + startGrpcServer() startReconciler() loadGroups() loadServers() @@ -109,7 +111,8 @@ class ControllerRuntime( hostRepository, groupRepository, controllerStartCommand.forwardingSecret, - authCallCredentials + authCallCredentials, + PubSubClient(controllerStartCommand.grpcHost, controllerStartCommand.pubSubGrpcPort, authCallCredentials) ) ) .intercept(AuthSecretInterceptor(controllerStartCommand.authSecret)) @@ -118,7 +121,7 @@ class ControllerRuntime( private fun createPubSubGrpcServer(): Server { return ServerBuilder.forPort(controllerStartCommand.pubSubGrpcPort) - .addService(PubSubService()) + .addService(pubSubService) .intercept(AuthSecretInterceptor(controllerStartCommand.authSecret)) .build() } diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerService.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerService.kt index daa41b9..7b931e4 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerService.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerService.kt @@ -1,7 +1,6 @@ package app.simplecloud.controller.runtime.server import app.simplecloud.controller.runtime.group.GroupRepository -import app.simplecloud.controller.runtime.host.ServerHostException import app.simplecloud.controller.runtime.host.ServerHostRepository import app.simplecloud.controller.shared.auth.AuthCallCredentials import app.simplecloud.controller.shared.future.toCompletable @@ -9,6 +8,7 @@ import app.simplecloud.controller.shared.group.Group import app.simplecloud.controller.shared.host.ServerHost import app.simplecloud.controller.shared.server.Server import app.simplecloud.controller.shared.time.ProtoBufTimestamp +import app.simplecloud.pubsub.PubSubClient import build.buf.gen.simplecloud.controller.v1.* import io.grpc.Context import io.grpc.Status @@ -24,17 +24,21 @@ class ServerService( private val hostRepository: ServerHostRepository, private val groupRepository: GroupRepository, private val forwardingSecret: String, - private val authCallCredentials: AuthCallCredentials + private val authCallCredentials: AuthCallCredentials, + private val pubSubClient: PubSubClient, ) : ControllerServerServiceGrpc.ControllerServerServiceImplBase() { private val logger = LogManager.getLogger(ServerService::class.java) - override fun attachServerHost(request: AttachServerHostRequest, responseObserver: StreamObserver) { + override fun attachServerHost( + request: AttachServerHostRequest, + responseObserver: StreamObserver + ) { val serverHost = ServerHost.fromDefinition(request.serverHost) try { hostRepository.delete(serverHost) hostRepository.save(serverHost) - }catch (e: Exception) { + } catch (e: Exception) { responseObserver.onError( Status.INTERNAL .withDescription("Could not save serverhost") @@ -125,9 +129,11 @@ class ServerService( val deleted = request.deleted val server = Server.fromDefinition(request.server) if (!deleted) { + val before: Server try { + before = serverRepository.find(server.uniqueId).resultNow()!! serverRepository.save(server) - }catch (e: Exception) { + } catch (e: Exception) { responseObserver.onError( Status.INTERNAL .withDescription("Could not update server") @@ -136,12 +142,16 @@ class ServerService( ) return } + pubSubClient.publish("event", + ServerUpdateEvent.newBuilder().setUpdatedAt(ProtoBufTimestamp.fromLocalDateTime(LocalDateTime.now())) + .setServerBefore(before.toDefinition()).setServerAfter(request.server).build() + ) responseObserver.onNext(server.toDefinition()) responseObserver.onCompleted() } else { logger.info("Deleting server ${server.uniqueId} of group ${request.server.groupName}...") - serverRepository.delete(server).thenApply thenDelete@ { - if(!it) { + serverRepository.delete(server).thenApply thenDelete@{ + if (!it) { responseObserver.onError( Status.INTERNAL .withDescription("Could not delete server") @@ -150,6 +160,14 @@ class ServerService( return@thenDelete } logger.info("Deleted server ${server.uniqueId} of group ${request.server.groupName}.") + pubSubClient.publish( + "event", ServerStopEvent.newBuilder() + .setServer(request.server) + .setStoppedAt(ProtoBufTimestamp.fromLocalDateTime(LocalDateTime.now())) + .setStopCause(ServerStopCause.NATURAL_STOP) + .setTerminationMode(ServerTerminationMode.UNKNOWN_MODE) //TODO: Add proto fields to make changing this possible + .build() + ) responseObserver.onNext(server.toDefinition()) responseObserver.onCompleted() }.exceptionally { @@ -205,7 +223,10 @@ class ServerService( } } - override fun startServer(request: ControllerStartServerRequest, responseObserver: StreamObserver) { + override fun startServer( + request: ControllerStartServerRequest, + responseObserver: StreamObserver + ) { hostRepository.find(serverRepository).thenApply { host -> if (host == null) { responseObserver.onError( @@ -223,7 +244,15 @@ class ServerService( .asRuntimeException() ) } else { - startServer(host, group) + startServer(host, group).thenApply { + pubSubClient.publish( + "event", ServerStartEvent.newBuilder() + .setServer(it) + .setStartedAt(ProtoBufTimestamp.fromLocalDateTime(LocalDateTime.now())) + .setStartCause(request.startCause) + .build() + ) + } } }.exceptionally { logger.error("Error whilst starting server:", it) @@ -287,6 +316,14 @@ class ServerService( .asRuntimeException() } stopServer(server.toDefinition()).thenApply { + pubSubClient.publish( + "event", ServerStopEvent.newBuilder() + .setServer(it) + .setStoppedAt(ProtoBufTimestamp.fromLocalDateTime(LocalDateTime.now())) + .setStopCause(request.stopCause) + .setTerminationMode(ServerTerminationMode.UNKNOWN_MODE) //TODO: Add proto fields to make changing this possible + .build() + ) responseObserver.onNext(it) responseObserver.onCompleted() }.get() @@ -324,8 +361,14 @@ class ServerService( .withDescription("Server with id ${request.serverId} does not exist.") .asRuntimeException() } + val serverBefore = server.copy() server.properties[request.propertyKey] = request.propertyValue serverRepository.save(server) + pubSubClient.publish( + "event", + ServerUpdateEvent.newBuilder().setUpdatedAt(ProtoBufTimestamp.fromLocalDateTime(LocalDateTime.now())) + .setServerBefore(serverBefore.toDefinition()).setServerAfter(server.toDefinition()).build() + ) responseObserver.onNext(server.toDefinition()) responseObserver.onCompleted() }.exceptionally { @@ -343,8 +386,14 @@ class ServerService( .withDescription("Server with id ${request.serverState} does not exist.") .asRuntimeException() } + val serverBefore = server.copy() server.state = request.serverState serverRepository.save(server) + pubSubClient.publish( + "event", + ServerUpdateEvent.newBuilder().setUpdatedAt(ProtoBufTimestamp.fromLocalDateTime(LocalDateTime.now())) + .setServerBefore(serverBefore.toDefinition()).setServerAfter(server.toDefinition()).build() + ) responseObserver.onNext(server.toDefinition()) responseObserver.onCompleted() }.exceptionally { From ad6ee4bb9e406062d09e05aa2367e10a341c1683 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 5 Oct 2024 23:02:59 +0200 Subject: [PATCH 06/16] refactor: fix pubsub events --- .../controller/runtime/server/ServerService.kt | 13 +++++++------ gradle/libs.versions.toml | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerService.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerService.kt index 7b931e4..2d401ea 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerService.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerService.kt @@ -129,9 +129,14 @@ class ServerService( val deleted = request.deleted val server = Server.fromDefinition(request.server) if (!deleted) { - val before: Server try { - before = serverRepository.find(server.uniqueId).resultNow()!! + serverRepository.find(server.uniqueId).thenApply { before -> + if(before == null) return@thenApply + pubSubClient.publish("event", + ServerUpdateEvent.newBuilder().setUpdatedAt(ProtoBufTimestamp.fromLocalDateTime(LocalDateTime.now())) + .setServerBefore(before.toDefinition()).setServerAfter(request.server).build() + ) + } serverRepository.save(server) } catch (e: Exception) { responseObserver.onError( @@ -142,10 +147,6 @@ class ServerService( ) return } - pubSubClient.publish("event", - ServerUpdateEvent.newBuilder().setUpdatedAt(ProtoBufTimestamp.fromLocalDateTime(LocalDateTime.now())) - .setServerBefore(before.toDefinition()).setServerAfter(request.server).build() - ) responseObserver.onNext(server.toDefinition()) responseObserver.onCompleted() } else { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f7ffc5b..46ef1e6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ protobuf = "3.25.2" grpc = "1.61.0" grpcKotlin = "1.4.1" simpleCloudProtoSpecs = "1.4.1.1.20241001163139.58018cb317ed" -simpleCloudPubSub = "1.0.3" +simpleCloudPubSub = "1.0.4" jooq = "3.19.3" configurate = "4.1.2" sqliteJdbc = "3.44.1.0" From cf525f7241b3905a46a14a702c48e36f542cd0ab Mon Sep 17 00:00:00 2001 From: David Date: Sun, 6 Oct 2024 18:54:06 +0200 Subject: [PATCH 07/16] refactor: minimize controller-runtime build --- controller-runtime/build.gradle.kts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/controller-runtime/build.gradle.kts b/controller-runtime/build.gradle.kts index 7b4bdb5..8835135 100644 --- a/controller-runtime/build.gradle.kts +++ b/controller-runtime/build.gradle.kts @@ -1,3 +1,5 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + plugins { application alias(libs.plugins.jooqCodegen) @@ -14,6 +16,21 @@ dependencies { implementation(rootProject.libs.spotifyCompletableFutures) } +tasks.named("shadowJar", ShadowJar::class) { + dependencies { + include(dependency(rootProject.libs.kotlinCoroutines.get())) + include(dependency(rootProject.libs.qooq.get())) + include(dependency(rootProject.libs.qooqMeta.get())) + include(dependency(rootProject.libs.sqliteJdbc.get())) + include(dependency(rootProject.libs.clikt.get())) + include(dependency(rootProject.libs.spotifyCompletableFutures.get())) + include(dependency(rootProject.libs.log4jApi.get())) + include(dependency(rootProject.libs.log4jCore.get())) + include(dependency(rootProject.libs.log4jSlf4j.get())) + } + archiveFileName.set("${project.name}.jar") +} + application { mainClass.set("app.simplecloud.controller.runtime.launcher.LauncherKt") } From cb69abaeb1a7495084de1484160978f0370f8c42 Mon Sep 17 00:00:00 2001 From: Philipp Date: Mon, 7 Oct 2024 20:01:32 +0200 Subject: [PATCH 08/16] feat: ServerApi#getServerByNumerical --- .../app/simplecloud/controller/api/ServerApi.kt | 7 +++++++ .../simplecloud/controller/api/impl/ServerApiImpl.kt | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/ServerApi.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/ServerApi.kt index 3a71938..01258e9 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/ServerApi.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/ServerApi.kt @@ -27,6 +27,13 @@ interface ServerApi { */ fun getServersByGroup(groupName: String): CompletableFuture> + /** + * @param groupName the name of the server group. + * @param numericalId the numerical id of the server. + * @return a [CompletableFuture] with the [Server]. + */ + fun getServerByNumerical(groupName: String, numericalId: Long): CompletableFuture + /** * @param group The server group. * @return a [CompletableFuture] with a [List] of [Server]s of that group. diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ServerApiImpl.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ServerApiImpl.kt index 9eef408..3c4a52f 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ServerApiImpl.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ServerApiImpl.kt @@ -33,6 +33,17 @@ class ServerApiImpl( } } + override fun getServerByNumerical(groupName: String, numericalId: Long): CompletableFuture { + return serverServiceStub.getServerByNumerical( + GetServerByNumericalRequest.newBuilder() + .setGroupName(groupName) + .setNumericalId(numericalId) + .build() + ).toCompletable().thenApply { + Server.fromDefinition(it) + } + } + override fun getServersByGroup(groupName: String): CompletableFuture> { return serverServiceStub.getServersByGroup( GetServersByGroupRequest.newBuilder() From 6c0ffb77a43cc05d843a45cecaabe2433979f979 Mon Sep 17 00:00:00 2001 From: Philipp Date: Mon, 7 Oct 2024 20:02:03 +0200 Subject: [PATCH 09/16] update: controller version for api --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 92cb3e0..d35ebe1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "0.0.28-EXPERIMENTAL" + version = "0.0.29-EXPERIMENTAL" repositories { mavenCentral() From bcc8508a8f8f3fa391d994b58efc579dc1e15cd3 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 7 Oct 2024 20:15:46 +0200 Subject: [PATCH 10/16] refactor: minimize controller-runtime build --- controller-runtime/build.gradle.kts | 17 ----------------- controller-shared/build.gradle.kts | 1 + 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/controller-runtime/build.gradle.kts b/controller-runtime/build.gradle.kts index 8835135..7b4bdb5 100644 --- a/controller-runtime/build.gradle.kts +++ b/controller-runtime/build.gradle.kts @@ -1,5 +1,3 @@ -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar - plugins { application alias(libs.plugins.jooqCodegen) @@ -16,21 +14,6 @@ dependencies { implementation(rootProject.libs.spotifyCompletableFutures) } -tasks.named("shadowJar", ShadowJar::class) { - dependencies { - include(dependency(rootProject.libs.kotlinCoroutines.get())) - include(dependency(rootProject.libs.qooq.get())) - include(dependency(rootProject.libs.qooqMeta.get())) - include(dependency(rootProject.libs.sqliteJdbc.get())) - include(dependency(rootProject.libs.clikt.get())) - include(dependency(rootProject.libs.spotifyCompletableFutures.get())) - include(dependency(rootProject.libs.log4jApi.get())) - include(dependency(rootProject.libs.log4jCore.get())) - include(dependency(rootProject.libs.log4jSlf4j.get())) - } - archiveFileName.set("${project.name}.jar") -} - application { mainClass.set("app.simplecloud.controller.runtime.launcher.LauncherKt") } diff --git a/controller-shared/build.gradle.kts b/controller-shared/build.gradle.kts index 17decfa..7ff3313 100644 --- a/controller-shared/build.gradle.kts +++ b/controller-shared/build.gradle.kts @@ -2,4 +2,5 @@ dependencies { api(rootProject.libs.bundles.proto) api(rootProject.libs.simpleCloudPubSub) api(rootProject.libs.bundles.configurate) + api(rootProject.libs.clikt) } From 2e76659275fa4e17e1a5fd3b44531827258c0175 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 9 Oct 2024 10:54:28 +0200 Subject: [PATCH 11/16] refactor: upgrade runtime to coroutines --- build.gradle.kts | 5 +- .../controller/runtime/Repository.kt | 8 +- .../runtime/YamlDirectoryRepository.kt | 13 +- .../runtime/group/GroupRepository.kt | 8 +- .../controller/runtime/group/GroupService.kt | 128 ++---- .../runtime/host/ServerHostRepository.kt | 36 +- .../runtime/reconciler/GroupReconciler.kt | 21 +- .../runtime/reconciler/Reconciler.kt | 20 +- .../runtime/server/ServerRepository.kt | 92 ++-- .../runtime/server/ServerService.kt | 427 +++++++----------- gradle/libs.versions.toml | 10 +- 11 files changed, 284 insertions(+), 484 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index d35ebe1..5384740 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,4 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { alias(libs.plugins.kotlin) @@ -10,7 +9,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "0.0.29-EXPERIMENTAL" + version = "0.0.30-EXPERIMENTAL" repositories { mavenCentral() @@ -20,7 +19,7 @@ allprojects { subprojects { apply(plugin = "org.jetbrains.kotlin.jvm") - apply(plugin = "com.github.johnrengelman.shadow") + apply(plugin = "com.gradleup.shadow") apply(plugin = "net.thebugmc.gradle.sonatype-central-portal-publisher") apply(plugin = "maven-publish") diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Repository.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Repository.kt index 1785fa2..0fef36f 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Repository.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Repository.kt @@ -1,11 +1,9 @@ package app.simplecloud.controller.runtime -import java.util.concurrent.CompletableFuture - interface Repository { - fun delete(element: E): CompletableFuture + suspend fun delete(element: E): Boolean fun save(element: E) - fun find(identifier: I): CompletableFuture - fun getAll(): CompletableFuture> + suspend fun find(identifier: I): E? + suspend fun getAll(): List } \ No newline at end of file diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlDirectoryRepository.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlDirectoryRepository.kt index 34e627c..3197a0c 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlDirectoryRepository.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlDirectoryRepository.kt @@ -9,7 +9,6 @@ import org.spongepowered.configurate.yaml.NodeStyle import org.spongepowered.configurate.yaml.YamlConfigurationLoader import java.io.File import java.nio.file.* -import java.util.concurrent.CompletableFuture abstract class YamlDirectoryRepository( @@ -25,13 +24,13 @@ abstract class YamlDirectoryRepository( abstract fun getFileName(identifier: I): String - override fun delete(element: E): CompletableFuture { - val file = entities.keys.find { entities[it] == element } ?: return CompletableFuture.completedFuture(false) - return CompletableFuture.completedFuture(deleteFile(file)) + override suspend fun delete(element: E): Boolean { + val file = entities.keys.find { entities[it] == element } ?: return false + return deleteFile(file) } - override fun getAll(): CompletableFuture> { - return CompletableFuture.completedFuture(entities.values.toList()) + override suspend fun getAll(): List { + return entities.values.toList() } override fun load(): List { @@ -116,7 +115,7 @@ abstract class YamlDirectoryRepository( when (kind) { StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY - -> { + -> { load(resolvedPath.toFile()) } diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/group/GroupRepository.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/group/GroupRepository.kt index 73669fe..176920a 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/group/GroupRepository.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/group/GroupRepository.kt @@ -12,15 +12,15 @@ class GroupRepository( return "$identifier.yml" } - override fun find(identifier: String): CompletableFuture { - return CompletableFuture.completedFuture(entities.values.find { it.name == identifier }) + override suspend fun find(identifier: String): Group? { + return entities.values.find { it.name == identifier } } override fun save(element: Group) { save(getFileName(element.name), element) } - override fun getAll(): CompletableFuture> { - return CompletableFuture.completedFuture(entities.values.toList()) + override suspend fun getAll(): List { + return entities.values.toList() } } \ No newline at end of file diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/group/GroupService.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/group/GroupService.kt index d57945b..a57f9ab 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/group/GroupService.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/group/GroupService.kt @@ -3,130 +3,64 @@ package app.simplecloud.controller.runtime.group import app.simplecloud.controller.shared.group.Group import build.buf.gen.simplecloud.controller.v1.* import io.grpc.Status -import io.grpc.stub.StreamObserver +import io.grpc.StatusException class GroupService( private val groupRepository: GroupRepository -) : ControllerGroupServiceGrpc.ControllerGroupServiceImplBase() { - - override fun getGroupByName( - request: GetGroupByNameRequest, - responseObserver: StreamObserver - ) { - groupRepository.find(request.groupName).thenApply { group -> - if (group == null) { - responseObserver.onError( - Status.NOT_FOUND - .withDescription("This group does not exist") - .asRuntimeException() - ) - return@thenApply - } - - val response = GetGroupByNameResponse.newBuilder() - .setGroup(group.toDefinition()) - .build() - - responseObserver.onNext(response) - responseObserver.onCompleted() - } +) : ControllerGroupServiceGrpcKt.ControllerGroupServiceCoroutineImplBase() { + override suspend fun getGroupByName(request: GetGroupByNameRequest): GetGroupByNameResponse { + val group = groupRepository.find(request.groupName) + ?: throw StatusException(Status.NOT_FOUND.withDescription("This group does not exist")) + return getGroupByNameResponse { group.toDefinition() } } - override fun getAllGroups(request: GetAllGroupsRequest, responseObserver: StreamObserver) { - groupRepository.getAll().thenApply { groups -> - val response = GetAllGroupsResponse.newBuilder() - .addAllGroups(groups.map { it.toDefinition() }) - .build() - responseObserver.onNext(response) - responseObserver.onCompleted() + override suspend fun getAllGroups(request: GetAllGroupsRequest): GetAllGroupsResponse { + val allGroups = groupRepository.getAll() + return getAllGroupsResponse { + groups.addAll(allGroups.map { it.toDefinition() }) } - } - override fun getGroupsByType( - request: GetGroupsByTypeRequest, - responseObserver: StreamObserver - ) { + override suspend fun getGroupsByType(request: GetGroupsByTypeRequest): GetGroupsByTypeResponse { val type = request.serverType - groupRepository.getAll().thenApply { groups -> - val response = GetGroupsByTypeResponse.newBuilder() - .addAllGroups(groups.filter { it.type == type }.map { it.toDefinition() }) - .build() - responseObserver.onNext(response) - responseObserver.onCompleted() + val typedGroups = groupRepository.getAll().filter { it.type == type } + return getGroupsByTypeResponse { + groups.addAll(typedGroups.map { it.toDefinition() }) } - } - override fun updateGroup(request: UpdateGroupRequest, responseObserver: StreamObserver) { + override suspend fun updateGroup(request: UpdateGroupRequest): GroupDefinition { + groupRepository.find(request.group.name) + ?: throw StatusException(Status.NOT_FOUND.withDescription("This group does not exist")) val group = Group.fromDefinition(request.group) try { groupRepository.save(group) } catch (e: Exception) { - responseObserver.onError( - Status.INTERNAL - .withDescription("Error whilst updating group") - .withCause(e) - .asRuntimeException() - ) - return + throw StatusException(Status.INTERNAL.withDescription("Error whilst updating group").withCause(e)) } - - responseObserver.onNext(group.toDefinition()) - responseObserver.onCompleted() + return group.toDefinition() } - override fun createGroup(request: CreateGroupRequest, responseObserver: StreamObserver) { + override suspend fun createGroup(request: CreateGroupRequest): GroupDefinition { + if (groupRepository.find(request.group.name) != null) { + throw StatusException(Status.NOT_FOUND.withDescription("This group already exists")) + } val group = Group.fromDefinition(request.group) try { groupRepository.save(group) } catch (e: Exception) { - responseObserver.onError( - Status.INTERNAL - .withDescription("Error whilst creating group") - .withCause(e) - .asRuntimeException() - ) - return + throw StatusException(Status.INTERNAL.withDescription("Error whilst creating group").withCause(e)) } - - responseObserver.onNext(group.toDefinition()) - responseObserver.onCompleted() + return group.toDefinition() } - override fun deleteGroupByName(request: DeleteGroupByNameRequest, responseObserver: StreamObserver) { - groupRepository.find(request.groupName).thenApply { group -> - if (group == null) { - responseObserver.onError( - Status.NOT_FOUND - .withDescription("This group does not exist") - .asRuntimeException() - ) - return@thenApply - } - groupRepository.delete(group).thenApply thenDelete@ { successfullyDeleted -> - if(!successfullyDeleted) { - responseObserver.onError( - Status.INTERNAL - .withDescription("Could not delete group") - .asRuntimeException() - ) - - return@thenDelete - } - responseObserver.onNext(group.toDefinition()) - responseObserver.onCompleted() - }.exceptionally { - responseObserver.onError( - Status.INTERNAL - .withDescription("Could not delete group") - .withCause(it) - .asRuntimeException() - ) - } - } - + override suspend fun deleteGroupByName(request: DeleteGroupByNameRequest): GroupDefinition { + val group = groupRepository.find(request.groupName) + ?: throw StatusException(Status.NOT_FOUND.withDescription("This group does not exist")) + val deleted = groupRepository.delete(group) + if (!deleted) throw StatusException(Status.NOT_FOUND.withDescription("Could not delete this group")) + return group.toDefinition() } } \ No newline at end of file diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/host/ServerHostRepository.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/host/ServerHostRepository.kt index 14ddb8d..7590ce1 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/host/ServerHostRepository.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/host/ServerHostRepository.kt @@ -3,9 +3,8 @@ package app.simplecloud.controller.runtime.host import app.simplecloud.controller.runtime.Repository import app.simplecloud.controller.runtime.server.ServerRepository import app.simplecloud.controller.shared.host.ServerHost -import com.spotify.futures.CompletableFutures import io.grpc.ConnectivityState -import java.util.concurrent.CompletableFuture +import kotlinx.coroutines.coroutineScope import java.util.concurrent.ConcurrentHashMap class ServerHostRepository : Repository { @@ -20,16 +19,13 @@ class ServerHostRepository : Repository { hosts[element.id] = element } - override fun find(identifier: ServerRepository): CompletableFuture { - return mapHostsToServerHostWithServerCount(identifier).thenApply { - val serverHostWithServerCount = it.minByOrNull { it.serverCount } - serverHostWithServerCount?.serverHost - } + override suspend fun find(identifier: ServerRepository): ServerHost? { + return mapHostsToServerHostWithServerCount(identifier).minByOrNull { it.serverCount }?.serverHost } - fun areServerHostsAvailable(): CompletableFuture { - return CompletableFuture.supplyAsync { - hosts.any { + suspend fun areServerHostsAvailable(): Boolean { + return coroutineScope { + return@coroutineScope hosts.any { val channel = it.value.createChannel() val state = channel.getState(true) channel.shutdown() @@ -38,22 +34,18 @@ class ServerHostRepository : Repository { } } - override fun delete(element: ServerHost): CompletableFuture { - return CompletableFuture.completedFuture(hosts.remove(element.id, element)) + override suspend fun delete(element: ServerHost): Boolean { + return hosts.remove(element.id, element) } - override fun getAll(): CompletableFuture> { - return CompletableFuture.completedFuture(hosts.values.toList()) + override suspend fun getAll(): List { + return hosts.values.toList() } - private fun mapHostsToServerHostWithServerCount(identifier: ServerRepository): CompletableFuture> { - return CompletableFutures.allAsList( - hosts.values.map { serverHost -> - identifier.findServersByHostId(serverHost.id).thenApply { - ServerHostWithServerCount(serverHost, it.size) - } - } - ) + private suspend fun mapHostsToServerHostWithServerCount(identifier: ServerRepository): List { + return hosts.values.map { serverHost -> + ServerHostWithServerCount(serverHost, identifier.findServersByHostId(serverHost.id).size) + } } } diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/reconciler/GroupReconciler.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/reconciler/GroupReconciler.kt index 3b08b2e..cf4cdff 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/reconciler/GroupReconciler.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/reconciler/GroupReconciler.kt @@ -8,6 +8,7 @@ import app.simplecloud.controller.shared.group.Group import app.simplecloud.controller.shared.server.Server import build.buf.gen.simplecloud.controller.v1.* import build.buf.gen.simplecloud.controller.v1.ControllerServerServiceGrpc.ControllerServerServiceFutureStub +import kotlinx.coroutines.runBlocking import org.apache.logging.log4j.LogManager import java.time.LocalDateTime import kotlin.math.min @@ -21,11 +22,11 @@ class GroupReconciler( ) { private val logger = LogManager.getLogger(GroupReconciler::class.java) - private val servers = this.serverRepository.findServersByGroup(this.group.name).get() + private val servers = runBlocking { serverRepository.findServersByGroup(group.name) } private val availableServerCount = calculateAvailableServerCount() - fun reconcile() { + suspend fun reconcile() { cleanupServers() cleanupNumericalIds() startServers() @@ -96,17 +97,19 @@ class GroupReconciler( return server.updatedAt.isAfter(LocalDateTime.now().minusMinutes(INACTIVE_SERVER_TIME)) } - private fun startServers() { - serverHostRepository.areServerHostsAvailable().thenApply { - if (!it) return@thenApply - if (isNewServerNeeded()) - startServer() - } + private suspend fun startServers() { + val available = serverHostRepository.areServerHostsAvailable() + if(!available) return + if(isNewServerNeeded()) + startServer() } private fun startServer() { logger.info("Starting new instance of group ${this.group.name}") - serverStub.startServer(ControllerStartServerRequest.newBuilder().setGroupName(this.group.name).setStartCause(ServerStartCause.RECONCILER_START).build()).toCompletable() + serverStub.startServer( + ControllerStartServerRequest.newBuilder().setGroupName(this.group.name) + .setStartCause(ServerStartCause.RECONCILER_START).build() + ).toCompletable() .thenApply { logger.info("Started new instance ${it.groupName}-${it.numericalId}/${it.uniqueId} of group ${this.group.name} on ${it.serverIp}:${it.serverPort}") }.exceptionally { diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/reconciler/Reconciler.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/reconciler/Reconciler.kt index 93525d2..27398b9 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/reconciler/Reconciler.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/reconciler/Reconciler.kt @@ -20,17 +20,15 @@ class Reconciler( private val serverStub = ControllerServerServiceGrpc.newFutureStub(managedChannel) .withCallCredentials(authCallCredentials) - fun reconcile() { - this.groupRepository.getAll().thenApply { - it.forEach { group -> - GroupReconciler( - serverRepository, - serverHostRepository, - numericalIdRepository, - serverStub, - group - ).reconcile() - } + suspend fun reconcile() { + this.groupRepository.getAll().forEach { group -> + GroupReconciler( + serverRepository, + serverHostRepository, + numericalIdRepository, + serverStub, + group + ).reconcile() } } diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerRepository.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerRepository.kt index 7b3dea6..1c17a44 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerRepository.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerRepository.kt @@ -8,9 +8,11 @@ import app.simplecloud.controller.shared.db.tables.records.CloudServersRecord import app.simplecloud.controller.shared.server.Server import build.buf.gen.simplecloud.controller.v1.ServerState import build.buf.gen.simplecloud.controller.v1.ServerType +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import org.jooq.Result +import org.jooq.exception.DataAccessException import java.time.LocalDateTime -import java.util.concurrent.CompletableFuture class ServerRepository( private val database: Database, @@ -18,73 +20,75 @@ class ServerRepository( ) : LoadableRepository { - override fun find(identifier: String): CompletableFuture { - return CompletableFuture.supplyAsync { - val query = database.context.select() + override suspend fun find(identifier: String): Server? { + val query = withContext(Dispatchers.IO) { + database.context.select() .from(CLOUD_SERVERS) .where(CLOUD_SERVERS.UNIQUE_ID.eq(identifier)) + .limit(1) .fetchInto( CLOUD_SERVERS ) - return@supplyAsync toList(query).firstOrNull() } + return toList(query).firstOrNull() } - fun findServerByNumerical(group: String, id: Int): CompletableFuture { - return CompletableFuture.supplyAsync { - val query = database.context.select().from(CLOUD_SERVERS) + suspend fun findServerByNumerical(group: String, id: Int): Server? { + val query = withContext(Dispatchers.IO) { + database.context.select().from(CLOUD_SERVERS) .where( CLOUD_SERVERS.GROUP_NAME.eq(group) .and(CLOUD_SERVERS.NUMERICAL_ID.eq(id)) ) + .limit(1) .fetchInto(CLOUD_SERVERS) - return@supplyAsync toList(query).firstOrNull() } + return toList(query).firstOrNull() } - override fun getAll(): CompletableFuture> { - return CompletableFuture.supplyAsync { - val query = database.context.select() + override suspend fun getAll(): List { + val query = withContext(Dispatchers.IO) { + database.context.select() .from(CLOUD_SERVERS) .fetchInto(CLOUD_SERVERS) - return@supplyAsync toList(query) } + return toList(query) + } - fun findServersByHostId(id: String): CompletableFuture> { - return CompletableFuture.supplyAsync { - val query = database.context.select() + suspend fun findServersByHostId(id: String): List { + val query = withContext(Dispatchers.IO) { + database.context.select() .from(CLOUD_SERVERS) .where(CLOUD_SERVERS.HOST_ID.eq(id)) .fetchInto( CLOUD_SERVERS ) - return@supplyAsync toList(query) } + return toList(query) } - fun findServersByGroup(group: String): CompletableFuture> { - return CompletableFuture.supplyAsync { - val query = database.context.select() + suspend fun findServersByGroup(group: String): List { + val query = withContext(Dispatchers.IO) { + database.context.select() .from(CLOUD_SERVERS) .where(CLOUD_SERVERS.GROUP_NAME.eq(group)) .fetchInto( CLOUD_SERVERS ) - return@supplyAsync toList(query) } - + return toList(query) } - fun findServersByType(type: ServerType): CompletableFuture> { - return CompletableFuture.supplyAsync { - val query = database.context.select() + suspend fun findServersByType(type: ServerType): List { + val query = withContext(Dispatchers.IO) { + database.context.select() .from(CLOUD_SERVERS) .where(CLOUD_SERVERS.TYPE.eq(type.toString())) .fetchInto(CLOUD_SERVERS) - return@supplyAsync toList(query) } + return toList(query) } private fun toList(query: Result): List { @@ -120,27 +124,25 @@ class ServerRepository( return result } - override fun delete(element: Server): CompletableFuture { + override suspend fun delete(element: Server): Boolean { val canDelete = - database.context.deleteFrom(CLOUD_SERVER_PROPERTIES) - .where(CLOUD_SERVER_PROPERTIES.SERVER_ID.eq(element.uniqueId)) - .executeAsync().toCompletableFuture().thenApply { - return@thenApply true - }.exceptionally { - it.printStackTrace() - return@exceptionally false - }.get() - if (!canDelete) return CompletableFuture.completedFuture(false) - numericalIdRepository.removeNumericalId(element.group, element.numericalId) - return database.context.deleteFrom(CLOUD_SERVERS) - .where(CLOUD_SERVERS.UNIQUE_ID.eq(element.uniqueId)) - .executeAsync() - .toCompletableFuture().thenApply { - return@thenApply it > 0 - }.exceptionally { - it.printStackTrace() - return@exceptionally false + withContext(Dispatchers.IO) { + try { + database.context.deleteFrom(CLOUD_SERVER_PROPERTIES) + .where(CLOUD_SERVER_PROPERTIES.SERVER_ID.eq(element.uniqueId)) + .execute() + return@withContext true + } catch (e: DataAccessException) { + return@withContext false + } } + if (!canDelete) return false + numericalIdRepository.removeNumericalId(element.group, element.numericalId) + return withContext(Dispatchers.IO) { + database.context.deleteFrom(CLOUD_SERVERS) + .where(CLOUD_SERVERS.UNIQUE_ID.eq(element.uniqueId)) + .execute() > 0 + } } @Synchronized diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerService.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerService.kt index 2d401ea..7cb847a 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerService.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerService.kt @@ -3,20 +3,18 @@ package app.simplecloud.controller.runtime.server import app.simplecloud.controller.runtime.group.GroupRepository import app.simplecloud.controller.runtime.host.ServerHostRepository import app.simplecloud.controller.shared.auth.AuthCallCredentials -import app.simplecloud.controller.shared.future.toCompletable import app.simplecloud.controller.shared.group.Group import app.simplecloud.controller.shared.host.ServerHost import app.simplecloud.controller.shared.server.Server import app.simplecloud.controller.shared.time.ProtoBufTimestamp import app.simplecloud.pubsub.PubSubClient import build.buf.gen.simplecloud.controller.v1.* -import io.grpc.Context import io.grpc.Status -import io.grpc.stub.StreamObserver +import io.grpc.StatusException +import kotlinx.coroutines.coroutineScope import org.apache.logging.log4j.LogManager import java.time.LocalDateTime import java.util.* -import java.util.concurrent.CompletableFuture class ServerService( private val numericalIdRepository: ServerNumericalIdRepository, @@ -26,264 +24,169 @@ class ServerService( private val forwardingSecret: String, private val authCallCredentials: AuthCallCredentials, private val pubSubClient: PubSubClient, -) : ControllerServerServiceGrpc.ControllerServerServiceImplBase() { +) : ControllerServerServiceGrpcKt.ControllerServerServiceCoroutineImplBase() { private val logger = LogManager.getLogger(ServerService::class.java) - override fun attachServerHost( - request: AttachServerHostRequest, - responseObserver: StreamObserver - ) { + override suspend fun attachServerHost(request: AttachServerHostRequest): ServerHostDefinition { val serverHost = ServerHost.fromDefinition(request.serverHost) try { hostRepository.delete(serverHost) hostRepository.save(serverHost) } catch (e: Exception) { - responseObserver.onError( - Status.INTERNAL - .withDescription("Could not save serverhost") - .withCause(e) - .asRuntimeException() - ) - return + throw StatusException(Status.INTERNAL.withDescription("Could not save serverhost").withCause(e)) } logger.info("Successfully registered ServerHost ${serverHost.id}.") - responseObserver.onNext(serverHost.toDefinition()) - responseObserver.onCompleted() - Context.current().fork().run { + + coroutineScope { val channel = serverHost.createChannel() - val stub = ServerHostServiceGrpc.newFutureStub(channel) - .withCallCredentials(authCallCredentials) - serverRepository.findServersByHostId(serverHost.id).thenApply { - it.forEach { server -> - logger.info("Reattaching Server ${server.uniqueId} of group ${server.group}...") - stub.reattachServer(server.toDefinition()).toCompletable().thenApply { - logger.info("Success!") - }.exceptionally { - logger.error("Server was found to be offline, unregistering...") - serverRepository.delete(server) - }.get() + val stub = + ServerHostServiceGrpcKt.ServerHostServiceCoroutineStub(channel).withCallCredentials(authCallCredentials) + serverRepository.findServersByHostId(serverHost.id).forEach { server -> + logger.info("Reattaching Server ${server.uniqueId} of group ${server.group}...") + try { + val result = stub.reattachServer(server.toDefinition()) + serverRepository.save(Server.fromDefinition(result)) + logger.info("Success!") + } catch (e: Exception) { + logger.error("Server was found to be offline, unregistering...") + serverRepository.delete(server) } - channel.shutdown() } + channel.shutdown() } + return serverHost.toDefinition() } - override fun getAllServers( - request: GetAllServersRequest, - responseObserver: StreamObserver - ) { - serverRepository.getAll().thenApply { servers -> - responseObserver.onNext( - GetAllServersResponse.newBuilder() - .addAllServers(servers.map { it.toDefinition() }) - .build() - ) - responseObserver.onCompleted() - } + override suspend fun getAllServers(request: GetAllServersRequest): GetAllServersResponse { + val currentServers = serverRepository.getAll() + return getAllServersResponse { servers.addAll(currentServers.map { it.toDefinition() }) } } - override fun getServerByNumerical( - request: GetServerByNumericalRequest, - responseObserver: StreamObserver - ) { - serverRepository.findServerByNumerical(request.groupName, request.numericalId.toInt()).thenApply { server -> - if (server == null) { - responseObserver.onError( - Status.NOT_FOUND - .withDescription("No server was found matching this group and numerical id") - .asRuntimeException() - ) - return@thenApply - } - responseObserver.onNext(server.toDefinition()) - responseObserver.onCompleted() - } + override suspend fun getServerByNumerical(request: GetServerByNumericalRequest): ServerDefinition { + val server = serverRepository.findServerByNumerical(request.groupName, request.numericalId.toInt()) + ?: throw StatusException(Status.NOT_FOUND.withDescription("No server was found matching this group and numerical id")) + return server.toDefinition() } - override fun stopServerByNumerical( - request: StopServerByNumericalRequest, - responseObserver: StreamObserver - ) { - - serverRepository.findServerByNumerical(request.groupName, request.numericalId.toInt()).thenApply { server -> - if (server == null) { - responseObserver.onError( - Status.NOT_FOUND - .withDescription("No server was found matching this group and numerical id") - .asRuntimeException() - ) - return@thenApply - } - stopServer(server.toDefinition()).thenApply { - responseObserver.onNext(it) - responseObserver.onCompleted() - }.exceptionally { - responseObserver.onError(it) - } + override suspend fun stopServerByNumerical(request: StopServerByNumericalRequest): ServerDefinition { + val server = serverRepository.findServerByNumerical(request.groupName, request.numericalId.toInt()) + ?: throw StatusException(Status.NOT_FOUND.withDescription("No server was found matching this group and numerical id")) + try { + return stopServer(server.toDefinition()) + } catch (e: Exception) { + throw StatusException( + Status.INTERNAL.withDescription("Error occured whilest cleaning up stopped server: ").withCause(e) + ) } - } - override fun updateServer(request: UpdateServerRequest, responseObserver: StreamObserver) { + override suspend fun updateServer(request: UpdateServerRequest): ServerDefinition { val deleted = request.deleted val server = Server.fromDefinition(request.server) if (!deleted) { try { - serverRepository.find(server.uniqueId).thenApply { before -> - if(before == null) return@thenApply - pubSubClient.publish("event", - ServerUpdateEvent.newBuilder().setUpdatedAt(ProtoBufTimestamp.fromLocalDateTime(LocalDateTime.now())) - .setServerBefore(before.toDefinition()).setServerAfter(request.server).build() - ) - } + val before = serverRepository.find(server.uniqueId) + ?: throw StatusException(Status.NOT_FOUND.withDescription("Server not found")) + pubSubClient.publish( + "event", + ServerUpdateEvent.newBuilder() + .setUpdatedAt(ProtoBufTimestamp.fromLocalDateTime(LocalDateTime.now())) + .setServerBefore(before.toDefinition()).setServerAfter(request.server).build() + ) serverRepository.save(server) + return server.toDefinition() } catch (e: Exception) { - responseObserver.onError( + throw StatusException( Status.INTERNAL .withDescription("Could not update server") .withCause(e) - .asRuntimeException() ) - return } - responseObserver.onNext(server.toDefinition()) - responseObserver.onCompleted() } else { logger.info("Deleting server ${server.uniqueId} of group ${request.server.groupName}...") - serverRepository.delete(server).thenApply thenDelete@{ - if (!it) { - responseObserver.onError( - Status.INTERNAL - .withDescription("Could not delete server") - .asRuntimeException() - ) - return@thenDelete - } - logger.info("Deleted server ${server.uniqueId} of group ${request.server.groupName}.") - pubSubClient.publish( - "event", ServerStopEvent.newBuilder() - .setServer(request.server) - .setStoppedAt(ProtoBufTimestamp.fromLocalDateTime(LocalDateTime.now())) - .setStopCause(ServerStopCause.NATURAL_STOP) - .setTerminationMode(ServerTerminationMode.UNKNOWN_MODE) //TODO: Add proto fields to make changing this possible - .build() - ) - responseObserver.onNext(server.toDefinition()) - responseObserver.onCompleted() - }.exceptionally { - responseObserver.onError( + val deleteSuccess = serverRepository.delete(server) + if (!deleteSuccess) { + throw StatusException( Status.INTERNAL .withDescription("Could not delete server") - .withCause(it) - .asRuntimeException() ) } + logger.info("Deleted server ${server.uniqueId} of group ${request.server.groupName}.") + pubSubClient.publish( + "event", ServerStopEvent.newBuilder() + .setServer(request.server) + .setStoppedAt(ProtoBufTimestamp.fromLocalDateTime(LocalDateTime.now())) + .setStopCause(ServerStopCause.NATURAL_STOP) + .setTerminationMode(ServerTerminationMode.UNKNOWN_MODE) //TODO: Add proto fields to make changing this possible + .build() + ) + return server.toDefinition() } } - override fun getServerById(request: GetServerByIdRequest, responseObserver: StreamObserver) { - serverRepository.find(request.serverId).thenApply { server -> - if (server == null) { - responseObserver.onError( - Status.NOT_FOUND - .withDescription("No server was found matching this unique id") - .asRuntimeException() - ) - return@thenApply - } - responseObserver.onNext(server.toDefinition()) - responseObserver.onCompleted() - } - + override suspend fun getServerById(request: GetServerByIdRequest): ServerDefinition { + val server = serverRepository.find(request.serverId) ?: throw StatusException( + Status.NOT_FOUND + .withDescription("No server was found matching this unique id") + ) + return server.toDefinition() } - override fun getServersByGroup( - request: GetServersByGroupRequest, - responseObserver: StreamObserver - ) { - serverRepository.findServersByGroup(request.groupName).thenApply { servers -> - val response = GetServersByGroupResponse.newBuilder() - .addAllServers(servers.map { it.toDefinition() }) - .build() - responseObserver.onNext(response) - responseObserver.onCompleted() - } + override suspend fun getServersByGroup(request: GetServersByGroupRequest): GetServersByGroupResponse { + val groupServers = serverRepository.findServersByGroup(request.groupName) + return getServersByGroupResponse { servers.addAll(groupServers.map { it.toDefinition() }) } } - override fun getServersByType( - request: ServerTypeRequest, - responseObserver: StreamObserver - ) { - serverRepository.findServersByType(request.serverType).thenApply { servers -> - val response = GetServersByTypeResponse.newBuilder() - .addAllServers(servers.map { it.toDefinition() }) - .build() - responseObserver.onNext(response) - responseObserver.onCompleted() - } + override suspend fun getServersByType(request: ServerTypeRequest): GetServersByTypeResponse { + val typeServers = serverRepository.findServersByType(request.serverType) + return getServersByTypeResponse { servers.addAll(typeServers.map { it.toDefinition() }) } } - override fun startServer( - request: ControllerStartServerRequest, - responseObserver: StreamObserver - ) { - hostRepository.find(serverRepository).thenApply { host -> - if (host == null) { - responseObserver.onError( - Status.NOT_FOUND - .withDescription("No server host found, could not start server") - .asRuntimeException() - ) - return@thenApply - } - groupRepository.find(request.groupName).thenApply { group -> - if (group == null) { - responseObserver.onError( - Status.NOT_FOUND - .withDescription("No group was found matching this name") - .asRuntimeException() - ) - } else { - startServer(host, group).thenApply { - pubSubClient.publish( - "event", ServerStartEvent.newBuilder() - .setServer(it) - .setStartedAt(ProtoBufTimestamp.fromLocalDateTime(LocalDateTime.now())) - .setStartCause(request.startCause) - .build() - ) - } - } - }.exceptionally { - logger.error("Error whilst starting server:", it) - } + override suspend fun startServer(request: ControllerStartServerRequest): ServerDefinition { + val host = hostRepository.find(serverRepository) + ?: throw StatusException(Status.NOT_FOUND.withDescription("No server host found, could not start server")) + val group = groupRepository.find(request.groupName) + ?: throw StatusException(Status.NOT_FOUND.withDescription("No group was found matching this name")) + try { + val server = startServer(host, group) + pubSubClient.publish( + "event", ServerStartEvent.newBuilder() + .setServer(server) + .setStartedAt(ProtoBufTimestamp.fromLocalDateTime(LocalDateTime.now())) + .setStartCause(request.startCause) + .build() + ) + return server + } catch (e: Exception) { + throw StatusException(Status.INTERNAL.withDescription("Error whilst starting server").withCause(e)) } } - private fun startServer(host: ServerHost, group: Group): CompletableFuture { + private suspend fun startServer(host: ServerHost, group: Group): ServerDefinition { val numericalId = numericalIdRepository.findNextNumericalId(group.name) val server = buildServer(group, numericalId, forwardingSecret) serverRepository.save(server) val channel = host.createChannel() - val stub = ServerHostServiceGrpc.newFutureStub(channel) + val stub = ServerHostServiceGrpcKt.ServerHostServiceCoroutineStub(channel) .withCallCredentials(authCallCredentials) serverRepository.save(server) - return stub.startServer( - ServerHostStartServerRequest.newBuilder() - .setGroup(group.toDefinition()) - .setServer(server.toDefinition()) - .build() - ).toCompletable().thenApply { - serverRepository.save(Server.fromDefinition(it)) + try { + val result = stub.startServer( + ServerHostStartServerRequest.newBuilder() + .setGroup(group.toDefinition()) + .setServer(server.toDefinition()) + .build() + ) + serverRepository.save(Server.fromDefinition(result)) channel.shutdown() - return@thenApply it - }.exceptionally { + return result + } catch (e: Exception) { serverRepository.delete(server) numericalIdRepository.removeNumericalId(group.name, server.numericalId) channel.shutdown() - logger.error("Error whilst starting server:", it) - throw it + logger.error("Error whilst starting server:", e) + throw e } } @@ -309,97 +212,69 @@ class ServerService( ) } - override fun stopServer(request: StopServerRequest, responseObserver: StreamObserver) { - serverRepository.find(request.serverId).thenApply { server -> - if (server == null) { - throw Status.NOT_FOUND - .withDescription("No server was found matching this id.") - .asRuntimeException() - } - stopServer(server.toDefinition()).thenApply { - pubSubClient.publish( - "event", ServerStopEvent.newBuilder() - .setServer(it) - .setStoppedAt(ProtoBufTimestamp.fromLocalDateTime(LocalDateTime.now())) - .setStopCause(request.stopCause) - .setTerminationMode(ServerTerminationMode.UNKNOWN_MODE) //TODO: Add proto fields to make changing this possible - .build() - ) - responseObserver.onNext(it) - responseObserver.onCompleted() - }.get() - }.exceptionally { - logger.error("Error whilst stopping server:", it) - responseObserver.onError(it) + override suspend fun stopServer(request: StopServerRequest): ServerDefinition { + val server = serverRepository.find(request.serverId) + ?: throw StatusException(Status.NOT_FOUND.withDescription("No server was found matching this id.")) + try { + val stopped = stopServer(server.toDefinition()) + pubSubClient.publish( + "event", ServerStopEvent.newBuilder() + .setServer(stopped) + .setStoppedAt(ProtoBufTimestamp.fromLocalDateTime(LocalDateTime.now())) + .setStopCause(request.stopCause) + .setTerminationMode(ServerTerminationMode.UNKNOWN_MODE) //TODO: Add proto fields to make changing this possible + .build() + ) + return stopped + } catch (e: Exception) { + throw StatusException(Status.INTERNAL.withDescription("Error whilst stopping server").withCause(e)) } } - private fun stopServer(server: ServerDefinition): CompletableFuture { + private suspend fun stopServer(server: ServerDefinition): ServerDefinition { val host = hostRepository.findServerHostById(server.hostId) ?: throw Status.NOT_FOUND .withDescription("No server host was found matching this server.") .asRuntimeException() val channel = host.createChannel() - val stub = ServerHostServiceGrpc.newFutureStub(channel) + val stub = ServerHostServiceGrpcKt.ServerHostServiceCoroutineStub(channel) .withCallCredentials(authCallCredentials) - return stub.stopServer(server).toCompletable().thenApply { - serverRepository.delete(Server.fromDefinition(server)) + try { + val stopped = stub.stopServer(server) channel.shutdown() - return@thenApply it - }.exceptionally { - logger.error("Server stop error occured:", it) - throw it + return stopped + } catch (e: Exception) { + logger.error("Server stop error occured:", e) + throw e } } - override fun updateServerProperty( - request: UpdateServerPropertyRequest, - responseObserver: StreamObserver - ) { - serverRepository.find(request.serverId).thenApply { server -> - if (server == null) { - throw Status.NOT_FOUND - .withDescription("Server with id ${request.serverId} does not exist.") - .asRuntimeException() - } - val serverBefore = server.copy() - server.properties[request.propertyKey] = request.propertyValue - serverRepository.save(server) - pubSubClient.publish( - "event", - ServerUpdateEvent.newBuilder().setUpdatedAt(ProtoBufTimestamp.fromLocalDateTime(LocalDateTime.now())) - .setServerBefore(serverBefore.toDefinition()).setServerAfter(server.toDefinition()).build() - ) - responseObserver.onNext(server.toDefinition()) - responseObserver.onCompleted() - }.exceptionally { - responseObserver.onError(it) - } + override suspend fun updateServerProperty(request: UpdateServerPropertyRequest): ServerDefinition { + val server = serverRepository.find(request.serverId) + ?: throw StatusException(Status.NOT_FOUND.withDescription("Server with id ${request.serverId} does not exist.")) + val serverBefore = server.copy() + server.properties[request.propertyKey] = request.propertyValue + serverRepository.save(server) + pubSubClient.publish( + "event", + ServerUpdateEvent.newBuilder().setUpdatedAt(ProtoBufTimestamp.fromLocalDateTime(LocalDateTime.now())) + .setServerBefore(serverBefore.toDefinition()).setServerAfter(server.toDefinition()).build() + ) + return server.toDefinition() } - override fun updateServerState( - request: UpdateServerStateRequest, - responseObserver: StreamObserver - ) { - serverRepository.find(request.serverId).thenApply { server -> - if (server == null) { - throw Status.NOT_FOUND - .withDescription("Server with id ${request.serverState} does not exist.") - .asRuntimeException() - } - val serverBefore = server.copy() - server.state = request.serverState - serverRepository.save(server) - pubSubClient.publish( - "event", - ServerUpdateEvent.newBuilder().setUpdatedAt(ProtoBufTimestamp.fromLocalDateTime(LocalDateTime.now())) - .setServerBefore(serverBefore.toDefinition()).setServerAfter(server.toDefinition()).build() - ) - responseObserver.onNext(server.toDefinition()) - responseObserver.onCompleted() - }.exceptionally { - responseObserver.onError(it) - } + override suspend fun updateServerState(request: UpdateServerStateRequest): ServerDefinition { + val server = serverRepository.find(request.serverId) + ?: throw StatusException(Status.NOT_FOUND.withDescription("Server with id ${request.serverId} does not exist.")) + val serverBefore = server.copy() + server.state = request.serverState + serverRepository.save(server) + pubSubClient.publish( + "event", + ServerUpdateEvent.newBuilder().setUpdatedAt(ProtoBufTimestamp.fromLocalDateTime(LocalDateTime.now())) + .setServerBefore(serverBefore.toDefinition()).setServerAfter(server.toDefinition()).build() + ) + return server.toDefinition() } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 46ef1e6..fd1eb8a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,13 +1,13 @@ [versions] kotlin = "2.0.20" -kotlinCoroutines = "1.4.2" -shadow = "8.1.1" +kotlinCoroutines = "1.9.0" +shadow = "8.3.3" log4j = "2.20.0" protobuf = "3.25.2" grpc = "1.61.0" grpcKotlin = "1.4.1" simpleCloudProtoSpecs = "1.4.1.1.20241001163139.58018cb317ed" -simpleCloudPubSub = "1.0.4" +simpleCloudPubSub = "1.0.5" jooq = "3.19.3" configurate = "4.1.2" sqliteJdbc = "3.44.1.0" @@ -59,7 +59,7 @@ proto = [ "grpcKotlinStub", "grpcProtobuf", "grpcNettyShaded", - "simpleCloudProtoSpecs" + "simpleCloudProtoSpecs", ] jooq = [ "qooq", @@ -72,6 +72,6 @@ configurate = [ [plugins] kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } -shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" } +shadow = { id = "com.gradleup.shadow", version.ref = "shadow" } jooqCodegen = { id = "org.jooq.jooq-codegen-gradle", version.ref = "jooq" } sonatypeCentralPortalPublisher = { id = "net.thebugmc.gradle.sonatype-central-portal-publisher", version.ref = "sonatypeCentralPortalPublisher" } \ No newline at end of file From bffc980c4ae3bd0602dad1dd2afab80578ef6c45 Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 9 Oct 2024 11:09:50 +0200 Subject: [PATCH 12/16] refactor: controller api with coroutines --- .../controller/api/ControllerApi.kt | 86 +++++-- .../simplecloud/controller/api/GroupApi.kt | 108 ++++++--- .../simplecloud/controller/api/ServerApi.kt | 220 ++++++++++++------ .../coroutines/ControllerApiCoroutineImpl.kt | 55 +++++ .../impl/coroutines/GroupApiCoroutineImpl.kt | 64 +++++ .../impl/coroutines/ServerApiCoroutineImpl.kt | 126 ++++++++++ .../ControllerApiFutureImpl.kt} | 19 +- .../GroupApiFutureImpl.kt} | 6 +- .../ServerApiFutureImpl.kt} | 6 +- 9 files changed, 551 insertions(+), 139 deletions(-) create mode 100644 controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/coroutines/ControllerApiCoroutineImpl.kt create mode 100644 controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/coroutines/GroupApiCoroutineImpl.kt create mode 100644 controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/coroutines/ServerApiCoroutineImpl.kt rename controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/{ControllerApiImpl.kt => future/ControllerApiFutureImpl.kt} (71%) rename controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/{GroupApiImpl.kt => future/GroupApiFutureImpl.kt} (97%) rename controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/{ServerApiImpl.kt => future/ServerApiFutureImpl.kt} (97%) diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/ControllerApi.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/ControllerApi.kt index 2eae9f0..9180f96 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/ControllerApi.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/ControllerApi.kt @@ -1,45 +1,89 @@ package app.simplecloud.controller.api -import app.simplecloud.controller.api.impl.ControllerApiImpl +import app.simplecloud.controller.api.impl.coroutines.ControllerApiCoroutineImpl +import app.simplecloud.controller.api.impl.future.ControllerApiFutureImpl import app.simplecloud.pubsub.PubSubClient interface ControllerApi { - /** - * @return the Controller [GroupApi] - */ - fun getGroups(): GroupApi + interface Future { - /** - * @return the Controller [ServerApi] - */ - fun getServers(): ServerApi + /** + * @return the Controller [GroupApi.Future] + */ + fun getGroups(): GroupApi.Future + + /** + * @return the Controller [ServerApi.Future] + */ + fun getServers(): ServerApi.Future - /** - * @return the [PubSubClient] to subscribe to Controller events and send messages - */ - fun getPubSubClient(): PubSubClient + /** + * @return the [PubSubClient] to subscribe to Controller events and send messages + */ + fun getPubSubClient(): PubSubClient + + } + + interface Coroutine { + + /** + * @return the Controller [GroupApi.Coroutine] + */ + fun getGroups(): GroupApi.Coroutine + + /** + * @return the Controller [ServerApi.Coroutine] + */ + fun getServers(): ServerApi.Coroutine + + /** + * @return the [PubSubClient] to subscribe to Controller events and send messages + */ + fun getPubSubClient(): PubSubClient + + } companion object { /** - * Creates a new [ControllerApi] instance - * @return the created [ControllerApi] + * Creates a new [ControllerApi.Future] instance + * @return the created [ControllerApi.Future] + */ + @JvmStatic + fun createFutureApi(): Future { + val authSecret = System.getenv("CONTROLLER_SECRET") + return createFutureApi(authSecret) + } + + /** + * Creates a new [ControllerApi.Future] instance + * @param authSecret the authentication key used by the Controller + * @return the created [ControllerApi.Future] + */ + @JvmStatic + fun createFutureApi(authSecret: String): Future { + return ControllerApiFutureImpl(authSecret) + } + + /** + * Creates a new [ControllerApi.Coroutine] instance + * @return the created [ControllerApi.Coroutine] */ @JvmStatic - fun create(): ControllerApi { + fun createCoroutineApi(): Coroutine { val authSecret = System.getenv("CONTROLLER_SECRET") - return create(authSecret) + return createCoroutineApi(authSecret) } /** - * Creates a new [ControllerApi] instance + * Creates a new [ControllerApi.Coroutine] instance * @param authSecret the authentication key used by the Controller - * @return the created [ControllerApi] + * @return the created [ControllerApi.Coroutine] */ @JvmStatic - fun create(authSecret: String): ControllerApi { - return ControllerApiImpl(authSecret) + fun createCoroutineApi(authSecret: String): Coroutine { + return ControllerApiCoroutineImpl(authSecret) } } diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/GroupApi.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/GroupApi.kt index 8289c1f..d70c760 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/GroupApi.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/GroupApi.kt @@ -6,38 +6,80 @@ import java.util.concurrent.CompletableFuture interface GroupApi { - /** - * @param name the name of the group. - * @return a [CompletableFuture] with the [Group]. - */ - fun getGroupByName(name: String): CompletableFuture - - /** - * @param name the name of the group. - * @return the deleted [Group]. - */ - fun deleteGroup(name: String): CompletableFuture - - /** - * @param group the [Group] to create. - * @return the created [Group]. - */ - fun createGroup(group: Group): CompletableFuture - - /** - * @param group the [Group] to update. - * @return the updated [Group]. - */ - fun updateGroup(group: Group): CompletableFuture - /** - * @return a [CompletableFuture] with a list of all groups. - */ - fun getAllGroups(): CompletableFuture> - - /** - * @param type the [ServerType] of the group - * @return a [CompletableFuture] with a list of all groups matching this type. - */ - fun getGroupsByType(type: ServerType): CompletableFuture> + interface Future { + + /** + * @param name the name of the group. + * @return a [CompletableFuture] with the [Group]. + */ + fun getGroupByName(name: String): CompletableFuture + + /** + * @param name the name of the group. + * @return the deleted [Group]. + */ + fun deleteGroup(name: String): CompletableFuture + + /** + * @param group the [Group] to create. + * @return the created [Group]. + */ + fun createGroup(group: Group): CompletableFuture + + /** + * @param group the [Group] to update. + * @return the updated [Group]. + */ + fun updateGroup(group: Group): CompletableFuture + /** + * @return a [CompletableFuture] with a list of all groups. + */ + fun getAllGroups(): CompletableFuture> + + /** + * @param type the [ServerType] of the group + * @return a [CompletableFuture] with a list of all groups matching this type. + */ + fun getGroupsByType(type: ServerType): CompletableFuture> + + } + + interface Coroutine { + + /** + * @param name the name of the group. + * @return the [Group]. + */ + suspend fun getGroupByName(name: String): Group + + /** + * @param name the name of the group. + * @return the deleted [Group]. + */ + suspend fun deleteGroup(name: String): Group + + /** + * @param group the [Group] to create. + * @return the created [Group]. + */ + suspend fun createGroup(group: Group): Group + + /** + * @param group the [Group] to update. + * @return the updated [Group]. + */ + suspend fun updateGroup(group: Group): Group + /** + * @return a list of all groups. + */ + suspend fun getAllGroups(): List + + /** + * @param type the [ServerType] of the group + * @return a list of all groups matching this type. + */ + suspend fun getGroupsByType(type: ServerType): List + + } } \ No newline at end of file diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/ServerApi.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/ServerApi.kt index 01258e9..75675bc 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/ServerApi.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/ServerApi.kt @@ -10,74 +10,156 @@ import java.util.concurrent.CompletableFuture interface ServerApi { - /** - * @return a [CompletableFuture] with a [List] of all [Server]s - */ - fun getAllServers(): CompletableFuture> - - /** - * @param id the id of the server. - * @return a [CompletableFuture] with the [Server]. - */ - fun getServerById(id: String): CompletableFuture - - /** - * @param groupName the name of the server group. - * @return a [CompletableFuture] with a [List] of [Server]s of that group. - */ - fun getServersByGroup(groupName: String): CompletableFuture> - - /** - * @param groupName the name of the server group. - * @param numericalId the numerical id of the server. - * @return a [CompletableFuture] with the [Server]. - */ - fun getServerByNumerical(groupName: String, numericalId: Long): CompletableFuture - - /** - * @param group The server group. - * @return a [CompletableFuture] with a [List] of [Server]s of that group. - */ - fun getServersByGroup(group: Group): CompletableFuture> - - /** - * @param type The servers type - * @return a [CompletableFuture] with a [List] of all [Server]s with this type - */ - fun getServersByType(type: ServerType): CompletableFuture> - - /** - * @param groupName the group name of the group the new server should be of. - * @return a [CompletableFuture] with a [Server] or null. - */ - fun startServer(groupName: String, startCause: ServerStartCause = ServerStartCause.API_START): CompletableFuture - - /** - * @param groupName the group name of the servers group. - * @param numericalId the numerical id of the server. - * @return a [CompletableFuture] with the stopped [Server]. - */ - fun stopServer(groupName: String, numericalId: Long, stopCause: ServerStopCause = ServerStopCause.API_STOP): CompletableFuture - - /** - * @param id the id of the server. - * @return a [CompletableFuture] with the stopped [Server]. - */ - fun stopServer(id: String, stopCause: ServerStopCause = ServerStopCause.API_STOP): CompletableFuture - - /** - * @param id the id of the server. - * @param state the new state of the server. - * @return a [CompletableFuture] with the updated [Server]. - */ - fun updateServerState(id: String, state: ServerState): CompletableFuture - - /** - * @param id the id of the server. - * @param key the server property key - * @param value the new property value - * @return a [CompletableFuture] with the updated [Server]. - */ - fun updateServerProperty(id: String, key: String, value: Any): CompletableFuture + interface Future { + + /** + * @return a [CompletableFuture] with a [List] of all [Server]s + */ + fun getAllServers(): CompletableFuture> + + /** + * @param id the id of the server. + * @return a [CompletableFuture] with the [Server]. + */ + fun getServerById(id: String): CompletableFuture + + /** + * @param groupName the name of the server group. + * @return a [CompletableFuture] with a [List] of [Server]s of that group. + */ + fun getServersByGroup(groupName: String): CompletableFuture> + + /** + * @param groupName the name of the server group. + * @param numericalId the numerical id of the server. + * @return a [CompletableFuture] with the [Server]. + */ + fun getServerByNumerical(groupName: String, numericalId: Long): CompletableFuture + + /** + * @param group The server group. + * @return a [CompletableFuture] with a [List] of [Server]s of that group. + */ + fun getServersByGroup(group: Group): CompletableFuture> + + /** + * @param type The servers type + * @return a [CompletableFuture] with a [List] of all [Server]s with this type + */ + fun getServersByType(type: ServerType): CompletableFuture> + + /** + * @param groupName the group name of the group the new server should be of. + * @return a [CompletableFuture] with a [Server] or null. + */ + fun startServer(groupName: String, startCause: ServerStartCause = ServerStartCause.API_START): CompletableFuture + + /** + * @param groupName the group name of the servers group. + * @param numericalId the numerical id of the server. + * @return a [CompletableFuture] with the stopped [Server]. + */ + fun stopServer(groupName: String, numericalId: Long, stopCause: ServerStopCause = ServerStopCause.API_STOP): CompletableFuture + + /** + * @param id the id of the server. + * @return a [CompletableFuture] with the stopped [Server]. + */ + fun stopServer(id: String, stopCause: ServerStopCause = ServerStopCause.API_STOP): CompletableFuture + + /** + * @param id the id of the server. + * @param state the new state of the server. + * @return a [CompletableFuture] with the updated [Server]. + */ + fun updateServerState(id: String, state: ServerState): CompletableFuture + + /** + * @param id the id of the server. + * @param key the server property key + * @param value the new property value + * @return a [CompletableFuture] with the updated [Server]. + */ + fun updateServerProperty(id: String, key: String, value: Any): CompletableFuture + + } + + interface Coroutine { + + /** + * @return a [List] of all [Server]s + */ + suspend fun getAllServers(): List + + /** + * @param id the id of the server. + * @return the [Server]. + */ + suspend fun getServerById(id: String): Server + + /** + * @param groupName the name of the server group. + * @return a [List] of [Server]s of that group. + */ + suspend fun getServersByGroup(groupName: String): List + + /** + * @param groupName the name of the server group. + * @param numericalId the numerical id of the server. + * @return the [Server]. + */ + suspend fun getServerByNumerical(groupName: String, numericalId: Long): Server + + /** + * @param group The server group. + * @return a [List] of [Server]s of that group. + */ + suspend fun getServersByGroup(group: Group): List + + /** + * @param type The servers type + * @return a [List] of all [Server]s with this type + */ + suspend fun getServersByType(type: ServerType): List + + /** + * @param groupName the group name of the group the new server should be of. + * @return a [Server] or null. + */ + suspend fun startServer(groupName: String, startCause: ServerStartCause = ServerStartCause.API_START): Server + + /** + * @param groupName the group name of the servers group. + * @param numericalId the numerical id of the server. + * @return the stopped [Server]. + */ + suspend fun stopServer( + groupName: String, + numericalId: Long, + stopCause: ServerStopCause = ServerStopCause.API_STOP + ): Server + + /** + * @param id the id of the server. + * @return the stopped [Server]. + */ + suspend fun stopServer(id: String, stopCause: ServerStopCause = ServerStopCause.API_STOP): Server + + /** + * @param id the id of the server. + * @param state the new state of the server. + * @return the updated [Server]. + */ + suspend fun updateServerState(id: String, state: ServerState): Server + + /** + * @param id the id of the server. + * @param key the server property key + * @param value the new property value + * @return the updated [Server]. + */ + suspend fun updateServerProperty(id: String, key: String, value: Any): Server + + } } \ No newline at end of file diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/coroutines/ControllerApiCoroutineImpl.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/coroutines/ControllerApiCoroutineImpl.kt new file mode 100644 index 0000000..ae4ac39 --- /dev/null +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/coroutines/ControllerApiCoroutineImpl.kt @@ -0,0 +1,55 @@ +package app.simplecloud.controller.api.impl.coroutines + +import app.simplecloud.controller.api.ControllerApi +import app.simplecloud.controller.api.GroupApi +import app.simplecloud.controller.api.ServerApi +import app.simplecloud.controller.shared.auth.AuthCallCredentials +import app.simplecloud.pubsub.PubSubClient +import io.grpc.ManagedChannel +import io.grpc.ManagedChannelBuilder + +class ControllerApiCoroutineImpl( + authSecret: String +): ControllerApi.Coroutine { + + private val authCallCredentials = AuthCallCredentials(authSecret) + + private val managedChannel = createManagedChannelFromEnv() + private val groups: GroupApi.Coroutine = GroupApiCoroutineImpl(managedChannel, authCallCredentials) + private val servers: ServerApi.Coroutine = ServerApiCoroutineImpl(managedChannel, authCallCredentials) + + + private val pubSubClient = PubSubClient( + System.getenv("CONTROLLER_PUBSUB_HOST") ?: "localhost", + System.getenv("CONTROLLER_PUBSUB_PORT")?.toInt() ?: 5817, + authCallCredentials, + ) + + /** + * @return The controllers [GroupApi.Coroutine] + */ + override fun getGroups(): GroupApi.Coroutine { + return groups + } + + /** + * @return The controllers [ServerApi.Coroutine] + */ + override fun getServers(): ServerApi.Coroutine { + return servers + } + + /** + * @return The [PubSubClient] to subscribe to Controller events and send messages + */ + override fun getPubSubClient(): PubSubClient { + return pubSubClient + } + + private fun createManagedChannelFromEnv(): ManagedChannel { + val host = System.getenv("CONTROLLER_HOST") ?: "127.0.0.1" + val port = System.getenv("CONTROLLER_PORT")?.toInt() ?: 5816 + return ManagedChannelBuilder.forAddress(host, port).usePlaintext().build() + } + +} \ No newline at end of file diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/coroutines/GroupApiCoroutineImpl.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/coroutines/GroupApiCoroutineImpl.kt new file mode 100644 index 0000000..091b3aa --- /dev/null +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/coroutines/GroupApiCoroutineImpl.kt @@ -0,0 +1,64 @@ +package app.simplecloud.controller.api.impl.coroutines + +import app.simplecloud.controller.api.GroupApi +import app.simplecloud.controller.shared.auth.AuthCallCredentials +import app.simplecloud.controller.shared.group.Group +import build.buf.gen.simplecloud.controller.v1.* +import io.grpc.ManagedChannel + +class GroupApiCoroutineImpl( + managedChannel: ManagedChannel, + authCallCredentials: AuthCallCredentials +) : GroupApi.Coroutine { + + private val groupServiceStub: ControllerGroupServiceGrpcKt.ControllerGroupServiceCoroutineStub = + ControllerGroupServiceGrpcKt.ControllerGroupServiceCoroutineStub(managedChannel) + .withCallCredentials(authCallCredentials) + + override suspend fun getGroupByName(name: String): Group { + return Group.fromDefinition( + groupServiceStub.getGroupByName(getGroupByNameRequest { + groupName = name + }).group + ) + } + + override suspend fun deleteGroup(name: String): Group { + return Group.fromDefinition( + groupServiceStub.deleteGroupByName(deleteGroupByNameRequest { + groupName = name + }) + ) + } + + override suspend fun createGroup(group: Group): Group { + return Group.fromDefinition( + groupServiceStub.createGroup(createGroupRequest { + this.group = group.toDefinition() + }) + ) + } + + override suspend fun updateGroup(group: Group): Group { + return Group.fromDefinition( + groupServiceStub.updateGroup(updateGroupRequest { + this.group = group.toDefinition() + }) + ) + } + + override suspend fun getAllGroups(): List { + return groupServiceStub.getAllGroups(getAllGroupsRequest {}).groupsList.map { + Group.fromDefinition(it) + } + } + + override suspend fun getGroupsByType(type: ServerType): List { + return groupServiceStub.getGroupsByType(getGroupsByTypeRequest { + serverType = type + }).groupsList.map { + Group.fromDefinition(it) + } + } + +} \ No newline at end of file diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/coroutines/ServerApiCoroutineImpl.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/coroutines/ServerApiCoroutineImpl.kt new file mode 100644 index 0000000..c8e1f3f --- /dev/null +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/coroutines/ServerApiCoroutineImpl.kt @@ -0,0 +1,126 @@ +package app.simplecloud.controller.api.impl.coroutines + +import app.simplecloud.controller.api.ServerApi +import app.simplecloud.controller.shared.auth.AuthCallCredentials +import app.simplecloud.controller.shared.group.Group +import build.buf.gen.simplecloud.controller.v1.* +import app.simplecloud.controller.shared.server.Server +import io.grpc.ManagedChannel + +class ServerApiCoroutineImpl( + managedChannel: ManagedChannel, + authCallCredentials: AuthCallCredentials +) : ServerApi.Coroutine { + + private val serverServiceStub: ControllerServerServiceGrpcKt.ControllerServerServiceCoroutineStub = + ControllerServerServiceGrpcKt.ControllerServerServiceCoroutineStub(managedChannel).withCallCredentials(authCallCredentials) + + override suspend fun getAllServers(): List { + return serverServiceStub.getAllServers(getAllServersRequest {}).serversList.map { + Server.fromDefinition(it) + } + } + + override suspend fun getServerById(id: String): Server { + return Server.fromDefinition( + serverServiceStub.getServerById( + getServerByIdRequest { + this.serverId = id + } + ) + ) + } + + override suspend fun getServersByGroup(groupName: String): List { + return serverServiceStub.getServersByGroup( + getServersByGroupRequest { + this.groupName = groupName + } + ).serversList.map { + Server.fromDefinition(it) + } + } + + override suspend fun getServersByGroup(group: Group): List { + return getServersByGroup(group.name) + } + + override suspend fun getServerByNumerical(groupName: String, numericalId: Long): Server { + return Server.fromDefinition( + serverServiceStub.getServerByNumerical( + getServerByNumericalRequest { + this.groupName = groupName + this.numericalId = numericalId + } + ) + ) + } + + override suspend fun getServersByType(type: ServerType): List { + return serverServiceStub.getServersByType( + ServerTypeRequest.newBuilder() + .setServerType(type) + .build() + ).serversList.map { + Server.fromDefinition(it) + } + } + + override suspend fun startServer(groupName: String, startCause: ServerStartCause): Server { + return Server.fromDefinition( + serverServiceStub.startServer( + controllerStartServerRequest { + this.groupName = groupName + this.startCause = startCause + } + ) + ) + } + + override suspend fun stopServer(groupName: String, numericalId: Long, stopCause: ServerStopCause): Server { + return Server.fromDefinition( + serverServiceStub.stopServerByNumerical( + stopServerByNumericalRequest { + this.groupName = groupName + this.numericalId = numericalId + this.stopCause = stopCause + } + ) + ) + } + + override suspend fun stopServer(id: String, stopCause: ServerStopCause): Server { + return Server.fromDefinition( + serverServiceStub.stopServer( + stopServerRequest { + this.serverId = id + this.stopCause = stopCause + } + ) + ) + } + + override suspend fun updateServerState(id: String, state: ServerState): Server { + return Server.fromDefinition( + serverServiceStub.updateServerState( + updateServerStateRequest { + this.serverState = state + this.serverId = id + } + ) + ) + } + + override suspend fun updateServerProperty(id: String, key: String, value: Any): Server { + return Server.fromDefinition( + serverServiceStub.updateServerProperty( + updateServerPropertyRequest { + this.propertyKey = key + this.propertyValue = value.toString() + this.serverId = id + } + ) + ) + } + +} \ No newline at end of file diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ControllerApiImpl.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/future/ControllerApiFutureImpl.kt similarity index 71% rename from controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ControllerApiImpl.kt rename to controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/future/ControllerApiFutureImpl.kt index 137cd6b..c72b56a 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ControllerApiImpl.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/future/ControllerApiFutureImpl.kt @@ -1,4 +1,4 @@ -package app.simplecloud.controller.api.impl +package app.simplecloud.controller.api.impl.future import app.simplecloud.controller.api.ControllerApi import app.simplecloud.controller.api.GroupApi @@ -8,15 +8,15 @@ import app.simplecloud.pubsub.PubSubClient import io.grpc.ManagedChannel import io.grpc.ManagedChannelBuilder -class ControllerApiImpl( +class ControllerApiFutureImpl( authSecret: String -): ControllerApi { +): ControllerApi.Future { private val authCallCredentials = AuthCallCredentials(authSecret) private val managedChannel = createManagedChannelFromEnv() - private val groups: GroupApi = GroupApiImpl(managedChannel, authCallCredentials) - private val servers: ServerApi = ServerApiImpl(managedChannel, authCallCredentials) + private val groups: GroupApi.Future = GroupApiFutureImpl(managedChannel, authCallCredentials) + private val servers: ServerApi.Future = ServerApiFutureImpl(managedChannel, authCallCredentials) private val pubSubClient = PubSubClient( @@ -26,16 +26,16 @@ class ControllerApiImpl( ) /** - * @return The controllers [GroupApi] + * @return The controllers [GroupApi.Future] */ - override fun getGroups(): GroupApi { + override fun getGroups(): GroupApi.Future { return groups } /** - * @return The controllers [ServerApi] + * @return The controllers [ServerApi.Future] */ - override fun getServers(): ServerApi { + override fun getServers(): ServerApi.Future { return servers } @@ -52,5 +52,4 @@ class ControllerApiImpl( return ManagedChannelBuilder.forAddress(host, port).usePlaintext().build() } - } \ No newline at end of file diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/GroupApiImpl.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/future/GroupApiFutureImpl.kt similarity index 97% rename from controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/GroupApiImpl.kt rename to controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/future/GroupApiFutureImpl.kt index 8464445..5119bc6 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/GroupApiImpl.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/future/GroupApiFutureImpl.kt @@ -1,4 +1,4 @@ -package app.simplecloud.controller.api.impl +package app.simplecloud.controller.api.impl.future import app.simplecloud.controller.api.GroupApi import app.simplecloud.controller.shared.auth.AuthCallCredentials @@ -15,10 +15,10 @@ import build.buf.gen.simplecloud.controller.v1.UpdateGroupRequest import io.grpc.ManagedChannel import java.util.concurrent.CompletableFuture -class GroupApiImpl( +class GroupApiFutureImpl( managedChannel: ManagedChannel, authCallCredentials: AuthCallCredentials -) : GroupApi { +) : GroupApi.Future { private val groupServiceStub: ControllerGroupServiceGrpc.ControllerGroupServiceFutureStub = ControllerGroupServiceGrpc.newFutureStub(managedChannel) diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ServerApiImpl.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/future/ServerApiFutureImpl.kt similarity index 97% rename from controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ServerApiImpl.kt rename to controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/future/ServerApiFutureImpl.kt index 3c4a52f..65a6a9b 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ServerApiImpl.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/future/ServerApiFutureImpl.kt @@ -1,4 +1,4 @@ -package app.simplecloud.controller.api.impl +package app.simplecloud.controller.api.impl.future import app.simplecloud.controller.api.ServerApi import app.simplecloud.controller.shared.auth.AuthCallCredentials @@ -9,10 +9,10 @@ import app.simplecloud.controller.shared.server.Server import io.grpc.ManagedChannel import java.util.concurrent.CompletableFuture -class ServerApiImpl( +class ServerApiFutureImpl( managedChannel: ManagedChannel, authCallCredentials: AuthCallCredentials -) : ServerApi { +) : ServerApi.Future { private val serverServiceStub: ControllerServerServiceGrpc.ControllerServerServiceFutureStub = ControllerServerServiceGrpc.newFutureStub(managedChannel).withCallCredentials(authCallCredentials) From ce568d1e5660546a62bcfdf57369d80acc8b10bf Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 9 Oct 2024 11:20:38 +0200 Subject: [PATCH 13/16] refactor: reconciler now works with coroutines --- .../runtime/reconciler/GroupReconciler.kt | 2 +- .../server/ServerNumericalIdRepository.kt | 30 +++++++++++-------- .../runtime/server/ServerRepository.kt | 1 - 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/reconciler/GroupReconciler.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/reconciler/GroupReconciler.kt index cf4cdff..039a255 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/reconciler/GroupReconciler.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/reconciler/GroupReconciler.kt @@ -80,7 +80,7 @@ class GroupReconciler( } } - private fun cleanupNumericalIds() { + private suspend fun cleanupNumericalIds() { val usedNumericalIds = this.servers.map { it.numericalId } val numericalIds = this.numericalIdRepository.findNumericalIds(this.group.name) diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerNumericalIdRepository.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerNumericalIdRepository.kt index d8962cd..f87b265 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerNumericalIdRepository.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerNumericalIdRepository.kt @@ -1,33 +1,37 @@ package app.simplecloud.controller.runtime.server +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import java.util.concurrent.ConcurrentHashMap class ServerNumericalIdRepository { private val numericalIds = ConcurrentHashMap>() - - @Synchronized - fun findNextNumericalId(group: String): Int { - val numericalIds = findNumericalIds(group) - var nextId = 1 - while (numericalIds.contains(nextId)) { - nextId++ + private val mutex = Mutex() + + suspend fun findNextNumericalId(group: String): Int { + return mutex.withLock { + val numericalIds = findNumericalIds(group) + var nextId = 1 + while (numericalIds.contains(nextId)) { + nextId++ + } + saveNumericalId(group, nextId) + nextId } - saveNumericalId(group, nextId) - return nextId } fun saveNumericalId(group: String, id: Int) { numericalIds.compute(group) { _, v -> v?.plus(id) ?: setOf(id) } } - @Synchronized - fun removeNumericalId(group: String, id: Int): Boolean { - return numericalIds.computeIfPresent(group) { _, v -> v.minus(id) } != null + suspend fun removeNumericalId(group: String, id: Int): Boolean { + return mutex.withLock { + numericalIds.computeIfPresent(group) { _, v -> v.minus(id) } != null + } } fun findNumericalIds(group: String): Set { return numericalIds[group] ?: emptySet() } - } diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerRepository.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerRepository.kt index 1c17a44..75fe852 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerRepository.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerRepository.kt @@ -19,7 +19,6 @@ class ServerRepository( private val numericalIdRepository: ServerNumericalIdRepository ) : LoadableRepository { - override suspend fun find(identifier: String): Server? { val query = withContext(Dispatchers.IO) { database.context.select() From f33dcb1c352758d1e36b23d165c23c5494fa8c51 Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 9 Oct 2024 12:11:32 +0200 Subject: [PATCH 14/16] refactor: start using coroutines for jooq --- controller-runtime/build.gradle.kts | 1 + .../runtime/server/ServerRepository.kt | 160 ++++++++---------- gradle/libs.versions.toml | 6 +- 3 files changed, 74 insertions(+), 93 deletions(-) diff --git a/controller-runtime/build.gradle.kts b/controller-runtime/build.gradle.kts index 7b4bdb5..b5a93c4 100644 --- a/controller-runtime/build.gradle.kts +++ b/controller-runtime/build.gradle.kts @@ -40,6 +40,7 @@ tasks.named("compileKotlin") { jooq { configuration { generator { + name = "org.jooq.codegen.KotlinGenerator" target { directory = "build/generated/source/db/main/java" packageName = "app.simplecloud.controller.shared.db" diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerRepository.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerRepository.kt index 75fe852..81c5ef2 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerRepository.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerRepository.kt @@ -2,15 +2,17 @@ package app.simplecloud.controller.runtime.server import app.simplecloud.controller.runtime.LoadableRepository import app.simplecloud.controller.runtime.database.Database -import app.simplecloud.controller.shared.db.Tables.CLOUD_SERVERS -import app.simplecloud.controller.shared.db.Tables.CLOUD_SERVER_PROPERTIES import app.simplecloud.controller.shared.db.tables.records.CloudServersRecord +import app.simplecloud.controller.shared.db.tables.references.CLOUD_SERVERS +import app.simplecloud.controller.shared.db.tables.references.CLOUD_SERVER_PROPERTIES import app.simplecloud.controller.shared.server.Server import build.buf.gen.simplecloud.controller.v1.ServerState import build.buf.gen.simplecloud.controller.v1.ServerType import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.toCollection +import kotlinx.coroutines.reactive.asFlow +import kotlinx.coroutines.reactive.awaitFirstOrNull import kotlinx.coroutines.withContext -import org.jooq.Result import org.jooq.exception.DataAccessException import java.time.LocalDateTime @@ -20,107 +22,82 @@ class ServerRepository( ) : LoadableRepository { override suspend fun find(identifier: String): Server? { - val query = withContext(Dispatchers.IO) { - database.context.select() - .from(CLOUD_SERVERS) - .where(CLOUD_SERVERS.UNIQUE_ID.eq(identifier)) - .limit(1) - .fetchInto( - CLOUD_SERVERS - ) - } - return toList(query).firstOrNull() + return database.context.selectFrom(CLOUD_SERVERS) + .where(CLOUD_SERVERS.UNIQUE_ID.eq(identifier)) + .limit(1) + .awaitFirstOrNull() + ?.let { record -> mapCloudServersRecordToServer(record) } } suspend fun findServerByNumerical(group: String, id: Int): Server? { - val query = withContext(Dispatchers.IO) { - database.context.select().from(CLOUD_SERVERS) - .where( - CLOUD_SERVERS.GROUP_NAME.eq(group) - .and(CLOUD_SERVERS.NUMERICAL_ID.eq(id)) - ) - .limit(1) - .fetchInto(CLOUD_SERVERS) - } - return toList(query).firstOrNull() + return database.context.selectFrom(CLOUD_SERVERS) + .where( + CLOUD_SERVERS.GROUP_NAME.eq(group) + .and(CLOUD_SERVERS.NUMERICAL_ID.eq(id)) + ) + .limit(1) + .awaitFirstOrNull() + ?.let { record -> mapCloudServersRecordToServer(record) } } override suspend fun getAll(): List { - val query = withContext(Dispatchers.IO) { - database.context.select() - .from(CLOUD_SERVERS) - .fetchInto(CLOUD_SERVERS) - } - return toList(query) - + return database.context.selectFrom(CLOUD_SERVERS) + .asFlow() + .toCollection(mutableListOf()) + .map { record -> mapCloudServersRecordToServer(record) } } suspend fun findServersByHostId(id: String): List { - val query = withContext(Dispatchers.IO) { - database.context.select() - .from(CLOUD_SERVERS) - .where(CLOUD_SERVERS.HOST_ID.eq(id)) - .fetchInto( - CLOUD_SERVERS - ) - } - return toList(query) + return database.context.selectFrom(CLOUD_SERVERS) + .where(CLOUD_SERVERS.HOST_ID.eq(id)) + .asFlow() + .toCollection(mutableListOf()) + .map { record -> mapCloudServersRecordToServer(record) } } suspend fun findServersByGroup(group: String): List { - val query = withContext(Dispatchers.IO) { - database.context.select() - .from(CLOUD_SERVERS) - .where(CLOUD_SERVERS.GROUP_NAME.eq(group)) - .fetchInto( - CLOUD_SERVERS - ) - } - return toList(query) + return database.context.selectFrom(CLOUD_SERVERS) + .where(CLOUD_SERVERS.GROUP_NAME.eq(group)) + .asFlow() + .toCollection(mutableListOf()) + .map { record -> mapCloudServersRecordToServer(record) } } suspend fun findServersByType(type: ServerType): List { - val query = withContext(Dispatchers.IO) { - database.context.select() - .from(CLOUD_SERVERS) - .where(CLOUD_SERVERS.TYPE.eq(type.toString())) - .fetchInto(CLOUD_SERVERS) - } - return toList(query) + return database.context.selectFrom(CLOUD_SERVERS) + .where(CLOUD_SERVERS.TYPE.eq(type.toString())) + .asFlow() + .toCollection(mutableListOf()) + .map { record -> mapCloudServersRecordToServer(record) } } - private fun toList(query: Result): List { - val result = mutableListOf() - query.map { - val propertiesQuery = - database.context.select() - .from(CLOUD_SERVER_PROPERTIES) - .where(CLOUD_SERVER_PROPERTIES.SERVER_ID.eq(it.uniqueId)) - .fetchInto(CLOUD_SERVER_PROPERTIES) - result.add( - Server( - it.uniqueId, - ServerType.valueOf(it.type), - it.groupName, - it.hostId, - it.numericalId, - it.ip, - it.port.toLong(), - it.minimumMemory.toLong(), - it.maximumMemory.toLong(), - it.maxPlayers.toLong(), - it.playerCount.toLong(), - propertiesQuery.map { item -> - item.key to item.value - }.toMap().toMutableMap(), - ServerState.valueOf(it.state), - it.createdAt, - it.updatedAt - ) - ) - } - return result + private fun mapCloudServersRecordToServer(record: CloudServersRecord): Server { + val propertiesQuery = + database.context.select() + .from(CLOUD_SERVER_PROPERTIES) + .where(CLOUD_SERVER_PROPERTIES.SERVER_ID.eq(record.uniqueId)) + .fetchInto(CLOUD_SERVER_PROPERTIES) + + return Server( + record.uniqueId!!, + ServerType.valueOf(record.type!!), + record.groupName!!, + record.hostId!!, + record.numericalId!!, + record.ip!!, + record.port!!.toLong(), + record.minimumMemory!!.toLong(), + record.maximumMemory!!.toLong(), + record.maxPlayers!!.toLong(), + record.playerCount!!.toLong(), + propertiesQuery.map { item -> + item.key!! to item.value!! + }.toMap().toMutableMap(), + ServerState.valueOf(record.state!!), + record.createdAt!!, + record.updatedAt!! + ) } override suspend fun delete(element: Server): Boolean { @@ -220,14 +197,15 @@ class ServerRepository( } override fun load(): List { - val query = database.context.select() - .from(CLOUD_SERVERS) + val cloudServerList = database.context.selectFrom(CLOUD_SERVERS) .fetchInto(CLOUD_SERVERS) - val list = toList(query) - list.forEach { + .map { record -> mapCloudServersRecordToServer(record) } + + cloudServerList.forEach { numericalIdRepository.saveNumericalId(it.group, it.numericalId) } - return list + + return cloudServerList } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fd1eb8a..600fb9c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -34,9 +34,10 @@ grpcNettyShaded = { module = "io.grpc:grpc-netty-shaded", version.ref = "grpc" } simpleCloudProtoSpecs = { module = "build.buf.gen:simplecloud_proto-specs_grpc_kotlin", version.ref = "simpleCloudProtoSpecs" } simpleCloudPubSub = { module = "app.simplecloud:simplecloud-pubsub", version.ref = "simpleCloudPubSub" } -qooq = { module = "org.jooq:jooq", version.ref = "jooq" } +qooq = { module = "org.jooq:jooq-kotlin", version.ref = "jooq" } qooqMeta = { module = "org.jooq:jooq-meta", version.ref = "jooq" } jooqMetaExtensions = { module = "org.jooq:jooq-meta-extensions", version.ref = "jooq" } +jooqKotlinCoroutines = { module = "org.jooq:jooq-kotlin-coroutines", version.ref = "jooq" } configurateYaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurate" } configurateExtraKotlin = { module = "org.spongepowered:configurate-extra-kotlin", version.ref = "configurate" } @@ -63,7 +64,8 @@ proto = [ ] jooq = [ "qooq", - "qooqMeta" + "qooqMeta", + "jooqKotlinCoroutines" ] configurate = [ "configurateYaml", From 17d4c0bb825c63be97a3d8372ef7cc465e25615b Mon Sep 17 00:00:00 2001 From: David Date: Sat, 12 Oct 2024 19:51:55 +0200 Subject: [PATCH 15/16] fix: reuse connections to fix all known connection bugs --- controller-runtime/build.gradle.kts | 1 - .../runtime/host/ServerHostRepository.kt | 7 ++- .../runtime/server/ServerService.kt | 44 +++++++------------ controller-shared/build.gradle.kts | 1 + .../controller/shared/host/ServerHost.kt | 24 ++++++---- 5 files changed, 39 insertions(+), 38 deletions(-) diff --git a/controller-runtime/build.gradle.kts b/controller-runtime/build.gradle.kts index 7b4bdb5..968af2d 100644 --- a/controller-runtime/build.gradle.kts +++ b/controller-runtime/build.gradle.kts @@ -5,7 +5,6 @@ plugins { dependencies { api(project(":controller-shared")) - api(rootProject.libs.kotlinCoroutines) api(rootProject.libs.bundles.jooq) api(rootProject.libs.sqliteJdbc) jooqCodegen(rootProject.libs.jooqMetaExtensions) diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/host/ServerHostRepository.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/host/ServerHostRepository.kt index 7590ce1..468d17e 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/host/ServerHostRepository.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/host/ServerHostRepository.kt @@ -4,8 +4,10 @@ import app.simplecloud.controller.runtime.Repository import app.simplecloud.controller.runtime.server.ServerRepository import app.simplecloud.controller.shared.host.ServerHost import io.grpc.ConnectivityState +import io.grpc.ManagedChannel import kotlinx.coroutines.coroutineScope import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.TimeUnit class ServerHostRepository : Repository { @@ -26,15 +28,16 @@ class ServerHostRepository : Repository { suspend fun areServerHostsAvailable(): Boolean { return coroutineScope { return@coroutineScope hosts.any { - val channel = it.value.createChannel() + val channel = it.value.stub.channel as ManagedChannel val state = channel.getState(true) - channel.shutdown() state == ConnectivityState.IDLE || state == ConnectivityState.READY } } } override suspend fun delete(element: ServerHost): Boolean { + val host = hosts.get(element.id) ?: return false + (host.stub.channel as ManagedChannel).shutdown().awaitTermination(5L, TimeUnit.SECONDS) return hosts.remove(element.id, element) } diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerService.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerService.kt index 7cb847a..4b44ef1 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerService.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerService.kt @@ -29,7 +29,7 @@ class ServerService( private val logger = LogManager.getLogger(ServerService::class.java) override suspend fun attachServerHost(request: AttachServerHostRequest): ServerHostDefinition { - val serverHost = ServerHost.fromDefinition(request.serverHost) + val serverHost = ServerHost.fromDefinition(request.serverHost, authCallCredentials) try { hostRepository.delete(serverHost) hostRepository.save(serverHost) @@ -39,13 +39,10 @@ class ServerService( logger.info("Successfully registered ServerHost ${serverHost.id}.") coroutineScope { - val channel = serverHost.createChannel() - val stub = - ServerHostServiceGrpcKt.ServerHostServiceCoroutineStub(channel).withCallCredentials(authCallCredentials) serverRepository.findServersByHostId(serverHost.id).forEach { server -> logger.info("Reattaching Server ${server.uniqueId} of group ${server.group}...") try { - val result = stub.reattachServer(server.toDefinition()) + val result = serverHost.stub.reattachServer(server.toDefinition()) serverRepository.save(Server.fromDefinition(result)) logger.info("Success!") } catch (e: Exception) { @@ -53,7 +50,6 @@ class ServerService( serverRepository.delete(server) } } - channel.shutdown() } return serverHost.toDefinition() } @@ -73,7 +69,7 @@ class ServerService( val server = serverRepository.findServerByNumerical(request.groupName, request.numericalId.toInt()) ?: throw StatusException(Status.NOT_FOUND.withDescription("No server was found matching this group and numerical id")) try { - return stopServer(server.toDefinition()) + return stopServer(server.toDefinition(), request.stopCause) } catch (e: Exception) { throw StatusException( Status.INTERNAL.withDescription("Error occured whilest cleaning up stopped server: ").withCause(e) @@ -167,9 +163,7 @@ class ServerService( val numericalId = numericalIdRepository.findNextNumericalId(group.name) val server = buildServer(group, numericalId, forwardingSecret) serverRepository.save(server) - val channel = host.createChannel() - val stub = ServerHostServiceGrpcKt.ServerHostServiceCoroutineStub(channel) - .withCallCredentials(authCallCredentials) + val stub = host.stub serverRepository.save(server) try { val result = stub.startServer( @@ -179,12 +173,10 @@ class ServerService( .build() ) serverRepository.save(Server.fromDefinition(result)) - channel.shutdown() return result } catch (e: Exception) { serverRepository.delete(server) numericalIdRepository.removeNumericalId(group.name, server.numericalId) - channel.shutdown() logger.error("Error whilst starting server:", e) throw e } @@ -206,7 +198,7 @@ class ServerService( .setUniqueId(UUID.randomUUID().toString().replace("-", "")).putAllCloudProperties( mapOf( *group.properties.entries.map { it.key to it.value }.toTypedArray(), - "forwarding-secret" to forwardingSecret, + "forwarding-secret" to forwardingSecret ) ).build() ) @@ -216,32 +208,30 @@ class ServerService( val server = serverRepository.find(request.serverId) ?: throw StatusException(Status.NOT_FOUND.withDescription("No server was found matching this id.")) try { - val stopped = stopServer(server.toDefinition()) - pubSubClient.publish( - "event", ServerStopEvent.newBuilder() - .setServer(stopped) - .setStoppedAt(ProtoBufTimestamp.fromLocalDateTime(LocalDateTime.now())) - .setStopCause(request.stopCause) - .setTerminationMode(ServerTerminationMode.UNKNOWN_MODE) //TODO: Add proto fields to make changing this possible - .build() - ) + val stopped = stopServer(server.toDefinition(), request.stopCause) return stopped } catch (e: Exception) { throw StatusException(Status.INTERNAL.withDescription("Error whilst stopping server").withCause(e)) } } - private suspend fun stopServer(server: ServerDefinition): ServerDefinition { + private suspend fun stopServer(server: ServerDefinition, cause: ServerStopCause = ServerStopCause.NATURAL_STOP): ServerDefinition { val host = hostRepository.findServerHostById(server.hostId) ?: throw Status.NOT_FOUND .withDescription("No server host was found matching this server.") .asRuntimeException() - val channel = host.createChannel() - val stub = ServerHostServiceGrpcKt.ServerHostServiceCoroutineStub(channel) - .withCallCredentials(authCallCredentials) + val stub = host.stub try { val stopped = stub.stopServer(server) - channel.shutdown() + pubSubClient.publish( + "event", ServerStopEvent.newBuilder() + .setServer(stopped) + .setStoppedAt(ProtoBufTimestamp.fromLocalDateTime(LocalDateTime.now())) + .setStopCause(cause) + .setTerminationMode(ServerTerminationMode.UNKNOWN_MODE) //TODO: Add proto fields to make changing this possible + .build() + ) + serverRepository.delete(Server.fromDefinition(stopped)) return stopped } catch (e: Exception) { logger.error("Server stop error occured:", e) diff --git a/controller-shared/build.gradle.kts b/controller-shared/build.gradle.kts index 7ff3313..9a3361e 100644 --- a/controller-shared/build.gradle.kts +++ b/controller-shared/build.gradle.kts @@ -3,4 +3,5 @@ dependencies { api(rootProject.libs.simpleCloudPubSub) api(rootProject.libs.bundles.configurate) api(rootProject.libs.clikt) + api(rootProject.libs.kotlinCoroutines) } diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/host/ServerHost.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/host/ServerHost.kt index c9f9c9c..d5aaefd 100644 --- a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/host/ServerHost.kt +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/host/ServerHost.kt @@ -1,6 +1,8 @@ package app.simplecloud.controller.shared.host +import app.simplecloud.controller.shared.auth.AuthCallCredentials import build.buf.gen.simplecloud.controller.v1.ServerHostDefinition +import build.buf.gen.simplecloud.controller.v1.ServerHostServiceGrpcKt import io.grpc.ManagedChannel import io.grpc.ManagedChannelBuilder import org.spongepowered.configurate.objectmapping.ConfigSerializable @@ -9,7 +11,8 @@ import org.spongepowered.configurate.objectmapping.ConfigSerializable data class ServerHost( val id: String, val host: String, - val port: Int + val port: Int, + val stub: ServerHostServiceGrpcKt.ServerHostServiceCoroutineStub, ) { fun toDefinition(): ServerHostDefinition { @@ -22,18 +25,23 @@ data class ServerHost( companion object { @JvmStatic - fun fromDefinition(serverHostDefinition: ServerHostDefinition): ServerHost { + fun fromDefinition(serverHostDefinition: ServerHostDefinition, credentials: AuthCallCredentials): ServerHost { return ServerHost( serverHostDefinition.hostId, serverHostDefinition.hostHost, - serverHostDefinition.hostPort + serverHostDefinition.hostPort, + ServerHostServiceGrpcKt.ServerHostServiceCoroutineStub( + createChannel( + serverHostDefinition.hostHost, + serverHostDefinition.hostPort + ) + ).withCallCredentials(credentials), ) } - } - - fun createChannel(): ManagedChannel { - return ManagedChannelBuilder.forAddress(host, port).usePlaintext().build() + @JvmStatic + fun createChannel(host: String, port: Int): ManagedChannel { + return ManagedChannelBuilder.forAddress(host, port).usePlaintext().build() + } } - } \ No newline at end of file From b571a5486f51fea1b449ff0edf45232e56eb05b0 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 13 Oct 2024 14:15:44 +0200 Subject: [PATCH 16/16] feat: env property file, dev workflow --- .github/workflows/release-dev.yml | 74 +++++++++++++++++++ build.gradle.kts | 6 +- .../launcher/ControllerStartCommand.kt | 12 ++- 3 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/release-dev.yml diff --git a/.github/workflows/release-dev.yml b/.github/workflows/release-dev.yml new file mode 100644 index 0000000..4c85976 --- /dev/null +++ b/.github/workflows/release-dev.yml @@ -0,0 +1,74 @@ +name: Create Release with ShadowJars + +on: + workflow_dispatch: + +jobs: + build: + name: Build ShadowJars and Create Release + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + ref: develop # Ensure it works from the develop branch + + - name: Set up JDK 21 + uses: actions/setup-java@v3 + with: + distribution: 'adopt' + java-version: '21' + + - name: Cache Gradle packages + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + gradle-${{ runner.os }} + + - name: Make gradlew executable + run: chmod +x ./gradlew + + - name: Build ShadowJars + run: ./gradlew clean build shadowJar + + - name: Get Gradle Version + id: gradle_version + run: echo "GRADLE_VERSION=$(./gradlew properties -q | grep "version:" | awk '{print $2}')" >> $GITHUB_ENV + + - name: Get Commit Hash + id: commit_hash + run: echo "COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + with: + tag_name: v${{ env.GRADLE_VERSION }}-dev.${{ env.COMMIT_HASH }} + release_name: v${{ env.GRADLE_VERSION }}-dev.${{ env.COMMIT_HASH }} + draft: false + prerelease: true + commitish: develop + body: | + This release contains dev builds for all Gradle modules. + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload ShadowJars to Release + run: | + # Find JAR files in any submodule's build/libs directory + for jar in $(find . -type f -name "*.jar" -path "*/build/libs/*.jar" -not -path "./build/libs/*"); do + # Check if the filename contains a version number (e.g., a dash followed by numbers) + if [[ $(basename "$jar") =~ -[0-9]+\.[0-9]+ ]]; then + echo "Skipping $jar due to version number" + else + echo "Uploading $jar" + gh release upload v${{ env.GRADLE_VERSION }}-dev.${{ env.COMMIT_HASH }} "$jar" + fi + done + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 5384740..9eaec1f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "0.0.30-EXPERIMENTAL" + version = "0.0.30" repositories { mavenCentral() @@ -37,11 +37,11 @@ subprojects { } java { - toolchain.languageVersion.set(JavaLanguageVersion.of(22)) + toolchain.languageVersion.set(JavaLanguageVersion.of(21)) } kotlin { - jvmToolchain(22) + jvmToolchain(21) compilerOptions { apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) } diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/ControllerStartCommand.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/ControllerStartCommand.kt index 918760d..505fde3 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/ControllerStartCommand.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/ControllerStartCommand.kt @@ -3,15 +3,24 @@ package app.simplecloud.controller.runtime.launcher import app.simplecloud.controller.runtime.ControllerRuntime import app.simplecloud.controller.shared.secret.AuthFileSecretFactory import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.core.context import com.github.ajalt.clikt.parameters.options.default import com.github.ajalt.clikt.parameters.options.defaultLazy import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.types.int import com.github.ajalt.clikt.parameters.types.path +import com.github.ajalt.clikt.sources.PropertiesValueSource +import java.io.File import java.nio.file.Path class ControllerStartCommand : CliktCommand() { + init { + context { + valueSource = PropertiesValueSource.from(File("controller.properties")) + } + } + private val defaultDatabaseUrl = "jdbc:sqlite:database.db" val groupPath: Path by option(help = "Path to the group files (default: groups)", envvar = "GROUPS_PATH") @@ -23,7 +32,8 @@ class ControllerStartCommand : CliktCommand() { val grpcHost: String by option(help = "Grpc host (default: localhost)", envvar = "GRPC_HOST").default("localhost") val grpcPort: Int by option(help = "Grpc port (default: 5816)", envvar = "GRPC_PORT").int().default(5816) - val pubSubGrpcPort: Int by option(help = "PubSub Grpc port (default: 5817)", envvar = "PUBSUB_GRPC_PORT").int().default(5817) + val pubSubGrpcPort: Int by option(help = "PubSub Grpc port (default: 5817)", envvar = "PUBSUB_GRPC_PORT").int() + .default(5817) private val authSecretPath: Path by option( help = "Path to auth secret file (default: .auth.secret)",