From fd36fd78a4cee2fd0c728b279f85be4466464dbc Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Mon, 5 Feb 2024 20:58:46 +0100 Subject: [PATCH 01/94] add: readme.md --- readme.md | 4 ++++ structure.png | Bin 0 -> 40629 bytes 2 files changed, 4 insertions(+) create mode 100644 readme.md create mode 100644 structure.png diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..167b1c3 --- /dev/null +++ b/readme.md @@ -0,0 +1,4 @@ +# SimpleCloud v3 Controller + +### Controller structure +![img.png](structure.png) \ No newline at end of file diff --git a/structure.png b/structure.png new file mode 100644 index 0000000000000000000000000000000000000000..a530aba1ca088d08a5ae4c1a18bdedb0489ef2b7 GIT binary patch literal 40629 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 Date: Tue, 6 Feb 2024 18:28:46 +0100 Subject: [PATCH 02/94] fix: grpc kotlin stub --- gradle/libs.versions.toml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3efaae8..3cf5d16 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,9 +2,10 @@ kotlin = "1.8.0" shadow = "8.1.1" log4j = "2.20.0" -protobuf = "3.23.1" -protobufPlugin = "0.8.19" -grpcVersion = "1.55.1" +protobuf = "3.25.2" +protobufPlugin = "0.9.4" +grpcVersion = "1.61.0" +grpcKotlinVersion = "1.4.1" [libraries] kotlinJvm = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } @@ -14,6 +15,7 @@ log4jApi = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j log4jSlf4j = { module = "org.apache.logging.log4j:log4j-slf4j-impl", version.ref = "log4j" } protobuf = { module = "com.google.protobuf:protobuf-kotlin", version.ref = "protobuf" } grpcStub = { module = "io.grpc:grpc-stub", version.ref = "grpcVersion" } +grpcKotlinStub = { module = "io.grpc:grpc-kotlin-stub", version.ref = "grpcKotlinVersion" } grpcProtobuf = { module = "io.grpc:grpc-protobuf", version.ref = "grpcVersion" } grpcNettyShaded = { module = "io.grpc:grpc-netty-shaded", version.ref = "grpcVersion" } @@ -26,6 +28,7 @@ log4j = [ proto = [ "protobuf", "grpcStub", + "grpcKotlinStub", "grpcProtobuf", "grpcNettyShaded" ] From 2e0acd6c3cbc6195d039a72d51aa4bb85f4a15ab Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Sun, 11 Feb 2024 12:41:36 +0100 Subject: [PATCH 03/94] feat: add controller backend logic for servers and groups --- .../simplecloud/controller/api/Controller.kt | 39 ++++++++++ .../controller/api/ControllerApiSingleton.kt | 17 ----- .../{ControllerApi.kt => group/GroupApi.kt} | 9 +-- .../controller/api/group/impl/GroupApiImpl.kt | 29 +++++++ .../controller/api/impl/ControllerApiImpl.kt | 36 --------- .../controller/api/server/ServerApi.kt | 39 ++++++++++ .../api/server/impl/ServerApiImpl.kt | 69 +++++++++++++++++ .../controller/runtime/ControllerRuntime.kt | 14 ++-- .../runtime/host/ServerHostException.kt | 3 + .../runtime/host/ServerHostRepository.kt | 27 +++++++ .../runtime/server/ServerRepository.kt | 21 ++++++ .../runtime/server/ServerService.kt | 75 +++++++++++++++++++ .../controller/shared/host/ServerHost.kt | 40 ++++++++++ .../controller/shared/server/Server.kt | 45 +++++++++++ .../controller/shared/status/ApiResponse.kt | 22 ++++++ .../main/proto/controller_server_api.proto | 27 +++++++ .../src/main/proto/controller_types.proto | 30 ++++---- .../src/main/proto/server_host_api.proto | 14 ++++ 18 files changed, 476 insertions(+), 80 deletions(-) create mode 100644 controller-api/src/main/kotlin/app/simplecloud/controller/api/Controller.kt delete mode 100644 controller-api/src/main/kotlin/app/simplecloud/controller/api/ControllerApiSingleton.kt rename controller-api/src/main/kotlin/app/simplecloud/controller/api/{ControllerApi.kt => group/GroupApi.kt} (55%) create mode 100644 controller-api/src/main/kotlin/app/simplecloud/controller/api/group/impl/GroupApiImpl.kt delete mode 100644 controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ControllerApiImpl.kt create mode 100644 controller-api/src/main/kotlin/app/simplecloud/controller/api/server/ServerApi.kt create mode 100644 controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt create mode 100644 controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/host/ServerHostException.kt create mode 100644 controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/host/ServerHostRepository.kt create mode 100644 controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerRepository.kt create mode 100644 controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerService.kt create mode 100644 controller-shared/src/main/kotlin/app/simplecloud/controller/shared/host/ServerHost.kt create mode 100644 controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/Server.kt create mode 100644 controller-shared/src/main/kotlin/app/simplecloud/controller/shared/status/ApiResponse.kt create mode 100644 controller-shared/src/main/proto/controller_server_api.proto create mode 100644 controller-shared/src/main/proto/server_host_api.proto diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/Controller.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/Controller.kt new file mode 100644 index 0000000..b98db66 --- /dev/null +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/Controller.kt @@ -0,0 +1,39 @@ +package app.simplecloud.controller.api + +import app.simplecloud.controller.api.group.GroupApi +import app.simplecloud.controller.api.group.impl.GroupApiImpl +import app.simplecloud.controller.api.server.ServerApi +import app.simplecloud.controller.api.server.impl.ServerApiImpl +import io.grpc.ManagedChannel +import io.grpc.ManagedChannelBuilder + +class Controller { + companion object { + @JvmStatic + lateinit var groupApi: GroupApi + private set + + @JvmStatic + lateinit var serverApi: ServerApi + private set + + fun init(){ + initGroupApi(GroupApiImpl()) + initServerApi(ServerApiImpl()) + } + + private fun initGroupApi(groupApi: GroupApi) { + this.groupApi = groupApi + } + + private fun initServerApi(serverApi: ServerApi) { + this.serverApi = serverApi + } + + 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/ControllerApiSingleton.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/ControllerApiSingleton.kt deleted file mode 100644 index 9f141c6..0000000 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/ControllerApiSingleton.kt +++ /dev/null @@ -1,17 +0,0 @@ -package app.simplecloud.controller.api - -import app.simplecloud.controller.api.impl.ControllerApiImpl - -class ControllerApiSingleton { - companion object { - @JvmStatic - lateinit var instance: ControllerApi - private set - - fun init() = init(ControllerApiImpl()) - - fun init(controllerApi: ControllerApi) { - instance = controllerApi - } - } -} \ No newline at end of file diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/ControllerApi.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/GroupApi.kt similarity index 55% rename from controller-api/src/main/kotlin/app/simplecloud/controller/api/ControllerApi.kt rename to controller-api/src/main/kotlin/app/simplecloud/controller/api/group/GroupApi.kt index ba58985..01addc7 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/ControllerApi.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/GroupApi.kt @@ -1,15 +1,12 @@ -package app.simplecloud.controller.api +package app.simplecloud.controller.api.group import app.simplecloud.controller.shared.group.Group import java.util.concurrent.CompletableFuture -interface ControllerApi { - +interface GroupApi { /** - * NOTE: This may be moved to a separate api file. * @param name the name of the group. - * @returns a [CompletableFuture] with the [Group]. + * @return a [CompletableFuture] with the [Group]. */ fun getGroupByName(name: String): CompletableFuture - } \ No newline at end of file diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/impl/GroupApiImpl.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/impl/GroupApiImpl.kt new file mode 100644 index 0000000..1e48a5f --- /dev/null +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/impl/GroupApiImpl.kt @@ -0,0 +1,29 @@ +package app.simplecloud.controller.api.group.impl + +import app.simplecloud.controller.api.Controller +import app.simplecloud.controller.api.group.GroupApi +import app.simplecloud.controller.shared.future.toCompletable +import app.simplecloud.controller.shared.group.Group +import app.simplecloud.controller.shared.proto.ControllerGroupServiceGrpc +import app.simplecloud.controller.shared.proto.GetGroupByNameRequest +import java.util.concurrent.CompletableFuture + +class GroupApiImpl : GroupApi { + + private val managedChannel = Controller.createManagedChannelFromEnv() + + private val groupServiceStub: ControllerGroupServiceGrpc.ControllerGroupServiceFutureStub = + ControllerGroupServiceGrpc.newFutureStub(managedChannel) + + override fun getGroupByName(name: String): CompletableFuture { + return groupServiceStub.getGroupByName( + GetGroupByNameRequest.newBuilder() + .setName(name) + .build() + ).toCompletable() + .thenApply { + Group.fromDefinition(it.group) + } + } + +} \ 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/ControllerApiImpl.kt deleted file mode 100644 index e7b7ff3..0000000 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ControllerApiImpl.kt +++ /dev/null @@ -1,36 +0,0 @@ -package app.simplecloud.controller.api.impl - -import app.simplecloud.controller.api.ControllerApi -import app.simplecloud.controller.shared.future.toCompletable -import app.simplecloud.controller.shared.group.Group -import app.simplecloud.controller.shared.proto.ControllerGroupServiceGrpc -import app.simplecloud.controller.shared.proto.GetGroupByNameRequest -import io.grpc.ManagedChannel -import io.grpc.ManagedChannelBuilder -import java.util.concurrent.CompletableFuture - -class ControllerApiImpl : ControllerApi { - - private val managedChannel = createManagedChannelFromEnv() - - protected val groupServiceStub: ControllerGroupServiceGrpc.ControllerGroupServiceFutureStub = - ControllerGroupServiceGrpc.newFutureStub(managedChannel) - - override fun getGroupByName(name: String): CompletableFuture { - return groupServiceStub.getGroupByName( - GetGroupByNameRequest.newBuilder() - .setName(name) - .build() - ).toCompletable() - .thenApply { - Group.fromDefinition(it.group) - } - } - - 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/server/ServerApi.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/ServerApi.kt new file mode 100644 index 0000000..dd1d528 --- /dev/null +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/ServerApi.kt @@ -0,0 +1,39 @@ +package app.simplecloud.controller.api.server + +import app.simplecloud.controller.shared.group.Group +import app.simplecloud.controller.shared.server.Server +import app.simplecloud.controller.shared.status.ApiResponse +import java.util.concurrent.CompletableFuture + +interface ServerApi { + + /** + * @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 group The server group. + * @return a [CompletableFuture] with a [List] of [Server]s of that group. + */ + fun getServersByGroup(group: Group): 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): CompletableFuture + + /** + * @param id the id of the server. + * @return a [CompletableFuture] with a [ApiResponse]. + */ + fun stopServer(id: String): CompletableFuture +} \ No newline at end of file diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt new file mode 100644 index 0000000..7a110b7 --- /dev/null +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt @@ -0,0 +1,69 @@ +package app.simplecloud.controller.api.server.impl + +import app.simplecloud.controller.api.Controller +import app.simplecloud.controller.api.server.ServerApi +import app.simplecloud.controller.shared.future.toCompletable +import app.simplecloud.controller.shared.group.Group +import app.simplecloud.controller.shared.proto.ControllerServerServiceGrpc +import app.simplecloud.controller.shared.proto.GroupNameRequest +import app.simplecloud.controller.shared.proto.ServerIdRequest +import app.simplecloud.controller.shared.server.Server +import app.simplecloud.controller.shared.status.ApiResponse +import com.google.protobuf.Api +import java.util.concurrent.CompletableFuture + +class ServerApiImpl : ServerApi { + + private val messageChannel = Controller.createManagedChannelFromEnv() + + private val serverServiceStub: ControllerServerServiceGrpc.ControllerServerServiceFutureStub = + ControllerServerServiceGrpc.newFutureStub(messageChannel) + + override fun getServerById(id: String): CompletableFuture { + return serverServiceStub.getServerById( + ServerIdRequest.newBuilder() + .setId(id) + .build() + ).toCompletable() + .thenApply { + Server.fromDefinition(it) + } + } + + override fun getServersByGroup(groupName: String): CompletableFuture> { + return serverServiceStub.getServersByGroup( + GroupNameRequest.newBuilder() + .setName(groupName) + .build() + ).toCompletable() + .thenApply { + Server.fromDefinition(it.serversList) + } + } + + override fun getServersByGroup(group: Group): CompletableFuture> { + return getServersByGroup(group.name) + } + + override fun startServer(groupName: String): CompletableFuture { + return serverServiceStub.startServer( + GroupNameRequest.newBuilder() + .setName(groupName) + .build() + ).toCompletable() + .thenApply { + Server.fromDefinition(it) + } + } + + override fun stopServer(id: String): CompletableFuture { + return serverServiceStub.stopServer( + ServerIdRequest.newBuilder() + .setId(id) + .build() + ).toCompletable() + .thenApply { + ApiResponse.fromDefinition(it) + } + } +} \ 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 b76455e..1ea9654 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 @@ -2,6 +2,9 @@ package app.simplecloud.controller.runtime import app.simplecloud.controller.runtime.group.GroupRepository import app.simplecloud.controller.runtime.group.GroupService +import app.simplecloud.controller.runtime.host.ServerHostRepository +import app.simplecloud.controller.runtime.server.ServerRepository +import app.simplecloud.controller.runtime.server.ServerService import io.grpc.Server import io.grpc.ServerBuilder import org.apache.logging.log4j.LogManager @@ -12,6 +15,8 @@ class ControllerRuntime { private val logger = LogManager.getLogger(ControllerRuntime::class.java) private val groupRepository = GroupRepository() + private val serverRepository = ServerRepository() + private val hostRepository = ServerHostRepository() private val server = createGrpcServerFromEnv() @@ -20,7 +25,7 @@ class ControllerRuntime { startGrpcServer() } - fun startGrpcServer() { + private fun startGrpcServer() { logger.info("Starting gRPC server...") thread { server.start() @@ -31,10 +36,9 @@ class ControllerRuntime { private fun createGrpcServerFromEnv(): Server { val port = System.getenv("GRPC_PORT")?.toInt() ?: 5816 return ServerBuilder.forPort(port) - .addService( - GroupService(groupRepository), - ) - .build() + .addService(GroupService(groupRepository)) + .addService(ServerService(serverRepository, hostRepository, groupRepository)) + .build() } } \ No newline at end of file diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/host/ServerHostException.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/host/ServerHostException.kt new file mode 100644 index 0000000..e4651e1 --- /dev/null +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/host/ServerHostException.kt @@ -0,0 +1,3 @@ +package app.simplecloud.controller.runtime.host + +class ServerHostException(message: String) : Exception(message) \ 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 new file mode 100644 index 0000000..fb7c7c5 --- /dev/null +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/host/ServerHostRepository.kt @@ -0,0 +1,27 @@ +package app.simplecloud.controller.runtime.host + +import app.simplecloud.controller.runtime.server.ServerRepository +import app.simplecloud.controller.shared.host.ServerHost + +class ServerHostRepository { + + // TODO: Load hosts from file + private val hosts = listOf() + fun findServerHostById(id: String): ServerHost? { + return hosts.firstOrNull { it.getId() == id } + } + + fun findLaziestServerHost(serverRepository: ServerRepository): ServerHost? { + var lastAmount = Int.MAX_VALUE + var lastHost: ServerHost? = null + for (host: ServerHost in hosts) { + val amount = serverRepository.findServersByHostId(host.getId()).size + if (amount < lastAmount) { + lastAmount = amount + lastHost = host + } + } + return lastHost + } +} + 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 new file mode 100644 index 0000000..3500da0 --- /dev/null +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerRepository.kt @@ -0,0 +1,21 @@ +package app.simplecloud.controller.runtime.server + +import app.simplecloud.controller.shared.proto.ServerDefinition +import app.simplecloud.controller.shared.server.Server + +class ServerRepository { + // TODO: Load servers from db + private val servers = listOf() + + fun findServerById(id: String): ServerDefinition? { + return servers.firstOrNull { it.id == id }?.toDefinition() + } + + fun findServersByHostId(id: String): List { + return servers.filter { it.host == id }.map { it.toDefinition() } + } + + fun findServersByGroup(group: String): List { + return servers.filter { server -> server.group == group }.map { server -> server.toDefinition() } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..0aac39f --- /dev/null +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerService.kt @@ -0,0 +1,75 @@ +package app.simplecloud.controller.runtime.server + +import app.simplecloud.controller.runtime.group.GroupRepository +import app.simplecloud.controller.runtime.host.ServerHostRepository +import app.simplecloud.controller.runtime.host.ServerHostException +import app.simplecloud.controller.shared.future.toCompletable +import app.simplecloud.controller.shared.group.Group +import app.simplecloud.controller.shared.proto.* +import app.simplecloud.controller.shared.server.Server +import io.grpc.stub.StreamObserver + +class ServerService( + private val serverRepository: ServerRepository, + private val hostRepository: ServerHostRepository, + private val groupRepository: GroupRepository, +) : ControllerServerServiceGrpc.ControllerServerServiceImplBase() { + override fun getServerById(request: ServerIdRequest, responseObserver: StreamObserver) { + val server = serverRepository.findServerById(request.id) + responseObserver.onNext(server) + responseObserver.onCompleted() + } + + override fun getServersByGroup(request: GroupNameRequest, responseObserver: StreamObserver) { + val servers = serverRepository.findServersByGroup(request.name) + val response = GetServersByGroupResponse.newBuilder().addAllServers(servers).build() + responseObserver.onNext(response) + responseObserver.onCompleted() + } + + override fun startServer(request: GroupNameRequest, responseObserver: StreamObserver) { + val host = hostRepository.findLaziestServerHost(serverRepository) + if (host == null) { + responseObserver.onError(ServerHostException("No server host found, could not start server.")) + responseObserver.onCompleted() + return; + } + val stub = ServerHostServiceGrpc.newFutureStub(host.getEndpoint()) + val groupDefinition = groupRepository.findGroupByName(request.name) + if (groupDefinition == null) { + responseObserver.onError(IllegalArgumentException("No group was found matching the group name.")) + responseObserver.onCompleted() + return; + } + val server = Server.create(Group.fromDefinition(groupDefinition), host).toDefinition() + stub.startServer(server).toCompletable().thenApply { + if (it.status.equals("200")) { + responseObserver.onNext(server) + responseObserver.onCompleted() + } else { + responseObserver.onError(ServerHostException("Could not start server, aborting.")) + } + } + } + + override fun stopServer(request: ServerIdRequest, responseObserver: StreamObserver) { + val server = serverRepository.findServerById(request.id) + if (server == null) { + responseObserver.onError(IllegalArgumentException("No server was found matching this id.")) + responseObserver.onCompleted() + return + } + val host = hostRepository.findServerHostById(server.hostId) + if (host == null) { + responseObserver.onError(ServerHostException("No server host was found matching this server.")) + responseObserver.onCompleted() + return + } + val stub = ServerHostServiceGrpc.newFutureStub(host.getEndpoint()) + stub.stopServer(request).toCompletable().thenApply { + responseObserver.onNext(it) + responseObserver.onCompleted() + } + } + +} \ No newline at end of file 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 new file mode 100644 index 0000000..cb07a1b --- /dev/null +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/host/ServerHost.kt @@ -0,0 +1,40 @@ +package app.simplecloud.controller.shared.host + +import io.grpc.ManagedChannel +import io.grpc.ManagedChannelBuilder + +class ServerHost { + + private var id: String; + private var host: String; + private var port: Int; + private var endpoint: ManagedChannel + + constructor(id: String, host: String, port: Int) { + this.id = id + this.host = host + this.port = port + this.endpoint = createChannel() + } + + fun getEndpoint(): ManagedChannel { + return endpoint + } + + fun getId(): String { + return id; + } + + fun getHost(): String { + return host; + } + + fun getPort(): Int { + return port + } + + private fun createChannel(): ManagedChannel { + return ManagedChannelBuilder.forAddress(host, port).usePlaintext().build() + } + +} \ No newline at end of file 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 new file mode 100644 index 0000000..b854808 --- /dev/null +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/Server.kt @@ -0,0 +1,45 @@ +package app.simplecloud.controller.shared.server + +import app.simplecloud.controller.shared.group.Group +import app.simplecloud.controller.shared.host.ServerHost +import app.simplecloud.controller.shared.proto.ServerDefinition +import java.util.* + +data class Server( + val id: String, + val host: String?, + val group: String, +) { + fun toDefinition(): ServerDefinition { + return ServerDefinition.newBuilder() + .setUniqueId(id) + .setGroupName(group) + .build() + } + + companion object { + @JvmStatic + fun fromDefinition(serverDefinition: ServerDefinition): Server { + return Server( + serverDefinition.uniqueId, + serverDefinition.hostId, + serverDefinition.groupName + ) + } + + + @JvmStatic + fun create(group: Group, serverHost: ServerHost): Server { + return Server( + UUID.randomUUID().toString(), + serverHost.getId(), + group.name, + ) + } + + @JvmStatic + fun fromDefinition(definitions: List): List { + return definitions.map { definition -> fromDefinition(definition) } + } + } +} \ No newline at end of file diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/status/ApiResponse.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/status/ApiResponse.kt new file mode 100644 index 0000000..d2374bd --- /dev/null +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/status/ApiResponse.kt @@ -0,0 +1,22 @@ +package app.simplecloud.controller.shared.status + +import app.simplecloud.controller.shared.proto.StatusResponse + +data class ApiResponse( + val status: String +) { + fun toDefinition(): StatusResponse { + return StatusResponse.newBuilder() + .setStatus(status) + .build() + } + + companion object { + @JvmStatic + fun fromDefinition(statusResponse: StatusResponse): ApiResponse { + return ApiResponse( + statusResponse.status + ) + } + } +} \ No newline at end of file diff --git a/controller-shared/src/main/proto/controller_server_api.proto b/controller-shared/src/main/proto/controller_server_api.proto new file mode 100644 index 0000000..39994f7 --- /dev/null +++ b/controller-shared/src/main/proto/controller_server_api.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +package app.simplecloud.controller.shared.proto; + +option java_package = "app.simplecloud.controller.shared.proto"; +option java_multiple_files = true; + +import "controller_types.proto"; + +message GroupNameRequest { + string name = 1; +} + +message ServerIdRequest { + string id = 1; +} + +message GetServersByGroupResponse { + repeated ServerDefinition servers = 1; +} + +service ControllerServerService { + rpc getServersByGroup(GroupNameRequest) returns (GetServersByGroupResponse); + rpc getServerById(ServerIdRequest) returns (ServerDefinition); + rpc startServer(GroupNameRequest) returns (ServerDefinition); + rpc stopServer(ServerIdRequest) returns (StatusResponse); +} \ No newline at end of file diff --git a/controller-shared/src/main/proto/controller_types.proto b/controller-shared/src/main/proto/controller_types.proto index 4aa24b8..fdbcaba 100644 --- a/controller-shared/src/main/proto/controller_types.proto +++ b/controller-shared/src/main/proto/controller_types.proto @@ -19,26 +19,24 @@ message GroupDefinition { message ServerDefinition { string unique_id = 1; string groupName = 2; - uint32 numerical_id = 3; - string template_name = 4; - uint64 port = 5; - uint64 minimum_memory = 6; - uint64 maximum_memory = 7; - uint64 player_count = 8; - map properties = 9; - ServerState state = 10; - ServerAllocationState allocation_state = 11; + string host_id = 3; + uint32 numerical_id = 4; + string template_name = 5; + uint64 port = 6; + uint64 minimum_memory = 7; + uint64 maximum_memory = 8; + uint64 player_count = 9; + map properties = 10; + ServerState state = 11; } enum ServerState { - UNKNOWN = 0; - STARTING = 1; - RUNNING = 2; + STARTING = 0; + AVAILABLE = 1; + INGAME = 2; STOPPING = 3; - STOPPED = 4; } -enum ServerAllocationState { - ALLOCATED = 0; - UNALLOCATED = 1; +message StatusResponse { + string status = 1; } \ No newline at end of file diff --git a/controller-shared/src/main/proto/server_host_api.proto b/controller-shared/src/main/proto/server_host_api.proto new file mode 100644 index 0000000..ff6d9ec --- /dev/null +++ b/controller-shared/src/main/proto/server_host_api.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package app.simplecloud.controller.shared.proto; + +option java_package = "app.simplecloud.controller.shared.proto"; +option java_multiple_files = true; + +import "controller_types.proto"; +import "controller_server_api.proto"; + +service ServerHostService { + rpc startServer(ServerDefinition) returns (StatusResponse); + rpc stopServer(ServerIdRequest) returns (StatusResponse); +} \ No newline at end of file From ffa2ca12ed3449016f7a29a295f9396f7a1e32bd Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Fri, 16 Feb 2024 18:01:32 +0100 Subject: [PATCH 04/94] feat: jooq with schema to java generation --- controller-shared/build.gradle.kts | 62 ++++++++++++++++++- controller-shared/src/main/db/schema.sql | 4 ++ .../controller/shared/db/Database.kt | 21 +++++++ 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 controller-shared/src/main/db/schema.sql create mode 100644 controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/Database.kt diff --git a/controller-shared/build.gradle.kts b/controller-shared/build.gradle.kts index cf71efc..6b6e000 100644 --- a/controller-shared/build.gradle.kts +++ b/controller-shared/build.gradle.kts @@ -2,10 +2,14 @@ import com.google.protobuf.gradle.* plugins { alias(libs.plugins.protobuf) + id("org.jooq.jooq-codegen-gradle") version "3.19.3" } dependencies { api(rootProject.libs.bundles.proto) + api("org.jooq:jooq:3.19.3") + api("org.jooq:jooq-meta:3.19.3") + jooqCodegen("org.jooq:jooq-meta-extensions:3.19.3") } sourceSets { @@ -48,4 +52,60 @@ protobuf { } } } -} \ No newline at end of file +} + +jooq { + configuration { + generator { + database { + name = "org.jooq.meta.extensions.ddl.DDLDatabase" + properties { + // Specify the location of your SQL script. + // You may use ant-style file matching, e.g. /path/**/to/*.sql + // + // Where: + // - ** matches any directory subtree + // - * matches any number of characters in a directory / file name + // - ? matches a single character in a directory / file name + property { + key = "scripts" + value = "src/main/db/schema.sql" + } + + // The sort order of the scripts within a directory, where: + // + // - semantic: sorts versions, e.g. v-3.10.0 is after v-3.9.0 (default) + // - alphanumeric: sorts strings, e.g. v-3.10.0 is before v-3.9.0 + // - flyway: sorts files the same way as flyway does + // - none: doesn't sort directory contents after fetching them from the directory + property { + key = "sort" + value = "semantic" + } + + // The default schema for unqualified objects: + // + // - public: all unqualified objects are located in the PUBLIC (upper case) schema + // - none: all unqualified objects are located in the default schema (default) + // + // This configuration can be overridden with the schema mapping feature + property { + key = "unqualifiedSchema" + value = "none" + } + + // The default name case for unquoted objects: + // + // - as_is: unquoted object names are kept unquoted + // - upper: unquoted object names are turned into upper case (most databases) + // - lower: unquoted object names are turned into lower case (e.g. PostgreSQL) + property { + key = "defaultNameCase" + value = "lower" + } + } + } + } + } +} + diff --git a/controller-shared/src/main/db/schema.sql b/controller-shared/src/main/db/schema.sql new file mode 100644 index 0000000..4fa5356 --- /dev/null +++ b/controller-shared/src/main/db/schema.sql @@ -0,0 +1,4 @@ +/** + This file represents the sql schema of the v3 backend. + Execute jooqCodegen to create java classes for these files. + */ \ No newline at end of file diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/Database.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/Database.kt new file mode 100644 index 0000000..9a5760f --- /dev/null +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/Database.kt @@ -0,0 +1,21 @@ +package app.simplecloud.controller.shared.db + +import org.jooq.DSLContext +import org.jooq.SQLDialect +import org.jooq.impl.DSL +import java.sql.Connection + + +class Database { + companion object { + @JvmStatic + private lateinit var instance: DSLContext + fun init(connection: Connection, dialect: SQLDialect = SQLDialect.SQLITE) { + instance = DSL.using(connection, dialect) + } + + fun get(): DSLContext { + return instance + } + } +} \ No newline at end of file From 6e10dd63b3d0c7d5750808300f42ea85eb61bad8 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Fri, 16 Feb 2024 18:09:43 +0100 Subject: [PATCH 05/94] refactor: remove redundant code --- .../simplecloud/controller/runtime/Repository.kt | 5 +++++ .../controller/runtime/group/GroupRepository.kt | 12 +++++++----- .../runtime/host/ServerHostRepository.kt | 14 ++++++++------ .../runtime/server/ServerRepository.kt | 16 ++++++++++------ 4 files changed, 30 insertions(+), 17 deletions(-) create mode 100644 controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Repository.kt 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 new file mode 100644 index 0000000..91a0e89 --- /dev/null +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Repository.kt @@ -0,0 +1,5 @@ +package app.simplecloud.controller.runtime + +abstract class Repository : ArrayList() { + abstract fun load() +} \ No newline at end of file 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 c96acf9..070f0c5 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 @@ -1,15 +1,17 @@ package app.simplecloud.controller.runtime.group +import app.simplecloud.controller.runtime.Repository import app.simplecloud.controller.shared.group.Group import app.simplecloud.controller.shared.proto.GroupDefinition -class GroupRepository { - - // TODO: Load groups from file - private val groups = listOf() +class GroupRepository : Repository() { fun findGroupByName(name: String): GroupDefinition? { - return groups.firstOrNull { it.name == name }?.toDefinition() + return firstOrNull { it.name == name }?.toDefinition() + } + + override fun load() { + TODO("Not yet implemented") } } \ 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 fb7c7c5..dc3956c 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 @@ -1,20 +1,18 @@ 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 +class ServerHostRepository : Repository() { -class ServerHostRepository { - - // TODO: Load hosts from file - private val hosts = listOf() fun findServerHostById(id: String): ServerHost? { - return hosts.firstOrNull { it.getId() == id } + return firstOrNull { it.getId() == id } } fun findLaziestServerHost(serverRepository: ServerRepository): ServerHost? { var lastAmount = Int.MAX_VALUE var lastHost: ServerHost? = null - for (host: ServerHost in hosts) { + for (host: ServerHost in this) { val amount = serverRepository.findServersByHostId(host.getId()).size if (amount < lastAmount) { lastAmount = amount @@ -23,5 +21,9 @@ class ServerHostRepository { } return lastHost } + + override fun load() { + TODO("Not yet implemented") + } } 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 3500da0..873fbbe 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 @@ -1,21 +1,25 @@ package app.simplecloud.controller.runtime.server +import app.simplecloud.controller.runtime.Repository import app.simplecloud.controller.shared.proto.ServerDefinition import app.simplecloud.controller.shared.server.Server -class ServerRepository { - // TODO: Load servers from db - private val servers = listOf() +class ServerRepository: Repository() { fun findServerById(id: String): ServerDefinition? { - return servers.firstOrNull { it.id == id }?.toDefinition() + return firstOrNull { it.id == id }?.toDefinition() } fun findServersByHostId(id: String): List { - return servers.filter { it.host == id }.map { it.toDefinition() } + return filter { it.host == id }.map { it.toDefinition() } } fun findServersByGroup(group: String): List { - return servers.filter { server -> server.group == group }.map { server -> server.toDefinition() } + return filter { server -> server.group == group }.map { server -> server.toDefinition() } } + + override fun load() { + TODO("Not yet implemented") + } + } \ No newline at end of file From 72bdf600e3617c51d90fb51326f0034ed4f6fafd Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Mon, 19 Feb 2024 17:51:45 +0100 Subject: [PATCH 06/94] feat: add sql structure and actually implement jooq generated files in build --- controller-shared/build.gradle.kts | 5 ++++ controller-shared/src/main/db/schema.sql | 24 ++++++++++++++++++- .../controller/shared/db/Database.kt | 4 ++++ .../src/main/proto/controller_types.proto | 4 ++-- 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/controller-shared/build.gradle.kts b/controller-shared/build.gradle.kts index 6b6e000..494fbc6 100644 --- a/controller-shared/build.gradle.kts +++ b/controller-shared/build.gradle.kts @@ -24,6 +24,7 @@ sourceSets { srcDirs( "build/generated/source/proto/main/grpc", "build/generated/source/proto/main/java", + "build/generated/source/db/main/java", ) } } @@ -57,6 +58,10 @@ protobuf { jooq { configuration { generator { + target { + directory = "build/generated/source/db/main/java" + packageName = "app.simplecloud.db" + } database { name = "org.jooq.meta.extensions.ddl.DDLDatabase" properties { diff --git a/controller-shared/src/main/db/schema.sql b/controller-shared/src/main/db/schema.sql index 4fa5356..57890ac 100644 --- a/controller-shared/src/main/db/schema.sql +++ b/controller-shared/src/main/db/schema.sql @@ -1,4 +1,26 @@ /** This file represents the sql schema of the v3 backend. Execute jooqCodegen to create java classes for these files. - */ \ No newline at end of file + */ + +CREATE TYPE cloud_server_state AS ENUM('starting', 'available', 'ingame', 'stopping'); + +CREATE TABLE IF NOT EXISTS cloud_servers( + unique_id text NOT NULL, + group_name text NOT NULL, + host_id text NOT NULL, + numerical_id int NOT NULL, + template_id text NOT NULL, + port int NOT NULL, + minimum_memory int NOT NULL, + maximum_memory int NOT NULL, + player_count int NOT NULL, + name text NOT NULL, + state cloud_server_state NOT NULL +); + +CREATE TABLE IF NOT EXISTS cloud_server_properties( + server_id text NOT NULL, + key text NOT NULL, + value text +); \ No newline at end of file diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/Database.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/Database.kt index 9a5760f..ad9face 100644 --- a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/Database.kt +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/Database.kt @@ -14,6 +14,10 @@ class Database { instance = DSL.using(connection, dialect) } + fun init(url: String, username: String, password: String) { + instance = DSL.using(url, username, password) + } + fun get(): DSLContext { return instance } diff --git a/controller-shared/src/main/proto/controller_types.proto b/controller-shared/src/main/proto/controller_types.proto index fdbcaba..9a71006 100644 --- a/controller-shared/src/main/proto/controller_types.proto +++ b/controller-shared/src/main/proto/controller_types.proto @@ -18,10 +18,10 @@ message GroupDefinition { message ServerDefinition { string unique_id = 1; - string groupName = 2; + string group_name = 2; string host_id = 3; uint32 numerical_id = 4; - string template_name = 5; + string template_id = 5; uint64 port = 6; uint64 minimum_memory = 7; uint64 maximum_memory = 8; From d0002cd48b408e9e520a937caa51e33e657fc8cf Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Mon, 19 Feb 2024 18:20:04 +0100 Subject: [PATCH 07/94] refactor: change package of jooq codegen, readd composite key --- controller-shared/build.gradle.kts | 7 ++++++- controller-shared/src/main/db/schema.sql | 17 +++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/controller-shared/build.gradle.kts b/controller-shared/build.gradle.kts index 494fbc6..1d5af08 100644 --- a/controller-shared/build.gradle.kts +++ b/controller-shared/build.gradle.kts @@ -27,6 +27,11 @@ sourceSets { "build/generated/source/db/main/java", ) } + resources { + srcDirs( + "src/main/db" + ) + } } } @@ -60,7 +65,7 @@ jooq { generator { target { directory = "build/generated/source/db/main/java" - packageName = "app.simplecloud.db" + packageName = "app.simplecloud.controller.shared.db" } database { name = "org.jooq.meta.extensions.ddl.DDLDatabase" diff --git a/controller-shared/src/main/db/schema.sql b/controller-shared/src/main/db/schema.sql index 57890ac..afbbd59 100644 --- a/controller-shared/src/main/db/schema.sql +++ b/controller-shared/src/main/db/schema.sql @@ -6,21 +6,22 @@ CREATE TYPE cloud_server_state AS ENUM('starting', 'available', 'ingame', 'stopping'); CREATE TABLE IF NOT EXISTS cloud_servers( - unique_id text NOT NULL, - group_name text NOT NULL, - host_id text NOT NULL, + unique_id varchar NOT NULL, + group_name varchar NOT NULL, + host_id varchar NOT NULL, numerical_id int NOT NULL, - template_id text NOT NULL, + template_id varchar NOT NULL, port int NOT NULL, minimum_memory int NOT NULL, maximum_memory int NOT NULL, player_count int NOT NULL, - name text NOT NULL, + name varchar NOT NULL, state cloud_server_state NOT NULL ); CREATE TABLE IF NOT EXISTS cloud_server_properties( - server_id text NOT NULL, - key text NOT NULL, - value text + server_id varchar NOT NULL, + key varchar NOT NULL, + value varchar, + CONSTRAINT compound_key PRIMARY KEY (server_id, key) ); \ No newline at end of file From 7ac3c6054aceaeace831edc3efa5eedf5a2999bb Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Tue, 20 Feb 2024 21:14:43 +0100 Subject: [PATCH 08/94] feat: database implementation, reformat code --- .../simplecloud/controller/api/Controller.kt | 2 +- .../controller/runtime/ControllerRuntime.kt | 29 +++++++++---- .../controller/runtime/Repository.kt | 4 ++ .../runtime/group/GroupRepository.kt | 10 +++++ .../controller/runtime/group/GroupService.kt | 10 ++--- .../runtime/host/ServerHostRepository.kt | 10 +++++ .../runtime/server/ServerRepository.kt | 41 +++++++++++++++++- .../runtime/server/ServerService.kt | 4 ++ .../src/main/resources/database-config.yml | 1 + controller-shared/build.gradle.kts | 3 ++ controller-shared/src/main/db/schema.sql | 4 +- .../controller/shared/db/Database.kt | 18 +++++--- .../controller/shared/db/DatabaseConfig.kt | 42 +++++++++++++++++++ .../controller/shared/group/Group.kt | 8 ++-- 14 files changed, 158 insertions(+), 28 deletions(-) create mode 100644 controller-runtime/src/main/resources/database-config.yml create mode 100644 controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/DatabaseConfig.kt diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/Controller.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/Controller.kt index b98db66..ff423e3 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/Controller.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/Controller.kt @@ -17,7 +17,7 @@ class Controller { lateinit var serverApi: ServerApi private set - fun init(){ + fun connect() { initGroupApi(GroupApiImpl()) initServerApi(ServerApiImpl()) } 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 1ea9654..b31509e 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 @@ -1,10 +1,12 @@ package app.simplecloud.controller.runtime +import app.simplecloud.controller.shared.db.DatabaseConfig import app.simplecloud.controller.runtime.group.GroupRepository import app.simplecloud.controller.runtime.group.GroupService import app.simplecloud.controller.runtime.host.ServerHostRepository import app.simplecloud.controller.runtime.server.ServerRepository import app.simplecloud.controller.runtime.server.ServerService +import app.simplecloud.controller.shared.db.Database import io.grpc.Server import io.grpc.ServerBuilder import org.apache.logging.log4j.LogManager @@ -14,22 +16,35 @@ class ControllerRuntime { private val logger = LogManager.getLogger(ControllerRuntime::class.java) - private val groupRepository = GroupRepository() - private val serverRepository = ServerRepository() - private val hostRepository = ServerHostRepository() + private val groupRepository: GroupRepository = GroupRepository() + private var serverRepository: ServerRepository? = null + private val hostRepository: ServerHostRepository = ServerHostRepository() + private var databaseConfig: DatabaseConfig? = null - private val server = createGrpcServerFromEnv() + private var server: Server? = null fun start() { + logger.info("Starting database...") + loadDB() logger.info("Starting controller...") + server = createGrpcServerFromEnv() startGrpcServer() } + + private fun loadDB() { + logger.info("Loading database configuration...") + databaseConfig = DatabaseConfig.load("/database-config.yml") + logger.info("Connecting database...") + Database.init(databaseConfig!!) + serverRepository = ServerRepository() + } + private fun startGrpcServer() { logger.info("Starting gRPC server...") thread { - server.start() - server.awaitTermination() + server!!.start() + server!!.awaitTermination() } } @@ -37,7 +52,7 @@ class ControllerRuntime { val port = System.getenv("GRPC_PORT")?.toInt() ?: 5816 return ServerBuilder.forPort(port) .addService(GroupService(groupRepository)) - .addService(ServerService(serverRepository, hostRepository, groupRepository)) + .addService(ServerService(serverRepository!!, hostRepository, groupRepository)) .build() } 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 91a0e89..2a0c13d 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,5 +1,9 @@ package app.simplecloud.controller.runtime +import java.util.concurrent.CompletableFuture + abstract class Repository : ArrayList() { abstract fun load() + abstract fun save(element: T) + abstract fun delete(element: T): CompletableFuture } \ No newline at end of file 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 070f0c5..f7587f0 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 @@ -3,6 +3,7 @@ package app.simplecloud.controller.runtime.group import app.simplecloud.controller.runtime.Repository import app.simplecloud.controller.shared.group.Group import app.simplecloud.controller.shared.proto.GroupDefinition +import java.util.concurrent.CompletableFuture class GroupRepository : Repository() { @@ -14,4 +15,13 @@ class GroupRepository : Repository() { TODO("Not yet implemented") } + override fun delete(element: Group): CompletableFuture { + throw UnsupportedOperationException("delete is not available on this repository") + } + + + override fun save(element: Group) { + throw UnsupportedOperationException("delete is not available on this repository") + } + } \ 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 d8cdef7..597a2cd 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 @@ -6,17 +6,17 @@ import app.simplecloud.controller.shared.proto.GetGroupByNameResponse import io.grpc.stub.StreamObserver class GroupService( - private val groupRepository: GroupRepository + private val groupRepository: GroupRepository ) : ControllerGroupServiceGrpc.ControllerGroupServiceImplBase() { override fun getGroupByName( - request: GetGroupByNameRequest, - responseObserver: StreamObserver + request: GetGroupByNameRequest, + responseObserver: StreamObserver ) { val group = groupRepository.findGroupByName(request.name) val response = GetGroupByNameResponse.newBuilder() - .setGroup(group) - .build() + .setGroup(group) + .build() responseObserver.onNext(response) responseObserver.onCompleted() 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 dc3956c..5e38e24 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,6 +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 java.util.concurrent.CompletableFuture + class ServerHostRepository : Repository() { fun findServerHostById(id: String): ServerHost? { @@ -25,5 +27,13 @@ class ServerHostRepository : Repository() { override fun load() { TODO("Not yet implemented") } + + override fun delete(element: ServerHost): CompletableFuture { + throw UnsupportedOperationException("delete is not available on this repository") + } + + override fun save(element: ServerHost) { + throw UnsupportedOperationException("delete is not available on this repository") + } } 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 873fbbe..c1f71e2 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 @@ -1,10 +1,15 @@ package app.simplecloud.controller.runtime.server import app.simplecloud.controller.runtime.Repository +import app.simplecloud.controller.shared.db.Database +import app.simplecloud.controller.shared.db.Tables.CLOUD_SERVERS import app.simplecloud.controller.shared.proto.ServerDefinition import app.simplecloud.controller.shared.server.Server +import java.util.concurrent.CompletableFuture -class ServerRepository: Repository() { +class ServerRepository : Repository() { + + private val db = Database.get() fun findServerById(id: String): ServerDefinition? { return firstOrNull { it.id == id }?.toDefinition() @@ -19,7 +24,39 @@ class ServerRepository: Repository() { } override fun load() { - TODO("Not yet implemented") + clear() + val query = db.select().from(CLOUD_SERVERS).fetchInto(CLOUD_SERVERS) + addAll(query.map { Server(it.uniqueId, it.hostId, it.groupName) }) + } + + override fun delete(element: Server): CompletableFuture { + val server = firstOrNull { it.id == element.id } + if (server == null) return CompletableFuture.completedFuture(false) + return db.deleteFrom(CLOUD_SERVERS).where(CLOUD_SERVERS.UNIQUE_ID.eq(server.id)).executeAsync().toCompletableFuture().thenApply { + return@thenApply it > 0 && remove(server) + } + } + + override fun save(element: Server) { + val server = firstOrNull { it.id == element.id } + if (server != null) { + val index = indexOf(server) + removeAt(index) + add(index, element) + } else { + add(element) + } + db.insertInto( + CLOUD_SERVERS, + + CLOUD_SERVERS.UNIQUE_ID, + CLOUD_SERVERS.HOST_ID, + CLOUD_SERVERS.GROUP_NAME, + ).values( + element.id, + element.host, + element.group, + ).onDuplicateKeyUpdate() } } \ No newline at end of file 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 0aac39f..0e470a3 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 @@ -44,6 +44,7 @@ class ServerService( val server = Server.create(Group.fromDefinition(groupDefinition), host).toDefinition() stub.startServer(server).toCompletable().thenApply { if (it.status.equals("200")) { + serverRepository.save(Server.fromDefinition(server)) responseObserver.onNext(server) responseObserver.onCompleted() } else { @@ -67,6 +68,9 @@ class ServerService( } val stub = ServerHostServiceGrpc.newFutureStub(host.getEndpoint()) stub.stopServer(request).toCompletable().thenApply { + if (it.status == "success") { + serverRepository.delete(Server.fromDefinition(server)) + } responseObserver.onNext(it) responseObserver.onCompleted() } diff --git a/controller-runtime/src/main/resources/database-config.yml b/controller-runtime/src/main/resources/database-config.yml new file mode 100644 index 0000000..19940f5 --- /dev/null +++ b/controller-runtime/src/main/resources/database-config.yml @@ -0,0 +1 @@ +url: file:///db.sqlite \ No newline at end of file diff --git a/controller-shared/build.gradle.kts b/controller-shared/build.gradle.kts index 1d5af08..877190d 100644 --- a/controller-shared/build.gradle.kts +++ b/controller-shared/build.gradle.kts @@ -9,6 +9,9 @@ dependencies { api(rootProject.libs.bundles.proto) api("org.jooq:jooq:3.19.3") api("org.jooq:jooq-meta:3.19.3") + api("org.spongepowered:configurate-yaml:4.0.0") + api("org.spongepowered:configurate-extra-kotlin:4.1.2") + api("org.xerial:sqlite-jdbc:3.44.1.0") jooqCodegen("org.jooq:jooq-meta-extensions:3.19.3") } diff --git a/controller-shared/src/main/db/schema.sql b/controller-shared/src/main/db/schema.sql index afbbd59..4137919 100644 --- a/controller-shared/src/main/db/schema.sql +++ b/controller-shared/src/main/db/schema.sql @@ -3,8 +3,6 @@ Execute jooqCodegen to create java classes for these files. */ -CREATE TYPE cloud_server_state AS ENUM('starting', 'available', 'ingame', 'stopping'); - CREATE TABLE IF NOT EXISTS cloud_servers( unique_id varchar NOT NULL, group_name varchar NOT NULL, @@ -16,7 +14,7 @@ CREATE TABLE IF NOT EXISTS cloud_servers( maximum_memory int NOT NULL, player_count int NOT NULL, name varchar NOT NULL, - state cloud_server_state NOT NULL + state int NOT NULL ); CREATE TABLE IF NOT EXISTS cloud_server_properties( diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/Database.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/Database.kt index ad9face..6b87414 100644 --- a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/Database.kt +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/Database.kt @@ -1,21 +1,27 @@ package app.simplecloud.controller.shared.db import org.jooq.DSLContext -import org.jooq.SQLDialect import org.jooq.impl.DSL -import java.sql.Connection class Database { companion object { @JvmStatic private lateinit var instance: DSLContext - fun init(connection: Connection, dialect: SQLDialect = SQLDialect.SQLITE) { - instance = DSL.using(connection, dialect) + fun init(config: DatabaseConfig) { + instance = DSL.using(config.toDatabaseUrl()) + setup() } - fun init(url: String, username: String, password: String) { - instance = DSL.using(url, username, password) + private fun setup() { + val setupInputStream = Database::class.java.getResourceAsStream("/schema.sql") + ?: throw IllegalArgumentException("Database schema not found.") + val setupCommands = setupInputStream.bufferedReader().use { it.readText() }.split(";") + setupCommands.forEach { + val trimmed = it.trim() + if (trimmed.isNotEmpty()) + instance.execute(DSL.sql(trimmed)) + } } fun get(): DSLContext { diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/DatabaseConfig.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/DatabaseConfig.kt new file mode 100644 index 0000000..536e4b8 --- /dev/null +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/DatabaseConfig.kt @@ -0,0 +1,42 @@ +package app.simplecloud.controller.shared.db + +import org.spongepowered.configurate.kotlin.extensions.get +import org.spongepowered.configurate.kotlin.objectMapperFactory +import org.spongepowered.configurate.objectmapping.ConfigSerializable +import org.spongepowered.configurate.yaml.YamlConfigurationLoader +import java.io.File +import java.nio.file.Files +import java.nio.file.StandardCopyOption + +@ConfigSerializable +data class DatabaseConfig(var driver: String = "jdbc:sqlite", var url: String) { + companion object { + fun load(path: String): DatabaseConfig? { + if (System.getenv("DATABASE_URL") != null) { + return DatabaseConfig(System.getenv("DATABASE_DRIVER") ?: "jdb:sqlite", System.getenv("DATABASE_URL")) + } + val destination = File(path.substring(1, path.length)) + if (!destination.exists()) { + if (!destination.parentFile.exists()) + destination.parentFile.mkdirs() + destination.createNewFile() + Files.copy(DatabaseConfig::class.java.getResourceAsStream(path)!!, destination.toPath(), StandardCopyOption.REPLACE_EXISTING) + } + val loader = YamlConfigurationLoader.builder() + .path(destination.toPath()) + .defaultOptions { options -> + options.serializers { builder -> + builder.registerAnnotatedObjects(objectMapperFactory()) + } + }.build() + val node = loader.load() + val config = node.get() + return config + } + } + + fun toDatabaseUrl(): String { + val absoluteUrl = if (url.contains(":")) url else File("").absolutePath + url + return "$driver:$absoluteUrl" + } +} \ No newline at end of file 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 2897e7f..ceefc6e 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 @@ -3,20 +3,20 @@ package app.simplecloud.controller.shared.group import app.simplecloud.controller.shared.proto.GroupDefinition data class Group( - val name: String, + val name: String, ) { fun toDefinition(): GroupDefinition { return GroupDefinition.newBuilder() - .setName(name) - .build() + .setName(name) + .build() } companion object { @JvmStatic fun fromDefinition(groupDefinition: GroupDefinition): Group { return Group( - groupDefinition.name + groupDefinition.name ) } } From 0cc147e905e7a02add20a211e1c4a1e36d6dbe00 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Tue, 20 Feb 2024 22:00:56 +0100 Subject: [PATCH 09/94] fix: jooq messages, main class detection, truthful database-config.yml --- controller-runtime/build.gradle.kts | 5 +++-- controller-runtime/src/main/resources/database-config.yml | 3 ++- .../kotlin/app/simplecloud/controller/shared/db/Database.kt | 2 ++ .../app/simplecloud/controller/shared/db/DatabaseConfig.kt | 6 ++++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/controller-runtime/build.gradle.kts b/controller-runtime/build.gradle.kts index 32e3518..8dde9d1 100644 --- a/controller-runtime/build.gradle.kts +++ b/controller-runtime/build.gradle.kts @@ -8,5 +8,6 @@ dependencies { } application { - mainClass.set("app.simplecloud.controller.runtime.launcher.LauncherKt") -} \ No newline at end of file + mainClass.set("app.simplecloud.controller.runtime.launcher.LauncherKtKt") +} + diff --git a/controller-runtime/src/main/resources/database-config.yml b/controller-runtime/src/main/resources/database-config.yml index 19940f5..382bd3b 100644 --- a/controller-runtime/src/main/resources/database-config.yml +++ b/controller-runtime/src/main/resources/database-config.yml @@ -1 +1,2 @@ -url: file:///db.sqlite \ No newline at end of file +driver: 'jdbc:sqlite' +url: '/database.db' \ No newline at end of file diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/Database.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/Database.kt index 6b87414..a0a0bb1 100644 --- a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/Database.kt +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/Database.kt @@ -14,6 +14,8 @@ class Database { } private fun setup() { + System.setProperty("org.jooq.no-logo", "true") + System.setProperty("org.jooq.no-tips", "true") val setupInputStream = Database::class.java.getResourceAsStream("/schema.sql") ?: throw IllegalArgumentException("Database schema not found.") val setupCommands = setupInputStream.bufferedReader().use { it.readText() }.split(";") diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/DatabaseConfig.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/DatabaseConfig.kt index 536e4b8..78368fb 100644 --- a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/DatabaseConfig.kt +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/DatabaseConfig.kt @@ -6,7 +6,10 @@ import org.spongepowered.configurate.objectmapping.ConfigSerializable import org.spongepowered.configurate.yaml.YamlConfigurationLoader import java.io.File import java.nio.file.Files +import java.nio.file.Path import java.nio.file.StandardCopyOption +import kotlin.io.path.copyToRecursively +import kotlin.io.path.createDirectories @ConfigSerializable data class DatabaseConfig(var driver: String = "jdbc:sqlite", var url: String) { @@ -17,8 +20,7 @@ data class DatabaseConfig(var driver: String = "jdbc:sqlite", var url: String) { } val destination = File(path.substring(1, path.length)) if (!destination.exists()) { - if (!destination.parentFile.exists()) - destination.parentFile.mkdirs() + Files.createDirectories(destination.toPath()) destination.createNewFile() Files.copy(DatabaseConfig::class.java.getResourceAsStream(path)!!, destination.toPath(), StandardCopyOption.REPLACE_EXISTING) } From 98aee660e978c8c54dcc4bcf96475e8eb30fea33 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Thu, 22 Feb 2024 22:29:13 +0100 Subject: [PATCH 10/94] feat: GroupApi CRUD operations, YamlRepository --- .../controller/api/group/GroupApi.kt | 13 ++++ .../controller/api/group/impl/GroupApiImpl.kt | 21 +++++++ controller-runtime/build.gradle.kts | 2 +- .../controller/runtime/ControllerRuntime.kt | 5 +- .../controller/runtime/YamlRepository.kt | 59 +++++++++++++++++++ .../runtime/group/GroupRepository.kt | 17 ++---- .../controller/runtime/group/GroupService.kt | 37 +++++++++++- .../runtime/host/ServerHostRepository.kt | 17 ++---- .../launcher/{LauncherKt.kt => Launcher.kt} | 0 .../src/main/resources/groups.yml | 1 + .../src/main/resources/hosts.yml | 1 + .../controller/shared/db/DatabaseConfig.kt | 2 - .../src/main/proto/controller_group_api.proto | 2 + 13 files changed, 144 insertions(+), 33 deletions(-) create mode 100644 controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt rename controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/{LauncherKt.kt => Launcher.kt} (100%) create mode 100644 controller-runtime/src/main/resources/groups.yml create mode 100644 controller-runtime/src/main/resources/hosts.yml diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/GroupApi.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/GroupApi.kt index 01addc7..2a74d38 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/GroupApi.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/GroupApi.kt @@ -1,6 +1,7 @@ package app.simplecloud.controller.api.group import app.simplecloud.controller.shared.group.Group +import app.simplecloud.controller.shared.status.ApiResponse import java.util.concurrent.CompletableFuture interface GroupApi { @@ -9,4 +10,16 @@ interface GroupApi { * @return a [CompletableFuture] with the [Group]. */ fun getGroupByName(name: String): CompletableFuture + + /** + * @param name the name of the group. + * @return a status [ApiResponse] of the delete state. + */ + fun deleteGroup(name: String): CompletableFuture + + /** + * @param group the [Group] to create. + * @return a status [ApiResponse] of the creation state. + */ + fun createGroup(group: Group): CompletableFuture } \ No newline at end of file diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/impl/GroupApiImpl.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/impl/GroupApiImpl.kt index 1e48a5f..e40f6c4 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/impl/GroupApiImpl.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/impl/GroupApiImpl.kt @@ -6,6 +6,7 @@ import app.simplecloud.controller.shared.future.toCompletable import app.simplecloud.controller.shared.group.Group import app.simplecloud.controller.shared.proto.ControllerGroupServiceGrpc import app.simplecloud.controller.shared.proto.GetGroupByNameRequest +import app.simplecloud.controller.shared.status.ApiResponse import java.util.concurrent.CompletableFuture class GroupApiImpl : GroupApi { @@ -26,4 +27,24 @@ class GroupApiImpl : GroupApi { } } + override fun deleteGroup(name: String): CompletableFuture { + return groupServiceStub.deleteGroupByName( + GetGroupByNameRequest.newBuilder() + .setName(name) + .build() + ).toCompletable() + .thenApply { + ApiResponse.fromDefinition(it) + } + } + + override fun createGroup(group: Group): CompletableFuture { + return groupServiceStub.createGroup( + group.toDefinition() + ).toCompletable() + .thenApply { + ApiResponse.fromDefinition(it) + } + } + } \ No newline at end of file diff --git a/controller-runtime/build.gradle.kts b/controller-runtime/build.gradle.kts index 8dde9d1..c50ab20 100644 --- a/controller-runtime/build.gradle.kts +++ b/controller-runtime/build.gradle.kts @@ -8,6 +8,6 @@ dependencies { } application { - mainClass.set("app.simplecloud.controller.runtime.launcher.LauncherKtKt") + mainClass.set("app.simplecloud.controller.runtime.launcher.LauncherKt") } 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 b31509e..dcdc3c1 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 @@ -10,15 +10,16 @@ import app.simplecloud.controller.shared.db.Database import io.grpc.Server import io.grpc.ServerBuilder import org.apache.logging.log4j.LogManager +import java.io.File import kotlin.concurrent.thread class ControllerRuntime { private val logger = LogManager.getLogger(ControllerRuntime::class.java) - private val groupRepository: GroupRepository = GroupRepository() + private val groupRepository: GroupRepository = GroupRepository("/groups.yml") private var serverRepository: ServerRepository? = null - private val hostRepository: ServerHostRepository = ServerHostRepository() + private val hostRepository: ServerHostRepository = ServerHostRepository("/hosts.yml") private var databaseConfig: DatabaseConfig? = null private var server: Server? = null diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt new file mode 100644 index 0000000..11d0016 --- /dev/null +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt @@ -0,0 +1,59 @@ +package app.simplecloud.controller.runtime + +import org.spongepowered.configurate.ConfigurationNode +import org.spongepowered.configurate.kotlin.extensions.get +import org.spongepowered.configurate.kotlin.objectMapperFactory +import org.spongepowered.configurate.objectmapping.ConfigSerializable +import org.spongepowered.configurate.yaml.YamlConfigurationLoader +import java.io.File +import java.nio.file.Files +import java.nio.file.StandardCopyOption +import java.util.concurrent.CompletableFuture + +abstract class YamlRepository(path: String) : Repository() { + + private lateinit var node: ConfigurationNode + private var destination: File = File(path.substring(1, path.length)) + init { + if(!destination.exists()) { + Files.copy(YamlRepository::class.java.getResourceAsStream(path)!!, destination.toPath(), StandardCopyOption.REPLACE_EXISTING) + } + } + override fun load() { + val loader = YamlConfigurationLoader.builder() + .path(destination.toPath()) + .defaultOptions { options -> + options.serializers { builder -> + builder.registerAnnotatedObjects(objectMapperFactory()) + } + }.build() + node = loader.load() + addAll(node.get>()?.items ?: ArrayList()) + } + + override fun delete(element: T): CompletableFuture { + val index = findIndex(element) + if(index == -1) { + return CompletableFuture.completedFuture(false) + } + removeAt(index) + node.set(YamlRepositoryType(this)) + return CompletableFuture.completedFuture(true) + } + + override fun save(element: T) { + val index = findIndex(element) + if(index != -1) { + removeAt(index) + add(index, element) + }else { + add(element) + } + node.set(YamlRepositoryType(this)) + } + + abstract fun findIndex(element: T): Int +} + +@ConfigSerializable +data class YamlRepositoryType(var items: List) \ No newline at end of file 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 f7587f0..db0bf5f 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 @@ -1,27 +1,18 @@ package app.simplecloud.controller.runtime.group -import app.simplecloud.controller.runtime.Repository +import app.simplecloud.controller.runtime.YamlRepository import app.simplecloud.controller.shared.group.Group import app.simplecloud.controller.shared.proto.GroupDefinition -import java.util.concurrent.CompletableFuture -class GroupRepository : Repository() { +class GroupRepository(path: String) : YamlRepository(path) { fun findGroupByName(name: String): GroupDefinition? { return firstOrNull { it.name == name }?.toDefinition() } - override fun load() { - TODO("Not yet implemented") + override fun findIndex(element: Group): Int { + return indexOf(firstOrNull { it.name == element.name }) } - override fun delete(element: Group): CompletableFuture { - throw UnsupportedOperationException("delete is not available on this repository") - } - - - override fun save(element: Group) { - throw UnsupportedOperationException("delete is not available on this repository") - } } \ 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 597a2cd..ba7d8e7 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 @@ -1,8 +1,8 @@ package app.simplecloud.controller.runtime.group -import app.simplecloud.controller.shared.proto.ControllerGroupServiceGrpc -import app.simplecloud.controller.shared.proto.GetGroupByNameRequest -import app.simplecloud.controller.shared.proto.GetGroupByNameResponse +import app.simplecloud.controller.shared.group.Group +import app.simplecloud.controller.shared.proto.* +import app.simplecloud.controller.shared.status.ApiResponse import io.grpc.stub.StreamObserver class GroupService( @@ -22,4 +22,35 @@ class GroupService( responseObserver.onCompleted() } + override fun createGroup(request: GroupDefinition, responseObserver: StreamObserver) { + val group = Group.fromDefinition(request) + try { + groupRepository.save(group) + responseObserver.onNext(ApiResponse(status = "success").toDefinition()) + responseObserver.onCompleted() + }catch (e: Exception) { + responseObserver.onError(e) + responseObserver.onCompleted() + } + } + + override fun deleteGroupByName(request: GetGroupByNameRequest, responseObserver: StreamObserver) { + val groupDefinition = groupRepository.findGroupByName(request.name) + if(groupDefinition == null) { + responseObserver.onNext(ApiResponse(status = "error").toDefinition()) + responseObserver.onCompleted() + return + } + val group = Group.fromDefinition(groupDefinition) + try { + groupRepository.delete(group).thenApply { + responseObserver.onNext(ApiResponse(status = if(it) "success" else "error").toDefinition()) + responseObserver.onCompleted() + } + }catch (e: Exception) { + responseObserver.onError(e) + responseObserver.onCompleted() + } + } + } \ 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 5e38e24..cd0d9f8 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 @@ -1,11 +1,11 @@ package app.simplecloud.controller.runtime.host -import app.simplecloud.controller.runtime.Repository +import app.simplecloud.controller.runtime.YamlRepository import app.simplecloud.controller.runtime.server.ServerRepository import app.simplecloud.controller.shared.host.ServerHost -import java.util.concurrent.CompletableFuture +import java.io.File -class ServerHostRepository : Repository() { +class ServerHostRepository(path: String) : YamlRepository(path) { fun findServerHostById(id: String): ServerHost? { return firstOrNull { it.getId() == id } @@ -24,16 +24,9 @@ class ServerHostRepository : Repository() { return lastHost } - override fun load() { - TODO("Not yet implemented") + override fun findIndex(element: ServerHost): Int { + return indexOf(firstOrNull { it.getId() == element.getId() }) } - override fun delete(element: ServerHost): CompletableFuture { - throw UnsupportedOperationException("delete is not available on this repository") - } - - override fun save(element: ServerHost) { - throw UnsupportedOperationException("delete is not available on this repository") - } } diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/LauncherKt.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/Launcher.kt similarity index 100% rename from controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/LauncherKt.kt rename to controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/Launcher.kt diff --git a/controller-runtime/src/main/resources/groups.yml b/controller-runtime/src/main/resources/groups.yml new file mode 100644 index 0000000..f54cdfb --- /dev/null +++ b/controller-runtime/src/main/resources/groups.yml @@ -0,0 +1 @@ +items: \ No newline at end of file diff --git a/controller-runtime/src/main/resources/hosts.yml b/controller-runtime/src/main/resources/hosts.yml new file mode 100644 index 0000000..f54cdfb --- /dev/null +++ b/controller-runtime/src/main/resources/hosts.yml @@ -0,0 +1 @@ +items: \ No newline at end of file diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/DatabaseConfig.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/DatabaseConfig.kt index 78368fb..fb8be3b 100644 --- a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/DatabaseConfig.kt +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/DatabaseConfig.kt @@ -20,8 +20,6 @@ data class DatabaseConfig(var driver: String = "jdbc:sqlite", var url: String) { } val destination = File(path.substring(1, path.length)) if (!destination.exists()) { - Files.createDirectories(destination.toPath()) - destination.createNewFile() Files.copy(DatabaseConfig::class.java.getResourceAsStream(path)!!, destination.toPath(), StandardCopyOption.REPLACE_EXISTING) } val loader = YamlConfigurationLoader.builder() diff --git a/controller-shared/src/main/proto/controller_group_api.proto b/controller-shared/src/main/proto/controller_group_api.proto index 6e40d92..308a939 100644 --- a/controller-shared/src/main/proto/controller_group_api.proto +++ b/controller-shared/src/main/proto/controller_group_api.proto @@ -17,4 +17,6 @@ message GetGroupByNameResponse { service ControllerGroupService { rpc getGroupByName(GetGroupByNameRequest) returns (GetGroupByNameResponse); + rpc deleteGroupByName(GetGroupByNameRequest) returns (StatusResponse); + rpc createGroup(GroupDefinition) returns (StatusResponse); } \ No newline at end of file From 433d81c02193fb09fa5d27422bc42df95ada47f0 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Sun, 25 Feb 2024 12:54:52 +0100 Subject: [PATCH 11/94] feat: add attributes to Group.kt and Server.kt --- .../runtime/server/ServerService.kt | 16 +++---- .../controller/shared/group/Group.kt | 26 +++++++++- .../controller/shared/server/Server.kt | 48 +++++++++++++------ .../src/main/proto/controller_types.proto | 20 ++++---- .../src/main/proto/server_host_api.proto | 2 +- 5 files changed, 78 insertions(+), 34 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 0e470a3..57e8bd4 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 @@ -41,15 +41,13 @@ class ServerService( responseObserver.onCompleted() return; } - val server = Server.create(Group.fromDefinition(groupDefinition), host).toDefinition() - stub.startServer(server).toCompletable().thenApply { - if (it.status.equals("200")) { - serverRepository.save(Server.fromDefinition(server)) - responseObserver.onNext(server) - responseObserver.onCompleted() - } else { - responseObserver.onError(ServerHostException("Could not start server, aborting.")) - } + stub.startServer(groupDefinition).toCompletable().thenApply { + serverRepository.save(Server.fromDefinition(it)) + responseObserver.onNext(it) + responseObserver.onCompleted() + }.exceptionally { + responseObserver.onError(ServerHostException("Could not start server, aborting.")) + 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 ceefc6e..a48092a 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 @@ -4,11 +4,27 @@ import app.simplecloud.controller.shared.proto.GroupDefinition data class Group( val name: String, + val templateName: String, + val minMemory: Long, + val maxMemory: Long, + val startPort: Long, + val onlineServers: Long, + val minOnlineCount: Long, + val maxOnlineCount: Long, + val properties: Map, ) { fun toDefinition(): GroupDefinition { return GroupDefinition.newBuilder() .setName(name) + .setTemplateName(templateName) + .setMinimumMemory(minMemory) + .setMaximumMemory(maxMemory) + .setStartPort(startPort) + .setOnlineServers(onlineServers) + .setMinimumOnlineCount(minOnlineCount) + .setMaximumOnlineCount(maxOnlineCount) + .putAllProperties(properties) .build() } @@ -16,7 +32,15 @@ data class Group( @JvmStatic fun fromDefinition(groupDefinition: GroupDefinition): Group { return Group( - groupDefinition.name + groupDefinition.name, + groupDefinition.templateName, + groupDefinition.minimumMemory, + groupDefinition.maximumMemory, + groupDefinition.startPort, + groupDefinition.onlineServers, + groupDefinition.minimumOnlineCount, + groupDefinition.maximumOnlineCount, + groupDefinition.propertiesMap ) } } 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 b854808..35d82e8 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 @@ -3,17 +3,38 @@ package app.simplecloud.controller.shared.server import app.simplecloud.controller.shared.group.Group import app.simplecloud.controller.shared.host.ServerHost import app.simplecloud.controller.shared.proto.ServerDefinition +import app.simplecloud.controller.shared.proto.ServerState import java.util.* +import kotlin.math.min data class Server( - val id: String, - val host: String?, + val uniqueId: String, val group: String, + val host: String?, + val numericalId: Int, + val templateId: String, + val ip: String, + val port: Long, + val minMemory: Long, + val maxMemory: Long, + val playerCount: Long, + val properties: Map, + val state: ServerState, ) { fun toDefinition(): ServerDefinition { return ServerDefinition.newBuilder() - .setUniqueId(id) + .setUniqueId(uniqueId) .setGroupName(group) + .setHostId(host) + .setIp(ip) + .setPort(port) + .setState(state) + .setMinimumMemory(minMemory) + .setMaximumMemory(maxMemory) + .setPlayerCount(playerCount) + .putAllProperties(properties) + .setTemplateId(templateId) + .setNumericalId(numericalId) .build() } @@ -23,17 +44,16 @@ data class Server( return Server( serverDefinition.uniqueId, serverDefinition.hostId, - serverDefinition.groupName - ) - } - - - @JvmStatic - fun create(group: Group, serverHost: ServerHost): Server { - return Server( - UUID.randomUUID().toString(), - serverHost.getId(), - group.name, + serverDefinition.groupName, + serverDefinition.numericalId, + serverDefinition.templateId, + serverDefinition.ip, + serverDefinition.port, + serverDefinition.minimumMemory, + serverDefinition.maximumMemory, + serverDefinition.playerCount, + serverDefinition.propertiesMap, + serverDefinition.state ) } diff --git a/controller-shared/src/main/proto/controller_types.proto b/controller-shared/src/main/proto/controller_types.proto index 9a71006..a60548a 100644 --- a/controller-shared/src/main/proto/controller_types.proto +++ b/controller-shared/src/main/proto/controller_types.proto @@ -11,9 +11,10 @@ message GroupDefinition { uint64 minimum_memory = 3; uint64 maximum_memory = 4; uint64 start_port = 5; - uint64 minimum_online_count = 6; - uint64 maximum_online_count = 7; - map properties = 8; + uint64 online_servers = 6; + uint64 minimum_online_count = 7; + uint64 maximum_online_count = 8; + map properties = 9; } message ServerDefinition { @@ -22,12 +23,13 @@ message ServerDefinition { string host_id = 3; uint32 numerical_id = 4; string template_id = 5; - uint64 port = 6; - uint64 minimum_memory = 7; - uint64 maximum_memory = 8; - uint64 player_count = 9; - map properties = 10; - ServerState state = 11; + string ip = 6; + uint64 port = 7; + uint64 minimum_memory = 8; + uint64 maximum_memory = 9; + uint64 player_count = 10; + map properties = 11; + ServerState state = 12; } enum ServerState { diff --git a/controller-shared/src/main/proto/server_host_api.proto b/controller-shared/src/main/proto/server_host_api.proto index ff6d9ec..7ec1715 100644 --- a/controller-shared/src/main/proto/server_host_api.proto +++ b/controller-shared/src/main/proto/server_host_api.proto @@ -9,6 +9,6 @@ import "controller_types.proto"; import "controller_server_api.proto"; service ServerHostService { - rpc startServer(ServerDefinition) returns (StatusResponse); + rpc startServer(GroupDefinition) returns (ServerDefinition); rpc stopServer(ServerIdRequest) returns (StatusResponse); } \ No newline at end of file From 0489a84d6779692bd3b8eb4e3ce2db152f9f0588 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Sun, 25 Feb 2024 13:19:02 +0100 Subject: [PATCH 12/94] refactor: types for repositories --- .../controller/runtime/YamlRepository.kt | 3 ++ .../runtime/host/ServerHostRepository.kt | 7 ++- .../runtime/server/ServerRepository.kt | 52 ++++++++++++++++--- controller-shared/src/main/db/schema.sql | 4 +- 4 files changed, 52 insertions(+), 14 deletions(-) diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt index 11d0016..3725974 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt @@ -29,6 +29,9 @@ abstract class YamlRepository(path: String) : Repository() { }.build() node = loader.load() addAll(node.get>()?.items ?: ArrayList()) + forEach { + println(it) + } } override fun delete(element: T): CompletableFuture { 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 cd0d9f8..f2ddf85 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 @@ -6,16 +6,15 @@ import app.simplecloud.controller.shared.host.ServerHost import java.io.File class ServerHostRepository(path: String) : YamlRepository(path) { - fun findServerHostById(id: String): ServerHost? { - return firstOrNull { it.getId() == id } + return firstOrNull { it.id == id } } fun findLaziestServerHost(serverRepository: ServerRepository): ServerHost? { var lastAmount = Int.MAX_VALUE var lastHost: ServerHost? = null for (host: ServerHost in this) { - val amount = serverRepository.findServersByHostId(host.getId()).size + val amount = serverRepository.findServersByHostId(host.id).size if (amount < lastAmount) { lastAmount = amount lastHost = host @@ -25,7 +24,7 @@ class ServerHostRepository(path: String) : YamlRepository(path) { } override fun findIndex(element: ServerHost): Int { - return indexOf(firstOrNull { it.getId() == element.getId() }) + return indexOf(firstOrNull { it.id == element.id }) } } 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 c1f71e2..f01d7f2 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 @@ -3,7 +3,9 @@ package app.simplecloud.controller.runtime.server import app.simplecloud.controller.runtime.Repository import app.simplecloud.controller.shared.db.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.proto.ServerDefinition +import app.simplecloud.controller.shared.proto.ServerState import app.simplecloud.controller.shared.server.Server import java.util.concurrent.CompletableFuture @@ -12,7 +14,7 @@ class ServerRepository : Repository() { private val db = Database.get() fun findServerById(id: String): ServerDefinition? { - return firstOrNull { it.id == id }?.toDefinition() + return firstOrNull { it.uniqueId == id }?.toDefinition() } fun findServersByHostId(id: String): List { @@ -26,19 +28,37 @@ class ServerRepository : Repository() { override fun load() { clear() val query = db.select().from(CLOUD_SERVERS).fetchInto(CLOUD_SERVERS) - addAll(query.map { Server(it.uniqueId, it.hostId, it.groupName) }) + query.map { + val propertiesQuery = db.select().from(CLOUD_SERVER_PROPERTIES).fetchInto(CLOUD_SERVER_PROPERTIES) + add(Server( + it.uniqueId, + it.groupName, + it.hostId, + it.numericalId, + it.templateId, + it.ip, + it.port.toLong(), + it.minimumMemory.toLong(), + it.maximumMemory.toLong(), + it.playerCount.toLong(), + propertiesQuery.map { item -> + item.key to item.value + }.toMap(), + ServerState.valueOf(it.state) + )) + } } override fun delete(element: Server): CompletableFuture { - val server = firstOrNull { it.id == element.id } + val server = firstOrNull { it.uniqueId == element.uniqueId } if (server == null) return CompletableFuture.completedFuture(false) - return db.deleteFrom(CLOUD_SERVERS).where(CLOUD_SERVERS.UNIQUE_ID.eq(server.id)).executeAsync().toCompletableFuture().thenApply { + return db.deleteFrom(CLOUD_SERVERS).where(CLOUD_SERVERS.UNIQUE_ID.eq(server.uniqueId)).executeAsync().toCompletableFuture().thenApply { return@thenApply it > 0 && remove(server) } } override fun save(element: Server) { - val server = firstOrNull { it.id == element.id } + val server = firstOrNull { it.uniqueId == element.uniqueId } if (server != null) { val index = indexOf(server) removeAt(index) @@ -50,12 +70,28 @@ class ServerRepository : Repository() { CLOUD_SERVERS, CLOUD_SERVERS.UNIQUE_ID, - CLOUD_SERVERS.HOST_ID, CLOUD_SERVERS.GROUP_NAME, + CLOUD_SERVERS.HOST_ID, + CLOUD_SERVERS.NUMERICAL_ID, + CLOUD_SERVERS.TEMPLATE_ID, + CLOUD_SERVERS.IP, + CLOUD_SERVERS.PORT, + CLOUD_SERVERS.MINIMUM_MEMORY, + CLOUD_SERVERS.MINIMUM_MEMORY, + CLOUD_SERVERS.PLAYER_COUNT, + CLOUD_SERVERS.STATE, ).values( - element.id, - element.host, + element.uniqueId, element.group, + element.host, + element.numericalId, + element.templateId, + element.ip, + element.port.toInt(), + element.minMemory.toInt(), + element.maxMemory.toInt(), + element.playerCount.toInt(), + element.state.toString() ).onDuplicateKeyUpdate() } diff --git a/controller-shared/src/main/db/schema.sql b/controller-shared/src/main/db/schema.sql index 4137919..6f1d6e9 100644 --- a/controller-shared/src/main/db/schema.sql +++ b/controller-shared/src/main/db/schema.sql @@ -9,12 +9,12 @@ CREATE TABLE IF NOT EXISTS cloud_servers( host_id varchar NOT NULL, numerical_id int NOT NULL, template_id varchar NOT NULL, + ip varchar NOT NULL, port int NOT NULL, minimum_memory int NOT NULL, maximum_memory int NOT NULL, player_count int NOT NULL, - name varchar NOT NULL, - state int NOT NULL + state varchar NOT NULL ); CREATE TABLE IF NOT EXISTS cloud_server_properties( From 952805c3a993885f90065d9e6d38b573e981d3ba Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Sun, 25 Feb 2024 13:19:28 +0100 Subject: [PATCH 13/94] refactor: cleanup ServerHost.kt definition --- .../controller/shared/host/ServerHost.kt | 37 ++++--------------- 1 file changed, 8 insertions(+), 29 deletions(-) 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 cb07a1b..c7f0bdf 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 @@ -3,35 +3,14 @@ package app.simplecloud.controller.shared.host import io.grpc.ManagedChannel import io.grpc.ManagedChannelBuilder -class ServerHost { - - private var id: String; - private var host: String; - private var port: Int; - private var endpoint: ManagedChannel - - constructor(id: String, host: String, port: Int) { - this.id = id - this.host = host - this.port = port - this.endpoint = createChannel() - } - - fun getEndpoint(): ManagedChannel { - return endpoint - } - - fun getId(): String { - return id; - } - - fun getHost(): String { - return host; - } - - fun getPort(): Int { - return port - } +data class ServerHost( + val id: String, + val host: String, + val port: Int +) { + + @Transient + val endpoint: ManagedChannel = createChannel() private fun createChannel(): ManagedChannel { return ManagedChannelBuilder.forAddress(host, port).usePlaintext().build() From 2f1f2adfc8c646c1335e3e498888ed86288a6d91 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Sun, 25 Feb 2024 13:23:08 +0100 Subject: [PATCH 14/94] refactor: cleanup code --- .../simplecloud/controller/runtime/server/ServerService.kt | 4 ++-- 1 file changed, 2 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 57e8bd4..949d580 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 @@ -34,7 +34,7 @@ class ServerService( responseObserver.onCompleted() return; } - val stub = ServerHostServiceGrpc.newFutureStub(host.getEndpoint()) + val stub = ServerHostServiceGrpc.newFutureStub(host.endpoint) val groupDefinition = groupRepository.findGroupByName(request.name) if (groupDefinition == null) { responseObserver.onError(IllegalArgumentException("No group was found matching the group name.")) @@ -64,7 +64,7 @@ class ServerService( responseObserver.onCompleted() return } - val stub = ServerHostServiceGrpc.newFutureStub(host.getEndpoint()) + val stub = ServerHostServiceGrpc.newFutureStub(host.endpoint) stub.stopServer(request).toCompletable().thenApply { if (it.status == "success") { serverRepository.delete(Server.fromDefinition(server)) From 49c04cd51bbbdc2c2c8b903ad7ca88b2cc32a46c Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Sun, 25 Feb 2024 13:29:42 +0100 Subject: [PATCH 15/94] feat: add property management logic --- .../runtime/server/ServerRepository.kt | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) 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 f01d7f2..9bf7007 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 @@ -52,9 +52,15 @@ class ServerRepository : Repository() { override fun delete(element: Server): CompletableFuture { val server = firstOrNull { it.uniqueId == element.uniqueId } if (server == null) return CompletableFuture.completedFuture(false) + val canDelete = db.deleteFrom(CLOUD_SERVER_PROPERTIES).where(CLOUD_SERVER_PROPERTIES.SERVER_ID.eq(server.uniqueId)).executeAsync().toCompletableFuture().thenApply { + return@thenApply true + }.exceptionally { + return@exceptionally false + }.get() + if(!canDelete) return CompletableFuture.completedFuture(false) return db.deleteFrom(CLOUD_SERVERS).where(CLOUD_SERVERS.UNIQUE_ID.eq(server.uniqueId)).executeAsync().toCompletableFuture().thenApply { return@thenApply it > 0 && remove(server) - } + }.exceptionally { return@exceptionally false } } override fun save(element: Server) { @@ -93,6 +99,19 @@ class ServerRepository : Repository() { element.playerCount.toInt(), element.state.toString() ).onDuplicateKeyUpdate() + element.properties.forEach { + db.insertInto( + CLOUD_SERVER_PROPERTIES, + + CLOUD_SERVER_PROPERTIES.SERVER_ID, + CLOUD_SERVER_PROPERTIES.KEY, + CLOUD_SERVER_PROPERTIES.VALUE + ).values( + element.uniqueId, + it.key, + it.value + ).onDuplicateKeyUpdate() + } } } \ No newline at end of file From 11a3c60bb1f41a10695f5b270a403328e75fa6b2 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Sun, 25 Feb 2024 23:02:53 +0100 Subject: [PATCH 16/94] feat: add Reconciler --- controller-runtime/build.gradle.kts | 1 + .../controller/runtime/ControllerRuntime.kt | 44 ++++++++++++++----- .../controller/runtime/Reconciler.kt | 35 +++++++++++++++ 3 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt diff --git a/controller-runtime/build.gradle.kts b/controller-runtime/build.gradle.kts index c50ab20..6f42184 100644 --- a/controller-runtime/build.gradle.kts +++ b/controller-runtime/build.gradle.kts @@ -5,6 +5,7 @@ plugins { dependencies { api(project(":controller-shared")) implementation(rootProject.libs.bundles.log4j) + api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2") } application { 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 dcdc3c1..f5227f8 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 @@ -7,10 +7,12 @@ import app.simplecloud.controller.runtime.host.ServerHostRepository import app.simplecloud.controller.runtime.server.ServerRepository import app.simplecloud.controller.runtime.server.ServerService import app.simplecloud.controller.shared.db.Database +import io.grpc.ManagedChannel +import io.grpc.ManagedChannelBuilder import io.grpc.Server import io.grpc.ServerBuilder +import kotlinx.coroutines.* import org.apache.logging.log4j.LogManager -import java.io.File import kotlin.concurrent.thread class ControllerRuntime { @@ -18,11 +20,11 @@ class ControllerRuntime { private val logger = LogManager.getLogger(ControllerRuntime::class.java) private val groupRepository: GroupRepository = GroupRepository("/groups.yml") - private var serverRepository: ServerRepository? = null + private lateinit var serverRepository: ServerRepository private val hostRepository: ServerHostRepository = ServerHostRepository("/hosts.yml") - private var databaseConfig: DatabaseConfig? = null - - private var server: Server? = null + private lateinit var databaseConfig: DatabaseConfig + private lateinit var reconciler: Reconciler + private lateinit var server: Server fun start() { logger.info("Starting database...") @@ -30,31 +32,53 @@ class ControllerRuntime { logger.info("Starting controller...") server = createGrpcServerFromEnv() startGrpcServer() + startReconciler() } private fun loadDB() { logger.info("Loading database configuration...") - databaseConfig = DatabaseConfig.load("/database-config.yml") + databaseConfig = DatabaseConfig.load("/database-config.yml")!! logger.info("Connecting database...") - Database.init(databaseConfig!!) + Database.init(databaseConfig) serverRepository = ServerRepository() } private fun startGrpcServer() { logger.info("Starting gRPC server...") thread { - server!!.start() - server!!.awaitTermination() + server.start() + server.awaitTermination() } } + private fun startReconciler() { + logger.info("Starting Reconciler...") + reconciler = Reconciler(groupRepository, serverRepository, createManagedChannel()) + startReconcilerJob(3000L) + } + private fun createGrpcServerFromEnv(): Server { val port = System.getenv("GRPC_PORT")?.toInt() ?: 5816 return ServerBuilder.forPort(port) .addService(GroupService(groupRepository)) - .addService(ServerService(serverRepository!!, hostRepository, groupRepository)) + .addService(ServerService(serverRepository, hostRepository, groupRepository)) .build() } + private fun createManagedChannel(): ManagedChannel { + val port = System.getenv("GRPC_PORT")?.toInt() ?: 5816 + return ManagedChannelBuilder.forAddress("localhost", port).build() + } + + @OptIn(InternalCoroutinesApi::class) + private fun startReconcilerJob(timeInterval: Long): Job { + return CoroutineScope(Dispatchers.Default).launch { + while (NonCancellable.isActive) { + reconciler.reconcile() + delay(timeInterval) + } + } + } + } \ No newline at end of file diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt new file mode 100644 index 0000000..5a23b17 --- /dev/null +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt @@ -0,0 +1,35 @@ +package app.simplecloud.controller.runtime + +import app.simplecloud.controller.runtime.group.GroupRepository +import app.simplecloud.controller.runtime.server.ServerRepository +import app.simplecloud.controller.shared.future.toCompletable +import app.simplecloud.controller.shared.proto.ControllerServerServiceGrpc +import app.simplecloud.controller.shared.proto.GroupNameRequest +import io.grpc.ManagedChannel +import org.apache.logging.log4j.LogManager + +class Reconciler( + private val groupRepository: GroupRepository, + private val serverRepository: ServerRepository, + managedChannel: ManagedChannel, +) { + + private val serverStub = ControllerServerServiceGrpc.newFutureStub(managedChannel) + private val logger = LogManager.getLogger(Reconciler::class.java) + fun reconcile() { + groupRepository.forEach { group -> + val servers = serverRepository.findServersByGroup(group.name) + val full = servers.filter { server -> + server.playerCount >= group.maxOnlineCount + } + if(servers.size < group.minOnlineCount || (full.size >= servers.size && servers.size < group.maxOnlineCount)) { + serverStub.startServer(GroupNameRequest.newBuilder().setName(group.name).build()).toCompletable().thenApply { + logger.info("Started new instance ${it.uniqueId} of group ${group.name} on ${it.ip}:${it.port}") + }.exceptionally { + logger.error(it) + } + } + } + } + +} \ No newline at end of file From 8f98d197c9c17ecbc0b647aed5b0786fc53caffc Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Mon, 26 Feb 2024 17:43:02 +0100 Subject: [PATCH 17/94] refactor: update readme.md --- readme.md | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 91 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 167b1c3..863d718 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,93 @@ # SimpleCloud v3 Controller +Process that (automatically) manages minecraft server deployments (across multiple root-servers). +At least one [ServerHost](#serverhosts) is needed to actually start servers. +> You can take a look at the [controller structure](structure.png) to learn how exactly it works + +## Features +- [x] Reconciler (auto-deploying for servers) +- [x] [API](#api-usage) using [gRPC](https://grpc.io/) +- [x] Server [SQL](https://en.wikipedia.org/wiki/SQL)-Database (any dialect) + +## ServerHosts +ServerHosts are processes, that directly handle minecraft server deployments. Each root-server should have exactly one ServerHost online. We provide a [default implementation](), +however, you can write your [own implementation](). To enable the Controller to communicate with ServerHosts, you need to add the ServerHost to the controllers' configuration. You can have as many ServerHost instances as you like. + +### Example hosts.yml +````yaml +items: + # ServerHost on same machine + - id: 'my-local-server-host-1' + ip: 127.0.0.1 + port: 12345 + # ServerHost on different machine + - id: 'my-external-server-host-1' + ip: 123.23.12.1 + port: 12344 +```` + +## API usage +The SimpleCloud v3 Controller provides API for both server groups and actual servers. +The group API is used for [CRUD-Operations](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) of server groups, whereas the server API is used to manage running servers or starting new ones. +````kotlin +//Getting the Server API +val serverApi = Controller.serverApi + +//Getting the Group API +val groupApi = Controller.groupApi +```` + +### Group API functions +> Group update functionality is yet to be implemented +````kotlin + /** + * @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 a status [ApiResponse] of the delete state. + */ + fun deleteGroup(name: String): CompletableFuture + + /** + * @param group the [Group] to create. + * @return a status [ApiResponse] of the creation state. + */ + fun createGroup(group: Group): CompletableFuture +```` + +### Server API functions +````kotlin + /** + * @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 group The server group. + * @return a [CompletableFuture] with a [List] of [Server]s of that group. + */ + fun getServersByGroup(group: Group): 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): CompletableFuture + + /** + * @param id the id of the server. + * @return a [CompletableFuture] with a [ApiResponse]. + */ + fun stopServer(id: String): CompletableFuture +```` -### Controller structure -![img.png](structure.png) \ No newline at end of file From 1e60b9c9e8a1fbd9e85a8c63a2de00c6476ebecf Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Mon, 26 Feb 2024 21:47:30 +0100 Subject: [PATCH 18/94] implement: github packages workflow --- .github/workflows/publish-dev.yml | 45 +++++++++++++++++++++++++++++++ build.gradle.kts | 20 ++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 .github/workflows/publish-dev.yml diff --git a/.github/workflows/publish-dev.yml b/.github/workflows/publish-dev.yml new file mode 100644 index 0000000..60f757e --- /dev/null +++ b/.github/workflows/publish-dev.yml @@ -0,0 +1,45 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will build a package using Gradle and then publish it to GitHub packages when a release is created +# For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle + +name: Gradle Package Dev + +on: + push: + branches: + - develop + +jobs: + build: + + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + server-id: github # Value of the distributionManagement/repository/id field of the pom.xml + settings-path: ${{ github.workspace }} # location for the settings.xml file + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0 + + - name: Build with Gradle + run: ./gradlew build + + # The USERNAME and TOKEN need to correspond to the credentials environment variables used in + # the publishing section of your build.gradle + - name: Publish to GitHub Packages Dev + run: ./gradlew publish-dev + env: + USERNAME: ${{ github.actor }} + TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/build.gradle.kts b/build.gradle.kts index eab14b2..81efc42 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,6 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import java.net.URI plugins { alias(libs.plugins.kotlin) @@ -19,6 +20,25 @@ allprojects { subprojects { apply(plugin = "org.jetbrains.kotlin.jvm") apply(plugin = "com.github.johnrengelman.shadow") + apply(plugin = "maven-publish") + + configure { + repositories { + maven { + name = "GitHubPackages" + url = URI.create("https://maven.pkg.github.com/theSimpleCloud/simplecloud-controller") + credentials { + username = System.getenv("USERNAME") + password = System.getenv("TOKEN") + } + } + } + publications { + register("gpr") { + from(components["java"]) + } + } + } dependencies { testImplementation(rootProject.libs.kotlinTest) From 83939a8c3f4de3889978fa596bdd229df1a32b8a Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Mon, 26 Feb 2024 22:01:04 +0100 Subject: [PATCH 19/94] fix: run jooqCodegen when building --- controller-shared/build.gradle.kts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/controller-shared/build.gradle.kts b/controller-shared/build.gradle.kts index 877190d..6dd09de 100644 --- a/controller-shared/build.gradle.kts +++ b/controller-shared/build.gradle.kts @@ -63,6 +63,10 @@ protobuf { } } +tasks.named("build") { + finalizedBy(tasks.jooqCodegen) +} + jooq { configuration { generator { From 3d50c65279f0d3698bdb6c7b6873d01379ac9204 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Mon, 26 Feb 2024 22:12:43 +0100 Subject: [PATCH 20/94] fix: build order --- controller-shared/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/controller-shared/build.gradle.kts b/controller-shared/build.gradle.kts index 6dd09de..34a1adf 100644 --- a/controller-shared/build.gradle.kts +++ b/controller-shared/build.gradle.kts @@ -63,8 +63,8 @@ protobuf { } } -tasks.named("build") { - finalizedBy(tasks.jooqCodegen) +tasks.named("compileKotlin") { + dependsOn(tasks.jooqCodegen) } jooq { From 0507ab9d182d1b8d043e445137a2e2a8f4b52f0f Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Mon, 26 Feb 2024 22:16:22 +0100 Subject: [PATCH 21/94] fix: typo in workflow file --- .github/workflows/publish-dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-dev.yml b/.github/workflows/publish-dev.yml index 60f757e..6fc599b 100644 --- a/.github/workflows/publish-dev.yml +++ b/.github/workflows/publish-dev.yml @@ -39,7 +39,7 @@ jobs: # The USERNAME and TOKEN need to correspond to the credentials environment variables used in # the publishing section of your build.gradle - name: Publish to GitHub Packages Dev - run: ./gradlew publish-dev + run: ./gradlew publish env: USERNAME: ${{ github.actor }} TOKEN: ${{ secrets.GITHUB_TOKEN }} From 592366268fb0a5483a396522fc5e638bb902ab2c Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Tue, 27 Feb 2024 21:02:06 +0100 Subject: [PATCH 22/94] feat: numerical id inclusion for server starts --- .../simplecloud/controller/runtime/server/ServerService.kt | 4 +++- controller-shared/src/main/proto/server_host_api.proto | 7 ++++++- 2 files changed, 9 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 949d580..8562796 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 @@ -41,7 +41,9 @@ class ServerService( responseObserver.onCompleted() return; } - stub.startServer(groupDefinition).toCompletable().thenApply { + stub.startServer(StartServerRequest.newBuilder() + .setGroup(groupDefinition) + .setNumericalId(serverRepository.findServersByGroup(groupDefinition.name).size + 1).build()).toCompletable().thenApply { serverRepository.save(Server.fromDefinition(it)) responseObserver.onNext(it) responseObserver.onCompleted() diff --git a/controller-shared/src/main/proto/server_host_api.proto b/controller-shared/src/main/proto/server_host_api.proto index 7ec1715..25d8838 100644 --- a/controller-shared/src/main/proto/server_host_api.proto +++ b/controller-shared/src/main/proto/server_host_api.proto @@ -8,7 +8,12 @@ option java_multiple_files = true; import "controller_types.proto"; import "controller_server_api.proto"; +message StartServerRequest { + GroupDefinition group = 1; + uint32 numerical_id = 2; +} + service ServerHostService { - rpc startServer(GroupDefinition) returns (ServerDefinition); + rpc startServer(StartServerRequest) returns (ServerDefinition); rpc stopServer(ServerIdRequest) returns (StatusResponse); } \ No newline at end of file From 3969fcdc2d1926802e9ffc3b88045554708e4a5b Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Tue, 27 Feb 2024 21:10:08 +0100 Subject: [PATCH 23/94] refactor: update controller version --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 81efc42..a0eecdb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "1.0-SNAPSHOT" + version = "1.0.1-SNAPSHOT" repositories { mavenCentral() From 5f3e83afc901c0220d6864fec35243c43018b9fe Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Sun, 10 Mar 2024 13:13:35 +0100 Subject: [PATCH 24/94] refactor: update group types --- build.gradle.kts | 2 +- .../controller/shared/group/Group.kt | 12 +++++++++--- .../src/main/proto/controller_types.proto | 18 ++++++++++-------- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index a0eecdb..81520c4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "1.0.1-SNAPSHOT" + version = "1.0.2-SNAPSHOT" repositories { mavenCentral() 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 a48092a..b8b6eac 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 @@ -4,7 +4,9 @@ import app.simplecloud.controller.shared.proto.GroupDefinition data class Group( val name: String, - val templateName: String, + val javaUrl: String, + val templateUrl: String, + val templateId: String, val minMemory: Long, val maxMemory: Long, val startPort: Long, @@ -17,7 +19,9 @@ data class Group( fun toDefinition(): GroupDefinition { return GroupDefinition.newBuilder() .setName(name) - .setTemplateName(templateName) + .setJavaUrl(javaUrl) + .setTemplateUrl(templateUrl) + .setTemplateId(templateId) .setMinimumMemory(minMemory) .setMaximumMemory(maxMemory) .setStartPort(startPort) @@ -33,7 +37,9 @@ data class Group( fun fromDefinition(groupDefinition: GroupDefinition): Group { return Group( groupDefinition.name, - groupDefinition.templateName, + groupDefinition.javaUrl, + groupDefinition.templateUrl, + groupDefinition.templateId, groupDefinition.minimumMemory, groupDefinition.maximumMemory, groupDefinition.startPort, diff --git a/controller-shared/src/main/proto/controller_types.proto b/controller-shared/src/main/proto/controller_types.proto index a60548a..64fdbeb 100644 --- a/controller-shared/src/main/proto/controller_types.proto +++ b/controller-shared/src/main/proto/controller_types.proto @@ -7,14 +7,16 @@ option java_multiple_files = true; message GroupDefinition { string name = 1; - string template_name = 2; - uint64 minimum_memory = 3; - uint64 maximum_memory = 4; - uint64 start_port = 5; - uint64 online_servers = 6; - uint64 minimum_online_count = 7; - uint64 maximum_online_count = 8; - map properties = 9; + string java_url = 2; + string template_url = 3; + string template_id = 4; + uint64 minimum_memory = 5; + uint64 maximum_memory = 6; + uint64 start_port = 7; + uint64 online_servers = 8; + uint64 minimum_online_count = 9; + uint64 maximum_online_count = 10; + map properties = 11; } message ServerDefinition { From f78779034e1d264c23bd947afe23fd544529ec25 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Sun, 10 Mar 2024 13:58:15 +0100 Subject: [PATCH 25/94] refactor: update group types --- build.gradle.kts | 2 +- .../controller/shared/group/Group.kt | 9 +++------ .../src/main/proto/controller_types.proto | 19 +++++++++---------- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 81520c4..7e634d3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "1.0.2-SNAPSHOT" + version = "1.0.3-SNAPSHOT" repositories { mavenCentral() 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 b8b6eac..86c25a7 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 @@ -4,8 +4,7 @@ import app.simplecloud.controller.shared.proto.GroupDefinition data class Group( val name: String, - val javaUrl: String, - val templateUrl: String, + val serverUrl: String, val templateId: String, val minMemory: Long, val maxMemory: Long, @@ -19,8 +18,7 @@ data class Group( fun toDefinition(): GroupDefinition { return GroupDefinition.newBuilder() .setName(name) - .setJavaUrl(javaUrl) - .setTemplateUrl(templateUrl) + .setServerUrl(serverUrl) .setTemplateId(templateId) .setMinimumMemory(minMemory) .setMaximumMemory(maxMemory) @@ -37,8 +35,7 @@ data class Group( fun fromDefinition(groupDefinition: GroupDefinition): Group { return Group( groupDefinition.name, - groupDefinition.javaUrl, - groupDefinition.templateUrl, + groupDefinition.serverUrl, groupDefinition.templateId, groupDefinition.minimumMemory, groupDefinition.maximumMemory, diff --git a/controller-shared/src/main/proto/controller_types.proto b/controller-shared/src/main/proto/controller_types.proto index 64fdbeb..f6059d7 100644 --- a/controller-shared/src/main/proto/controller_types.proto +++ b/controller-shared/src/main/proto/controller_types.proto @@ -7,16 +7,15 @@ option java_multiple_files = true; message GroupDefinition { string name = 1; - string java_url = 2; - string template_url = 3; - string template_id = 4; - uint64 minimum_memory = 5; - uint64 maximum_memory = 6; - uint64 start_port = 7; - uint64 online_servers = 8; - uint64 minimum_online_count = 9; - uint64 maximum_online_count = 10; - map properties = 11; + string server_url = 2; + string template_id = 3; + uint64 minimum_memory = 4; + uint64 maximum_memory = 5; + uint64 start_port = 6; + uint64 online_servers = 7; + uint64 minimum_online_count = 8; + uint64 maximum_online_count = 9; + map properties = 10; } message ServerDefinition { From f8775341affd673abdcc8055b1e190af5e47101e Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Sun, 10 Mar 2024 18:10:41 +0100 Subject: [PATCH 26/94] fix: YamlRepository issues, gRPC connection issues --- build.gradle.kts | 2 +- .../controller/runtime/ControllerRuntime.kt | 4 +++- .../controller/runtime/YamlRepository.kt | 24 +++++++++---------- .../runtime/group/GroupRepository.kt | 2 +- .../runtime/host/ServerHostRepository.kt | 2 +- .../src/main/resources/groups.yml | 10 +++++++- .../src/main/resources/hosts.yml | 4 +++- .../controller/shared/group/Group.kt | 4 +++- .../controller/shared/host/ServerHost.kt | 2 ++ 9 files changed, 35 insertions(+), 19 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 7e634d3..c59dcec 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "1.0.3-SNAPSHOT" + version = "1.0.4-SNAPSHOT" repositories { mavenCentral() 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 f5227f8..1dd10ba 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 @@ -7,6 +7,7 @@ import app.simplecloud.controller.runtime.host.ServerHostRepository import app.simplecloud.controller.runtime.server.ServerRepository import app.simplecloud.controller.runtime.server.ServerService import app.simplecloud.controller.shared.db.Database +import app.simplecloud.controller.shared.group.Group import io.grpc.ManagedChannel import io.grpc.ManagedChannelBuilder import io.grpc.Server @@ -42,6 +43,7 @@ class ControllerRuntime { logger.info("Connecting database...") Database.init(databaseConfig) serverRepository = ServerRepository() + serverRepository.load() } private fun startGrpcServer() { @@ -68,7 +70,7 @@ class ControllerRuntime { private fun createManagedChannel(): ManagedChannel { val port = System.getenv("GRPC_PORT")?.toInt() ?: 5816 - return ManagedChannelBuilder.forAddress("localhost", port).build() + return ManagedChannelBuilder.forAddress("localhost", port).usePlaintext().build() } @OptIn(InternalCoroutinesApi::class) diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt index 3725974..2ccc355 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt @@ -2,24 +2,28 @@ package app.simplecloud.controller.runtime import org.spongepowered.configurate.ConfigurationNode import org.spongepowered.configurate.kotlin.extensions.get +import org.spongepowered.configurate.kotlin.objectMapper import org.spongepowered.configurate.kotlin.objectMapperFactory import org.spongepowered.configurate.objectmapping.ConfigSerializable import org.spongepowered.configurate.yaml.YamlConfigurationLoader import java.io.File import java.nio.file.Files import java.nio.file.StandardCopyOption +import java.util.Collections import java.util.concurrent.CompletableFuture -abstract class YamlRepository(path: String) : Repository() { +abstract class YamlRepository(path: String, private var clazz: Class) : Repository() { private lateinit var node: ConfigurationNode + private lateinit var loader: YamlConfigurationLoader private var destination: File = File(path.substring(1, path.length)) init { if(!destination.exists()) { Files.copy(YamlRepository::class.java.getResourceAsStream(path)!!, destination.toPath(), StandardCopyOption.REPLACE_EXISTING) } + load() } - override fun load() { + final override fun load() { val loader = YamlConfigurationLoader.builder() .path(destination.toPath()) .defaultOptions { options -> @@ -27,11 +31,9 @@ abstract class YamlRepository(path: String) : Repository() { builder.registerAnnotatedObjects(objectMapperFactory()) } }.build() + this.loader = loader node = loader.load() - addAll(node.get>()?.items ?: ArrayList()) - forEach { - println(it) - } + addAll(node.getList(clazz) ?: ArrayList()) } override fun delete(element: T): CompletableFuture { @@ -40,7 +42,7 @@ abstract class YamlRepository(path: String) : Repository() { return CompletableFuture.completedFuture(false) } removeAt(index) - node.set(YamlRepositoryType(this)) + node.set(clazz, this) return CompletableFuture.completedFuture(true) } @@ -52,11 +54,9 @@ abstract class YamlRepository(path: String) : Repository() { }else { add(element) } - node.set(YamlRepositoryType(this)) + node.setList(clazz, this) + loader.save(node) } abstract fun findIndex(element: T): Int -} - -@ConfigSerializable -data class YamlRepositoryType(var items: List) \ No newline at end of file +} \ No newline at end of file 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 db0bf5f..fd16c5f 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 @@ -4,7 +4,7 @@ import app.simplecloud.controller.runtime.YamlRepository import app.simplecloud.controller.shared.group.Group import app.simplecloud.controller.shared.proto.GroupDefinition -class GroupRepository(path: String) : YamlRepository(path) { +class GroupRepository(path: String) : YamlRepository(path, Group::class.java) { fun findGroupByName(name: String): GroupDefinition? { return firstOrNull { it.name == name }?.toDefinition() 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 f2ddf85..33f0612 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 @@ -5,7 +5,7 @@ import app.simplecloud.controller.runtime.server.ServerRepository import app.simplecloud.controller.shared.host.ServerHost import java.io.File -class ServerHostRepository(path: String) : YamlRepository(path) { +class ServerHostRepository(path: String) : YamlRepository(path, ServerHost::class.java) { fun findServerHostById(id: String): ServerHost? { return firstOrNull { it.id == id } } diff --git a/controller-runtime/src/main/resources/groups.yml b/controller-runtime/src/main/resources/groups.yml index f54cdfb..32b9dc3 100644 --- a/controller-runtime/src/main/resources/groups.yml +++ b/controller-runtime/src/main/resources/groups.yml @@ -1 +1,9 @@ -items: \ No newline at end of file +- name: Survival + server-url: https://api.papermc.io/v2/projects/paper/versions/1.20.4/builds/450/downloads/paper-1.20.4-450.jar + template-id: default + min-memory: 1024 + max-memory: 1024 + start-port: 25565 + min-online-count: 1 + max-online-count: 2 + properties: {} diff --git a/controller-runtime/src/main/resources/hosts.yml b/controller-runtime/src/main/resources/hosts.yml index f54cdfb..b05f829 100644 --- a/controller-runtime/src/main/resources/hosts.yml +++ b/controller-runtime/src/main/resources/hosts.yml @@ -1 +1,3 @@ -items: \ No newline at end of file +- id: "my-local-server-host" + host: "localhost" + port: "5820" \ No newline at end of file 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 86c25a7..00f56ad 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 @@ -1,7 +1,9 @@ package app.simplecloud.controller.shared.group import app.simplecloud.controller.shared.proto.GroupDefinition +import org.spongepowered.configurate.objectmapping.ConfigSerializable +@ConfigSerializable data class Group( val name: String, val serverUrl: String, @@ -9,7 +11,7 @@ data class Group( val minMemory: Long, val maxMemory: Long, val startPort: Long, - val onlineServers: Long, + @Transient val onlineServers: Long, val minOnlineCount: Long, val maxOnlineCount: Long, val properties: Map, 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 c7f0bdf..a19a4e4 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 @@ -2,7 +2,9 @@ package app.simplecloud.controller.shared.host import io.grpc.ManagedChannel import io.grpc.ManagedChannelBuilder +import org.spongepowered.configurate.objectmapping.ConfigSerializable +@ConfigSerializable data class ServerHost( val id: String, val host: String, From 7eb1dbbdb734e4076a42d8250e26dfe7c295d71b Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Wed, 13 Mar 2024 01:04:52 +0100 Subject: [PATCH 27/94] fix: db issues --- build.gradle.kts | 2 +- .../runtime/server/ServerRepository.kt | 39 +++++++++++-------- .../controller/shared/server/Server.kt | 6 +-- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index c59dcec..c1e87b6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "1.0.4-SNAPSHOT" + version = "1.0.5-SNAPSHOT" repositories { mavenCentral() 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 9bf7007..0084cc6 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 @@ -43,7 +43,7 @@ class ServerRepository : Repository() { it.playerCount.toLong(), propertiesQuery.map { item -> item.key to item.value - }.toMap(), + }.toMap().toMutableMap(), ServerState.valueOf(it.state) )) } @@ -64,15 +64,16 @@ class ServerRepository : Repository() { } override fun save(element: Server) { - val server = firstOrNull { it.uniqueId == element.uniqueId } - if (server != null) { - val index = indexOf(server) - removeAt(index) - add(index, element) - } else { - add(element) - } - db.insertInto( + try { + val server = firstOrNull { it.uniqueId == element.uniqueId } + if (server != null) { + val index = indexOf(server) + removeAt(index) + add(index, element) + } else { + add(element) + } + db.insertInto( CLOUD_SERVERS, CLOUD_SERVERS.UNIQUE_ID, @@ -83,10 +84,10 @@ class ServerRepository : Repository() { CLOUD_SERVERS.IP, CLOUD_SERVERS.PORT, CLOUD_SERVERS.MINIMUM_MEMORY, - CLOUD_SERVERS.MINIMUM_MEMORY, + CLOUD_SERVERS.MAXIMUM_MEMORY, CLOUD_SERVERS.PLAYER_COUNT, CLOUD_SERVERS.STATE, - ).values( + ).values( element.uniqueId, element.group, element.host, @@ -98,19 +99,23 @@ class ServerRepository : Repository() { element.maxMemory.toInt(), element.playerCount.toInt(), element.state.toString() - ).onDuplicateKeyUpdate() - element.properties.forEach { - db.insertInto( + ).onDuplicateKeyUpdate().set(CLOUD_SERVERS.UNIQUE_ID, element.uniqueId).executeAsync() + element.properties.forEach { + db.insertInto( CLOUD_SERVER_PROPERTIES, CLOUD_SERVER_PROPERTIES.SERVER_ID, CLOUD_SERVER_PROPERTIES.KEY, CLOUD_SERVER_PROPERTIES.VALUE - ).values( + ).values( element.uniqueId, it.key, it.value - ).onDuplicateKeyUpdate() + ).onDuplicateKeyUpdate().set(CLOUD_SERVER_PROPERTIES.SERVER_ID, element.uniqueId).executeAsync() + } + } catch (e: Exception) { + e.printStackTrace() + println(e.message) } } 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 35d82e8..d4ff6a6 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,11 +1,7 @@ package app.simplecloud.controller.shared.server -import app.simplecloud.controller.shared.group.Group -import app.simplecloud.controller.shared.host.ServerHost import app.simplecloud.controller.shared.proto.ServerDefinition import app.simplecloud.controller.shared.proto.ServerState -import java.util.* -import kotlin.math.min data class Server( val uniqueId: String, @@ -43,8 +39,8 @@ data class Server( fun fromDefinition(serverDefinition: ServerDefinition): Server { return Server( serverDefinition.uniqueId, - serverDefinition.hostId, serverDefinition.groupName, + serverDefinition.hostId, serverDefinition.numericalId, serverDefinition.templateId, serverDefinition.ip, From 309cea755d6d5c009b6d2c72286e4d41f1847fd5 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Wed, 13 Mar 2024 01:12:36 +0100 Subject: [PATCH 28/94] fix: add support to mutate properties on Server objects --- build.gradle.kts | 2 +- .../kotlin/app/simplecloud/controller/shared/server/Server.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index c1e87b6..5b612d1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "1.0.5-SNAPSHOT" + version = "1.0.6-SNAPSHOT" repositories { mavenCentral() 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 d4ff6a6..d77d844 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 @@ -14,7 +14,7 @@ data class Server( val minMemory: Long, val maxMemory: Long, val playerCount: Long, - val properties: Map, + val properties: MutableMap, val state: ServerState, ) { fun toDefinition(): ServerDefinition { From 8d1054f49b4f5fe3239343927276d3277667f769 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Fri, 15 Mar 2024 10:34:03 +0100 Subject: [PATCH 29/94] impl: add ServerHost on the fly registration and server reattaching --- build.gradle.kts | 2 +- .../controller/runtime/ControllerRuntime.kt | 2 +- .../runtime/host/ServerHostRepository.kt | 18 ++++++++---- .../runtime/server/ServerService.kt | 28 +++++++++++++++++-- .../controller/shared/host/ServerHost.kt | 22 +++++++++++++-- .../main/proto/controller_server_api.proto | 1 + .../src/main/proto/controller_types.proto | 6 ++++ .../src/main/proto/server_host_api.proto | 3 +- 8 files changed, 70 insertions(+), 12 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 5b612d1..32a352f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "1.0.6-SNAPSHOT" + version = "1.0.7-EXPERIMENTAL" repositories { mavenCentral() 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 1dd10ba..4c8c16f 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 @@ -22,7 +22,7 @@ class ControllerRuntime { private val groupRepository: GroupRepository = GroupRepository("/groups.yml") private lateinit var serverRepository: ServerRepository - private val hostRepository: ServerHostRepository = ServerHostRepository("/hosts.yml") + private val hostRepository: ServerHostRepository = ServerHostRepository() private lateinit var databaseConfig: DatabaseConfig private lateinit var reconciler: Reconciler private lateinit var server: Server 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 33f0612..5ac667a 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 @@ -1,11 +1,11 @@ package app.simplecloud.controller.runtime.host -import app.simplecloud.controller.runtime.YamlRepository +import app.simplecloud.controller.runtime.Repository import app.simplecloud.controller.runtime.server.ServerRepository import app.simplecloud.controller.shared.host.ServerHost -import java.io.File +import java.util.concurrent.CompletableFuture -class ServerHostRepository(path: String) : YamlRepository(path, ServerHost::class.java) { +class ServerHostRepository : Repository() { fun findServerHostById(id: String): ServerHost? { return firstOrNull { it.id == id } } @@ -23,8 +23,16 @@ class ServerHostRepository(path: String) : YamlRepository(path, Serv return lastHost } - override fun findIndex(element: ServerHost): Int { - return indexOf(firstOrNull { it.id == element.id }) + override fun load() { + throw UnsupportedOperationException("This method is not implemented.") + } + + override fun delete(element: ServerHost): CompletableFuture { + return CompletableFuture.completedFuture(add(element)) + } + + override fun save(element: ServerHost) { + throw UnsupportedOperationException("This method is not implemented.") } } 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 8562796..6dbf751 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 @@ -4,9 +4,10 @@ import app.simplecloud.controller.runtime.group.GroupRepository import app.simplecloud.controller.runtime.host.ServerHostRepository import app.simplecloud.controller.runtime.host.ServerHostException 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.proto.* import app.simplecloud.controller.shared.server.Server +import app.simplecloud.controller.shared.status.ApiResponse import io.grpc.stub.StreamObserver class ServerService( @@ -14,6 +15,29 @@ class ServerService( private val hostRepository: ServerHostRepository, private val groupRepository: GroupRepository, ) : ControllerServerServiceGrpc.ControllerServerServiceImplBase() { + + private var serversToAttach = mutableListOf() + + init { + serversToAttach.addAll(serverRepository) + } + + override fun attachServerHost(request: ServerHostDefinition, responseObserver: StreamObserver) { + val serverHost = ServerHost.fromDefinition(request) + if(hostRepository.findServerHostById(serverHost.id) != null) { + responseObserver.onNext(ApiResponse("error").toDefinition()) + responseObserver.onCompleted() + return + } + hostRepository.add(serverHost) + val stub = ServerHostServiceGrpc.newFutureStub(serverHost.endpoint) + serversToAttach.filter { it.host == serverHost.id }.forEach { + stub.reattachServer(it.toDefinition()) + } + responseObserver.onNext(ApiResponse("success").toDefinition()) + responseObserver.onCompleted() + } + override fun getServerById(request: ServerIdRequest, responseObserver: StreamObserver) { val server = serverRepository.findServerById(request.id) responseObserver.onNext(server) @@ -67,7 +91,7 @@ class ServerService( return } val stub = ServerHostServiceGrpc.newFutureStub(host.endpoint) - stub.stopServer(request).toCompletable().thenApply { + stub.stopServer(server).toCompletable().thenApply { if (it.status == "success") { serverRepository.delete(Server.fromDefinition(server)) } 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 a19a4e4..dc04635 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,16 +1,34 @@ package app.simplecloud.controller.shared.host +import app.simplecloud.controller.shared.proto.ServerHostDefinition import io.grpc.ManagedChannel import io.grpc.ManagedChannelBuilder -import org.spongepowered.configurate.objectmapping.ConfigSerializable -@ConfigSerializable data class ServerHost( val id: String, val host: String, val port: Int ) { + fun toDefinition(): ServerHostDefinition { + return ServerHostDefinition.newBuilder() + .setHost(host) + .setPort(port) + .setUniqueId(id) + .build() + } + + companion object { + @JvmStatic + fun fromDefinition(serverHostDefinition: ServerHostDefinition): ServerHost { + return ServerHost( + serverHostDefinition.uniqueId, + serverHostDefinition.host, + serverHostDefinition.port + ) + } + } + @Transient val endpoint: ManagedChannel = createChannel() diff --git a/controller-shared/src/main/proto/controller_server_api.proto b/controller-shared/src/main/proto/controller_server_api.proto index 39994f7..68bb5a2 100644 --- a/controller-shared/src/main/proto/controller_server_api.proto +++ b/controller-shared/src/main/proto/controller_server_api.proto @@ -24,4 +24,5 @@ service ControllerServerService { rpc getServerById(ServerIdRequest) returns (ServerDefinition); rpc startServer(GroupNameRequest) returns (ServerDefinition); rpc stopServer(ServerIdRequest) returns (StatusResponse); + rpc attachServerHost(ServerHostDefinition) returns (StatusResponse); } \ No newline at end of file diff --git a/controller-shared/src/main/proto/controller_types.proto b/controller-shared/src/main/proto/controller_types.proto index f6059d7..b82a2b1 100644 --- a/controller-shared/src/main/proto/controller_types.proto +++ b/controller-shared/src/main/proto/controller_types.proto @@ -33,6 +33,12 @@ message ServerDefinition { ServerState state = 12; } +message ServerHostDefinition { + string unique_id = 1; + string host = 2; + uint32 port = 3; +} + enum ServerState { STARTING = 0; AVAILABLE = 1; diff --git a/controller-shared/src/main/proto/server_host_api.proto b/controller-shared/src/main/proto/server_host_api.proto index 25d8838..d0bd17c 100644 --- a/controller-shared/src/main/proto/server_host_api.proto +++ b/controller-shared/src/main/proto/server_host_api.proto @@ -15,5 +15,6 @@ message StartServerRequest { service ServerHostService { rpc startServer(StartServerRequest) returns (ServerDefinition); - rpc stopServer(ServerIdRequest) returns (StatusResponse); + rpc stopServer(ServerDefinition) returns (StatusResponse); + rpc reattachServer(ServerDefinition) returns (StatusResponse); } \ No newline at end of file From 29214d97298e96ec6cc940bbf94366f2bd5fbb2e Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Sat, 16 Mar 2024 13:16:11 +0100 Subject: [PATCH 30/94] refactor: make ServerHost config serializable --- build.gradle.kts | 2 +- .../kotlin/app/simplecloud/controller/shared/host/ServerHost.kt | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 32a352f..0bb096d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "1.0.7-EXPERIMENTAL" + version = "1.0.8-EXPERIMENTAL" repositories { mavenCentral() 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 dc04635..5f3ca68 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 @@ -3,7 +3,9 @@ package app.simplecloud.controller.shared.host import app.simplecloud.controller.shared.proto.ServerHostDefinition import io.grpc.ManagedChannel import io.grpc.ManagedChannelBuilder +import org.spongepowered.configurate.objectmapping.ConfigSerializable +@ConfigSerializable data class ServerHost( val id: String, val host: String, From 48ea1f56183b4755850d74532b4e70d6806f9f32 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Mon, 18 Mar 2024 14:51:34 +0100 Subject: [PATCH 31/94] impl: ServerHost reattaching --- build.gradle.kts | 2 +- .../controller/api/group/impl/GroupApiImpl.kt | 34 ++++----- .../api/server/impl/ServerApiImpl.kt | 50 ++++++------- .../controller/runtime/ControllerRuntime.kt | 12 +-- .../controller/runtime/Reconciler.kt | 18 +++-- .../controller/runtime/YamlRepository.kt | 28 ++++--- .../controller/runtime/group/GroupService.kt | 20 +++-- .../runtime/server/ServerRepository.kt | 31 +++++--- .../runtime/server/ServerService.kt | 65 +++++++++------- .../controller/shared/db/Database.kt | 2 +- .../controller/shared/db/DatabaseConfig.kt | 18 +++-- .../shared/future/ListenableFutureAdapter.kt | 1 + .../controller/shared/group/Group.kt | 42 +++++------ .../controller/shared/host/ServerHost.kt | 6 +- .../controller/shared/server/Server.kt | 74 +++++++++---------- .../controller/shared/status/ApiResponse.kt | 8 +- 16 files changed, 220 insertions(+), 191 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 0bb096d..ca1e1f9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "1.0.8-EXPERIMENTAL" + version = "1.0.9-EXPERIMENTAL" repositories { mavenCentral() diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/impl/GroupApiImpl.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/impl/GroupApiImpl.kt index e40f6c4..7ee5bb5 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/impl/GroupApiImpl.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/impl/GroupApiImpl.kt @@ -14,37 +14,37 @@ class GroupApiImpl : GroupApi { private val managedChannel = Controller.createManagedChannelFromEnv() private val groupServiceStub: ControllerGroupServiceGrpc.ControllerGroupServiceFutureStub = - ControllerGroupServiceGrpc.newFutureStub(managedChannel) + ControllerGroupServiceGrpc.newFutureStub(managedChannel) override fun getGroupByName(name: String): CompletableFuture { return groupServiceStub.getGroupByName( - GetGroupByNameRequest.newBuilder() - .setName(name) - .build() + GetGroupByNameRequest.newBuilder() + .setName(name) + .build() ).toCompletable() - .thenApply { - Group.fromDefinition(it.group) - } + .thenApply { + Group.fromDefinition(it.group) + } } override fun deleteGroup(name: String): CompletableFuture { return groupServiceStub.deleteGroupByName( - GetGroupByNameRequest.newBuilder() - .setName(name) - .build() + GetGroupByNameRequest.newBuilder() + .setName(name) + .build() ).toCompletable() - .thenApply { - ApiResponse.fromDefinition(it) - } + .thenApply { + ApiResponse.fromDefinition(it) + } } override fun createGroup(group: Group): CompletableFuture { return groupServiceStub.createGroup( - group.toDefinition() + group.toDefinition() ).toCompletable() - .thenApply { - ApiResponse.fromDefinition(it) - } + .thenApply { + ApiResponse.fromDefinition(it) + } } } \ No newline at end of file diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt index 7a110b7..e901a07 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt @@ -17,28 +17,28 @@ class ServerApiImpl : ServerApi { private val messageChannel = Controller.createManagedChannelFromEnv() private val serverServiceStub: ControllerServerServiceGrpc.ControllerServerServiceFutureStub = - ControllerServerServiceGrpc.newFutureStub(messageChannel) + ControllerServerServiceGrpc.newFutureStub(messageChannel) override fun getServerById(id: String): CompletableFuture { return serverServiceStub.getServerById( - ServerIdRequest.newBuilder() - .setId(id) - .build() + ServerIdRequest.newBuilder() + .setId(id) + .build() ).toCompletable() - .thenApply { - Server.fromDefinition(it) - } + .thenApply { + Server.fromDefinition(it) + } } override fun getServersByGroup(groupName: String): CompletableFuture> { return serverServiceStub.getServersByGroup( - GroupNameRequest.newBuilder() - .setName(groupName) - .build() + GroupNameRequest.newBuilder() + .setName(groupName) + .build() ).toCompletable() - .thenApply { - Server.fromDefinition(it.serversList) - } + .thenApply { + Server.fromDefinition(it.serversList) + } } override fun getServersByGroup(group: Group): CompletableFuture> { @@ -47,23 +47,23 @@ class ServerApiImpl : ServerApi { override fun startServer(groupName: String): CompletableFuture { return serverServiceStub.startServer( - GroupNameRequest.newBuilder() - .setName(groupName) - .build() + GroupNameRequest.newBuilder() + .setName(groupName) + .build() ).toCompletable() - .thenApply { - Server.fromDefinition(it) - } + .thenApply { + Server.fromDefinition(it) + } } override fun stopServer(id: String): CompletableFuture { return serverServiceStub.stopServer( - ServerIdRequest.newBuilder() - .setId(id) - .build() + ServerIdRequest.newBuilder() + .setId(id) + .build() ).toCompletable() - .thenApply { - ApiResponse.fromDefinition(it) - } + .thenApply { + ApiResponse.fromDefinition(it) + } } } \ 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 4c8c16f..041423d 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 @@ -57,15 +57,15 @@ class ControllerRuntime { private fun startReconciler() { logger.info("Starting Reconciler...") reconciler = Reconciler(groupRepository, serverRepository, createManagedChannel()) - startReconcilerJob(3000L) + startReconcilerJob() } private fun createGrpcServerFromEnv(): Server { val port = System.getenv("GRPC_PORT")?.toInt() ?: 5816 return ServerBuilder.forPort(port) - .addService(GroupService(groupRepository)) - .addService(ServerService(serverRepository, hostRepository, groupRepository)) - .build() + .addService(GroupService(groupRepository)) + .addService(ServerService(serverRepository, hostRepository, groupRepository)) + .build() } private fun createManagedChannel(): ManagedChannel { @@ -74,11 +74,11 @@ class ControllerRuntime { } @OptIn(InternalCoroutinesApi::class) - private fun startReconcilerJob(timeInterval: Long): Job { + private fun startReconcilerJob(): Job { return CoroutineScope(Dispatchers.Default).launch { while (NonCancellable.isActive) { reconciler.reconcile() - delay(timeInterval) + delay(5000L) } } } diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt index 5a23b17..da6a7b4 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt @@ -1,6 +1,7 @@ package app.simplecloud.controller.runtime import app.simplecloud.controller.runtime.group.GroupRepository +import app.simplecloud.controller.runtime.host.ServerHostRepository import app.simplecloud.controller.runtime.server.ServerRepository import app.simplecloud.controller.shared.future.toCompletable import app.simplecloud.controller.shared.proto.ControllerServerServiceGrpc @@ -9,9 +10,9 @@ import io.grpc.ManagedChannel import org.apache.logging.log4j.LogManager class Reconciler( - private val groupRepository: GroupRepository, - private val serverRepository: ServerRepository, - managedChannel: ManagedChannel, + private val groupRepository: GroupRepository, + private val serverRepository: ServerRepository, + managedChannel: ManagedChannel, ) { private val serverStub = ControllerServerServiceGrpc.newFutureStub(managedChannel) @@ -22,11 +23,12 @@ class Reconciler( val full = servers.filter { server -> server.playerCount >= group.maxOnlineCount } - if(servers.size < group.minOnlineCount || (full.size >= servers.size && servers.size < group.maxOnlineCount)) { - serverStub.startServer(GroupNameRequest.newBuilder().setName(group.name).build()).toCompletable().thenApply { - logger.info("Started new instance ${it.uniqueId} of group ${group.name} on ${it.ip}:${it.port}") - }.exceptionally { - logger.error(it) + if (servers.size < group.minOnlineCount || (full.size >= servers.size && servers.size < group.maxOnlineCount)) { + serverStub.startServer(GroupNameRequest.newBuilder().setName(group.name).build()).toCompletable() + .thenApply { + logger.info("Started new instance ${it.uniqueId} of group ${group.name} on ${it.ip}:${it.port}") + }.exceptionally { + logger.error("Could not start a new instance of group ${group.name}: ${it.message}") } } } diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt index 2ccc355..7a18085 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt @@ -17,20 +17,26 @@ abstract class YamlRepository(path: String, private var clazz: Class) : Re private lateinit var node: ConfigurationNode private lateinit var loader: YamlConfigurationLoader private var destination: File = File(path.substring(1, path.length)) + init { - if(!destination.exists()) { - Files.copy(YamlRepository::class.java.getResourceAsStream(path)!!, destination.toPath(), StandardCopyOption.REPLACE_EXISTING) + if (!destination.exists()) { + Files.copy( + YamlRepository::class.java.getResourceAsStream(path)!!, + destination.toPath(), + StandardCopyOption.REPLACE_EXISTING + ) } load() } + final override fun load() { val loader = YamlConfigurationLoader.builder() - .path(destination.toPath()) - .defaultOptions { options -> - options.serializers { builder -> - builder.registerAnnotatedObjects(objectMapperFactory()) - } - }.build() + .path(destination.toPath()) + .defaultOptions { options -> + options.serializers { builder -> + builder.registerAnnotatedObjects(objectMapperFactory()) + } + }.build() this.loader = loader node = loader.load() addAll(node.getList(clazz) ?: ArrayList()) @@ -38,7 +44,7 @@ abstract class YamlRepository(path: String, private var clazz: Class) : Re override fun delete(element: T): CompletableFuture { val index = findIndex(element) - if(index == -1) { + if (index == -1) { return CompletableFuture.completedFuture(false) } removeAt(index) @@ -48,10 +54,10 @@ abstract class YamlRepository(path: String, private var clazz: Class) : Re override fun save(element: T) { val index = findIndex(element) - if(index != -1) { + if (index != -1) { removeAt(index) add(index, element) - }else { + } else { add(element) } node.setList(clazz, this) 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 ba7d8e7..70ac2b3 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 @@ -6,17 +6,17 @@ import app.simplecloud.controller.shared.status.ApiResponse import io.grpc.stub.StreamObserver class GroupService( - private val groupRepository: GroupRepository + private val groupRepository: GroupRepository ) : ControllerGroupServiceGrpc.ControllerGroupServiceImplBase() { override fun getGroupByName( - request: GetGroupByNameRequest, - responseObserver: StreamObserver + request: GetGroupByNameRequest, + responseObserver: StreamObserver ) { val group = groupRepository.findGroupByName(request.name) val response = GetGroupByNameResponse.newBuilder() - .setGroup(group) - .build() + .setGroup(group) + .build() responseObserver.onNext(response) responseObserver.onCompleted() @@ -28,15 +28,14 @@ class GroupService( groupRepository.save(group) responseObserver.onNext(ApiResponse(status = "success").toDefinition()) responseObserver.onCompleted() - }catch (e: Exception) { + } catch (e: Exception) { responseObserver.onError(e) - responseObserver.onCompleted() } } override fun deleteGroupByName(request: GetGroupByNameRequest, responseObserver: StreamObserver) { val groupDefinition = groupRepository.findGroupByName(request.name) - if(groupDefinition == null) { + if (groupDefinition == null) { responseObserver.onNext(ApiResponse(status = "error").toDefinition()) responseObserver.onCompleted() return @@ -44,12 +43,11 @@ class GroupService( val group = Group.fromDefinition(groupDefinition) try { groupRepository.delete(group).thenApply { - responseObserver.onNext(ApiResponse(status = if(it) "success" else "error").toDefinition()) + responseObserver.onNext(ApiResponse(status = if (it) "success" else "error").toDefinition()) responseObserver.onCompleted() } - }catch (e: Exception) { + } catch (e: Exception) { responseObserver.onError(e) - responseObserver.onCompleted() } } 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 0084cc6..dea0ab7 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 @@ -30,7 +30,8 @@ class ServerRepository : Repository() { val query = db.select().from(CLOUD_SERVERS).fetchInto(CLOUD_SERVERS) query.map { val propertiesQuery = db.select().from(CLOUD_SERVER_PROPERTIES).fetchInto(CLOUD_SERVER_PROPERTIES) - add(Server( + add( + Server( it.uniqueId, it.groupName, it.hostId, @@ -42,25 +43,33 @@ class ServerRepository : Repository() { it.maximumMemory.toLong(), it.playerCount.toLong(), propertiesQuery.map { item -> - item.key to item.value + item.key to item.value }.toMap().toMutableMap(), ServerState.valueOf(it.state) - )) + ) + ) } } override fun delete(element: Server): CompletableFuture { val server = firstOrNull { it.uniqueId == element.uniqueId } if (server == null) return CompletableFuture.completedFuture(false) - val canDelete = db.deleteFrom(CLOUD_SERVER_PROPERTIES).where(CLOUD_SERVER_PROPERTIES.SERVER_ID.eq(server.uniqueId)).executeAsync().toCompletableFuture().thenApply { - return@thenApply true + val canDelete = + db.deleteFrom(CLOUD_SERVER_PROPERTIES).where(CLOUD_SERVER_PROPERTIES.SERVER_ID.eq(server.uniqueId)) + .executeAsync().toCompletableFuture().thenApply { + return@thenApply true }.exceptionally { - return@exceptionally false - }.get() - if(!canDelete) return CompletableFuture.completedFuture(false) - return db.deleteFrom(CLOUD_SERVERS).where(CLOUD_SERVERS.UNIQUE_ID.eq(server.uniqueId)).executeAsync().toCompletableFuture().thenApply { - return@thenApply it > 0 && remove(server) - }.exceptionally { return@exceptionally false } + it.printStackTrace() + return@exceptionally false + }.get() + if (!canDelete) return CompletableFuture.completedFuture(false) + return db.deleteFrom(CLOUD_SERVERS).where(CLOUD_SERVERS.UNIQUE_ID.eq(server.uniqueId)).executeAsync() + .toCompletableFuture().thenApply { + return@thenApply it > 0 && remove(server) + }.exceptionally { + it.printStackTrace() + return@exceptionally false + } } override fun save(element: Server) { 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 6dbf751..8a2198c 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,34 +8,42 @@ import app.simplecloud.controller.shared.host.ServerHost import app.simplecloud.controller.shared.proto.* import app.simplecloud.controller.shared.server.Server import app.simplecloud.controller.shared.status.ApiResponse +import io.grpc.Context +import io.grpc.Status +import io.grpc.StatusException +import io.grpc.protobuf.StatusProto import io.grpc.stub.StreamObserver +import org.apache.logging.log4j.LogManager class ServerService( - private val serverRepository: ServerRepository, - private val hostRepository: ServerHostRepository, - private val groupRepository: GroupRepository, + private val serverRepository: ServerRepository, + private val hostRepository: ServerHostRepository, + private val groupRepository: GroupRepository, ) : ControllerServerServiceGrpc.ControllerServerServiceImplBase() { - private var serversToAttach = mutableListOf() - - init { - serversToAttach.addAll(serverRepository) - } + private val logger = LogManager.getLogger(ServerService::class.java) override fun attachServerHost(request: ServerHostDefinition, responseObserver: StreamObserver) { val serverHost = ServerHost.fromDefinition(request) - if(hostRepository.findServerHostById(serverHost.id) != null) { - responseObserver.onNext(ApiResponse("error").toDefinition()) - responseObserver.onCompleted() - return - } + hostRepository.remove(serverHost) hostRepository.add(serverHost) - val stub = ServerHostServiceGrpc.newFutureStub(serverHost.endpoint) - serversToAttach.filter { it.host == serverHost.id }.forEach { - stub.reattachServer(it.toDefinition()) - } + logger.info("Successfully registered ServerHost ${serverHost.id}.") responseObserver.onNext(ApiResponse("success").toDefinition()) responseObserver.onCompleted() + Context.current().fork().run { + val stub = ServerHostServiceGrpc.newFutureStub(serverHost.endpoint) + serverRepository.filter { it.host == serverHost.id }.forEach { + logger.info("Reattaching Server ${it.uniqueId} of group ${it.group}...") + val status = ApiResponse.fromDefinition(stub.reattachServer(it.toDefinition()).toCompletable().get()) + if (status.status == "success") { + logger.info("Success!") + } else { + logger.error("Server was found to be offline, unregistering...") + serverRepository.delete(it) + } + } + } + } override fun getServerById(request: ServerIdRequest, responseObserver: StreamObserver) { @@ -44,7 +52,10 @@ class ServerService( responseObserver.onCompleted() } - override fun getServersByGroup(request: GroupNameRequest, responseObserver: StreamObserver) { + override fun getServersByGroup( + request: GroupNameRequest, + responseObserver: StreamObserver + ) { val servers = serverRepository.findServersByGroup(request.name) val response = GetServersByGroupResponse.newBuilder().addAllServers(servers).build() responseObserver.onNext(response) @@ -55,25 +66,25 @@ class ServerService( val host = hostRepository.findLaziestServerHost(serverRepository) if (host == null) { responseObserver.onError(ServerHostException("No server host found, could not start server.")) - responseObserver.onCompleted() - return; + return } val stub = ServerHostServiceGrpc.newFutureStub(host.endpoint) val groupDefinition = groupRepository.findGroupByName(request.name) if (groupDefinition == null) { responseObserver.onError(IllegalArgumentException("No group was found matching the group name.")) - responseObserver.onCompleted() - return; + return } - stub.startServer(StartServerRequest.newBuilder() - .setGroup(groupDefinition) - .setNumericalId(serverRepository.findServersByGroup(groupDefinition.name).size + 1).build()).toCompletable().thenApply { + stub.startServer( + StartServerRequest.newBuilder() + .setGroup(groupDefinition) + .setNumericalId(serverRepository.findServersByGroup(groupDefinition.name).size + 1).build() + ).toCompletable().thenApply { serverRepository.save(Server.fromDefinition(it)) responseObserver.onNext(it) responseObserver.onCompleted() + return@thenApply }.exceptionally { responseObserver.onError(ServerHostException("Could not start server, aborting.")) - responseObserver.onCompleted() } } @@ -81,13 +92,11 @@ class ServerService( val server = serverRepository.findServerById(request.id) if (server == null) { responseObserver.onError(IllegalArgumentException("No server was found matching this id.")) - responseObserver.onCompleted() return } val host = hostRepository.findServerHostById(server.hostId) if (host == null) { responseObserver.onError(ServerHostException("No server host was found matching this server.")) - responseObserver.onCompleted() return } val stub = ServerHostServiceGrpc.newFutureStub(host.endpoint) diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/Database.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/Database.kt index a0a0bb1..999f765 100644 --- a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/Database.kt +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/Database.kt @@ -17,7 +17,7 @@ class Database { System.setProperty("org.jooq.no-logo", "true") System.setProperty("org.jooq.no-tips", "true") val setupInputStream = Database::class.java.getResourceAsStream("/schema.sql") - ?: throw IllegalArgumentException("Database schema not found.") + ?: throw IllegalArgumentException("Database schema not found.") val setupCommands = setupInputStream.bufferedReader().use { it.readText() }.split(";") setupCommands.forEach { val trimmed = it.trim() diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/DatabaseConfig.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/DatabaseConfig.kt index fb8be3b..dd5e4c7 100644 --- a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/DatabaseConfig.kt +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/DatabaseConfig.kt @@ -20,15 +20,19 @@ data class DatabaseConfig(var driver: String = "jdbc:sqlite", var url: String) { } val destination = File(path.substring(1, path.length)) if (!destination.exists()) { - Files.copy(DatabaseConfig::class.java.getResourceAsStream(path)!!, destination.toPath(), StandardCopyOption.REPLACE_EXISTING) + Files.copy( + DatabaseConfig::class.java.getResourceAsStream(path)!!, + destination.toPath(), + StandardCopyOption.REPLACE_EXISTING + ) } val loader = YamlConfigurationLoader.builder() - .path(destination.toPath()) - .defaultOptions { options -> - options.serializers { builder -> - builder.registerAnnotatedObjects(objectMapperFactory()) - } - }.build() + .path(destination.toPath()) + .defaultOptions { options -> + options.serializers { builder -> + builder.registerAnnotatedObjects(objectMapperFactory()) + } + }.build() val node = loader.load() val config = node.get() return config diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/future/ListenableFutureAdapter.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/future/ListenableFutureAdapter.kt index f91c395..013f43d 100644 --- a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/future/ListenableFutureAdapter.kt +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/future/ListenableFutureAdapter.kt @@ -30,6 +30,7 @@ class ListenableFutureAdapter( } }, ForkJoinPool.commonPool()) } + companion object { fun toCompletable(listenableFuture: ListenableFuture): CompletableFuture { val listenableFutureAdapter: ListenableFutureAdapter = ListenableFutureAdapter(listenableFuture) 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 00f56ad..d48c27b 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 @@ -19,33 +19,33 @@ data class Group( fun toDefinition(): GroupDefinition { return GroupDefinition.newBuilder() - .setName(name) - .setServerUrl(serverUrl) - .setTemplateId(templateId) - .setMinimumMemory(minMemory) - .setMaximumMemory(maxMemory) - .setStartPort(startPort) - .setOnlineServers(onlineServers) - .setMinimumOnlineCount(minOnlineCount) - .setMaximumOnlineCount(maxOnlineCount) - .putAllProperties(properties) - .build() + .setName(name) + .setServerUrl(serverUrl) + .setTemplateId(templateId) + .setMinimumMemory(minMemory) + .setMaximumMemory(maxMemory) + .setStartPort(startPort) + .setOnlineServers(onlineServers) + .setMinimumOnlineCount(minOnlineCount) + .setMaximumOnlineCount(maxOnlineCount) + .putAllProperties(properties) + .build() } companion object { @JvmStatic fun fromDefinition(groupDefinition: GroupDefinition): Group { return Group( - groupDefinition.name, - groupDefinition.serverUrl, - groupDefinition.templateId, - groupDefinition.minimumMemory, - groupDefinition.maximumMemory, - groupDefinition.startPort, - groupDefinition.onlineServers, - groupDefinition.minimumOnlineCount, - groupDefinition.maximumOnlineCount, - groupDefinition.propertiesMap + groupDefinition.name, + groupDefinition.serverUrl, + groupDefinition.templateId, + groupDefinition.minimumMemory, + groupDefinition.maximumMemory, + groupDefinition.startPort, + groupDefinition.onlineServers, + groupDefinition.minimumOnlineCount, + groupDefinition.maximumOnlineCount, + groupDefinition.propertiesMap ) } } 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 5f3ca68..23bc0d6 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 @@ -7,9 +7,9 @@ import org.spongepowered.configurate.objectmapping.ConfigSerializable @ConfigSerializable data class ServerHost( - val id: String, - val host: String, - val port: Int + val id: String, + val host: String, + val port: Int ) { fun toDefinition(): ServerHostDefinition { 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 d77d844..a276fa6 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 @@ -4,52 +4,52 @@ import app.simplecloud.controller.shared.proto.ServerDefinition import app.simplecloud.controller.shared.proto.ServerState data class Server( - val uniqueId: String, - val group: String, - val host: String?, - val numericalId: Int, - val templateId: String, - val ip: String, - val port: Long, - val minMemory: Long, - val maxMemory: Long, - val playerCount: Long, - val properties: MutableMap, - val state: ServerState, + val uniqueId: String, + val group: String, + val host: String?, + val numericalId: Int, + val templateId: String, + val ip: String, + val port: Long, + val minMemory: Long, + val maxMemory: Long, + val playerCount: Long, + val properties: MutableMap, + val state: ServerState, ) { fun toDefinition(): ServerDefinition { return ServerDefinition.newBuilder() - .setUniqueId(uniqueId) - .setGroupName(group) - .setHostId(host) - .setIp(ip) - .setPort(port) - .setState(state) - .setMinimumMemory(minMemory) - .setMaximumMemory(maxMemory) - .setPlayerCount(playerCount) - .putAllProperties(properties) - .setTemplateId(templateId) - .setNumericalId(numericalId) - .build() + .setUniqueId(uniqueId) + .setGroupName(group) + .setHostId(host) + .setIp(ip) + .setPort(port) + .setState(state) + .setMinimumMemory(minMemory) + .setMaximumMemory(maxMemory) + .setPlayerCount(playerCount) + .putAllProperties(properties) + .setTemplateId(templateId) + .setNumericalId(numericalId) + .build() } companion object { @JvmStatic fun fromDefinition(serverDefinition: ServerDefinition): Server { return Server( - serverDefinition.uniqueId, - serverDefinition.groupName, - serverDefinition.hostId, - serverDefinition.numericalId, - serverDefinition.templateId, - serverDefinition.ip, - serverDefinition.port, - serverDefinition.minimumMemory, - serverDefinition.maximumMemory, - serverDefinition.playerCount, - serverDefinition.propertiesMap, - serverDefinition.state + serverDefinition.uniqueId, + serverDefinition.groupName, + serverDefinition.hostId, + serverDefinition.numericalId, + serverDefinition.templateId, + serverDefinition.ip, + serverDefinition.port, + serverDefinition.minimumMemory, + serverDefinition.maximumMemory, + serverDefinition.playerCount, + serverDefinition.propertiesMap, + serverDefinition.state ) } diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/status/ApiResponse.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/status/ApiResponse.kt index d2374bd..fdfd97e 100644 --- a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/status/ApiResponse.kt +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/status/ApiResponse.kt @@ -3,19 +3,19 @@ package app.simplecloud.controller.shared.status import app.simplecloud.controller.shared.proto.StatusResponse data class ApiResponse( - val status: String + val status: String ) { fun toDefinition(): StatusResponse { return StatusResponse.newBuilder() - .setStatus(status) - .build() + .setStatus(status) + .build() } companion object { @JvmStatic fun fromDefinition(statusResponse: StatusResponse): ApiResponse { return ApiResponse( - statusResponse.status + statusResponse.status ) } } From 234cd89bffcdd94e010dadd2736f73099fbe5733 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Mon, 18 Mar 2024 14:54:38 +0100 Subject: [PATCH 32/94] update: readme.md --- readme.md | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/readme.md b/readme.md index 863d718..a8c789c 100644 --- a/readme.md +++ b/readme.md @@ -10,25 +10,15 @@ At least one [ServerHost](#serverhosts) is needed to actually start servers. ## ServerHosts ServerHosts are processes, that directly handle minecraft server deployments. Each root-server should have exactly one ServerHost online. We provide a [default implementation](), -however, you can write your [own implementation](). To enable the Controller to communicate with ServerHosts, you need to add the ServerHost to the controllers' configuration. You can have as many ServerHost instances as you like. - -### Example hosts.yml -````yaml -items: - # ServerHost on same machine - - id: 'my-local-server-host-1' - ip: 127.0.0.1 - port: 12345 - # ServerHost on different machine - - id: 'my-external-server-host-1' - ip: 123.23.12.1 - port: 12344 -```` +however, you can write your [own implementation](). You can have as many ServerHost instances as you like. ## API usage The SimpleCloud v3 Controller provides API for both server groups and actual servers. The group API is used for [CRUD-Operations](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) of server groups, whereas the server API is used to manage running servers or starting new ones. ````kotlin +//Initializing a Controller connection +Controller.connect() + //Getting the Server API val serverApi = Controller.serverApi From 005217b8d27d48c288305c7673b49830a7fb0e1c Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Fri, 22 Mar 2024 18:25:32 +0100 Subject: [PATCH 33/94] refactor: make port generation a controller task --- build.gradle.kts | 2 +- .../simplecloud/controller/runtime/server/ServerService.kt | 6 +++++- .../kotlin/app/simplecloud/controller/shared/group/Group.kt | 3 --- controller-shared/src/main/proto/server_host_api.proto | 1 + 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index ca1e1f9..551f959 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "1.0.9-EXPERIMENTAL" + version = "1.0.10-EXPERIMENTAL" repositories { mavenCentral() 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 8a2198c..ab8e820 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 @@ -74,10 +74,14 @@ class ServerService( responseObserver.onError(IllegalArgumentException("No group was found matching the group name.")) return } + val numericalId = serverRepository.findServersByGroup(groupDefinition.name).size + 1 stub.startServer( StartServerRequest.newBuilder() .setGroup(groupDefinition) - .setNumericalId(serverRepository.findServersByGroup(groupDefinition.name).size + 1).build() + .setNumericalId(numericalId) + //TODO: Smart port generation + .setPort((groupDefinition.startPort + numericalId - 1).toInt()) + .build() ).toCompletable().thenApply { serverRepository.save(Server.fromDefinition(it)) responseObserver.onNext(it) 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 d48c27b..43c5f16 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,6 @@ import org.spongepowered.configurate.objectmapping.ConfigSerializable data class Group( val name: String, val serverUrl: String, - val templateId: String, val minMemory: Long, val maxMemory: Long, val startPort: Long, @@ -21,7 +20,6 @@ data class Group( return GroupDefinition.newBuilder() .setName(name) .setServerUrl(serverUrl) - .setTemplateId(templateId) .setMinimumMemory(minMemory) .setMaximumMemory(maxMemory) .setStartPort(startPort) @@ -38,7 +36,6 @@ data class Group( return Group( groupDefinition.name, groupDefinition.serverUrl, - groupDefinition.templateId, groupDefinition.minimumMemory, groupDefinition.maximumMemory, groupDefinition.startPort, diff --git a/controller-shared/src/main/proto/server_host_api.proto b/controller-shared/src/main/proto/server_host_api.proto index d0bd17c..63214e8 100644 --- a/controller-shared/src/main/proto/server_host_api.proto +++ b/controller-shared/src/main/proto/server_host_api.proto @@ -11,6 +11,7 @@ import "controller_server_api.proto"; message StartServerRequest { GroupDefinition group = 1; uint32 numerical_id = 2; + uint32 port = 3; } service ServerHostService { From 1c1a1da171fcf9c78458489b4b5af8b8b93c2a9f Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Sat, 23 Mar 2024 00:10:50 +0100 Subject: [PATCH 34/94] refactor: remove gh-packages --- .github/workflows/publish-dev.yml | 45 ------------------------------- build.gradle.kts | 27 +++++++------------ 2 files changed, 9 insertions(+), 63 deletions(-) delete mode 100644 .github/workflows/publish-dev.yml diff --git a/.github/workflows/publish-dev.yml b/.github/workflows/publish-dev.yml deleted file mode 100644 index 6fc599b..0000000 --- a/.github/workflows/publish-dev.yml +++ /dev/null @@ -1,45 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. -# This workflow will build a package using Gradle and then publish it to GitHub packages when a release is created -# For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle - -name: Gradle Package Dev - -on: - push: - branches: - - develop - -jobs: - build: - - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - - steps: - - uses: actions/checkout@v4 - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - server-id: github # Value of the distributionManagement/repository/id field of the pom.xml - settings-path: ${{ github.workspace }} # location for the settings.xml file - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0 - - - name: Build with Gradle - run: ./gradlew build - - # The USERNAME and TOKEN need to correspond to the credentials environment variables used in - # the publishing section of your build.gradle - - name: Publish to GitHub Packages Dev - run: ./gradlew publish - env: - USERNAME: ${{ github.actor }} - TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/build.gradle.kts b/build.gradle.kts index 551f959..c8217f4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,6 +5,7 @@ import java.net.URI plugins { alias(libs.plugins.kotlin) alias(libs.plugins.shadow) + `maven-publish` } allprojects { @@ -22,24 +23,6 @@ subprojects { apply(plugin = "com.github.johnrengelman.shadow") apply(plugin = "maven-publish") - configure { - repositories { - maven { - name = "GitHubPackages" - url = URI.create("https://maven.pkg.github.com/theSimpleCloud/simplecloud-controller") - credentials { - username = System.getenv("USERNAME") - password = System.getenv("TOKEN") - } - } - } - publications { - register("gpr") { - from(components["java"]) - } - } - } - dependencies { testImplementation(rootProject.libs.kotlinTest) implementation(rootProject.libs.kotlinJvm) @@ -49,6 +32,14 @@ subprojects { jvmToolchain(17) } + publishing { + publications { + create("mavenJava") { + from(components["java"]) + } + } + } + tasks.withType { kotlinOptions.jvmTarget = "17" } From 353f8e6f4331a18007512f981414711e2c958783 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Thu, 28 Mar 2024 18:17:59 +0100 Subject: [PATCH 35/94] impl: controller args, numerical id fix --- build.gradle.kts | 2 +- .../controller/runtime/ControllerRuntime.kt | 6 +++--- .../controller/runtime/group/GroupRepository.kt | 6 +----- .../controller/runtime/launcher/Launcher.kt | 14 ++++++++++++-- .../controller/runtime/server/ServerRepository.kt | 8 ++++++++ .../controller/runtime/server/ServerService.kt | 5 +---- .../src/main/proto/server_host_api.proto | 1 - 7 files changed, 26 insertions(+), 16 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index c8217f4..f198626 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,7 +11,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "1.0.10-EXPERIMENTAL" + version = "1.0.11-EXPERIMENTAL" repositories { mavenCentral() 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 041423d..afc6491 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 @@ -7,7 +7,6 @@ import app.simplecloud.controller.runtime.host.ServerHostRepository import app.simplecloud.controller.runtime.server.ServerRepository import app.simplecloud.controller.runtime.server.ServerService import app.simplecloud.controller.shared.db.Database -import app.simplecloud.controller.shared.group.Group import io.grpc.ManagedChannel import io.grpc.ManagedChannelBuilder import io.grpc.Server @@ -20,14 +19,15 @@ class ControllerRuntime { private val logger = LogManager.getLogger(ControllerRuntime::class.java) - private val groupRepository: GroupRepository = GroupRepository("/groups.yml") + private lateinit var groupRepository: GroupRepository private lateinit var serverRepository: ServerRepository private val hostRepository: ServerHostRepository = ServerHostRepository() private lateinit var databaseConfig: DatabaseConfig private lateinit var reconciler: Reconciler private lateinit var server: Server - fun start() { + fun start(args: MutableMap) { + groupRepository = GroupRepository("/groups.yml", args.getOrDefault("groups-path", null)) logger.info("Starting database...") loadDB() logger.info("Starting controller...") 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 fd16c5f..21d05db 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 @@ -4,15 +4,11 @@ import app.simplecloud.controller.runtime.YamlRepository import app.simplecloud.controller.shared.group.Group import app.simplecloud.controller.shared.proto.GroupDefinition -class GroupRepository(path: String) : YamlRepository(path, Group::class.java) { - +class GroupRepository(path: String, parent: String?) : YamlRepository(if(parent != null) "$parent$path" else path, Group::class.java) { fun findGroupByName(name: String): GroupDefinition? { return firstOrNull { it.name == name }?.toDefinition() } - override fun findIndex(element: Group): Int { return indexOf(firstOrNull { it.name == element.name }) } - - } \ No newline at end of file diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/Launcher.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/Launcher.kt index 60744fe..bdddb16 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/Launcher.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/Launcher.kt @@ -2,7 +2,17 @@ package app.simplecloud.controller.runtime.launcher import app.simplecloud.controller.runtime.ControllerRuntime -fun main() { + +fun main(args: Array) { + val arguments: MutableMap = HashMap() + for (arg in args) { + if (arg.startsWith("--") && arg.contains("=")) { + val parts = arg.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + val key = parts[0].substring(2) + val value = parts[1] + arguments[key] = value + } + } val controllerRuntime = ControllerRuntime() - controllerRuntime.start() + controllerRuntime.start(arguments) } \ No newline at end of file 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 dea0ab7..cc1b5c2 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 @@ -25,6 +25,14 @@ class ServerRepository : Repository() { return filter { server -> server.group == group }.map { server -> server.toDefinition() } } + fun findNextNumericalId(group: String): Int { + var id = 1 + findServersByGroup(group).sortedWith(compareBy { it.numericalId }).forEach { + if(it.numericalId == id) id++ + } + return id + } + override fun load() { clear() val query = db.select().from(CLOUD_SERVERS).fetchInto(CLOUD_SERVERS) 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 ab8e820..dd2e607 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 @@ -74,13 +74,10 @@ class ServerService( responseObserver.onError(IllegalArgumentException("No group was found matching the group name.")) return } - val numericalId = serverRepository.findServersByGroup(groupDefinition.name).size + 1 stub.startServer( StartServerRequest.newBuilder() .setGroup(groupDefinition) - .setNumericalId(numericalId) - //TODO: Smart port generation - .setPort((groupDefinition.startPort + numericalId - 1).toInt()) + .setNumericalId(serverRepository.findNextNumericalId(groupDefinition.name)) .build() ).toCompletable().thenApply { serverRepository.save(Server.fromDefinition(it)) diff --git a/controller-shared/src/main/proto/server_host_api.proto b/controller-shared/src/main/proto/server_host_api.proto index 63214e8..d0bd17c 100644 --- a/controller-shared/src/main/proto/server_host_api.proto +++ b/controller-shared/src/main/proto/server_host_api.proto @@ -11,7 +11,6 @@ import "controller_server_api.proto"; message StartServerRequest { GroupDefinition group = 1; uint32 numerical_id = 2; - uint32 port = 3; } service ServerHostService { From b919aa7e349d2265e42149aca3ee41f920291250 Mon Sep 17 00:00:00 2001 From: Philipp Date: Thu, 28 Mar 2024 18:23:28 +0100 Subject: [PATCH 36/94] fix: npe in group config --- controller-runtime/src/main/resources/groups.yml | 5 +++-- .../kotlin/app/simplecloud/controller/shared/group/Group.kt | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/controller-runtime/src/main/resources/groups.yml b/controller-runtime/src/main/resources/groups.yml index 32b9dc3..e932f7c 100644 --- a/controller-runtime/src/main/resources/groups.yml +++ b/controller-runtime/src/main/resources/groups.yml @@ -1,4 +1,4 @@ -- name: Survival +- name: lobby server-url: https://api.papermc.io/v2/projects/paper/versions/1.20.4/builds/450/downloads/paper-1.20.4-450.jar template-id: default min-memory: 1024 @@ -6,4 +6,5 @@ start-port: 25565 min-online-count: 1 max-online-count: 2 - properties: {} + properties: + configurator: spigot 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 43c5f16..a39d79d 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 @@ -10,7 +10,7 @@ data class Group( val minMemory: Long, val maxMemory: Long, val startPort: Long, - @Transient val onlineServers: Long, + @Transient val onlineServers: Long?, val minOnlineCount: Long, val maxOnlineCount: Long, val properties: Map, @@ -23,7 +23,7 @@ data class Group( .setMinimumMemory(minMemory) .setMaximumMemory(maxMemory) .setStartPort(startPort) - .setOnlineServers(onlineServers) + .setOnlineServers(onlineServers?: 0) .setMinimumOnlineCount(minOnlineCount) .setMaximumOnlineCount(maxOnlineCount) .putAllProperties(properties) From 59eb2624b1a548cdb14a48697e118a5da11dc397 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Thu, 28 Mar 2024 22:32:47 +0100 Subject: [PATCH 37/94] refactor: add prepared state to server --- build.gradle.kts | 2 +- .../runtime/server/ServerService.kt | 16 +++-- .../controller/shared/server/ServerFactory.kt | 63 +++++++++++++++++++ .../src/main/proto/controller_types.proto | 9 +-- .../src/main/proto/server_host_api.proto | 3 +- 5 files changed, 81 insertions(+), 12 deletions(-) create mode 100644 controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt diff --git a/build.gradle.kts b/build.gradle.kts index f198626..446b36a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,7 +11,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "1.0.11-EXPERIMENTAL" + version = "1.0.12-EXPERIMENTAL" repositories { mavenCentral() 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 dd2e607..6ef489f 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 @@ -4,9 +4,11 @@ import app.simplecloud.controller.runtime.group.GroupRepository import app.simplecloud.controller.runtime.host.ServerHostRepository import app.simplecloud.controller.runtime.host.ServerHostException 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.proto.* import app.simplecloud.controller.shared.server.Server +import app.simplecloud.controller.shared.server.ServerFactory import app.simplecloud.controller.shared.status.ApiResponse import io.grpc.Context import io.grpc.Status @@ -14,6 +16,7 @@ import io.grpc.StatusException import io.grpc.protobuf.StatusProto import io.grpc.stub.StreamObserver import org.apache.logging.log4j.LogManager +import java.util.* class ServerService( private val serverRepository: ServerRepository, @@ -74,17 +77,18 @@ class ServerService( responseObserver.onError(IllegalArgumentException("No group was found matching the group name.")) return } - stub.startServer( - StartServerRequest.newBuilder() - .setGroup(groupDefinition) - .setNumericalId(serverRepository.findNextNumericalId(groupDefinition.name)) - .build() - ).toCompletable().thenApply { + val server = ServerFactory.builder() + .setGroup(Group.fromDefinition(groupDefinition)) + .setNumericalId(serverRepository.findNextNumericalId(groupDefinition.name).toLong()) + .build() + serverRepository.save(server) + stub.startServer(server.toDefinition()).toCompletable().thenApply { serverRepository.save(Server.fromDefinition(it)) responseObserver.onNext(it) responseObserver.onCompleted() return@thenApply }.exceptionally { + serverRepository.delete(server) responseObserver.onError(ServerHostException("Could not start server, aborting.")) } } diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt new file mode 100644 index 0000000..ffab214 --- /dev/null +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt @@ -0,0 +1,63 @@ +package app.simplecloud.controller.shared.server + +import app.simplecloud.controller.shared.group.Group +import app.simplecloud.controller.shared.host.ServerHost +import app.simplecloud.controller.shared.proto.ServerState +import java.util.* +import kotlin.properties.Delegates + +class ServerFactory { + + companion object { + fun builder(): ServerFactory { + return ServerFactory() + } + } + + private lateinit var group: Group + + fun setGroup(group: Group): ServerFactory { + this.group = group + return this + } + + private var host: ServerHost? = null + + fun setHost(host: ServerHost): ServerFactory { + this.host = host + return this + } + + private var numericalId by Delegates.notNull() + + fun setNumericalId(numericalId: Long): ServerFactory { + this.numericalId = numericalId + return this + } + private var port: Long? = null + fun setPort(port: Long): ServerFactory { + this.port = port + return this + } + + fun build(): Server { + return Server( + uniqueId = UUID.randomUUID().toString().replace("-", ""), + port = port ?: -1, + group = group.name, + minMemory = group.minMemory, + maxMemory = group.maxMemory, + host = host?.id ?: "unknown", + ip = host?.host ?: "unknown", + state = ServerState.PREPARING, + numericalId = numericalId.toInt(), + playerCount = 0, + templateId = "", + properties = mutableMapOf( + "serverUrl" to group.serverUrl, + *group.properties.entries.map { it.key to it.value }.toTypedArray() + ) + ) + } + +} \ No newline at end of file diff --git a/controller-shared/src/main/proto/controller_types.proto b/controller-shared/src/main/proto/controller_types.proto index b82a2b1..a27d33d 100644 --- a/controller-shared/src/main/proto/controller_types.proto +++ b/controller-shared/src/main/proto/controller_types.proto @@ -40,10 +40,11 @@ message ServerHostDefinition { } enum ServerState { - STARTING = 0; - AVAILABLE = 1; - INGAME = 2; - STOPPING = 3; + PREPARING = 0; + STARTING = 1; + AVAILABLE = 2; + INGAME = 3; + STOPPING = 4; } message StatusResponse { diff --git a/controller-shared/src/main/proto/server_host_api.proto b/controller-shared/src/main/proto/server_host_api.proto index d0bd17c..9eb44a6 100644 --- a/controller-shared/src/main/proto/server_host_api.proto +++ b/controller-shared/src/main/proto/server_host_api.proto @@ -11,10 +11,11 @@ import "controller_server_api.proto"; message StartServerRequest { GroupDefinition group = 1; uint32 numerical_id = 2; + string unique_id = 3; } service ServerHostService { - rpc startServer(StartServerRequest) returns (ServerDefinition); + rpc startServer(ServerDefinition) returns (ServerDefinition); rpc stopServer(ServerDefinition) returns (StatusResponse); rpc reattachServer(ServerDefinition) returns (StatusResponse); } \ No newline at end of file From ec42e7ccd66979593674c332b858120ae985a414 Mon Sep 17 00:00:00 2001 From: Philipp Date: Thu, 28 Mar 2024 22:34:12 +0100 Subject: [PATCH 38/94] refactor: improve reconiler and numerical ids --- .../controller/runtime/ControllerRuntime.kt | 10 +- .../controller/runtime/Reconciler.kt | 111 ++++++++++++++---- .../runtime/host/ServerHostRepository.kt | 8 ++ .../server/ServerNumericalIdRepository.kt | 34 ++++++ .../runtime/server/ServerRepository.kt | 20 ++-- .../runtime/server/ServerService.kt | 10 +- 6 files changed, 154 insertions(+), 39 deletions(-) create mode 100644 controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerNumericalIdRepository.kt 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 afc6491..05d5e61 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 @@ -4,6 +4,7 @@ import app.simplecloud.controller.shared.db.DatabaseConfig import app.simplecloud.controller.runtime.group.GroupRepository import app.simplecloud.controller.runtime.group.GroupService import app.simplecloud.controller.runtime.host.ServerHostRepository +import app.simplecloud.controller.runtime.server.ServerNumericalIdRepository import app.simplecloud.controller.runtime.server.ServerRepository import app.simplecloud.controller.runtime.server.ServerService import app.simplecloud.controller.shared.db.Database @@ -20,6 +21,7 @@ class ControllerRuntime { private val logger = LogManager.getLogger(ControllerRuntime::class.java) private lateinit var groupRepository: GroupRepository + private val numericalIdRepository = ServerNumericalIdRepository() private lateinit var serverRepository: ServerRepository private val hostRepository: ServerHostRepository = ServerHostRepository() private lateinit var databaseConfig: DatabaseConfig @@ -42,7 +44,7 @@ class ControllerRuntime { databaseConfig = DatabaseConfig.load("/database-config.yml")!! logger.info("Connecting database...") Database.init(databaseConfig) - serverRepository = ServerRepository() + serverRepository = ServerRepository(numericalIdRepository) serverRepository.load() } @@ -56,7 +58,7 @@ class ControllerRuntime { private fun startReconciler() { logger.info("Starting Reconciler...") - reconciler = Reconciler(groupRepository, serverRepository, createManagedChannel()) + reconciler = Reconciler(groupRepository, serverRepository, hostRepository, createManagedChannel()) startReconcilerJob() } @@ -64,7 +66,7 @@ class ControllerRuntime { val port = System.getenv("GRPC_PORT")?.toInt() ?: 5816 return ServerBuilder.forPort(port) .addService(GroupService(groupRepository)) - .addService(ServerService(serverRepository, hostRepository, groupRepository)) + .addService(ServerService(numericalIdRepository, serverRepository, hostRepository, groupRepository)) .build() } @@ -78,7 +80,7 @@ class ControllerRuntime { return CoroutineScope(Dispatchers.Default).launch { while (NonCancellable.isActive) { reconciler.reconcile() - delay(5000L) + delay(2000L) } } } diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt index da6a7b4..a2a4e41 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt @@ -4,34 +4,105 @@ import app.simplecloud.controller.runtime.group.GroupRepository import app.simplecloud.controller.runtime.host.ServerHostRepository 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.proto.ControllerServerServiceGrpc import app.simplecloud.controller.shared.proto.GroupNameRequest +import app.simplecloud.controller.shared.proto.ServerState import io.grpc.ManagedChannel import org.apache.logging.log4j.LogManager +import java.util.concurrent.ConcurrentHashMap class Reconciler( - private val groupRepository: GroupRepository, - private val serverRepository: ServerRepository, - managedChannel: ManagedChannel, + private val groupRepository: GroupRepository, + private val serverRepository: ServerRepository, + private val serverHostRepository: ServerHostRepository, + managedChannel: ManagedChannel, ) { - private val serverStub = ControllerServerServiceGrpc.newFutureStub(managedChannel) - private val logger = LogManager.getLogger(Reconciler::class.java) - fun reconcile() { - groupRepository.forEach { group -> - val servers = serverRepository.findServersByGroup(group.name) - val full = servers.filter { server -> - server.playerCount >= group.maxOnlineCount - } - if (servers.size < group.minOnlineCount || (full.size >= servers.size && servers.size < group.maxOnlineCount)) { - serverStub.startServer(GroupNameRequest.newBuilder().setName(group.name).build()).toCompletable() - .thenApply { - logger.info("Started new instance ${it.uniqueId} of group ${group.name} on ${it.ip}:${it.port}") - }.exceptionally { - logger.error("Could not start a new instance of group ${group.name}: ${it.message}") - } - } - } + private val serverStub = ControllerServerServiceGrpc.newFutureStub(managedChannel) + private val logger = LogManager.getLogger(Reconciler::class.java) + + private val startingGroupNames = ConcurrentHashMap() + + fun reconcile() { + groupRepository.forEach { group -> + val servers = serverRepository.findServersByGroup(group.name) + val availableServerCount = servers.count { server -> + server.state == ServerState.AVAILABLE || server.state == ServerState.STARTING + } + val startingServers = startingGroupNames.getOrDefault(group.name, 0) + + cleanupServers() + startServers(group, availableServerCount, startingServers, servers.size) } + } + + private fun cleanupServers() { + + } + + private fun startServers( + group: Group, + availableServerCount: Int, + startingServers: Int, + serverCount: Int + ) { + if (!checkIfNewServerCanBeStarted(group, availableServerCount, startingServers, serverCount) || + !serverHostRepository.areServerHostsAvailable() + ) { + return + } + + startingGroupNames[group.name] = startingGroupNames.getOrDefault(group.name, 0) + 1 + startServer(group) + } + + private fun startServer(group: Group) { + logger.info("Starting new instance of group ${group.name}") + serverStub.startServer(GroupNameRequest.newBuilder().setName(group.name).build()).toCompletable() + .thenApply { + cleanupStartingGroup(group) + logger.info("Started new instance ${it.groupName}-${it.numericalId}/${it.uniqueId} of group ${group.name} on ${it.ip}:${it.port}") + }.exceptionally { + cleanupStartingGroup(group) + logger.error("Could not start a new instance of group ${group.name}: ${it.message}") + } + } + + @Synchronized + private fun cleanupStartingGroup(group: Group) { + if (startingGroupNames.getOrDefault(group.name, 0) <= 1) { + startingGroupNames.remove(group.name) + return + } + + startingGroupNames[group.name] = startingGroupNames[group.name]!! - 1 + } + + private fun checkIfNewServerCanBeStarted( + group: Group, + availableServerCount: Int, + startingServers: Int, + serverCount: Int + ): Boolean { + return getNeededServerCount(group, availableServerCount, startingServers, serverCount) > 0 + } + + private fun getNeededServerCount( + group: Group, + availableServerCount: Int, + startingServers: Int, + serverCount: Int + ): Int { + if (!checkIfServersAreNeeded(group, availableServerCount, serverCount)) { + return 0 + } + + return (group.minOnlineCount - availableServerCount - startingServers).toInt() + } + + private fun checkIfServersAreNeeded(group: Group, availableServerCount: Int, serverCount: Int): Boolean { + return availableServerCount < group.minOnlineCount && serverCount < group.maxOnlineCount + } } \ 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 5ac667a..e106461 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,6 +3,7 @@ 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 io.grpc.ConnectivityState import java.util.concurrent.CompletableFuture class ServerHostRepository : Repository() { @@ -23,6 +24,13 @@ class ServerHostRepository : Repository() { return lastHost } + fun areServerHostsAvailable(): Boolean { + return any { + val state = it.endpoint.getState(true) + state == ConnectivityState.IDLE || state == ConnectivityState.READY + } + } + override fun load() { throw UnsupportedOperationException("This method is not implemented.") } 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 new file mode 100644 index 0000000..a1e586d --- /dev/null +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/server/ServerNumericalIdRepository.kt @@ -0,0 +1,34 @@ +package app.simplecloud.controller.runtime.server + +import com.google.common.collect.ArrayListMultimap +import com.google.common.collect.Multimaps + +class ServerNumericalIdRepository { + + + private val numericalIds = Multimaps.synchronizedListMultimap(ArrayListMultimap.create()) + + @Synchronized + fun findNextNumericalId(group: String): Int { + val numericalIds = findNumericalIds(group) + var nextId = 1 + while (numericalIds.contains(nextId)) { + nextId++ + } + saveNumericalId(group, nextId) + return nextId + } + + fun saveNumericalId(group: String, id: Int) { + numericalIds.put(group, id) + } + + fun removeNumericalId(group: String, id: Int) { + numericalIds.remove(group, id) + } + + private fun findNumericalIds(group: String): List { + return numericalIds.get(group) + } + +} 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 cc1b5c2..9266ea0 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 @@ -9,7 +9,9 @@ import app.simplecloud.controller.shared.proto.ServerState import app.simplecloud.controller.shared.server.Server import java.util.concurrent.CompletableFuture -class ServerRepository : Repository() { +class ServerRepository( + private val numericalIdRepository: ServerNumericalIdRepository +) : Repository() { private val db = Database.get() @@ -25,19 +27,12 @@ class ServerRepository : Repository() { return filter { server -> server.group == group }.map { server -> server.toDefinition() } } - fun findNextNumericalId(group: String): Int { - var id = 1 - findServersByGroup(group).sortedWith(compareBy { it.numericalId }).forEach { - if(it.numericalId == id) id++ - } - return id - } - override fun load() { clear() val query = db.select().from(CLOUD_SERVERS).fetchInto(CLOUD_SERVERS) query.map { val propertiesQuery = db.select().from(CLOUD_SERVER_PROPERTIES).fetchInto(CLOUD_SERVER_PROPERTIES) + numericalIdRepository.saveNumericalId(it.groupName, it.numericalId) add( Server( it.uniqueId, @@ -60,8 +55,7 @@ class ServerRepository : Repository() { } override fun delete(element: Server): CompletableFuture { - val server = firstOrNull { it.uniqueId == element.uniqueId } - if (server == null) return CompletableFuture.completedFuture(false) + val server = firstOrNull { it.uniqueId == element.uniqueId } ?: return CompletableFuture.completedFuture(false) val canDelete = db.deleteFrom(CLOUD_SERVER_PROPERTIES).where(CLOUD_SERVER_PROPERTIES.SERVER_ID.eq(server.uniqueId)) .executeAsync().toCompletableFuture().thenApply { @@ -71,6 +65,7 @@ class ServerRepository : Repository() { return@exceptionally false }.get() if (!canDelete) return CompletableFuture.completedFuture(false) + numericalIdRepository.removeNumericalId(server.group, server.numericalId) return db.deleteFrom(CLOUD_SERVERS).where(CLOUD_SERVERS.UNIQUE_ID.eq(server.uniqueId)).executeAsync() .toCompletableFuture().thenApply { return@thenApply it > 0 && remove(server) @@ -90,6 +85,9 @@ class ServerRepository : Repository() { } else { add(element) } + + numericalIdRepository.saveNumericalId(element.group, element.numericalId) + db.insertInto( CLOUD_SERVERS, 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 dd2e607..a26d767 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 @@ -9,13 +9,11 @@ import app.simplecloud.controller.shared.proto.* import app.simplecloud.controller.shared.server.Server import app.simplecloud.controller.shared.status.ApiResponse import io.grpc.Context -import io.grpc.Status -import io.grpc.StatusException -import io.grpc.protobuf.StatusProto import io.grpc.stub.StreamObserver import org.apache.logging.log4j.LogManager class ServerService( + private val numericalIdRepository: ServerNumericalIdRepository, private val serverRepository: ServerRepository, private val hostRepository: ServerHostRepository, private val groupRepository: GroupRepository, @@ -74,10 +72,12 @@ class ServerService( responseObserver.onError(IllegalArgumentException("No group was found matching the group name.")) return } + + val numericalId = numericalIdRepository.findNextNumericalId(groupDefinition.name) stub.startServer( StartServerRequest.newBuilder() .setGroup(groupDefinition) - .setNumericalId(serverRepository.findNextNumericalId(groupDefinition.name)) + .setNumericalId(numericalId) .build() ).toCompletable().thenApply { serverRepository.save(Server.fromDefinition(it)) @@ -85,6 +85,8 @@ class ServerService( responseObserver.onCompleted() return@thenApply }.exceptionally { + + numericalIdRepository.removeNumericalId(groupDefinition.name, numericalId) responseObserver.onError(ServerHostException("Could not start server, aborting.")) } } From 4826f7a2ddedf17251e3a3d300a0da484b9f691c Mon Sep 17 00:00:00 2001 From: Philipp Date: Thu, 28 Mar 2024 23:31:31 +0100 Subject: [PATCH 39/94] fix: server starting --- build.gradle.kts | 3 +- .../controller/runtime/Reconciler.kt | 34 +++++-------------- .../runtime/server/ServerService.kt | 8 +++-- .../src/main/proto/server_host_api.proto | 5 ++- 4 files changed, 17 insertions(+), 33 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 446b36a..4cd0839 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,5 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import java.net.URI plugins { alias(libs.plugins.kotlin) @@ -11,7 +10,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "1.0.12-EXPERIMENTAL" + version = "1.0.14-EXPERIMENTAL" repositories { mavenCentral() diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt index a2a4e41..078cdf7 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt @@ -10,7 +10,6 @@ import app.simplecloud.controller.shared.proto.GroupNameRequest import app.simplecloud.controller.shared.proto.ServerState import io.grpc.ManagedChannel import org.apache.logging.log4j.LogManager -import java.util.concurrent.ConcurrentHashMap class Reconciler( private val groupRepository: GroupRepository, @@ -22,18 +21,19 @@ class Reconciler( private val serverStub = ControllerServerServiceGrpc.newFutureStub(managedChannel) private val logger = LogManager.getLogger(Reconciler::class.java) - private val startingGroupNames = ConcurrentHashMap() - fun reconcile() { groupRepository.forEach { group -> val servers = serverRepository.findServersByGroup(group.name) val availableServerCount = servers.count { server -> - server.state == ServerState.AVAILABLE || server.state == ServerState.STARTING + server.state == ServerState.AVAILABLE + || server.state == ServerState.STARTING + || server.state == ServerState.PREPARING } - val startingServers = startingGroupNames.getOrDefault(group.name, 0) + + logger.info("Reconciling group ${group.name} with ${servers.size} servers, $availableServerCount available servers") cleanupServers() - startServers(group, availableServerCount, startingServers, servers.size) + startServers(group, availableServerCount, servers.size) } } @@ -44,16 +44,14 @@ class Reconciler( private fun startServers( group: Group, availableServerCount: Int, - startingServers: Int, serverCount: Int ) { - if (!checkIfNewServerCanBeStarted(group, availableServerCount, startingServers, serverCount) || + if (!checkIfNewServerCanBeStarted(group, availableServerCount, serverCount) || !serverHostRepository.areServerHostsAvailable() ) { return } - startingGroupNames[group.name] = startingGroupNames.getOrDefault(group.name, 0) + 1 startServer(group) } @@ -61,44 +59,30 @@ class Reconciler( logger.info("Starting new instance of group ${group.name}") serverStub.startServer(GroupNameRequest.newBuilder().setName(group.name).build()).toCompletable() .thenApply { - cleanupStartingGroup(group) logger.info("Started new instance ${it.groupName}-${it.numericalId}/${it.uniqueId} of group ${group.name} on ${it.ip}:${it.port}") }.exceptionally { - cleanupStartingGroup(group) logger.error("Could not start a new instance of group ${group.name}: ${it.message}") } } - @Synchronized - private fun cleanupStartingGroup(group: Group) { - if (startingGroupNames.getOrDefault(group.name, 0) <= 1) { - startingGroupNames.remove(group.name) - return - } - - startingGroupNames[group.name] = startingGroupNames[group.name]!! - 1 - } - private fun checkIfNewServerCanBeStarted( group: Group, availableServerCount: Int, - startingServers: Int, serverCount: Int ): Boolean { - return getNeededServerCount(group, availableServerCount, startingServers, serverCount) > 0 + return getNeededServerCount(group, availableServerCount, serverCount) > 0 } private fun getNeededServerCount( group: Group, availableServerCount: Int, - startingServers: Int, serverCount: Int ): Int { if (!checkIfServersAreNeeded(group, availableServerCount, serverCount)) { return 0 } - return (group.minOnlineCount - availableServerCount - startingServers).toInt() + return (group.minOnlineCount - availableServerCount).toInt() } private fun checkIfServersAreNeeded(group: Group, availableServerCount: Int, serverCount: Int): Boolean { 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 dd5376c..a95eef2 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 @@ -13,7 +13,6 @@ import app.simplecloud.controller.shared.status.ApiResponse import io.grpc.Context import io.grpc.stub.StreamObserver import org.apache.logging.log4j.LogManager -import java.util.* class ServerService( private val numericalIdRepository: ServerNumericalIdRepository, @@ -79,10 +78,13 @@ class ServerService( val numericalId = numericalIdRepository.findNextNumericalId(groupDefinition.name) val server = ServerFactory.builder() .setGroup(Group.fromDefinition(groupDefinition)) - .setNumericalId(numericalId) + .setNumericalId(numericalId.toLong()) .build() serverRepository.save(server) - stub.startServer(server.toDefinition()).toCompletable().thenApply { + stub.startServer(StartServerRequest.newBuilder() + .setGroup(groupDefinition) + .setServer(server.toDefinition()) + .build()).toCompletable().thenApply { serverRepository.save(Server.fromDefinition(it)) responseObserver.onNext(it) responseObserver.onCompleted() diff --git a/controller-shared/src/main/proto/server_host_api.proto b/controller-shared/src/main/proto/server_host_api.proto index 9eb44a6..22e1b6d 100644 --- a/controller-shared/src/main/proto/server_host_api.proto +++ b/controller-shared/src/main/proto/server_host_api.proto @@ -10,12 +10,11 @@ import "controller_server_api.proto"; message StartServerRequest { GroupDefinition group = 1; - uint32 numerical_id = 2; - string unique_id = 3; + ServerDefinition server = 2; } service ServerHostService { - rpc startServer(ServerDefinition) returns (ServerDefinition); + rpc startServer(StartServerRequest) returns (ServerDefinition); rpc stopServer(ServerDefinition) returns (StatusResponse); rpc reattachServer(ServerDefinition) returns (StatusResponse); } \ No newline at end of file From 2949401485548fe47fc45fcab59f6645aa81c45f Mon Sep 17 00:00:00 2001 From: Philipp Date: Thu, 28 Mar 2024 23:54:51 +0100 Subject: [PATCH 40/94] fix: duplicate database entries --- .../runtime/server/ServerRepository.kt | 238 ++++++++++-------- controller-shared/src/main/db/schema.sql | 2 +- 2 files changed, 130 insertions(+), 110 deletions(-) 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 9266ea0..9aee146 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 @@ -10,128 +10,148 @@ import app.simplecloud.controller.shared.server.Server import java.util.concurrent.CompletableFuture class ServerRepository( - private val numericalIdRepository: ServerNumericalIdRepository + private val numericalIdRepository: ServerNumericalIdRepository ) : Repository() { - private val db = Database.get() + private val db = Database.get() - fun findServerById(id: String): ServerDefinition? { - return firstOrNull { it.uniqueId == id }?.toDefinition() - } + fun findServerById(id: String): ServerDefinition? { + return firstOrNull { it.uniqueId == id }?.toDefinition() + } - fun findServersByHostId(id: String): List { - return filter { it.host == id }.map { it.toDefinition() } - } + fun findServersByHostId(id: String): List { + return filter { it.host == id }.map { it.toDefinition() } + } - fun findServersByGroup(group: String): List { - return filter { server -> server.group == group }.map { server -> server.toDefinition() } - } + fun findServersByGroup(group: String): List { + return filter { server -> server.group == group }.map { server -> server.toDefinition() } + } - override fun load() { - clear() - val query = db.select().from(CLOUD_SERVERS).fetchInto(CLOUD_SERVERS) - query.map { - val propertiesQuery = db.select().from(CLOUD_SERVER_PROPERTIES).fetchInto(CLOUD_SERVER_PROPERTIES) - numericalIdRepository.saveNumericalId(it.groupName, it.numericalId) - add( - Server( - it.uniqueId, - it.groupName, - it.hostId, - it.numericalId, - it.templateId, - it.ip, - it.port.toLong(), - it.minimumMemory.toLong(), - it.maximumMemory.toLong(), - it.playerCount.toLong(), - propertiesQuery.map { item -> - item.key to item.value - }.toMap().toMutableMap(), - ServerState.valueOf(it.state) - ) - ) - } + override fun load() { + clear() + val query = db.select().from(CLOUD_SERVERS).fetchInto(CLOUD_SERVERS) + query.map { + val propertiesQuery = db.select().from(CLOUD_SERVER_PROPERTIES).fetchInto(CLOUD_SERVER_PROPERTIES) + numericalIdRepository.saveNumericalId(it.groupName, it.numericalId) + add( + Server( + it.uniqueId, + it.groupName, + it.hostId, + it.numericalId, + it.templateId, + it.ip, + it.port.toLong(), + it.minimumMemory.toLong(), + it.maximumMemory.toLong(), + it.playerCount.toLong(), + propertiesQuery.map { item -> + item.key to item.value + }.toMap().toMutableMap(), + ServerState.valueOf(it.state) + ) + ) } + } - override fun delete(element: Server): CompletableFuture { - val server = firstOrNull { it.uniqueId == element.uniqueId } ?: return CompletableFuture.completedFuture(false) - val canDelete = - db.deleteFrom(CLOUD_SERVER_PROPERTIES).where(CLOUD_SERVER_PROPERTIES.SERVER_ID.eq(server.uniqueId)) - .executeAsync().toCompletableFuture().thenApply { - return@thenApply true - }.exceptionally { - it.printStackTrace() - return@exceptionally false - }.get() - if (!canDelete) return CompletableFuture.completedFuture(false) - numericalIdRepository.removeNumericalId(server.group, server.numericalId) - return db.deleteFrom(CLOUD_SERVERS).where(CLOUD_SERVERS.UNIQUE_ID.eq(server.uniqueId)).executeAsync() - .toCompletableFuture().thenApply { - return@thenApply it > 0 && remove(server) - }.exceptionally { - it.printStackTrace() - return@exceptionally false - } - } + override fun delete(element: Server): CompletableFuture { + val server = firstOrNull { it.uniqueId == element.uniqueId } ?: return CompletableFuture.completedFuture(false) + val canDelete = + db.deleteFrom(CLOUD_SERVER_PROPERTIES).where(CLOUD_SERVER_PROPERTIES.SERVER_ID.eq(server.uniqueId)) + .executeAsync().toCompletableFuture().thenApply { + return@thenApply true + }.exceptionally { + it.printStackTrace() + return@exceptionally false + }.get() + if (!canDelete) return CompletableFuture.completedFuture(false) + numericalIdRepository.removeNumericalId(server.group, server.numericalId) + return db.deleteFrom(CLOUD_SERVERS).where(CLOUD_SERVERS.UNIQUE_ID.eq(server.uniqueId)).executeAsync() + .toCompletableFuture().thenApply { + return@thenApply it > 0 && remove(server) + }.exceptionally { + it.printStackTrace() + return@exceptionally false + } + } - override fun save(element: Server) { - try { - val server = firstOrNull { it.uniqueId == element.uniqueId } - if (server != null) { - val index = indexOf(server) - removeAt(index) - add(index, element) - } else { - add(element) - } + override fun save(element: Server) { + try { + val server = firstOrNull { it.uniqueId == element.uniqueId } + if (server != null) { + val index = indexOf(server) + removeAt(index) + add(index, element) + } else { + add(element) + } - numericalIdRepository.saveNumericalId(element.group, element.numericalId) + numericalIdRepository.saveNumericalId(element.group, element.numericalId) - db.insertInto( - CLOUD_SERVERS, + db.insertInto( + CLOUD_SERVERS, - CLOUD_SERVERS.UNIQUE_ID, - CLOUD_SERVERS.GROUP_NAME, - CLOUD_SERVERS.HOST_ID, - CLOUD_SERVERS.NUMERICAL_ID, - CLOUD_SERVERS.TEMPLATE_ID, - CLOUD_SERVERS.IP, - CLOUD_SERVERS.PORT, - CLOUD_SERVERS.MINIMUM_MEMORY, - CLOUD_SERVERS.MAXIMUM_MEMORY, - CLOUD_SERVERS.PLAYER_COUNT, - CLOUD_SERVERS.STATE, - ).values( - element.uniqueId, - element.group, - element.host, - element.numericalId, - element.templateId, - element.ip, - element.port.toInt(), - element.minMemory.toInt(), - element.maxMemory.toInt(), - element.playerCount.toInt(), - element.state.toString() - ).onDuplicateKeyUpdate().set(CLOUD_SERVERS.UNIQUE_ID, element.uniqueId).executeAsync() - element.properties.forEach { - db.insertInto( - CLOUD_SERVER_PROPERTIES, + CLOUD_SERVERS.UNIQUE_ID, + CLOUD_SERVERS.GROUP_NAME, + CLOUD_SERVERS.HOST_ID, + CLOUD_SERVERS.NUMERICAL_ID, + CLOUD_SERVERS.TEMPLATE_ID, + CLOUD_SERVERS.IP, + CLOUD_SERVERS.PORT, + CLOUD_SERVERS.MINIMUM_MEMORY, + CLOUD_SERVERS.MAXIMUM_MEMORY, + CLOUD_SERVERS.PLAYER_COUNT, + CLOUD_SERVERS.STATE, + ) + .values( + element.uniqueId, + element.group, + element.host, + element.numericalId, + element.templateId, + element.ip, + element.port.toInt(), + element.minMemory.toInt(), + element.maxMemory.toInt(), + element.playerCount.toInt(), + element.state.toString() + ) + .onDuplicateKeyUpdate() + .set(CLOUD_SERVERS.UNIQUE_ID, element.uniqueId) + .set(CLOUD_SERVERS.GROUP_NAME, element.group) + .set(CLOUD_SERVERS.HOST_ID, element.host) + .set(CLOUD_SERVERS.NUMERICAL_ID, element.numericalId) + .set(CLOUD_SERVERS.TEMPLATE_ID, element.templateId) + .set(CLOUD_SERVERS.IP, element.ip) + .set(CLOUD_SERVERS.PORT, element.port.toInt()) + .set(CLOUD_SERVERS.MINIMUM_MEMORY, element.minMemory.toInt()) + .set(CLOUD_SERVERS.MAXIMUM_MEMORY, element.maxMemory.toInt()) + .set(CLOUD_SERVERS.PLAYER_COUNT, element.playerCount.toInt()) + .set(CLOUD_SERVERS.STATE, element.state.toString()) + .executeAsync() + element.properties.forEach { + db.insertInto( + CLOUD_SERVER_PROPERTIES, - CLOUD_SERVER_PROPERTIES.SERVER_ID, - CLOUD_SERVER_PROPERTIES.KEY, - CLOUD_SERVER_PROPERTIES.VALUE - ).values( - element.uniqueId, - it.key, - it.value - ).onDuplicateKeyUpdate().set(CLOUD_SERVER_PROPERTIES.SERVER_ID, element.uniqueId).executeAsync() - } - } catch (e: Exception) { - e.printStackTrace() - println(e.message) - } + CLOUD_SERVER_PROPERTIES.SERVER_ID, + CLOUD_SERVER_PROPERTIES.KEY, + CLOUD_SERVER_PROPERTIES.VALUE + ) + .values( + element.uniqueId, + it.key, + it.value + ) + .onDuplicateKeyUpdate() + .set(CLOUD_SERVER_PROPERTIES.SERVER_ID, element.uniqueId) + .set(CLOUD_SERVER_PROPERTIES.KEY, it.key) + .set(CLOUD_SERVER_PROPERTIES.VALUE, it.value) + .executeAsync() + } + } catch (e: Exception) { + e.printStackTrace() + println(e.message) } + } } \ No newline at end of file diff --git a/controller-shared/src/main/db/schema.sql b/controller-shared/src/main/db/schema.sql index 6f1d6e9..2f245c9 100644 --- a/controller-shared/src/main/db/schema.sql +++ b/controller-shared/src/main/db/schema.sql @@ -4,7 +4,7 @@ */ CREATE TABLE IF NOT EXISTS cloud_servers( - unique_id varchar NOT NULL, + unique_id varchar NOT NULL PRIMARY KEY, group_name varchar NOT NULL, host_id varchar NOT NULL, numerical_id int NOT NULL, From 49830972d93594608ddd0c1be3a26b4c8e19c56e Mon Sep 17 00:00:00 2001 From: Philipp Date: Fri, 29 Mar 2024 10:47:34 +0100 Subject: [PATCH 41/94] feat: reconciler is now stopping unused servers --- build.gradle.kts | 2 +- .../controller/runtime/Reconciler.kt | 31 ++- .../runtime/server/ServerRepository.kt | 149 +++++++------- .../runtime/server/ServerService.kt | 182 +++++++++--------- controller-shared/src/main/db/schema.sql | 4 +- .../controller/shared/server/Server.kt | 9 +- .../controller/shared/server/ServerFactory.kt | 5 +- .../src/main/proto/controller_types.proto | 2 + 8 files changed, 215 insertions(+), 169 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 4cd0839..c6b6e3a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "1.0.14-EXPERIMENTAL" + version = "1.0.15-EXPERIMENTAL" repositories { mavenCentral() diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt index 078cdf7..e695cba 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt @@ -7,9 +7,12 @@ import app.simplecloud.controller.shared.future.toCompletable import app.simplecloud.controller.shared.group.Group import app.simplecloud.controller.shared.proto.ControllerServerServiceGrpc import app.simplecloud.controller.shared.proto.GroupNameRequest +import app.simplecloud.controller.shared.proto.ServerIdRequest import app.simplecloud.controller.shared.proto.ServerState +import app.simplecloud.controller.shared.server.Server import io.grpc.ManagedChannel import org.apache.logging.log4j.LogManager +import java.time.LocalDateTime class Reconciler( private val groupRepository: GroupRepository, @@ -18,6 +21,8 @@ class Reconciler( managedChannel: ManagedChannel, ) { + val INACTIVE_SERVER_TIME = 5L + private val serverStub = ControllerServerServiceGrpc.newFutureStub(managedChannel) private val logger = LogManager.getLogger(Reconciler::class.java) @@ -32,13 +37,34 @@ class Reconciler( logger.info("Reconciling group ${group.name} with ${servers.size} servers, $availableServerCount available servers") - cleanupServers() + cleanupServers(group, servers, availableServerCount) startServers(group, availableServerCount, servers.size) } } - private fun cleanupServers() { + private fun cleanupServers(group: Group, servers: List, availableServerCount: Int) { + val hasMoreServersThenNeeded = availableServerCount > group.minOnlineCount + servers + .filter { it.state == ServerState.AVAILABLE } + .forEach { server -> + if (hasMoreServersThenNeeded && !wasUpdatedRecently(server)) { + logger.info("Stopping server ${server.uniqueId} of group ${group.name}") + serverStub.stopServer( + ServerIdRequest.newBuilder() + .setId(server.uniqueId) + .build() + ).toCompletable() + .thenApply { + logger.info("Stopped server ${server.uniqueId} of group ${group.name}") + }.exceptionally { + logger.error("Could not stop server ${server.uniqueId} of group ${group.name}: ${it.message}") + } + } + } + } + private fun wasUpdatedRecently(server: Server): Boolean { + return server.updatedAt.isAfter(LocalDateTime.now().minusMinutes(INACTIVE_SERVER_TIME)) } private fun startServers( @@ -61,6 +87,7 @@ class Reconciler( .thenApply { logger.info("Started new instance ${it.groupName}-${it.numericalId}/${it.uniqueId} of group ${group.name} on ${it.ip}:${it.port}") }.exceptionally { + it.printStackTrace() logger.error("Could not start a new instance of group ${group.name}: ${it.message}") } } 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 9aee146..a4811b5 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 @@ -4,9 +4,9 @@ import app.simplecloud.controller.runtime.Repository import app.simplecloud.controller.shared.db.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.proto.ServerDefinition import app.simplecloud.controller.shared.proto.ServerState import app.simplecloud.controller.shared.server.Server +import java.time.LocalDateTime import java.util.concurrent.CompletableFuture class ServerRepository( @@ -15,16 +15,16 @@ class ServerRepository( private val db = Database.get() - fun findServerById(id: String): ServerDefinition? { - return firstOrNull { it.uniqueId == id }?.toDefinition() + fun findServerById(id: String): Server? { + return firstOrNull { it.uniqueId == id } } - fun findServersByHostId(id: String): List { - return filter { it.host == id }.map { it.toDefinition() } + fun findServersByHostId(id: String): List { + return filter { it.host == id } } - fun findServersByGroup(group: String): List { - return filter { server -> server.group == group }.map { server -> server.toDefinition() } + fun findServersByGroup(group: String): List { + return filter { server -> server.group == group } } override fun load() { @@ -48,7 +48,9 @@ class ServerRepository( propertiesQuery.map { item -> item.key to item.value }.toMap().toMutableMap(), - ServerState.valueOf(it.state) + ServerState.valueOf(it.state), + it.createdAt, + it.updatedAt ) ) } @@ -76,81 +78,82 @@ class ServerRepository( } override fun save(element: Server) { - try { - val server = firstOrNull { it.uniqueId == element.uniqueId } - if (server != null) { - val index = indexOf(server) - removeAt(index) - add(index, element) - } else { - add(element) - } + val server = firstOrNull { it.uniqueId == element.uniqueId } + if (server != null) { + val index = indexOf(server) + removeAt(index) + add(index, element) + } else { + add(element) + } - numericalIdRepository.saveNumericalId(element.group, element.numericalId) + numericalIdRepository.saveNumericalId(element.group, element.numericalId) + val currentTimestamp = LocalDateTime.now() + db.insertInto( + CLOUD_SERVERS, + + CLOUD_SERVERS.UNIQUE_ID, + CLOUD_SERVERS.GROUP_NAME, + CLOUD_SERVERS.HOST_ID, + CLOUD_SERVERS.NUMERICAL_ID, + CLOUD_SERVERS.TEMPLATE_ID, + CLOUD_SERVERS.IP, + CLOUD_SERVERS.PORT, + CLOUD_SERVERS.MINIMUM_MEMORY, + CLOUD_SERVERS.MAXIMUM_MEMORY, + CLOUD_SERVERS.PLAYER_COUNT, + CLOUD_SERVERS.STATE, + CLOUD_SERVERS.CREATED_AT, + CLOUD_SERVERS.UPDATED_AT + ) + .values( + element.uniqueId, + element.group, + element.host, + element.numericalId, + element.templateId, + element.ip, + element.port.toInt(), + element.minMemory.toInt(), + element.maxMemory.toInt(), + element.playerCount.toInt(), + element.state.toString(), + currentTimestamp, + currentTimestamp + ) + .onDuplicateKeyUpdate() + .set(CLOUD_SERVERS.UNIQUE_ID, element.uniqueId) + .set(CLOUD_SERVERS.GROUP_NAME, element.group) + .set(CLOUD_SERVERS.HOST_ID, element.host) + .set(CLOUD_SERVERS.NUMERICAL_ID, element.numericalId) + .set(CLOUD_SERVERS.TEMPLATE_ID, element.templateId) + .set(CLOUD_SERVERS.IP, element.ip) + .set(CLOUD_SERVERS.PORT, element.port.toInt()) + .set(CLOUD_SERVERS.MINIMUM_MEMORY, element.minMemory.toInt()) + .set(CLOUD_SERVERS.MAXIMUM_MEMORY, element.maxMemory.toInt()) + .set(CLOUD_SERVERS.PLAYER_COUNT, element.playerCount.toInt()) + .set(CLOUD_SERVERS.STATE, element.state.toString()) + .set(CLOUD_SERVERS.UPDATED_AT, currentTimestamp) + .executeAsync() + element.properties.forEach { db.insertInto( - CLOUD_SERVERS, + CLOUD_SERVER_PROPERTIES, - CLOUD_SERVERS.UNIQUE_ID, - CLOUD_SERVERS.GROUP_NAME, - CLOUD_SERVERS.HOST_ID, - CLOUD_SERVERS.NUMERICAL_ID, - CLOUD_SERVERS.TEMPLATE_ID, - CLOUD_SERVERS.IP, - CLOUD_SERVERS.PORT, - CLOUD_SERVERS.MINIMUM_MEMORY, - CLOUD_SERVERS.MAXIMUM_MEMORY, - CLOUD_SERVERS.PLAYER_COUNT, - CLOUD_SERVERS.STATE, + CLOUD_SERVER_PROPERTIES.SERVER_ID, + CLOUD_SERVER_PROPERTIES.KEY, + CLOUD_SERVER_PROPERTIES.VALUE ) .values( element.uniqueId, - element.group, - element.host, - element.numericalId, - element.templateId, - element.ip, - element.port.toInt(), - element.minMemory.toInt(), - element.maxMemory.toInt(), - element.playerCount.toInt(), - element.state.toString() + it.key, + it.value ) .onDuplicateKeyUpdate() - .set(CLOUD_SERVERS.UNIQUE_ID, element.uniqueId) - .set(CLOUD_SERVERS.GROUP_NAME, element.group) - .set(CLOUD_SERVERS.HOST_ID, element.host) - .set(CLOUD_SERVERS.NUMERICAL_ID, element.numericalId) - .set(CLOUD_SERVERS.TEMPLATE_ID, element.templateId) - .set(CLOUD_SERVERS.IP, element.ip) - .set(CLOUD_SERVERS.PORT, element.port.toInt()) - .set(CLOUD_SERVERS.MINIMUM_MEMORY, element.minMemory.toInt()) - .set(CLOUD_SERVERS.MAXIMUM_MEMORY, element.maxMemory.toInt()) - .set(CLOUD_SERVERS.PLAYER_COUNT, element.playerCount.toInt()) - .set(CLOUD_SERVERS.STATE, element.state.toString()) + .set(CLOUD_SERVER_PROPERTIES.SERVER_ID, element.uniqueId) + .set(CLOUD_SERVER_PROPERTIES.KEY, it.key) + .set(CLOUD_SERVER_PROPERTIES.VALUE, it.value) .executeAsync() - element.properties.forEach { - db.insertInto( - CLOUD_SERVER_PROPERTIES, - - CLOUD_SERVER_PROPERTIES.SERVER_ID, - CLOUD_SERVER_PROPERTIES.KEY, - CLOUD_SERVER_PROPERTIES.VALUE - ) - .values( - element.uniqueId, - it.key, - it.value - ) - .onDuplicateKeyUpdate() - .set(CLOUD_SERVER_PROPERTIES.SERVER_ID, element.uniqueId) - .set(CLOUD_SERVER_PROPERTIES.KEY, it.key) - .set(CLOUD_SERVER_PROPERTIES.VALUE, it.value) - .executeAsync() - } - } catch (e: Exception) { - e.printStackTrace() - println(e.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 a95eef2..b4a16c3 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,8 +1,8 @@ package app.simplecloud.controller.runtime.server import app.simplecloud.controller.runtime.group.GroupRepository -import app.simplecloud.controller.runtime.host.ServerHostRepository import app.simplecloud.controller.runtime.host.ServerHostException +import app.simplecloud.controller.runtime.host.ServerHostRepository import app.simplecloud.controller.shared.future.toCompletable import app.simplecloud.controller.shared.group.Group import app.simplecloud.controller.shared.host.ServerHost @@ -15,106 +15,108 @@ import io.grpc.stub.StreamObserver import org.apache.logging.log4j.LogManager class ServerService( - private val numericalIdRepository: ServerNumericalIdRepository, - private val serverRepository: ServerRepository, - private val hostRepository: ServerHostRepository, - private val groupRepository: GroupRepository, + private val numericalIdRepository: ServerNumericalIdRepository, + private val serverRepository: ServerRepository, + private val hostRepository: ServerHostRepository, + private val groupRepository: GroupRepository, ) : ControllerServerServiceGrpc.ControllerServerServiceImplBase() { - private val logger = LogManager.getLogger(ServerService::class.java) + private val logger = LogManager.getLogger(ServerService::class.java) - override fun attachServerHost(request: ServerHostDefinition, responseObserver: StreamObserver) { - val serverHost = ServerHost.fromDefinition(request) - hostRepository.remove(serverHost) - hostRepository.add(serverHost) - logger.info("Successfully registered ServerHost ${serverHost.id}.") - responseObserver.onNext(ApiResponse("success").toDefinition()) - responseObserver.onCompleted() - Context.current().fork().run { - val stub = ServerHostServiceGrpc.newFutureStub(serverHost.endpoint) - serverRepository.filter { it.host == serverHost.id }.forEach { - logger.info("Reattaching Server ${it.uniqueId} of group ${it.group}...") - val status = ApiResponse.fromDefinition(stub.reattachServer(it.toDefinition()).toCompletable().get()) - if (status.status == "success") { - logger.info("Success!") - } else { - logger.error("Server was found to be offline, unregistering...") - serverRepository.delete(it) - } - } + override fun attachServerHost(request: ServerHostDefinition, responseObserver: StreamObserver) { + val serverHost = ServerHost.fromDefinition(request) + hostRepository.remove(serverHost) + hostRepository.add(serverHost) + logger.info("Successfully registered ServerHost ${serverHost.id}.") + responseObserver.onNext(ApiResponse("success").toDefinition()) + responseObserver.onCompleted() + Context.current().fork().run { + val stub = ServerHostServiceGrpc.newFutureStub(serverHost.endpoint) + serverRepository.filter { it.host == serverHost.id }.forEach { + logger.info("Reattaching Server ${it.uniqueId} of group ${it.group}...") + val status = ApiResponse.fromDefinition(stub.reattachServer(it.toDefinition()).toCompletable().get()) + if (status.status == "success") { + logger.info("Success!") + } else { + logger.error("Server was found to be offline, unregistering...") + serverRepository.delete(it) } - + } } - override fun getServerById(request: ServerIdRequest, responseObserver: StreamObserver) { - val server = serverRepository.findServerById(request.id) - responseObserver.onNext(server) - responseObserver.onCompleted() - } + } - override fun getServersByGroup( - request: GroupNameRequest, - responseObserver: StreamObserver - ) { - val servers = serverRepository.findServersByGroup(request.name) - val response = GetServersByGroupResponse.newBuilder().addAllServers(servers).build() - responseObserver.onNext(response) - responseObserver.onCompleted() - } + override fun getServerById(request: ServerIdRequest, responseObserver: StreamObserver) { + val server = serverRepository.findServerById(request.id)?.toDefinition() + responseObserver.onNext(server) + responseObserver.onCompleted() + } - override fun startServer(request: GroupNameRequest, responseObserver: StreamObserver) { - val host = hostRepository.findLaziestServerHost(serverRepository) - if (host == null) { - responseObserver.onError(ServerHostException("No server host found, could not start server.")) - return - } - val stub = ServerHostServiceGrpc.newFutureStub(host.endpoint) - val groupDefinition = groupRepository.findGroupByName(request.name) - if (groupDefinition == null) { - responseObserver.onError(IllegalArgumentException("No group was found matching the group name.")) - return - } + override fun getServersByGroup( + request: GroupNameRequest, + responseObserver: StreamObserver + ) { + val servers = serverRepository.findServersByGroup(request.name).map { it.toDefinition() } + val response = GetServersByGroupResponse.newBuilder().addAllServers(servers).build() + responseObserver.onNext(response) + responseObserver.onCompleted() + } - val numericalId = numericalIdRepository.findNextNumericalId(groupDefinition.name) - val server = ServerFactory.builder() - .setGroup(Group.fromDefinition(groupDefinition)) - .setNumericalId(numericalId.toLong()) - .build() - serverRepository.save(server) - stub.startServer(StartServerRequest.newBuilder() - .setGroup(groupDefinition) - .setServer(server.toDefinition()) - .build()).toCompletable().thenApply { - serverRepository.save(Server.fromDefinition(it)) - responseObserver.onNext(it) - responseObserver.onCompleted() - return@thenApply - }.exceptionally { - serverRepository.delete(server) - numericalIdRepository.removeNumericalId(groupDefinition.name, numericalId) - responseObserver.onError(ServerHostException("Could not start server, aborting.")) - } + override fun startServer(request: GroupNameRequest, responseObserver: StreamObserver) { + val host = hostRepository.findLaziestServerHost(serverRepository) + if (host == null) { + responseObserver.onError(ServerHostException("No server host found, could not start server.")) + return + } + val stub = ServerHostServiceGrpc.newFutureStub(host.endpoint) + val groupDefinition = groupRepository.findGroupByName(request.name) + if (groupDefinition == null) { + responseObserver.onError(IllegalArgumentException("No group was found matching the group name.")) + return } - override fun stopServer(request: ServerIdRequest, responseObserver: StreamObserver) { - val server = serverRepository.findServerById(request.id) - if (server == null) { - responseObserver.onError(IllegalArgumentException("No server was found matching this id.")) - return - } - val host = hostRepository.findServerHostById(server.hostId) - if (host == null) { - responseObserver.onError(ServerHostException("No server host was found matching this server.")) - return - } - val stub = ServerHostServiceGrpc.newFutureStub(host.endpoint) - stub.stopServer(server).toCompletable().thenApply { - if (it.status == "success") { - serverRepository.delete(Server.fromDefinition(server)) - } - responseObserver.onNext(it) - responseObserver.onCompleted() - } + val numericalId = numericalIdRepository.findNextNumericalId(groupDefinition.name) + val server = ServerFactory.builder() + .setGroup(Group.fromDefinition(groupDefinition)) + .setNumericalId(numericalId.toLong()) + .build() + serverRepository.save(server) + stub.startServer( + StartServerRequest.newBuilder() + .setGroup(groupDefinition) + .setServer(server.toDefinition()) + .build() + ).toCompletable().thenApply { + serverRepository.save(Server.fromDefinition(it)) + responseObserver.onNext(it) + responseObserver.onCompleted() + return@thenApply + }.exceptionally { + serverRepository.delete(server) + numericalIdRepository.removeNumericalId(groupDefinition.name, numericalId) + responseObserver.onError(ServerHostException("Could not start server, aborting.")) + } + } + + override fun stopServer(request: ServerIdRequest, responseObserver: StreamObserver) { + val server = serverRepository.findServerById(request.id)?.toDefinition() + if (server == null) { + responseObserver.onError(IllegalArgumentException("No server was found matching this id.")) + return + } + val host = hostRepository.findServerHostById(server.hostId) + if (host == null) { + responseObserver.onError(ServerHostException("No server host was found matching this server.")) + return + } + val stub = ServerHostServiceGrpc.newFutureStub(host.endpoint) + stub.stopServer(server).toCompletable().thenApply { + if (it.status == "success") { + serverRepository.delete(Server.fromDefinition(server)) + } + responseObserver.onNext(it) + responseObserver.onCompleted() } + } } \ No newline at end of file diff --git a/controller-shared/src/main/db/schema.sql b/controller-shared/src/main/db/schema.sql index 2f245c9..e6ca5d0 100644 --- a/controller-shared/src/main/db/schema.sql +++ b/controller-shared/src/main/db/schema.sql @@ -14,7 +14,9 @@ CREATE TABLE IF NOT EXISTS cloud_servers( minimum_memory int NOT NULL, maximum_memory int NOT NULL, player_count int NOT NULL, - state varchar NOT NULL + state varchar NOT NULL, + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS cloud_server_properties( 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 a276fa6..4c3df01 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 @@ -2,6 +2,7 @@ package app.simplecloud.controller.shared.server import app.simplecloud.controller.shared.proto.ServerDefinition import app.simplecloud.controller.shared.proto.ServerState +import java.time.LocalDateTime data class Server( val uniqueId: String, @@ -16,6 +17,8 @@ data class Server( val playerCount: Long, val properties: MutableMap, val state: ServerState, + val createdAt: LocalDateTime, + val updatedAt: LocalDateTime ) { fun toDefinition(): ServerDefinition { return ServerDefinition.newBuilder() @@ -31,6 +34,8 @@ data class Server( .putAllProperties(properties) .setTemplateId(templateId) .setNumericalId(numericalId) + .setCreatedAt(createdAt.toString()) + .setUpdatedAt(updatedAt.toString()) .build() } @@ -49,7 +54,9 @@ data class Server( serverDefinition.maximumMemory, serverDefinition.playerCount, serverDefinition.propertiesMap, - serverDefinition.state + serverDefinition.state, + LocalDateTime.parse(serverDefinition.createdAt), + LocalDateTime.parse(serverDefinition.updatedAt) ) } diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt index ffab214..82e5b1c 100644 --- a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt @@ -3,6 +3,7 @@ package app.simplecloud.controller.shared.server import app.simplecloud.controller.shared.group.Group import app.simplecloud.controller.shared.host.ServerHost import app.simplecloud.controller.shared.proto.ServerState +import java.time.LocalDateTime import java.util.* import kotlin.properties.Delegates @@ -56,7 +57,9 @@ class ServerFactory { properties = mutableMapOf( "serverUrl" to group.serverUrl, *group.properties.entries.map { it.key to it.value }.toTypedArray() - ) + ), + createdAt = LocalDateTime.now(), + updatedAt = LocalDateTime.now() ) } diff --git a/controller-shared/src/main/proto/controller_types.proto b/controller-shared/src/main/proto/controller_types.proto index a27d33d..e470576 100644 --- a/controller-shared/src/main/proto/controller_types.proto +++ b/controller-shared/src/main/proto/controller_types.proto @@ -31,6 +31,8 @@ message ServerDefinition { uint64 player_count = 10; map properties = 11; ServerState state = 12; + string created_at = 13; + string updated_at = 14; } message ServerHostDefinition { From e4892253183098648491d502d9df19c10c1661e0 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Fri, 29 Mar 2024 18:43:20 +0100 Subject: [PATCH 42/94] impl: Server update support --- build.gradle.kts | 2 +- .../controller/runtime/server/ServerService.kt | 18 ++++++++++++++++++ .../src/main/proto/controller_server_api.proto | 6 ++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index c6b6e3a..7d5a876 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "1.0.15-EXPERIMENTAL" + version = "1.0.16-EXPERIMENTAL" repositories { mavenCentral() 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 b4a16c3..ddcbee4 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 @@ -10,6 +10,7 @@ import app.simplecloud.controller.shared.proto.* import app.simplecloud.controller.shared.server.Server import app.simplecloud.controller.shared.server.ServerFactory import app.simplecloud.controller.shared.status.ApiResponse +import com.google.protobuf.Api import io.grpc.Context import io.grpc.stub.StreamObserver import org.apache.logging.log4j.LogManager @@ -43,7 +44,24 @@ class ServerService( } } } + } + override fun updateServer(request: ServerUpdateRequest, responseObserver: StreamObserver) { + val deleted = request.deleted + val server = Server.fromDefinition(request.server) + if(!deleted) { + serverRepository.save(server) + responseObserver.onNext(ApiResponse("success").toDefinition()) + responseObserver.onCompleted() + }else { + serverRepository.delete(server).thenApply { + responseObserver.onNext(ApiResponse("success").toDefinition()) + responseObserver.onCompleted() + }.exceptionally { + responseObserver.onNext(ApiResponse("error").toDefinition()) + responseObserver.onCompleted() + } + } } override fun getServerById(request: ServerIdRequest, responseObserver: StreamObserver) { diff --git a/controller-shared/src/main/proto/controller_server_api.proto b/controller-shared/src/main/proto/controller_server_api.proto index 68bb5a2..5e738cc 100644 --- a/controller-shared/src/main/proto/controller_server_api.proto +++ b/controller-shared/src/main/proto/controller_server_api.proto @@ -19,10 +19,16 @@ message GetServersByGroupResponse { repeated ServerDefinition servers = 1; } +message ServerUpdateRequest { + ServerDefinition server = 1; + bool deleted = 2; +} + service ControllerServerService { rpc getServersByGroup(GroupNameRequest) returns (GetServersByGroupResponse); rpc getServerById(ServerIdRequest) returns (ServerDefinition); rpc startServer(GroupNameRequest) returns (ServerDefinition); rpc stopServer(ServerIdRequest) returns (StatusResponse); + rpc updateServer(ServerUpdateRequest) returns (StatusResponse); rpc attachServerHost(ServerHostDefinition) returns (StatusResponse); } \ No newline at end of file From 94688f1e09b3afab5b5bc453f94d518dffbbb201 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Sat, 30 Mar 2024 18:53:28 +0100 Subject: [PATCH 43/94] fix: ManagedChannel garbage collection error --- .../runtime/host/ServerHostRepository.kt | 4 ++- .../runtime/server/ServerService.kt | 27 ++++++++++++------- .../controller/shared/host/ServerHost.kt | 4 +-- 3 files changed, 22 insertions(+), 13 deletions(-) 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 e106461..bc53230 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 @@ -26,7 +26,9 @@ class ServerHostRepository : Repository() { fun areServerHostsAvailable(): Boolean { return any { - val state = it.endpoint.getState(true) + val channel = it.createChannel() + val state = channel.getState(true) + channel.shutdown() state == ConnectivityState.IDLE || state == ConnectivityState.READY } } 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 ddcbee4..e08653c 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 @@ -32,15 +32,19 @@ class ServerService( responseObserver.onNext(ApiResponse("success").toDefinition()) responseObserver.onCompleted() Context.current().fork().run { - val stub = ServerHostServiceGrpc.newFutureStub(serverHost.endpoint) + val channel = serverHost.createChannel() + val stub = ServerHostServiceGrpc.newFutureStub(channel) serverRepository.filter { it.host == serverHost.id }.forEach { logger.info("Reattaching Server ${it.uniqueId} of group ${it.group}...") - val status = ApiResponse.fromDefinition(stub.reattachServer(it.toDefinition()).toCompletable().get()) - if (status.status == "success") { - logger.info("Success!") - } else { - logger.error("Server was found to be offline, unregistering...") - serverRepository.delete(it) + stub.reattachServer(it.toDefinition()).toCompletable().thenApply { response -> + val status = ApiResponse.fromDefinition(response) + if (status.status == "success") { + logger.info("Success!") + } else { + logger.error("Server was found to be offline, unregistering...") + serverRepository.delete(it) + } + channel.shutdown() } } } @@ -86,7 +90,8 @@ class ServerService( responseObserver.onError(ServerHostException("No server host found, could not start server.")) return } - val stub = ServerHostServiceGrpc.newFutureStub(host.endpoint) + val channel = host.createChannel() + val stub = ServerHostServiceGrpc.newFutureStub(channel) val groupDefinition = groupRepository.findGroupByName(request.name) if (groupDefinition == null) { responseObserver.onError(IllegalArgumentException("No group was found matching the group name.")) @@ -108,11 +113,13 @@ class ServerService( serverRepository.save(Server.fromDefinition(it)) responseObserver.onNext(it) responseObserver.onCompleted() + channel.shutdown() return@thenApply }.exceptionally { serverRepository.delete(server) numericalIdRepository.removeNumericalId(groupDefinition.name, numericalId) responseObserver.onError(ServerHostException("Could not start server, aborting.")) + channel.shutdown() } } @@ -127,13 +134,15 @@ class ServerService( responseObserver.onError(ServerHostException("No server host was found matching this server.")) return } - val stub = ServerHostServiceGrpc.newFutureStub(host.endpoint) + val channel = host.createChannel() + val stub = ServerHostServiceGrpc.newFutureStub(channel) stub.stopServer(server).toCompletable().thenApply { if (it.status == "success") { serverRepository.delete(Server.fromDefinition(server)) } responseObserver.onNext(it) responseObserver.onCompleted() + channel.shutdown() } } 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 23bc0d6..c1a08a9 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 @@ -31,10 +31,8 @@ data class ServerHost( } } - @Transient - val endpoint: ManagedChannel = createChannel() - private fun createChannel(): ManagedChannel { + fun createChannel(): ManagedChannel { return ManagedChannelBuilder.forAddress(host, port).usePlaintext().build() } From 1f463be6a4f2b4645aaad3cca019735e028dad79 Mon Sep 17 00:00:00 2001 From: Philipp Date: Sun, 31 Mar 2024 12:03:57 +0200 Subject: [PATCH 44/94] fix: numerical ids --- .../runtime/server/ServerNumericalIdRepository.kt | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) 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 a1e586d..839e383 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,12 +1,10 @@ package app.simplecloud.controller.runtime.server -import com.google.common.collect.ArrayListMultimap -import com.google.common.collect.Multimaps +import java.util.concurrent.ConcurrentHashMap class ServerNumericalIdRepository { - - private val numericalIds = Multimaps.synchronizedListMultimap(ArrayListMultimap.create()) + private val numericalIds = ConcurrentHashMap>() @Synchronized fun findNextNumericalId(group: String): Int { @@ -20,15 +18,16 @@ class ServerNumericalIdRepository { } fun saveNumericalId(group: String, id: Int) { - numericalIds.put(group, id) + numericalIds.compute(group) { _, v -> v?.plus(id) ?: setOf(id) } } - fun removeNumericalId(group: String, id: Int) { - numericalIds.remove(group, id) + @Synchronized + fun removeNumericalId(group: String, id: Int): Boolean { + return numericalIds.computeIfPresent(group) { _, v -> v.minus(id) } != null } private fun findNumericalIds(group: String): List { - return numericalIds.get(group) + return numericalIds[group]?.toList() ?: emptyList() } } From ac0b48b8e3325ed6ee841702dc065435152058d2 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Sun, 31 Mar 2024 14:57:24 +0200 Subject: [PATCH 45/94] add: max-startup-seconds to default conf --- controller-runtime/src/main/resources/groups.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/controller-runtime/src/main/resources/groups.yml b/controller-runtime/src/main/resources/groups.yml index e932f7c..694a701 100644 --- a/controller-runtime/src/main/resources/groups.yml +++ b/controller-runtime/src/main/resources/groups.yml @@ -8,3 +8,5 @@ max-online-count: 2 properties: configurator: spigot + max-players: 20 + max-startup-seconds: 20 From 7d64ecbfbe5b1b66a58bb04c1f0e6dc0939e64ad Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Mon, 1 Apr 2024 00:05:49 +0200 Subject: [PATCH 46/94] add: server types and server type queries --- build.gradle.kts | 2 +- .../controller/api/server/ServerApi.kt | 6 +++ .../api/server/impl/ServerApiImpl.kt | 13 +++++ .../runtime/server/ServerRepository.kt | 9 ++++ .../runtime/server/ServerService.kt | 10 ++++ .../src/main/resources/groups.yml | 2 + controller-shared/src/main/db/schema.sql | 1 + .../controller/shared/group/Group.kt | 3 ++ .../controller/shared/server/Server.kt | 3 ++ .../controller/shared/server/ServerFactory.kt | 1 + .../main/proto/controller_server_api.proto | 5 ++ .../src/main/proto/controller_types.proto | 52 +++++++++++-------- 12 files changed, 84 insertions(+), 23 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 7d5a876..6b0c9bb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "1.0.16-EXPERIMENTAL" + version = "1.0.17-EXPERIMENTAL" repositories { mavenCentral() diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/ServerApi.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/ServerApi.kt index dd1d528..428cec9 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/ServerApi.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/ServerApi.kt @@ -1,6 +1,7 @@ package app.simplecloud.controller.api.server import app.simplecloud.controller.shared.group.Group +import app.simplecloud.controller.shared.proto.ServerType import app.simplecloud.controller.shared.server.Server import app.simplecloud.controller.shared.status.ApiResponse import java.util.concurrent.CompletableFuture @@ -25,6 +26,11 @@ interface ServerApi { */ fun getServersByGroup(group: Group): CompletableFuture> + /** + * @return a [CompletableFuture] with a [List] of all [Server]s + */ + 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. diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt index e901a07..7114f64 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt @@ -7,6 +7,8 @@ import app.simplecloud.controller.shared.group.Group import app.simplecloud.controller.shared.proto.ControllerServerServiceGrpc import app.simplecloud.controller.shared.proto.GroupNameRequest import app.simplecloud.controller.shared.proto.ServerIdRequest +import app.simplecloud.controller.shared.proto.ServerType +import app.simplecloud.controller.shared.proto.ServerTypeRequest import app.simplecloud.controller.shared.server.Server import app.simplecloud.controller.shared.status.ApiResponse import com.google.protobuf.Api @@ -45,6 +47,17 @@ class ServerApiImpl : ServerApi { return getServersByGroup(group.name) } + override fun getServersByType(type: ServerType): CompletableFuture> { + return serverServiceStub.getServersByType( + ServerTypeRequest.newBuilder() + .setType(type) + .build() + ).toCompletable() + .thenApply { + Server.fromDefinition(it.serversList) + } + } + override fun startServer(groupName: String): CompletableFuture { return serverServiceStub.startServer( GroupNameRequest.newBuilder() 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 a4811b5..89d47e4 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 @@ -5,6 +5,7 @@ import app.simplecloud.controller.shared.db.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.proto.ServerState +import app.simplecloud.controller.shared.proto.ServerType import app.simplecloud.controller.shared.server.Server import java.time.LocalDateTime import java.util.concurrent.CompletableFuture @@ -27,6 +28,10 @@ class ServerRepository( return filter { server -> server.group == group } } + fun findServersByType(type: ServerType): List { + return filter { server -> server.type == type } + } + override fun load() { clear() val query = db.select().from(CLOUD_SERVERS).fetchInto(CLOUD_SERVERS) @@ -36,6 +41,7 @@ class ServerRepository( add( Server( it.uniqueId, + ServerType.valueOf(it.type), it.groupName, it.hostId, it.numericalId, @@ -94,6 +100,7 @@ class ServerRepository( CLOUD_SERVERS, CLOUD_SERVERS.UNIQUE_ID, + CLOUD_SERVERS.TYPE, CLOUD_SERVERS.GROUP_NAME, CLOUD_SERVERS.HOST_ID, CLOUD_SERVERS.NUMERICAL_ID, @@ -109,6 +116,7 @@ class ServerRepository( ) .values( element.uniqueId, + element.type.toString(), element.group, element.host, element.numericalId, @@ -124,6 +132,7 @@ class ServerRepository( ) .onDuplicateKeyUpdate() .set(CLOUD_SERVERS.UNIQUE_ID, element.uniqueId) + .set(CLOUD_SERVERS.TYPE, element.type.toString()) .set(CLOUD_SERVERS.GROUP_NAME, element.group) .set(CLOUD_SERVERS.HOST_ID, element.host) .set(CLOUD_SERVERS.NUMERICAL_ID, element.numericalId) 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 e08653c..1ae8b98 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 @@ -84,6 +84,16 @@ class ServerService( responseObserver.onCompleted() } + override fun getServersByType( + request: ServerTypeRequest, + responseObserver: StreamObserver + ) { + val servers = serverRepository.findServersByType(request.type).map { it.toDefinition() } + val response = GetServersByGroupResponse.newBuilder().addAllServers(servers).build() + responseObserver.onNext(response) + responseObserver.onCompleted() + } + override fun startServer(request: GroupNameRequest, responseObserver: StreamObserver) { val host = hostRepository.findLaziestServerHost(serverRepository) if (host == null) { diff --git a/controller-runtime/src/main/resources/groups.yml b/controller-runtime/src/main/resources/groups.yml index 694a701..8dca3ee 100644 --- a/controller-runtime/src/main/resources/groups.yml +++ b/controller-runtime/src/main/resources/groups.yml @@ -1,4 +1,5 @@ - name: lobby + type: SERVER server-url: https://api.papermc.io/v2/projects/paper/versions/1.20.4/builds/450/downloads/paper-1.20.4-450.jar template-id: default min-memory: 1024 @@ -7,6 +8,7 @@ min-online-count: 1 max-online-count: 2 properties: + fallback-server: true configurator: spigot max-players: 20 max-startup-seconds: 20 diff --git a/controller-shared/src/main/db/schema.sql b/controller-shared/src/main/db/schema.sql index e6ca5d0..4d54da0 100644 --- a/controller-shared/src/main/db/schema.sql +++ b/controller-shared/src/main/db/schema.sql @@ -15,6 +15,7 @@ CREATE TABLE IF NOT EXISTS cloud_servers( maximum_memory int NOT NULL, player_count int NOT NULL, state varchar NOT NULL, + type varchar NOT NULL, created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ); 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 a39d79d..c100869 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 @@ -1,11 +1,13 @@ package app.simplecloud.controller.shared.group import app.simplecloud.controller.shared.proto.GroupDefinition +import app.simplecloud.controller.shared.proto.ServerType import org.spongepowered.configurate.objectmapping.ConfigSerializable @ConfigSerializable data class Group( val name: String, + val type: ServerType, val serverUrl: String, val minMemory: Long, val maxMemory: Long, @@ -35,6 +37,7 @@ data class Group( fun fromDefinition(groupDefinition: GroupDefinition): Group { return Group( groupDefinition.name, + groupDefinition.type, groupDefinition.serverUrl, groupDefinition.minimumMemory, groupDefinition.maximumMemory, 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 4c3df01..cd1064d 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 @@ -2,10 +2,12 @@ package app.simplecloud.controller.shared.server import app.simplecloud.controller.shared.proto.ServerDefinition import app.simplecloud.controller.shared.proto.ServerState +import app.simplecloud.controller.shared.proto.ServerType import java.time.LocalDateTime data class Server( val uniqueId: String, + val type: ServerType, val group: String, val host: String?, val numericalId: Int, @@ -44,6 +46,7 @@ data class Server( fun fromDefinition(serverDefinition: ServerDefinition): Server { return Server( serverDefinition.uniqueId, + serverDefinition.type, serverDefinition.groupName, serverDefinition.hostId, serverDefinition.numericalId, diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt index 82e5b1c..7f9cf74 100644 --- a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt @@ -45,6 +45,7 @@ class ServerFactory { return Server( uniqueId = UUID.randomUUID().toString().replace("-", ""), port = port ?: -1, + type = group.type, group = group.name, minMemory = group.minMemory, maxMemory = group.maxMemory, diff --git a/controller-shared/src/main/proto/controller_server_api.proto b/controller-shared/src/main/proto/controller_server_api.proto index 5e738cc..9e9b2b0 100644 --- a/controller-shared/src/main/proto/controller_server_api.proto +++ b/controller-shared/src/main/proto/controller_server_api.proto @@ -15,6 +15,10 @@ message ServerIdRequest { string id = 1; } +message ServerTypeRequest { + ServerType type = 1; +} + message GetServersByGroupResponse { repeated ServerDefinition servers = 1; } @@ -25,6 +29,7 @@ message ServerUpdateRequest { } service ControllerServerService { + rpc getServersByType(ServerTypeRequest) returns (GetServersByGroupResponse); rpc getServersByGroup(GroupNameRequest) returns (GetServersByGroupResponse); rpc getServerById(ServerIdRequest) returns (ServerDefinition); rpc startServer(GroupNameRequest) returns (ServerDefinition); diff --git a/controller-shared/src/main/proto/controller_types.proto b/controller-shared/src/main/proto/controller_types.proto index e470576..8e12846 100644 --- a/controller-shared/src/main/proto/controller_types.proto +++ b/controller-shared/src/main/proto/controller_types.proto @@ -7,32 +7,34 @@ option java_multiple_files = true; message GroupDefinition { string name = 1; - string server_url = 2; - string template_id = 3; - uint64 minimum_memory = 4; - uint64 maximum_memory = 5; - uint64 start_port = 6; - uint64 online_servers = 7; - uint64 minimum_online_count = 8; - uint64 maximum_online_count = 9; - map properties = 10; + ServerType type = 2; + string server_url = 3; + string template_id = 4; + uint64 minimum_memory = 5; + uint64 maximum_memory = 6; + uint64 start_port = 7; + uint64 online_servers = 8; + uint64 minimum_online_count = 9; + uint64 maximum_online_count = 10; + map properties = 11; } message ServerDefinition { string unique_id = 1; - string group_name = 2; - string host_id = 3; - uint32 numerical_id = 4; - string template_id = 5; - string ip = 6; - uint64 port = 7; - uint64 minimum_memory = 8; - uint64 maximum_memory = 9; - uint64 player_count = 10; - map properties = 11; - ServerState state = 12; - string created_at = 13; - string updated_at = 14; + ServerType type = 2; + string group_name = 3; + string host_id = 4; + uint32 numerical_id = 5; + string template_id = 6; + string ip = 7; + uint64 port = 8; + uint64 minimum_memory = 9; + uint64 maximum_memory = 10; + uint64 player_count = 11; + map properties = 12; + ServerState state = 13; + string created_at = 14; + string updated_at = 15; } message ServerHostDefinition { @@ -49,6 +51,12 @@ enum ServerState { STOPPING = 4; } +enum ServerType { + SERVER = 0; + PROXY = 1; + OTHER = 2; +} + message StatusResponse { string status = 1; } \ No newline at end of file From 281c52dbba8b509db9ff37e5c08d3fafb530f1ba Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Mon, 1 Apr 2024 17:38:02 +0200 Subject: [PATCH 47/94] fix: broken grpc calls --- build.gradle.kts | 2 +- .../app/simplecloud/controller/runtime/server/ServerService.kt | 2 ++ .../kotlin/app/simplecloud/controller/shared/group/Group.kt | 1 + .../kotlin/app/simplecloud/controller/shared/server/Server.kt | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 6b0c9bb..aeb2d79 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "1.0.17-EXPERIMENTAL" + version = "1.0.19-EXPERIMENTAL" repositories { mavenCentral() 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 1ae8b98..23d2a67 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 @@ -109,10 +109,12 @@ class ServerService( } val numericalId = numericalIdRepository.findNextNumericalId(groupDefinition.name) + println(groupDefinition.type) val server = ServerFactory.builder() .setGroup(Group.fromDefinition(groupDefinition)) .setNumericalId(numericalId.toLong()) .build() + println(server.type) serverRepository.save(server) stub.startServer( StartServerRequest.newBuilder() 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 c100869..e151fda 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 @@ -21,6 +21,7 @@ data class Group( fun toDefinition(): GroupDefinition { return GroupDefinition.newBuilder() .setName(name) + .setType(type) .setServerUrl(serverUrl) .setMinimumMemory(minMemory) .setMaximumMemory(maxMemory) 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 cd1064d..61cd75a 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 @@ -25,6 +25,7 @@ data class Server( fun toDefinition(): ServerDefinition { return ServerDefinition.newBuilder() .setUniqueId(uniqueId) + .setType(type) .setGroupName(group) .setHostId(host) .setIp(ip) From 2250c07a9d6e69fe913cb60aeaf95a6283c8ecac Mon Sep 17 00:00:00 2001 From: Philipp Date: Mon, 1 Apr 2024 17:57:09 +0200 Subject: [PATCH 48/94] refactor: improve group repository --- .../controller/runtime/ControllerRuntime.kt | 4 +- .../controller/runtime/Reconciler.kt | 2 +- .../runtime/YamlDirectoryRepository.kt | 137 ++++++++++++++++++ .../runtime/group/GroupRepository.kt | 21 ++- .../controller/runtime/group/GroupService.kt | 21 +-- .../runtime/server/ServerService.kt | 20 +-- .../controller/shared/group/Group.kt | 18 +-- 7 files changed, 186 insertions(+), 37 deletions(-) create mode 100644 controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlDirectoryRepository.kt 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 05d5e61..03367cf 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 @@ -14,6 +14,7 @@ import io.grpc.Server import io.grpc.ServerBuilder import kotlinx.coroutines.* import org.apache.logging.log4j.LogManager +import java.nio.file.Path import kotlin.concurrent.thread class ControllerRuntime { @@ -29,7 +30,8 @@ class ControllerRuntime { private lateinit var server: Server fun start(args: MutableMap) { - groupRepository = GroupRepository("/groups.yml", args.getOrDefault("groups-path", null)) + groupRepository = GroupRepository(Path.of(args.getOrDefault("groups-path", "groups"))) + groupRepository.loadAll() logger.info("Starting database...") loadDB() logger.info("Starting controller...") diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt index e695cba..fd91ddc 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt @@ -27,7 +27,7 @@ class Reconciler( private val logger = LogManager.getLogger(Reconciler::class.java) fun reconcile() { - groupRepository.forEach { group -> + groupRepository.findAll().forEach { group -> val servers = serverRepository.findServersByGroup(group.name) val availableServerCount = servers.count { server -> server.state == ServerState.AVAILABLE 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 new file mode 100644 index 0000000..9c6ee33 --- /dev/null +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlDirectoryRepository.kt @@ -0,0 +1,137 @@ +package app.simplecloud.controller.runtime + +import kotlinx.coroutines.* +import org.apache.logging.log4j.LogManager +import org.spongepowered.configurate.ConfigurationOptions +import org.spongepowered.configurate.kotlin.objectMapperFactory +import org.spongepowered.configurate.yaml.YamlConfigurationLoader +import java.io.File +import java.nio.file.FileSystems +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardWatchEventKinds +import java.nio.file.WatchEvent + +abstract class YamlDirectoryRepository( + private val directory: Path, + private val clazz: Class, +) { + + private val logger = LogManager.getLogger(this::class.java) + + private val watchService = FileSystems.getDefault().newWatchService() + private val loaders = mutableMapOf() + protected val entities = mutableMapOf() + + abstract fun getFileName(identifier: I): String + + abstract fun find(identifier: I): T? + + abstract fun save(entity: T) + + fun delete(entity: T): Boolean { + val file = entities.keys.find { entities[it] == entity } ?: return false + return delete(file) + } + + fun findAll(): List { + return entities.values.toList() + } + + fun loadAll() { + logger.info("Loading all entities from $directory") + if (!directory.toFile().exists()) { + directory.toFile().mkdir() + } + + Files.list(directory) + .filter { !it.toFile().isDirectory && it.toString().endsWith(".yml") } + .forEach { + load(it.toFile()) + } + + registerWatcher() + } + + private fun load(file: File) { + val loader = getOrCreateLoader(file) + val node = loader.load(ConfigurationOptions.defaults()) + val entity = node.get(clazz) ?: return + entities[file] = entity + logger.info("Loaded $file") + } + + private fun delete(file: File): Boolean { + val deletedSuccessfully = file.delete() + val removedSuccessfully = entities.remove(file) != null + return deletedSuccessfully && removedSuccessfully + } + + protected fun save(fileName: String, entity: T) { + val file = directory.resolve(fileName).toFile() + val loader = getOrCreateLoader(file) + val node = loader.createNode(ConfigurationOptions.defaults()) + node.set(clazz, entity) + loader.save(node) + entities[file] = entity + } + + private fun getOrCreateLoader(file: File): YamlConfigurationLoader { + return loaders.getOrPut(file) { + YamlConfigurationLoader.builder() + .path(file.toPath()) + .defaultOptions { options -> + options.serializers { builder -> + builder.registerAnnotatedObjects(objectMapperFactory()) + } + }.build() + } + } + + @OptIn(InternalCoroutinesApi::class) + private fun registerWatcher(): Job { + directory.register( + watchService, + StandardWatchEventKinds.ENTRY_CREATE, + StandardWatchEventKinds.ENTRY_DELETE, + StandardWatchEventKinds.ENTRY_MODIFY + ) + + return CoroutineScope(Dispatchers.Default).launch { + while (NonCancellable.isActive) { + val key = watchService.take() + for (event in key.pollEvents()) { + val path = event.context() as? Path ?: continue + val resolvedPath = directory.resolve(path) + if (Files.isDirectory(resolvedPath)) { + continue + } + val kind = event.kind() + logger.info("Detected change in $resolvedPath (${getChangeStatus(kind)})") + when (kind) { + StandardWatchEventKinds.ENTRY_CREATE, + StandardWatchEventKinds.ENTRY_MODIFY + -> { + load(resolvedPath.toFile()) + } + + StandardWatchEventKinds.ENTRY_DELETE -> { + delete(resolvedPath.toFile()) + } + } + } + key.reset() + } + } + } + + private fun getChangeStatus(kind: WatchEvent.Kind<*>): String { + return when (kind) { + StandardWatchEventKinds.ENTRY_CREATE -> "Created" + StandardWatchEventKinds.ENTRY_DELETE -> "Deleted" + StandardWatchEventKinds.ENTRY_MODIFY -> "Modified" + else -> "Unknown" + } + } + +} \ No newline at end of file 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 21d05db..0ba7f5d 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 @@ -1,14 +1,21 @@ package app.simplecloud.controller.runtime.group -import app.simplecloud.controller.runtime.YamlRepository +import app.simplecloud.controller.runtime.YamlDirectoryRepository import app.simplecloud.controller.shared.group.Group -import app.simplecloud.controller.shared.proto.GroupDefinition +import java.nio.file.Path -class GroupRepository(path: String, parent: String?) : YamlRepository(if(parent != null) "$parent$path" else path, Group::class.java) { - fun findGroupByName(name: String): GroupDefinition? { - return firstOrNull { it.name == name }?.toDefinition() +class GroupRepository( + path: Path +): YamlDirectoryRepository(path, Group::class.java) { + override fun getFileName(identifier: String): String { + return "$identifier.yml" } - override fun findIndex(element: Group): Int { - return indexOf(firstOrNull { it.name == element.name }) + + override fun find(identifier: String): Group? { + return entities.values.find { it.name == identifier } + } + + override fun save(entity: Group) { + save(getFileName(entity.name), entity) } } \ 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 70ac2b3..8211459 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,9 +13,14 @@ class GroupService( request: GetGroupByNameRequest, responseObserver: StreamObserver ) { - val group = groupRepository.findGroupByName(request.name) + val group = groupRepository.find(request.name) + if (group == null) { + responseObserver.onError(Exception("Group not found")) + return + } + val response = GetGroupByNameResponse.newBuilder() - .setGroup(group) + .setGroup(group.toDefinition()) .build() responseObserver.onNext(response) @@ -34,18 +39,16 @@ class GroupService( } override fun deleteGroupByName(request: GetGroupByNameRequest, responseObserver: StreamObserver) { - val groupDefinition = groupRepository.findGroupByName(request.name) - if (groupDefinition == null) { + val group = groupRepository.find(request.name) + if (group == null) { responseObserver.onNext(ApiResponse(status = "error").toDefinition()) responseObserver.onCompleted() return } - val group = Group.fromDefinition(groupDefinition) try { - groupRepository.delete(group).thenApply { - responseObserver.onNext(ApiResponse(status = if (it) "success" else "error").toDefinition()) - responseObserver.onCompleted() - } + val successfullyDeleted = groupRepository.delete(group) + responseObserver.onNext(ApiResponse(status = if (successfullyDeleted) "success" else "error").toDefinition()) + responseObserver.onCompleted() } catch (e: Exception) { responseObserver.onError(e) } 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 e08653c..d604606 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 @@ -4,13 +4,11 @@ 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.future.toCompletable -import app.simplecloud.controller.shared.group.Group import app.simplecloud.controller.shared.host.ServerHost import app.simplecloud.controller.shared.proto.* import app.simplecloud.controller.shared.server.Server import app.simplecloud.controller.shared.server.ServerFactory import app.simplecloud.controller.shared.status.ApiResponse -import com.google.protobuf.Api import io.grpc.Context import io.grpc.stub.StreamObserver import org.apache.logging.log4j.LogManager @@ -53,12 +51,14 @@ class ServerService( override fun updateServer(request: ServerUpdateRequest, responseObserver: StreamObserver) { val deleted = request.deleted val server = Server.fromDefinition(request.server) - if(!deleted) { + if (!deleted) { serverRepository.save(server) responseObserver.onNext(ApiResponse("success").toDefinition()) responseObserver.onCompleted() - }else { + } else { + logger.info("Deleting server ${server.uniqueId} of group ${request.server.groupName}...") serverRepository.delete(server).thenApply { + logger.info("Deleted server ${server.uniqueId} of group ${request.server.groupName}.") responseObserver.onNext(ApiResponse("success").toDefinition()) responseObserver.onCompleted() }.exceptionally { @@ -92,21 +92,21 @@ class ServerService( } val channel = host.createChannel() val stub = ServerHostServiceGrpc.newFutureStub(channel) - val groupDefinition = groupRepository.findGroupByName(request.name) - if (groupDefinition == null) { + val group = groupRepository.find(request.name) + if (group == null) { responseObserver.onError(IllegalArgumentException("No group was found matching the group name.")) return } - val numericalId = numericalIdRepository.findNextNumericalId(groupDefinition.name) + val numericalId = numericalIdRepository.findNextNumericalId(group.name) val server = ServerFactory.builder() - .setGroup(Group.fromDefinition(groupDefinition)) + .setGroup(group) .setNumericalId(numericalId.toLong()) .build() serverRepository.save(server) stub.startServer( StartServerRequest.newBuilder() - .setGroup(groupDefinition) + .setGroup(group.toDefinition()) .setServer(server.toDefinition()) .build() ).toCompletable().thenApply { @@ -117,7 +117,7 @@ class ServerService( return@thenApply }.exceptionally { serverRepository.delete(server) - numericalIdRepository.removeNumericalId(groupDefinition.name, numericalId) + numericalIdRepository.removeNumericalId(group.name, numericalId) responseObserver.onError(ServerHostException("Could not start server, aborting.")) channel.shutdown() } 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 a39d79d..1ae0856 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 @@ -5,15 +5,15 @@ import org.spongepowered.configurate.objectmapping.ConfigSerializable @ConfigSerializable data class Group( - val name: String, - val serverUrl: String, - val minMemory: Long, - val maxMemory: Long, - val startPort: Long, - @Transient val onlineServers: Long?, - val minOnlineCount: Long, - val maxOnlineCount: Long, - val properties: Map, + val name: String = "", + val serverUrl: String = "", + val minMemory: Long = 0, + val maxMemory: Long = 0, + val startPort: Long = 0, + @Transient val onlineServers: Long? = 0, + val minOnlineCount: Long = 0, + val maxOnlineCount: Long = 0, + val properties: Map = mapOf() ) { fun toDefinition(): GroupDefinition { From 1d51c419b1a5c6485f7b4a5ca6650f9021e7b305 Mon Sep 17 00:00:00 2001 From: Philipp Date: Mon, 1 Apr 2024 18:10:21 +0200 Subject: [PATCH 49/94] fix: only allow .yml files in yaml directory repository --- .../simplecloud/controller/runtime/YamlDirectoryRepository.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9c6ee33..eaad5b8 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 @@ -103,7 +103,7 @@ abstract class YamlDirectoryRepository( for (event in key.pollEvents()) { val path = event.context() as? Path ?: continue val resolvedPath = directory.resolve(path) - if (Files.isDirectory(resolvedPath)) { + if (Files.isDirectory(resolvedPath) || !resolvedPath.toString().endsWith(".yml")) { continue } val kind = event.kind() From d39b6cee9160f3301773b2c4c52573019423c1e8 Mon Sep 17 00:00:00 2001 From: Philipp Date: Mon, 1 Apr 2024 18:13:06 +0200 Subject: [PATCH 50/94] refactor: improve start arguments --- controller-runtime/build.gradle.kts | 1 + .../controller/runtime/ControllerRuntime.kt | 119 +++++++++--------- .../launcher/ControllerStartCommand.kt | 17 +++ .../controller/runtime/launcher/Launcher.kt | 15 +-- 4 files changed, 79 insertions(+), 73 deletions(-) create mode 100644 controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/ControllerStartCommand.kt diff --git a/controller-runtime/build.gradle.kts b/controller-runtime/build.gradle.kts index 6f42184..460bda2 100644 --- a/controller-runtime/build.gradle.kts +++ b/controller-runtime/build.gradle.kts @@ -6,6 +6,7 @@ dependencies { api(project(":controller-shared")) implementation(rootProject.libs.bundles.log4j) api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2") + implementation("com.github.ajalt.clikt:clikt:4.3.0") } application { 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 03367cf..1257552 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 @@ -1,13 +1,14 @@ package app.simplecloud.controller.runtime -import app.simplecloud.controller.shared.db.DatabaseConfig import app.simplecloud.controller.runtime.group.GroupRepository import app.simplecloud.controller.runtime.group.GroupService import app.simplecloud.controller.runtime.host.ServerHostRepository +import app.simplecloud.controller.runtime.launcher.ControllerStartCommand import app.simplecloud.controller.runtime.server.ServerNumericalIdRepository import app.simplecloud.controller.runtime.server.ServerRepository import app.simplecloud.controller.runtime.server.ServerService import app.simplecloud.controller.shared.db.Database +import app.simplecloud.controller.shared.db.DatabaseConfig import io.grpc.ManagedChannel import io.grpc.ManagedChannelBuilder import io.grpc.Server @@ -17,74 +18,74 @@ import org.apache.logging.log4j.LogManager import java.nio.file.Path import kotlin.concurrent.thread -class ControllerRuntime { +class ControllerRuntime( + private val controllerStartCommand: ControllerStartCommand +) { - private val logger = LogManager.getLogger(ControllerRuntime::class.java) + private val logger = LogManager.getLogger(ControllerRuntime::class.java) - private lateinit var groupRepository: GroupRepository - private val numericalIdRepository = ServerNumericalIdRepository() - private lateinit var serverRepository: ServerRepository - private val hostRepository: ServerHostRepository = ServerHostRepository() - private lateinit var databaseConfig: DatabaseConfig - private lateinit var reconciler: Reconciler - private lateinit var server: Server - - fun start(args: MutableMap) { - groupRepository = GroupRepository(Path.of(args.getOrDefault("groups-path", "groups"))) - groupRepository.loadAll() - logger.info("Starting database...") - loadDB() - logger.info("Starting controller...") - server = createGrpcServerFromEnv() - startGrpcServer() - startReconciler() - } + private val groupRepository = GroupRepository(Path.of(controllerStartCommand.groupPath)) + private val numericalIdRepository = ServerNumericalIdRepository() + private lateinit var serverRepository: ServerRepository + private val hostRepository: ServerHostRepository = ServerHostRepository() + private lateinit var databaseConfig: DatabaseConfig + private lateinit var reconciler: Reconciler + private lateinit var server: Server + fun start() { + logger.info("Starting database...") + loadDB() + logger.info("Starting controller...") + server = createGrpcServerFromEnv() + startGrpcServer() + startReconciler() + groupRepository.loadAll() + } - private fun loadDB() { - logger.info("Loading database configuration...") - databaseConfig = DatabaseConfig.load("/database-config.yml")!! - logger.info("Connecting database...") - Database.init(databaseConfig) - serverRepository = ServerRepository(numericalIdRepository) - serverRepository.load() - } + private fun loadDB() { + logger.info("Loading database configuration...") + databaseConfig = DatabaseConfig.load(controllerStartCommand.databaseConfigPath)!! + logger.info("Connecting database...") + Database.init(databaseConfig) + serverRepository = ServerRepository(numericalIdRepository) + serverRepository.load() + } - private fun startGrpcServer() { - logger.info("Starting gRPC server...") - thread { - server.start() - server.awaitTermination() - } + private fun startGrpcServer() { + logger.info("Starting gRPC server...") + thread { + server.start() + server.awaitTermination() } + } - private fun startReconciler() { - logger.info("Starting Reconciler...") - reconciler = Reconciler(groupRepository, serverRepository, hostRepository, createManagedChannel()) - startReconcilerJob() - } + private fun startReconciler() { + logger.info("Starting Reconciler...") + reconciler = Reconciler(groupRepository, serverRepository, hostRepository, createManagedChannel()) + startReconcilerJob() + } - private fun createGrpcServerFromEnv(): Server { - val port = System.getenv("GRPC_PORT")?.toInt() ?: 5816 - return ServerBuilder.forPort(port) - .addService(GroupService(groupRepository)) - .addService(ServerService(numericalIdRepository, serverRepository, hostRepository, groupRepository)) - .build() - } + private fun createGrpcServerFromEnv(): Server { + val port = System.getenv("GRPC_PORT")?.toInt() ?: 5816 + return ServerBuilder.forPort(port) + .addService(GroupService(groupRepository)) + .addService(ServerService(numericalIdRepository, serverRepository, hostRepository, groupRepository)) + .build() + } - private fun createManagedChannel(): ManagedChannel { - val port = System.getenv("GRPC_PORT")?.toInt() ?: 5816 - return ManagedChannelBuilder.forAddress("localhost", port).usePlaintext().build() - } + private fun createManagedChannel(): ManagedChannel { + val port = System.getenv("GRPC_PORT")?.toInt() ?: 5816 + return ManagedChannelBuilder.forAddress("localhost", port).usePlaintext().build() + } - @OptIn(InternalCoroutinesApi::class) - private fun startReconcilerJob(): Job { - return CoroutineScope(Dispatchers.Default).launch { - while (NonCancellable.isActive) { - reconciler.reconcile() - delay(2000L) - } - } + @OptIn(InternalCoroutinesApi::class) + private fun startReconcilerJob(): Job { + return CoroutineScope(Dispatchers.Default).launch { + while (NonCancellable.isActive) { + reconciler.reconcile() + delay(2000L) + } } + } } \ No newline at end of file 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 new file mode 100644 index 0000000..6379c34 --- /dev/null +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/ControllerStartCommand.kt @@ -0,0 +1,17 @@ +package app.simplecloud.controller.runtime.launcher + +import app.simplecloud.controller.runtime.ControllerRuntime +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.option + +class ControllerStartCommand: CliktCommand() { + + val groupPath: String by option(help = "Path to the group files").default("groups") + val databaseConfigPath: String by option(help = "Path to the database config file").default("database-config.yml") + + override fun run() { + val controllerRuntime = ControllerRuntime(this) + controllerRuntime.start() + } +} \ No newline at end of file diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/Launcher.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/Launcher.kt index bdddb16..abb4b5d 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/Launcher.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/Launcher.kt @@ -1,18 +1,5 @@ package app.simplecloud.controller.runtime.launcher -import app.simplecloud.controller.runtime.ControllerRuntime - - fun main(args: Array) { - val arguments: MutableMap = HashMap() - for (arg in args) { - if (arg.startsWith("--") && arg.contains("=")) { - val parts = arg.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - val key = parts[0].substring(2) - val value = parts[1] - arguments[key] = value - } - } - val controllerRuntime = ControllerRuntime() - controllerRuntime.start(arguments) + ControllerStartCommand().main(args) } \ No newline at end of file From 27f1e95bfcffc3554c8bc8cb28166c75f811f4fd Mon Sep 17 00:00:00 2001 From: Philipp Date: Mon, 1 Apr 2024 18:15:59 +0200 Subject: [PATCH 51/94] fix: server type import --- .../controller/shared/group/Group.kt | 81 ++++++++++--------- 1 file changed, 41 insertions(+), 40 deletions(-) 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 d9fd9b7..45a98ee 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 @@ -1,53 +1,54 @@ package app.simplecloud.controller.shared.group import app.simplecloud.controller.shared.proto.GroupDefinition +import app.simplecloud.controller.shared.proto.ServerType import org.spongepowered.configurate.objectmapping.ConfigSerializable @ConfigSerializable data class Group( - val name: String = "", - val type: ServerType = ServerType.OTHER, - val serverUrl: String = "", - val minMemory: Long = 0, - val maxMemory: Long = 0, - val startPort: Long = 0, - @Transient val onlineServers: Long? = 0, - val minOnlineCount: Long = 0, - val maxOnlineCount: Long = 0, - val properties: Map = mapOf() + val name: String = "", + val type: ServerType = ServerType.OTHER, + val serverUrl: String = "", + val minMemory: Long = 0, + val maxMemory: Long = 0, + val startPort: Long = 0, + @Transient val onlineServers: Long? = 0, + val minOnlineCount: Long = 0, + val maxOnlineCount: Long = 0, + val properties: Map = mapOf() ) { - fun toDefinition(): GroupDefinition { - return GroupDefinition.newBuilder() - .setName(name) - .setType(type) - .setServerUrl(serverUrl) - .setMinimumMemory(minMemory) - .setMaximumMemory(maxMemory) - .setStartPort(startPort) - .setOnlineServers(onlineServers?: 0) - .setMinimumOnlineCount(minOnlineCount) - .setMaximumOnlineCount(maxOnlineCount) - .putAllProperties(properties) - .build() - } + fun toDefinition(): GroupDefinition { + return GroupDefinition.newBuilder() + .setName(name) + .setType(type) + .setServerUrl(serverUrl) + .setMinimumMemory(minMemory) + .setMaximumMemory(maxMemory) + .setStartPort(startPort) + .setOnlineServers(onlineServers ?: 0) + .setMinimumOnlineCount(minOnlineCount) + .setMaximumOnlineCount(maxOnlineCount) + .putAllProperties(properties) + .build() + } - companion object { - @JvmStatic - fun fromDefinition(groupDefinition: GroupDefinition): Group { - return Group( - groupDefinition.name, - groupDefinition.type, - groupDefinition.serverUrl, - groupDefinition.minimumMemory, - groupDefinition.maximumMemory, - groupDefinition.startPort, - groupDefinition.onlineServers, - groupDefinition.minimumOnlineCount, - groupDefinition.maximumOnlineCount, - groupDefinition.propertiesMap - ) - } + companion object { + @JvmStatic + fun fromDefinition(groupDefinition: GroupDefinition): Group { + return Group( + groupDefinition.name, + groupDefinition.type, + groupDefinition.serverUrl, + groupDefinition.minimumMemory, + groupDefinition.maximumMemory, + groupDefinition.startPort, + groupDefinition.onlineServers, + groupDefinition.minimumOnlineCount, + groupDefinition.maximumOnlineCount, + groupDefinition.propertiesMap + ) } + } } From 720de91227d3e601b39dda897445312e9d0ec81d Mon Sep 17 00:00:00 2001 From: Philipp Date: Mon, 1 Apr 2024 18:59:18 +0200 Subject: [PATCH 52/94] fix: database path --- .../controller/runtime/ControllerRuntime.kt | 2 +- .../launcher/ControllerStartCommand.kt | 2 +- .../{database-config.yml => database.yml} | 0 .../controller/shared/db/DatabaseConfig.kt | 65 ++++++++++--------- 4 files changed, 37 insertions(+), 32 deletions(-) rename controller-runtime/src/main/resources/{database-config.yml => database.yml} (100%) 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 1257552..b378380 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 @@ -44,7 +44,7 @@ class ControllerRuntime( private fun loadDB() { logger.info("Loading database configuration...") - databaseConfig = DatabaseConfig.load(controllerStartCommand.databaseConfigPath)!! + databaseConfig = DatabaseConfig.load(Path.of(controllerStartCommand.databaseConfigPath))!! logger.info("Connecting database...") Database.init(databaseConfig) serverRepository = ServerRepository(numericalIdRepository) 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 6379c34..58e46f4 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 @@ -8,7 +8,7 @@ import com.github.ajalt.clikt.parameters.options.option class ControllerStartCommand: CliktCommand() { val groupPath: String by option(help = "Path to the group files").default("groups") - val databaseConfigPath: String by option(help = "Path to the database config file").default("database-config.yml") + val databaseConfigPath: String by option(help = "Path to the database config file").default("") override fun run() { val controllerRuntime = ControllerRuntime(this) diff --git a/controller-runtime/src/main/resources/database-config.yml b/controller-runtime/src/main/resources/database.yml similarity index 100% rename from controller-runtime/src/main/resources/database-config.yml rename to controller-runtime/src/main/resources/database.yml diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/DatabaseConfig.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/DatabaseConfig.kt index dd5e4c7..dd09c8d 100644 --- a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/DatabaseConfig.kt +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/DatabaseConfig.kt @@ -8,39 +8,44 @@ import java.io.File import java.nio.file.Files import java.nio.file.Path import java.nio.file.StandardCopyOption -import kotlin.io.path.copyToRecursively -import kotlin.io.path.createDirectories @ConfigSerializable data class DatabaseConfig(var driver: String = "jdbc:sqlite", var url: String) { - companion object { - fun load(path: String): DatabaseConfig? { - if (System.getenv("DATABASE_URL") != null) { - return DatabaseConfig(System.getenv("DATABASE_DRIVER") ?: "jdb:sqlite", System.getenv("DATABASE_URL")) - } - val destination = File(path.substring(1, path.length)) - if (!destination.exists()) { - Files.copy( - DatabaseConfig::class.java.getResourceAsStream(path)!!, - destination.toPath(), - StandardCopyOption.REPLACE_EXISTING - ) - } - val loader = YamlConfigurationLoader.builder() - .path(destination.toPath()) - .defaultOptions { options -> - options.serializers { builder -> - builder.registerAnnotatedObjects(objectMapperFactory()) - } - }.build() - val node = loader.load() - val config = node.get() - return config - } - } + companion object { + const val FILE_NAME = "database.yml" + + fun load(directoryPath: Path): DatabaseConfig? { + if (System.getenv("DATABASE_URL") != null) { + return DatabaseConfig(System.getenv("DATABASE_DRIVER") ?: "jdb:sqlite", System.getenv("DATABASE_URL")) + } + if (!Files.exists(directoryPath)) { + Files.createDirectories(directoryPath) + } - fun toDatabaseUrl(): String { - val absoluteUrl = if (url.contains(":")) url else File("").absolutePath + url - return "$driver:$absoluteUrl" + val path = directoryPath.resolve(FILE_NAME) + + if (!Files.exists(path)) { + Files.copy( + DatabaseConfig::class.java.getResourceAsStream("/${FILE_NAME}")!!, + path, + StandardCopyOption.REPLACE_EXISTING + ) + } + val loader = YamlConfigurationLoader.builder() + .path(path) + .defaultOptions { options -> + options.serializers { builder -> + builder.registerAnnotatedObjects(objectMapperFactory()) + } + }.build() + val node = loader.load() + val config = node.get() + return config } + } + + fun toDatabaseUrl(): String { + val absoluteUrl = if (url.contains(":")) url else File("").absolutePath + url + return "$driver:$absoluteUrl" + } } \ No newline at end of file From 7a7c3f6139260d3f66448ed76516f98c66a4c93c Mon Sep 17 00:00:00 2001 From: Philipp Date: Mon, 1 Apr 2024 19:50:19 +0200 Subject: [PATCH 53/94] refactor: clean up --- .../controller/runtime/ControllerRuntime.kt | 47 +++++++++-------- .../runtime/YamlDirectoryRepository.kt | 9 ++-- .../controller/runtime/database/Database.kt | 22 ++++++++ .../runtime/database/DatabaseFactory.kt | 12 +++++ .../launcher/ControllerStartCommand.kt | 11 ++-- .../runtime/server/ServerRepository.kt | 17 +++---- .../controller/shared/db/Database.kt | 33 ------------ .../controller/shared/db/DatabaseConfig.kt | 51 ------------------- 8 files changed, 80 insertions(+), 122 deletions(-) create mode 100644 controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/database/Database.kt create mode 100644 controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/database/DatabaseFactory.kt delete mode 100644 controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/Database.kt delete mode 100644 controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/DatabaseConfig.kt 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 b378380..52a29a0 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 @@ -1,5 +1,6 @@ package app.simplecloud.controller.runtime +import app.simplecloud.controller.runtime.database.DatabaseFactory import app.simplecloud.controller.runtime.group.GroupRepository import app.simplecloud.controller.runtime.group.GroupService import app.simplecloud.controller.runtime.host.ServerHostRepository @@ -7,47 +8,44 @@ import app.simplecloud.controller.runtime.launcher.ControllerStartCommand import app.simplecloud.controller.runtime.server.ServerNumericalIdRepository import app.simplecloud.controller.runtime.server.ServerRepository import app.simplecloud.controller.runtime.server.ServerService -import app.simplecloud.controller.shared.db.Database -import app.simplecloud.controller.shared.db.DatabaseConfig import io.grpc.ManagedChannel import io.grpc.ManagedChannelBuilder import io.grpc.Server import io.grpc.ServerBuilder import kotlinx.coroutines.* import org.apache.logging.log4j.LogManager -import java.nio.file.Path import kotlin.concurrent.thread class ControllerRuntime( - private val controllerStartCommand: ControllerStartCommand + controllerStartCommand: ControllerStartCommand ) { private val logger = LogManager.getLogger(ControllerRuntime::class.java) - private val groupRepository = GroupRepository(Path.of(controllerStartCommand.groupPath)) + private val database = DatabaseFactory.createDatabase(controllerStartCommand.databaseUrl) + + private val groupRepository = GroupRepository(controllerStartCommand.groupPath) private val numericalIdRepository = ServerNumericalIdRepository() - private lateinit var serverRepository: ServerRepository - private val hostRepository: ServerHostRepository = ServerHostRepository() - private lateinit var databaseConfig: DatabaseConfig - private lateinit var reconciler: Reconciler - private lateinit var server: Server + private val serverRepository = ServerRepository(database, numericalIdRepository) + private val hostRepository = ServerHostRepository() + private val reconciler = Reconciler(groupRepository, serverRepository, hostRepository, createManagedChannel()) + private val server = createGrpcServerFromEnv() fun start() { - logger.info("Starting database...") - loadDB() - logger.info("Starting controller...") - server = createGrpcServerFromEnv() + setupDatabase() startGrpcServer() startReconciler() - groupRepository.loadAll() + loadGroups() + loadServers() + } + + private fun setupDatabase() { + logger.info("Setting up database...") + database.setup() } - private fun loadDB() { - logger.info("Loading database configuration...") - databaseConfig = DatabaseConfig.load(Path.of(controllerStartCommand.databaseConfigPath))!! - logger.info("Connecting database...") - Database.init(databaseConfig) - serverRepository = ServerRepository(numericalIdRepository) + private fun loadServers() { + logger.info("Loading servers...") serverRepository.load() } @@ -61,10 +59,15 @@ class ControllerRuntime( private fun startReconciler() { logger.info("Starting Reconciler...") - reconciler = Reconciler(groupRepository, serverRepository, hostRepository, createManagedChannel()) startReconcilerJob() } + private fun loadGroups() { + logger.info("Loading groups...") + val loadedGroups = groupRepository.loadAll() + logger.info("Loaded groups: ${loadedGroups.joinToString(",")}") + } + private fun createGrpcServerFromEnv(): Server { val port = System.getenv("GRPC_PORT")?.toInt() ?: 5816 return ServerBuilder.forPort(port) 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 eaad5b8..c262a57 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 @@ -11,6 +11,7 @@ import java.nio.file.Files import java.nio.file.Path import java.nio.file.StandardWatchEventKinds import java.nio.file.WatchEvent +import kotlin.io.path.name abstract class YamlDirectoryRepository( private val directory: Path, @@ -38,19 +39,22 @@ abstract class YamlDirectoryRepository( return entities.values.toList() } - fun loadAll() { - logger.info("Loading all entities from $directory") + fun loadAll(): List { if (!directory.toFile().exists()) { directory.toFile().mkdir() } + val fileNames = mutableListOf() + Files.list(directory) .filter { !it.toFile().isDirectory && it.toString().endsWith(".yml") } .forEach { load(it.toFile()) + fileNames.add(it.name) } registerWatcher() + return fileNames } private fun load(file: File) { @@ -58,7 +62,6 @@ abstract class YamlDirectoryRepository( val node = loader.load(ConfigurationOptions.defaults()) val entity = node.get(clazz) ?: return entities[file] = entity - logger.info("Loaded $file") } private fun delete(file: File): Boolean { diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/database/Database.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/database/Database.kt new file mode 100644 index 0000000..ca72bbb --- /dev/null +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/database/Database.kt @@ -0,0 +1,22 @@ +package app.simplecloud.controller.runtime.database + +import org.jooq.DSLContext + +class Database( + val context: DSLContext +) { + + fun setup() { + System.setProperty("org.jooq.no-logo", "true") + System.setProperty("org.jooq.no-tips", "true") + val setupInputStream = Database::class.java.getResourceAsStream("/schema.sql") + ?: throw IllegalArgumentException("Database schema not found.") + val setupCommands = setupInputStream.bufferedReader().use { it.readText() }.split(";") + setupCommands.forEach { + val trimmed = it.trim() + if (trimmed.isNotEmpty()) + context.execute(org.jooq.impl.DSL.sql(trimmed)) + } + } + +} \ No newline at end of file diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/database/DatabaseFactory.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/database/DatabaseFactory.kt new file mode 100644 index 0000000..0506bc6 --- /dev/null +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/database/DatabaseFactory.kt @@ -0,0 +1,12 @@ +package app.simplecloud.controller.runtime.database + +import org.jooq.impl.DSL + +object DatabaseFactory { + + fun createDatabase(databaseUrl: String): Database { + val databaseContext = DSL.using(databaseUrl) + return Database(databaseContext) + } + +} \ No newline at end of file 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 58e46f4..cc943b1 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 @@ -2,13 +2,16 @@ package app.simplecloud.controller.runtime.launcher import app.simplecloud.controller.runtime.ControllerRuntime import com.github.ajalt.clikt.core.CliktCommand -import com.github.ajalt.clikt.parameters.options.default -import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.options.* +import com.github.ajalt.clikt.parameters.types.path +import java.nio.file.Path class ControllerStartCommand: CliktCommand() { - val groupPath: String by option(help = "Path to the group files").default("groups") - val databaseConfigPath: String by option(help = "Path to the database config file").default("") + private val defaultDatabaseUrl = "jdbc:sqlite:database.db" + + val groupPath: Path by option(help = "Path to the group files (groups)", envvar = "GROUPS_PATH").path().default(Path.of("groups")) + val databaseUrl: String by option(help = "Database URL (${defaultDatabaseUrl})", envvar = "DATABASE_URL").default(defaultDatabaseUrl) override fun run() { val controllerRuntime = ControllerRuntime(this) 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 89d47e4..8f364c1 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 @@ -1,7 +1,7 @@ package app.simplecloud.controller.runtime.server import app.simplecloud.controller.runtime.Repository -import app.simplecloud.controller.shared.db.Database +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.proto.ServerState @@ -11,11 +11,10 @@ import java.time.LocalDateTime import java.util.concurrent.CompletableFuture class ServerRepository( + private val database: Database, private val numericalIdRepository: ServerNumericalIdRepository ) : Repository() { - private val db = Database.get() - fun findServerById(id: String): Server? { return firstOrNull { it.uniqueId == id } } @@ -34,9 +33,9 @@ class ServerRepository( override fun load() { clear() - val query = db.select().from(CLOUD_SERVERS).fetchInto(CLOUD_SERVERS) + val query = database.context.select().from(CLOUD_SERVERS).fetchInto(CLOUD_SERVERS) query.map { - val propertiesQuery = db.select().from(CLOUD_SERVER_PROPERTIES).fetchInto(CLOUD_SERVER_PROPERTIES) + val propertiesQuery = database.context.select().from(CLOUD_SERVER_PROPERTIES).fetchInto(CLOUD_SERVER_PROPERTIES) numericalIdRepository.saveNumericalId(it.groupName, it.numericalId) add( Server( @@ -65,7 +64,7 @@ class ServerRepository( override fun delete(element: Server): CompletableFuture { val server = firstOrNull { it.uniqueId == element.uniqueId } ?: return CompletableFuture.completedFuture(false) val canDelete = - db.deleteFrom(CLOUD_SERVER_PROPERTIES).where(CLOUD_SERVER_PROPERTIES.SERVER_ID.eq(server.uniqueId)) + database.context.deleteFrom(CLOUD_SERVER_PROPERTIES).where(CLOUD_SERVER_PROPERTIES.SERVER_ID.eq(server.uniqueId)) .executeAsync().toCompletableFuture().thenApply { return@thenApply true }.exceptionally { @@ -74,7 +73,7 @@ class ServerRepository( }.get() if (!canDelete) return CompletableFuture.completedFuture(false) numericalIdRepository.removeNumericalId(server.group, server.numericalId) - return db.deleteFrom(CLOUD_SERVERS).where(CLOUD_SERVERS.UNIQUE_ID.eq(server.uniqueId)).executeAsync() + return database.context.deleteFrom(CLOUD_SERVERS).where(CLOUD_SERVERS.UNIQUE_ID.eq(server.uniqueId)).executeAsync() .toCompletableFuture().thenApply { return@thenApply it > 0 && remove(server) }.exceptionally { @@ -96,7 +95,7 @@ class ServerRepository( numericalIdRepository.saveNumericalId(element.group, element.numericalId) val currentTimestamp = LocalDateTime.now() - db.insertInto( + database.context.insertInto( CLOUD_SERVERS, CLOUD_SERVERS.UNIQUE_ID, @@ -146,7 +145,7 @@ class ServerRepository( .set(CLOUD_SERVERS.UPDATED_AT, currentTimestamp) .executeAsync() element.properties.forEach { - db.insertInto( + database.context.insertInto( CLOUD_SERVER_PROPERTIES, CLOUD_SERVER_PROPERTIES.SERVER_ID, diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/Database.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/Database.kt deleted file mode 100644 index 999f765..0000000 --- a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/Database.kt +++ /dev/null @@ -1,33 +0,0 @@ -package app.simplecloud.controller.shared.db - -import org.jooq.DSLContext -import org.jooq.impl.DSL - - -class Database { - companion object { - @JvmStatic - private lateinit var instance: DSLContext - fun init(config: DatabaseConfig) { - instance = DSL.using(config.toDatabaseUrl()) - setup() - } - - private fun setup() { - System.setProperty("org.jooq.no-logo", "true") - System.setProperty("org.jooq.no-tips", "true") - val setupInputStream = Database::class.java.getResourceAsStream("/schema.sql") - ?: throw IllegalArgumentException("Database schema not found.") - val setupCommands = setupInputStream.bufferedReader().use { it.readText() }.split(";") - setupCommands.forEach { - val trimmed = it.trim() - if (trimmed.isNotEmpty()) - instance.execute(DSL.sql(trimmed)) - } - } - - fun get(): DSLContext { - return instance - } - } -} \ No newline at end of file diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/DatabaseConfig.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/DatabaseConfig.kt deleted file mode 100644 index dd09c8d..0000000 --- a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/db/DatabaseConfig.kt +++ /dev/null @@ -1,51 +0,0 @@ -package app.simplecloud.controller.shared.db - -import org.spongepowered.configurate.kotlin.extensions.get -import org.spongepowered.configurate.kotlin.objectMapperFactory -import org.spongepowered.configurate.objectmapping.ConfigSerializable -import org.spongepowered.configurate.yaml.YamlConfigurationLoader -import java.io.File -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.StandardCopyOption - -@ConfigSerializable -data class DatabaseConfig(var driver: String = "jdbc:sqlite", var url: String) { - companion object { - const val FILE_NAME = "database.yml" - - fun load(directoryPath: Path): DatabaseConfig? { - if (System.getenv("DATABASE_URL") != null) { - return DatabaseConfig(System.getenv("DATABASE_DRIVER") ?: "jdb:sqlite", System.getenv("DATABASE_URL")) - } - if (!Files.exists(directoryPath)) { - Files.createDirectories(directoryPath) - } - - val path = directoryPath.resolve(FILE_NAME) - - if (!Files.exists(path)) { - Files.copy( - DatabaseConfig::class.java.getResourceAsStream("/${FILE_NAME}")!!, - path, - StandardCopyOption.REPLACE_EXISTING - ) - } - val loader = YamlConfigurationLoader.builder() - .path(path) - .defaultOptions { options -> - options.serializers { builder -> - builder.registerAnnotatedObjects(objectMapperFactory()) - } - }.build() - val node = loader.load() - val config = node.get() - return config - } - } - - fun toDatabaseUrl(): String { - val absoluteUrl = if (url.contains(":")) url else File("").absolutePath + url - return "$driver:$absoluteUrl" - } -} \ No newline at end of file From c14bd2b56695a0289d60388ef6d13cfda5e73083 Mon Sep 17 00:00:00 2001 From: Philipp Date: Tue, 2 Apr 2024 13:51:21 +0200 Subject: [PATCH 54/94] fix: yaml file saving format --- .../controller/runtime/YamlDirectoryRepository.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) 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 c262a57..a5a51bc 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 @@ -1,18 +1,18 @@ package app.simplecloud.controller.runtime +import com.github.difflib.text.DiffRowGenerator import kotlinx.coroutines.* import org.apache.logging.log4j.LogManager +import org.spongepowered.configurate.BasicConfigurationNode import org.spongepowered.configurate.ConfigurationOptions import org.spongepowered.configurate.kotlin.objectMapperFactory +import org.spongepowered.configurate.yaml.NodeStyle import org.spongepowered.configurate.yaml.YamlConfigurationLoader import java.io.File -import java.nio.file.FileSystems -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.StandardWatchEventKinds -import java.nio.file.WatchEvent +import java.nio.file.* import kotlin.io.path.name + abstract class YamlDirectoryRepository( private val directory: Path, private val clazz: Class, @@ -83,6 +83,7 @@ abstract class YamlDirectoryRepository( return loaders.getOrPut(file) { YamlConfigurationLoader.builder() .path(file.toPath()) + .nodeStyle(NodeStyle.BLOCK) .defaultOptions { options -> options.serializers { builder -> builder.registerAnnotatedObjects(objectMapperFactory()) From 7693c86620a57751471411f4fc0172fbe35cd443 Mon Sep 17 00:00:00 2001 From: Philipp Date: Tue, 2 Apr 2024 13:51:41 +0200 Subject: [PATCH 55/94] refactor: optimize imports --- .../simplecloud/controller/runtime/YamlDirectoryRepository.kt | 2 -- 1 file changed, 2 deletions(-) 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 a5a51bc..f0fafd0 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 @@ -1,9 +1,7 @@ package app.simplecloud.controller.runtime -import com.github.difflib.text.DiffRowGenerator import kotlinx.coroutines.* import org.apache.logging.log4j.LogManager -import org.spongepowered.configurate.BasicConfigurationNode import org.spongepowered.configurate.ConfigurationOptions import org.spongepowered.configurate.kotlin.objectMapperFactory import org.spongepowered.configurate.yaml.NodeStyle From 6f934957de5b52053295c54bb01f74004068affe Mon Sep 17 00:00:00 2001 From: Philipp Date: Tue, 2 Apr 2024 14:08:28 +0200 Subject: [PATCH 56/94] refactor: clean up gradle files --- controller-runtime/build.gradle.kts | 4 +-- controller-shared/build.gradle.kts | 18 ++++++------- gradle/libs.versions.toml | 39 ++++++++++++++++++++++++++--- 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/controller-runtime/build.gradle.kts b/controller-runtime/build.gradle.kts index 460bda2..38cd259 100644 --- a/controller-runtime/build.gradle.kts +++ b/controller-runtime/build.gradle.kts @@ -4,9 +4,9 @@ plugins { dependencies { api(project(":controller-shared")) + api(rootProject.libs.kotlinCoroutines) implementation(rootProject.libs.bundles.log4j) - api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2") - implementation("com.github.ajalt.clikt:clikt:4.3.0") + implementation(rootProject.libs.clikt) } application { diff --git a/controller-shared/build.gradle.kts b/controller-shared/build.gradle.kts index 34a1adf..7ab956b 100644 --- a/controller-shared/build.gradle.kts +++ b/controller-shared/build.gradle.kts @@ -2,17 +2,15 @@ import com.google.protobuf.gradle.* plugins { alias(libs.plugins.protobuf) - id("org.jooq.jooq-codegen-gradle") version "3.19.3" + alias(libs.plugins.jooqCodegen) } dependencies { api(rootProject.libs.bundles.proto) - api("org.jooq:jooq:3.19.3") - api("org.jooq:jooq-meta:3.19.3") - api("org.spongepowered:configurate-yaml:4.0.0") - api("org.spongepowered:configurate-extra-kotlin:4.1.2") - api("org.xerial:sqlite-jdbc:3.44.1.0") - jooqCodegen("org.jooq:jooq-meta-extensions:3.19.3") + api(rootProject.libs.bundles.jooq) + api(rootProject.libs.bundles.configurate) + api(rootProject.libs.sqliteJdbc) + jooqCodegen(rootProject.libs.jooqMetaExtensions) } sourceSets { @@ -40,14 +38,14 @@ sourceSets { protobuf { protoc { - artifact = "com.google.protobuf:protoc:3.23.1" + artifact = libs.protobufProtoc.get().toString() } plugins { id("grpc") { - artifact = "io.grpc:protoc-gen-grpc-java:1.55.1" + artifact = libs.protobufGenGrpc.get().toString() } id("grpckt") { - artifact = "io.grpc:protoc-gen-grpc-kotlin:1.3.0:jdk8@jar" + artifact = libs.protobufGenGrpcKotlin.get().toString() } } generateProtoTasks { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3cf5d16..ed25e38 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,24 +1,48 @@ [versions] kotlin = "1.8.0" +kotlinCoroutines = "1.4.2" shadow = "8.1.1" log4j = "2.20.0" protobuf = "3.25.2" protobufPlugin = "0.9.4" grpcVersion = "1.61.0" grpcKotlinVersion = "1.4.1" +grpcGenKotlinVersion = "1.3.0:jdk8@jar" +jooqVersion = "3.19.3" +configurateVersion = "4.1.2" +sqliteJdbcVersion = "3.44.1.0" +cliktVersion = "4.3.0" [libraries] kotlinJvm = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } kotlinTest = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } +kotlinCoroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinCoroutines" } + log4jCore = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } log4jApi = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j" } log4jSlf4j = { module = "org.apache.logging.log4j:log4j-slf4j-impl", version.ref = "log4j" } -protobuf = { module = "com.google.protobuf:protobuf-kotlin", version.ref = "protobuf" } + +protobufKotlin = { module = "com.google.protobuf:protobuf-kotlin", version.ref = "protobuf" } +protobufProtoc = { module = "com.google.protobuf:protoc", version.ref = "protobuf" } +protobufGenGrpc = { module = "io.grpc:protoc-gen-grpc-java", version.ref = "grpcVersion" } +protobufGenGrpcKotlin = { module = "io.grpc:protoc-gen-grpc-kotlin", version.ref = "grpcGenKotlinVersion" } + grpcStub = { module = "io.grpc:grpc-stub", version.ref = "grpcVersion" } grpcKotlinStub = { module = "io.grpc:grpc-kotlin-stub", version.ref = "grpcKotlinVersion" } grpcProtobuf = { module = "io.grpc:grpc-protobuf", version.ref = "grpcVersion" } grpcNettyShaded = { module = "io.grpc:grpc-netty-shaded", version.ref = "grpcVersion" } +qooq = { module = "org.jooq:jooq", version.ref = "jooqVersion" } +qooqMeta = { module = "org.jooq:jooq-meta", version.ref = "jooqVersion" } +jooqMetaExtensions = { module = "org.jooq:jooq-meta-extensions", version.ref = "jooqVersion" } + +configurateYaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurateVersion" } +configurateExtraKotlin = { module = "org.spongepowered:configurate-extra-kotlin", version.ref = "configurateVersion" } + +sqliteJdbc = { module = "org.xerial:sqlite-jdbc", version.ref = "sqliteJdbcVersion" } + +clikt = { module = "com.github.ajalt.clikt:clikt", version.ref = "cliktVersion" } + [bundles] log4j = [ "log4jCore", @@ -26,14 +50,23 @@ log4j = [ "log4jSlf4j" ] proto = [ - "protobuf", + "protobufKotlin", "grpcStub", "grpcKotlinStub", "grpcProtobuf", "grpcNettyShaded" ] +jooq = [ + "qooq", + "qooqMeta" +] +configurate = [ + "configurateYaml", + "configurateExtraKotlin" +] [plugins] kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" } -protobuf = { id = "com.google.protobuf", version.ref = "protobufPlugin" } \ No newline at end of file +protobuf = { id = "com.google.protobuf", version.ref = "protobufPlugin" } +jooqCodegen = { id = "org.jooq.jooq-codegen-gradle", version.ref = "jooqVersion" } \ No newline at end of file From e0ae5844fda3f9119ac459ceed5eafeb44c7af79 Mon Sep 17 00:00:00 2001 From: Philipp Date: Tue, 2 Apr 2024 14:24:36 +0200 Subject: [PATCH 57/94] feat: codestyles --- .idea/codeStyles/Project.xml | 10 ++++++++++ .idea/codeStyles/codeStyleConfig.xml | 5 +++++ 2 files changed, 15 insertions(+) create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..51024d5 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..dcc2d5f --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file From 7daba0588ba834781d80e3d9af7df936adb332b5 Mon Sep 17 00:00:00 2001 From: Philipp Date: Tue, 2 Apr 2024 14:24:52 +0200 Subject: [PATCH 58/94] refactor: reformat code --- .../api/server/impl/ServerApiImpl.kt | 7 +- .../controller/runtime/ControllerRuntime.kt | 116 ++++---- .../controller/runtime/Reconciler.kt | 170 +++++------ .../runtime/YamlDirectoryRepository.kt | 210 ++++++------- .../controller/runtime/YamlRepository.kt | 4 - .../controller/runtime/database/Database.kt | 24 +- .../runtime/database/DatabaseFactory.kt | 8 +- .../runtime/group/GroupRepository.kt | 2 +- .../launcher/ControllerStartCommand.kt | 22 +- .../server/ServerNumericalIdRepository.kt | 48 +-- .../runtime/server/ServerRepository.kt | 279 +++++++++--------- .../runtime/server/ServerService.kt | 252 ++++++++-------- .../src/main/resources/groups.yml | 28 +- .../src/main/resources/hosts.yml | 6 +- .../src/main/resources/log4j2.xml | 3 +- controller-shared/build.gradle.kts | 2 +- .../controller/shared/group/Group.kt | 80 ++--- .../controller/shared/server/ServerFactory.kt | 1 + readme.md | 90 +++--- settings.gradle.kts | 6 +- 20 files changed, 684 insertions(+), 674 deletions(-) diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt index 7114f64..6d1d058 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt @@ -4,14 +4,9 @@ import app.simplecloud.controller.api.Controller import app.simplecloud.controller.api.server.ServerApi import app.simplecloud.controller.shared.future.toCompletable import app.simplecloud.controller.shared.group.Group -import app.simplecloud.controller.shared.proto.ControllerServerServiceGrpc -import app.simplecloud.controller.shared.proto.GroupNameRequest -import app.simplecloud.controller.shared.proto.ServerIdRequest -import app.simplecloud.controller.shared.proto.ServerType -import app.simplecloud.controller.shared.proto.ServerTypeRequest +import app.simplecloud.controller.shared.proto.* import app.simplecloud.controller.shared.server.Server import app.simplecloud.controller.shared.status.ApiResponse -import com.google.protobuf.Api import java.util.concurrent.CompletableFuture class ServerApiImpl : ServerApi { 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 52a29a0..a39ef33 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 @@ -17,78 +17,78 @@ import org.apache.logging.log4j.LogManager import kotlin.concurrent.thread class ControllerRuntime( - controllerStartCommand: ControllerStartCommand + controllerStartCommand: ControllerStartCommand ) { - private val logger = LogManager.getLogger(ControllerRuntime::class.java) + private val logger = LogManager.getLogger(ControllerRuntime::class.java) - private val database = DatabaseFactory.createDatabase(controllerStartCommand.databaseUrl) + private val database = DatabaseFactory.createDatabase(controllerStartCommand.databaseUrl) - private val groupRepository = GroupRepository(controllerStartCommand.groupPath) - private val numericalIdRepository = ServerNumericalIdRepository() - private val serverRepository = ServerRepository(database, numericalIdRepository) - private val hostRepository = ServerHostRepository() - private val reconciler = Reconciler(groupRepository, serverRepository, hostRepository, createManagedChannel()) - private val server = createGrpcServerFromEnv() + private val groupRepository = GroupRepository(controllerStartCommand.groupPath) + private val numericalIdRepository = ServerNumericalIdRepository() + private val serverRepository = ServerRepository(database, numericalIdRepository) + private val hostRepository = ServerHostRepository() + private val reconciler = Reconciler(groupRepository, serverRepository, hostRepository, createManagedChannel()) + private val server = createGrpcServerFromEnv() - fun start() { - setupDatabase() - startGrpcServer() - startReconciler() - loadGroups() - loadServers() - } + fun start() { + setupDatabase() + startGrpcServer() + startReconciler() + loadGroups() + loadServers() + } - private fun setupDatabase() { - logger.info("Setting up database...") - database.setup() - } + private fun setupDatabase() { + logger.info("Setting up database...") + database.setup() + } - private fun loadServers() { - logger.info("Loading servers...") - serverRepository.load() - } + private fun loadServers() { + logger.info("Loading servers...") + serverRepository.load() + } - private fun startGrpcServer() { - logger.info("Starting gRPC server...") - thread { - server.start() - server.awaitTermination() + private fun startGrpcServer() { + logger.info("Starting gRPC server...") + thread { + server.start() + server.awaitTermination() + } } - } - private fun startReconciler() { - logger.info("Starting Reconciler...") - startReconcilerJob() - } + private fun startReconciler() { + logger.info("Starting Reconciler...") + startReconcilerJob() + } - private fun loadGroups() { - logger.info("Loading groups...") - val loadedGroups = groupRepository.loadAll() - logger.info("Loaded groups: ${loadedGroups.joinToString(",")}") - } + private fun loadGroups() { + logger.info("Loading groups...") + val loadedGroups = groupRepository.loadAll() + logger.info("Loaded groups: ${loadedGroups.joinToString(",")}") + } - private fun createGrpcServerFromEnv(): Server { - val port = System.getenv("GRPC_PORT")?.toInt() ?: 5816 - return ServerBuilder.forPort(port) - .addService(GroupService(groupRepository)) - .addService(ServerService(numericalIdRepository, serverRepository, hostRepository, groupRepository)) - .build() - } + private fun createGrpcServerFromEnv(): Server { + val port = System.getenv("GRPC_PORT")?.toInt() ?: 5816 + return ServerBuilder.forPort(port) + .addService(GroupService(groupRepository)) + .addService(ServerService(numericalIdRepository, serverRepository, hostRepository, groupRepository)) + .build() + } - private fun createManagedChannel(): ManagedChannel { - val port = System.getenv("GRPC_PORT")?.toInt() ?: 5816 - return ManagedChannelBuilder.forAddress("localhost", port).usePlaintext().build() - } + private fun createManagedChannel(): ManagedChannel { + val port = System.getenv("GRPC_PORT")?.toInt() ?: 5816 + return ManagedChannelBuilder.forAddress("localhost", port).usePlaintext().build() + } - @OptIn(InternalCoroutinesApi::class) - private fun startReconcilerJob(): Job { - return CoroutineScope(Dispatchers.Default).launch { - while (NonCancellable.isActive) { - reconciler.reconcile() - delay(2000L) - } + @OptIn(InternalCoroutinesApi::class) + private fun startReconcilerJob(): Job { + return CoroutineScope(Dispatchers.Default).launch { + while (NonCancellable.isActive) { + reconciler.reconcile() + delay(2000L) + } + } } - } } \ No newline at end of file diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt index fd91ddc..6a6f117 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt @@ -15,105 +15,105 @@ import org.apache.logging.log4j.LogManager import java.time.LocalDateTime class Reconciler( - private val groupRepository: GroupRepository, - private val serverRepository: ServerRepository, - private val serverHostRepository: ServerHostRepository, - managedChannel: ManagedChannel, + private val groupRepository: GroupRepository, + private val serverRepository: ServerRepository, + private val serverHostRepository: ServerHostRepository, + managedChannel: ManagedChannel, ) { - val INACTIVE_SERVER_TIME = 5L + val INACTIVE_SERVER_TIME = 5L - private val serverStub = ControllerServerServiceGrpc.newFutureStub(managedChannel) - private val logger = LogManager.getLogger(Reconciler::class.java) + private val serverStub = ControllerServerServiceGrpc.newFutureStub(managedChannel) + private val logger = LogManager.getLogger(Reconciler::class.java) - fun reconcile() { - groupRepository.findAll().forEach { group -> - val servers = serverRepository.findServersByGroup(group.name) - val availableServerCount = servers.count { server -> - server.state == ServerState.AVAILABLE - || server.state == ServerState.STARTING - || server.state == ServerState.PREPARING - } + fun reconcile() { + groupRepository.findAll().forEach { group -> + val servers = serverRepository.findServersByGroup(group.name) + val availableServerCount = servers.count { server -> + server.state == ServerState.AVAILABLE + || server.state == ServerState.STARTING + || server.state == ServerState.PREPARING + } + + logger.info("Reconciling group ${group.name} with ${servers.size} servers, $availableServerCount available servers") + + cleanupServers(group, servers, availableServerCount) + startServers(group, availableServerCount, servers.size) + } + } - logger.info("Reconciling group ${group.name} with ${servers.size} servers, $availableServerCount available servers") + private fun cleanupServers(group: Group, servers: List, availableServerCount: Int) { + val hasMoreServersThenNeeded = availableServerCount > group.minOnlineCount + servers + .filter { it.state == ServerState.AVAILABLE } + .forEach { server -> + if (hasMoreServersThenNeeded && !wasUpdatedRecently(server)) { + logger.info("Stopping server ${server.uniqueId} of group ${group.name}") + serverStub.stopServer( + ServerIdRequest.newBuilder() + .setId(server.uniqueId) + .build() + ).toCompletable() + .thenApply { + logger.info("Stopped server ${server.uniqueId} of group ${group.name}") + }.exceptionally { + logger.error("Could not stop server ${server.uniqueId} of group ${group.name}: ${it.message}") + } + } + } + } - cleanupServers(group, servers, availableServerCount) - startServers(group, availableServerCount, servers.size) + private fun wasUpdatedRecently(server: Server): Boolean { + return server.updatedAt.isAfter(LocalDateTime.now().minusMinutes(INACTIVE_SERVER_TIME)) } - } - - private fun cleanupServers(group: Group, servers: List, availableServerCount: Int) { - val hasMoreServersThenNeeded = availableServerCount > group.minOnlineCount - servers - .filter { it.state == ServerState.AVAILABLE } - .forEach { server -> - if (hasMoreServersThenNeeded && !wasUpdatedRecently(server)) { - logger.info("Stopping server ${server.uniqueId} of group ${group.name}") - serverStub.stopServer( - ServerIdRequest.newBuilder() - .setId(server.uniqueId) - .build() - ).toCompletable() + + private fun startServers( + group: Group, + availableServerCount: Int, + serverCount: Int + ) { + if (!checkIfNewServerCanBeStarted(group, availableServerCount, serverCount) || + !serverHostRepository.areServerHostsAvailable() + ) { + return + } + + startServer(group) + } + + private fun startServer(group: Group) { + logger.info("Starting new instance of group ${group.name}") + serverStub.startServer(GroupNameRequest.newBuilder().setName(group.name).build()).toCompletable() .thenApply { - logger.info("Stopped server ${server.uniqueId} of group ${group.name}") + logger.info("Started new instance ${it.groupName}-${it.numericalId}/${it.uniqueId} of group ${group.name} on ${it.ip}:${it.port}") }.exceptionally { - logger.error("Could not stop server ${server.uniqueId} of group ${group.name}: ${it.message}") + it.printStackTrace() + logger.error("Could not start a new instance of group ${group.name}: ${it.message}") } - } - } - } - - private fun wasUpdatedRecently(server: Server): Boolean { - return server.updatedAt.isAfter(LocalDateTime.now().minusMinutes(INACTIVE_SERVER_TIME)) - } - - private fun startServers( - group: Group, - availableServerCount: Int, - serverCount: Int - ) { - if (!checkIfNewServerCanBeStarted(group, availableServerCount, serverCount) || - !serverHostRepository.areServerHostsAvailable() - ) { - return } - startServer(group) - } - - private fun startServer(group: Group) { - logger.info("Starting new instance of group ${group.name}") - serverStub.startServer(GroupNameRequest.newBuilder().setName(group.name).build()).toCompletable() - .thenApply { - logger.info("Started new instance ${it.groupName}-${it.numericalId}/${it.uniqueId} of group ${group.name} on ${it.ip}:${it.port}") - }.exceptionally { - it.printStackTrace() - logger.error("Could not start a new instance of group ${group.name}: ${it.message}") - } - } - - private fun checkIfNewServerCanBeStarted( - group: Group, - availableServerCount: Int, - serverCount: Int - ): Boolean { - return getNeededServerCount(group, availableServerCount, serverCount) > 0 - } - - private fun getNeededServerCount( - group: Group, - availableServerCount: Int, - serverCount: Int - ): Int { - if (!checkIfServersAreNeeded(group, availableServerCount, serverCount)) { - return 0 + private fun checkIfNewServerCanBeStarted( + group: Group, + availableServerCount: Int, + serverCount: Int + ): Boolean { + return getNeededServerCount(group, availableServerCount, serverCount) > 0 } - return (group.minOnlineCount - availableServerCount).toInt() - } + private fun getNeededServerCount( + group: Group, + availableServerCount: Int, + serverCount: Int + ): Int { + if (!checkIfServersAreNeeded(group, availableServerCount, serverCount)) { + return 0 + } + + return (group.minOnlineCount - availableServerCount).toInt() + } - private fun checkIfServersAreNeeded(group: Group, availableServerCount: Int, serverCount: Int): Boolean { - return availableServerCount < group.minOnlineCount && serverCount < group.maxOnlineCount - } + private fun checkIfServersAreNeeded(group: Group, availableServerCount: Int, serverCount: Int): Boolean { + return availableServerCount < group.minOnlineCount && serverCount < group.maxOnlineCount + } } \ 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 f0fafd0..79b7ca6 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 @@ -12,128 +12,128 @@ import kotlin.io.path.name abstract class YamlDirectoryRepository( - private val directory: Path, - private val clazz: Class, + private val directory: Path, + private val clazz: Class, ) { - private val logger = LogManager.getLogger(this::class.java) + private val logger = LogManager.getLogger(this::class.java) - private val watchService = FileSystems.getDefault().newWatchService() - private val loaders = mutableMapOf() - protected val entities = mutableMapOf() + private val watchService = FileSystems.getDefault().newWatchService() + private val loaders = mutableMapOf() + protected val entities = mutableMapOf() - abstract fun getFileName(identifier: I): String + abstract fun getFileName(identifier: I): String - abstract fun find(identifier: I): T? + abstract fun find(identifier: I): T? - abstract fun save(entity: T) + abstract fun save(entity: T) - fun delete(entity: T): Boolean { - val file = entities.keys.find { entities[it] == entity } ?: return false - return delete(file) - } + fun delete(entity: T): Boolean { + val file = entities.keys.find { entities[it] == entity } ?: return false + return delete(file) + } + + fun findAll(): List { + return entities.values.toList() + } + + fun loadAll(): List { + if (!directory.toFile().exists()) { + directory.toFile().mkdir() + } - fun findAll(): List { - return entities.values.toList() - } + val fileNames = mutableListOf() - fun loadAll(): List { - if (!directory.toFile().exists()) { - directory.toFile().mkdir() + Files.list(directory) + .filter { !it.toFile().isDirectory && it.toString().endsWith(".yml") } + .forEach { + load(it.toFile()) + fileNames.add(it.name) + } + + registerWatcher() + return fileNames } - val fileNames = mutableListOf() - - Files.list(directory) - .filter { !it.toFile().isDirectory && it.toString().endsWith(".yml") } - .forEach { - load(it.toFile()) - fileNames.add(it.name) - } - - registerWatcher() - return fileNames - } - - private fun load(file: File) { - val loader = getOrCreateLoader(file) - val node = loader.load(ConfigurationOptions.defaults()) - val entity = node.get(clazz) ?: return - entities[file] = entity - } - - private fun delete(file: File): Boolean { - val deletedSuccessfully = file.delete() - val removedSuccessfully = entities.remove(file) != null - return deletedSuccessfully && removedSuccessfully - } - - protected fun save(fileName: String, entity: T) { - val file = directory.resolve(fileName).toFile() - val loader = getOrCreateLoader(file) - val node = loader.createNode(ConfigurationOptions.defaults()) - node.set(clazz, entity) - loader.save(node) - entities[file] = entity - } - - private fun getOrCreateLoader(file: File): YamlConfigurationLoader { - return loaders.getOrPut(file) { - YamlConfigurationLoader.builder() - .path(file.toPath()) - .nodeStyle(NodeStyle.BLOCK) - .defaultOptions { options -> - options.serializers { builder -> - builder.registerAnnotatedObjects(objectMapperFactory()) - } - }.build() + private fun load(file: File) { + val loader = getOrCreateLoader(file) + val node = loader.load(ConfigurationOptions.defaults()) + val entity = node.get(clazz) ?: return + entities[file] = entity } - } - - @OptIn(InternalCoroutinesApi::class) - private fun registerWatcher(): Job { - directory.register( - watchService, - StandardWatchEventKinds.ENTRY_CREATE, - StandardWatchEventKinds.ENTRY_DELETE, - StandardWatchEventKinds.ENTRY_MODIFY - ) - - return CoroutineScope(Dispatchers.Default).launch { - while (NonCancellable.isActive) { - val key = watchService.take() - for (event in key.pollEvents()) { - val path = event.context() as? Path ?: continue - val resolvedPath = directory.resolve(path) - if (Files.isDirectory(resolvedPath) || !resolvedPath.toString().endsWith(".yml")) { - continue - } - val kind = event.kind() - logger.info("Detected change in $resolvedPath (${getChangeStatus(kind)})") - when (kind) { + + private fun delete(file: File): Boolean { + val deletedSuccessfully = file.delete() + val removedSuccessfully = entities.remove(file) != null + return deletedSuccessfully && removedSuccessfully + } + + protected fun save(fileName: String, entity: T) { + val file = directory.resolve(fileName).toFile() + val loader = getOrCreateLoader(file) + val node = loader.createNode(ConfigurationOptions.defaults()) + node.set(clazz, entity) + loader.save(node) + entities[file] = entity + } + + private fun getOrCreateLoader(file: File): YamlConfigurationLoader { + return loaders.getOrPut(file) { + YamlConfigurationLoader.builder() + .path(file.toPath()) + .nodeStyle(NodeStyle.BLOCK) + .defaultOptions { options -> + options.serializers { builder -> + builder.registerAnnotatedObjects(objectMapperFactory()) + } + }.build() + } + } + + @OptIn(InternalCoroutinesApi::class) + private fun registerWatcher(): Job { + directory.register( + watchService, StandardWatchEventKinds.ENTRY_CREATE, + StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY - -> { - load(resolvedPath.toFile()) + ) + + return CoroutineScope(Dispatchers.Default).launch { + while (NonCancellable.isActive) { + val key = watchService.take() + for (event in key.pollEvents()) { + val path = event.context() as? Path ?: continue + val resolvedPath = directory.resolve(path) + if (Files.isDirectory(resolvedPath) || !resolvedPath.toString().endsWith(".yml")) { + continue + } + val kind = event.kind() + logger.info("Detected change in $resolvedPath (${getChangeStatus(kind)})") + when (kind) { + StandardWatchEventKinds.ENTRY_CREATE, + StandardWatchEventKinds.ENTRY_MODIFY + -> { + load(resolvedPath.toFile()) + } + + StandardWatchEventKinds.ENTRY_DELETE -> { + delete(resolvedPath.toFile()) + } + } + } + key.reset() } - - StandardWatchEventKinds.ENTRY_DELETE -> { - delete(resolvedPath.toFile()) - } - } } - key.reset() - } } - } - - private fun getChangeStatus(kind: WatchEvent.Kind<*>): String { - return when (kind) { - StandardWatchEventKinds.ENTRY_CREATE -> "Created" - StandardWatchEventKinds.ENTRY_DELETE -> "Deleted" - StandardWatchEventKinds.ENTRY_MODIFY -> "Modified" - else -> "Unknown" + + private fun getChangeStatus(kind: WatchEvent.Kind<*>): String { + return when (kind) { + StandardWatchEventKinds.ENTRY_CREATE -> "Created" + StandardWatchEventKinds.ENTRY_DELETE -> "Deleted" + StandardWatchEventKinds.ENTRY_MODIFY -> "Modified" + else -> "Unknown" + } } - } } \ No newline at end of file diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt index 7a18085..9dc6762 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt @@ -1,15 +1,11 @@ package app.simplecloud.controller.runtime import org.spongepowered.configurate.ConfigurationNode -import org.spongepowered.configurate.kotlin.extensions.get -import org.spongepowered.configurate.kotlin.objectMapper import org.spongepowered.configurate.kotlin.objectMapperFactory -import org.spongepowered.configurate.objectmapping.ConfigSerializable import org.spongepowered.configurate.yaml.YamlConfigurationLoader import java.io.File import java.nio.file.Files import java.nio.file.StandardCopyOption -import java.util.Collections import java.util.concurrent.CompletableFuture abstract class YamlRepository(path: String, private var clazz: Class) : Repository() { diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/database/Database.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/database/Database.kt index ca72bbb..99266b9 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/database/Database.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/database/Database.kt @@ -3,20 +3,20 @@ package app.simplecloud.controller.runtime.database import org.jooq.DSLContext class Database( - val context: DSLContext + val context: DSLContext ) { - fun setup() { - System.setProperty("org.jooq.no-logo", "true") - System.setProperty("org.jooq.no-tips", "true") - val setupInputStream = Database::class.java.getResourceAsStream("/schema.sql") - ?: throw IllegalArgumentException("Database schema not found.") - val setupCommands = setupInputStream.bufferedReader().use { it.readText() }.split(";") - setupCommands.forEach { - val trimmed = it.trim() - if (trimmed.isNotEmpty()) - context.execute(org.jooq.impl.DSL.sql(trimmed)) + fun setup() { + System.setProperty("org.jooq.no-logo", "true") + System.setProperty("org.jooq.no-tips", "true") + val setupInputStream = Database::class.java.getResourceAsStream("/schema.sql") + ?: throw IllegalArgumentException("Database schema not found.") + val setupCommands = setupInputStream.bufferedReader().use { it.readText() }.split(";") + setupCommands.forEach { + val trimmed = it.trim() + if (trimmed.isNotEmpty()) + context.execute(org.jooq.impl.DSL.sql(trimmed)) + } } - } } \ No newline at end of file diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/database/DatabaseFactory.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/database/DatabaseFactory.kt index 0506bc6..d7bc914 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/database/DatabaseFactory.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/database/DatabaseFactory.kt @@ -4,9 +4,9 @@ import org.jooq.impl.DSL object DatabaseFactory { - fun createDatabase(databaseUrl: String): Database { - val databaseContext = DSL.using(databaseUrl) - return Database(databaseContext) - } + fun createDatabase(databaseUrl: String): Database { + val databaseContext = DSL.using(databaseUrl) + return Database(databaseContext) + } } \ No newline at end of file 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 0ba7f5d..1aac9c8 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 @@ -6,7 +6,7 @@ import java.nio.file.Path class GroupRepository( path: Path -): YamlDirectoryRepository(path, Group::class.java) { +) : YamlDirectoryRepository(path, Group::class.java) { override fun getFileName(identifier: String): String { return "$identifier.yml" } 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 cc943b1..82b1a0d 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 @@ -2,19 +2,23 @@ package app.simplecloud.controller.runtime.launcher import app.simplecloud.controller.runtime.ControllerRuntime import com.github.ajalt.clikt.core.CliktCommand -import com.github.ajalt.clikt.parameters.options.* +import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.types.path import java.nio.file.Path -class ControllerStartCommand: CliktCommand() { +class ControllerStartCommand : CliktCommand() { - private val defaultDatabaseUrl = "jdbc:sqlite:database.db" + private val defaultDatabaseUrl = "jdbc:sqlite:database.db" - val groupPath: Path by option(help = "Path to the group files (groups)", envvar = "GROUPS_PATH").path().default(Path.of("groups")) - val databaseUrl: String by option(help = "Database URL (${defaultDatabaseUrl})", envvar = "DATABASE_URL").default(defaultDatabaseUrl) + val groupPath: Path by option(help = "Path to the group files (groups)", envvar = "GROUPS_PATH") + .path() + .default(Path.of("groups")) + val databaseUrl: String by option(help = "Database URL (${defaultDatabaseUrl})", envvar = "DATABASE_URL") + .default(defaultDatabaseUrl) - override fun run() { - val controllerRuntime = ControllerRuntime(this) - controllerRuntime.start() - } + override fun run() { + val controllerRuntime = ControllerRuntime(this) + controllerRuntime.start() + } } \ No newline at end of file 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 839e383..208c6f6 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 @@ -4,30 +4,30 @@ 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 numericalIds = ConcurrentHashMap>() + + @Synchronized + fun findNextNumericalId(group: String): Int { + val numericalIds = findNumericalIds(group) + var nextId = 1 + while (numericalIds.contains(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 + } + + private fun findNumericalIds(group: String): List { + return numericalIds[group]?.toList() ?: emptyList() } - 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 - } - - private fun findNumericalIds(group: String): List { - return numericalIds[group]?.toList() ?: emptyList() - } } 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 8f364c1..8509539 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 @@ -11,158 +11,161 @@ import java.time.LocalDateTime import java.util.concurrent.CompletableFuture class ServerRepository( - private val database: Database, - private val numericalIdRepository: ServerNumericalIdRepository + private val database: Database, + private val numericalIdRepository: ServerNumericalIdRepository ) : Repository() { - fun findServerById(id: String): Server? { - return firstOrNull { it.uniqueId == id } - } - - fun findServersByHostId(id: String): List { - return filter { it.host == id } - } + fun findServerById(id: String): Server? { + return firstOrNull { it.uniqueId == id } + } - fun findServersByGroup(group: String): List { - return filter { server -> server.group == group } - } + fun findServersByHostId(id: String): List { + return filter { it.host == id } + } - fun findServersByType(type: ServerType): List { - return filter { server -> server.type == type } - } + fun findServersByGroup(group: String): List { + return filter { server -> server.group == group } + } - override fun load() { - clear() - val query = database.context.select().from(CLOUD_SERVERS).fetchInto(CLOUD_SERVERS) - query.map { - val propertiesQuery = database.context.select().from(CLOUD_SERVER_PROPERTIES).fetchInto(CLOUD_SERVER_PROPERTIES) - numericalIdRepository.saveNumericalId(it.groupName, it.numericalId) - add( - Server( - it.uniqueId, - ServerType.valueOf(it.type), - it.groupName, - it.hostId, - it.numericalId, - it.templateId, - it.ip, - it.port.toLong(), - it.minimumMemory.toLong(), - it.maximumMemory.toLong(), - it.playerCount.toLong(), - propertiesQuery.map { item -> - item.key to item.value - }.toMap().toMutableMap(), - ServerState.valueOf(it.state), - it.createdAt, - it.updatedAt - ) - ) + fun findServersByType(type: ServerType): List { + return filter { server -> server.type == type } } - } - override fun delete(element: Server): CompletableFuture { - val server = firstOrNull { it.uniqueId == element.uniqueId } ?: return CompletableFuture.completedFuture(false) - val canDelete = - database.context.deleteFrom(CLOUD_SERVER_PROPERTIES).where(CLOUD_SERVER_PROPERTIES.SERVER_ID.eq(server.uniqueId)) - .executeAsync().toCompletableFuture().thenApply { - return@thenApply true - }.exceptionally { - it.printStackTrace() - return@exceptionally false - }.get() - if (!canDelete) return CompletableFuture.completedFuture(false) - numericalIdRepository.removeNumericalId(server.group, server.numericalId) - return database.context.deleteFrom(CLOUD_SERVERS).where(CLOUD_SERVERS.UNIQUE_ID.eq(server.uniqueId)).executeAsync() - .toCompletableFuture().thenApply { - return@thenApply it > 0 && remove(server) - }.exceptionally { - it.printStackTrace() - return@exceptionally false - } - } + override fun load() { + clear() + val query = database.context.select().from(CLOUD_SERVERS).fetchInto(CLOUD_SERVERS) + query.map { + val propertiesQuery = + database.context.select().from(CLOUD_SERVER_PROPERTIES).fetchInto(CLOUD_SERVER_PROPERTIES) + numericalIdRepository.saveNumericalId(it.groupName, it.numericalId) + add( + Server( + it.uniqueId, + ServerType.valueOf(it.type), + it.groupName, + it.hostId, + it.numericalId, + it.templateId, + it.ip, + it.port.toLong(), + it.minimumMemory.toLong(), + it.maximumMemory.toLong(), + it.playerCount.toLong(), + propertiesQuery.map { item -> + item.key to item.value + }.toMap().toMutableMap(), + ServerState.valueOf(it.state), + it.createdAt, + it.updatedAt + ) + ) + } + } - override fun save(element: Server) { - val server = firstOrNull { it.uniqueId == element.uniqueId } - if (server != null) { - val index = indexOf(server) - removeAt(index) - add(index, element) - } else { - add(element) + override fun delete(element: Server): CompletableFuture { + val server = firstOrNull { it.uniqueId == element.uniqueId } ?: return CompletableFuture.completedFuture(false) + val canDelete = + database.context.deleteFrom(CLOUD_SERVER_PROPERTIES) + .where(CLOUD_SERVER_PROPERTIES.SERVER_ID.eq(server.uniqueId)) + .executeAsync().toCompletableFuture().thenApply { + return@thenApply true + }.exceptionally { + it.printStackTrace() + return@exceptionally false + }.get() + if (!canDelete) return CompletableFuture.completedFuture(false) + numericalIdRepository.removeNumericalId(server.group, server.numericalId) + return database.context.deleteFrom(CLOUD_SERVERS).where(CLOUD_SERVERS.UNIQUE_ID.eq(server.uniqueId)) + .executeAsync() + .toCompletableFuture().thenApply { + return@thenApply it > 0 && remove(server) + }.exceptionally { + it.printStackTrace() + return@exceptionally false + } } - numericalIdRepository.saveNumericalId(element.group, element.numericalId) + override fun save(element: Server) { + val server = firstOrNull { it.uniqueId == element.uniqueId } + if (server != null) { + val index = indexOf(server) + removeAt(index) + add(index, element) + } else { + add(element) + } - val currentTimestamp = LocalDateTime.now() - database.context.insertInto( - CLOUD_SERVERS, + numericalIdRepository.saveNumericalId(element.group, element.numericalId) - CLOUD_SERVERS.UNIQUE_ID, - CLOUD_SERVERS.TYPE, - CLOUD_SERVERS.GROUP_NAME, - CLOUD_SERVERS.HOST_ID, - CLOUD_SERVERS.NUMERICAL_ID, - CLOUD_SERVERS.TEMPLATE_ID, - CLOUD_SERVERS.IP, - CLOUD_SERVERS.PORT, - CLOUD_SERVERS.MINIMUM_MEMORY, - CLOUD_SERVERS.MAXIMUM_MEMORY, - CLOUD_SERVERS.PLAYER_COUNT, - CLOUD_SERVERS.STATE, - CLOUD_SERVERS.CREATED_AT, - CLOUD_SERVERS.UPDATED_AT - ) - .values( - element.uniqueId, - element.type.toString(), - element.group, - element.host, - element.numericalId, - element.templateId, - element.ip, - element.port.toInt(), - element.minMemory.toInt(), - element.maxMemory.toInt(), - element.playerCount.toInt(), - element.state.toString(), - currentTimestamp, - currentTimestamp - ) - .onDuplicateKeyUpdate() - .set(CLOUD_SERVERS.UNIQUE_ID, element.uniqueId) - .set(CLOUD_SERVERS.TYPE, element.type.toString()) - .set(CLOUD_SERVERS.GROUP_NAME, element.group) - .set(CLOUD_SERVERS.HOST_ID, element.host) - .set(CLOUD_SERVERS.NUMERICAL_ID, element.numericalId) - .set(CLOUD_SERVERS.TEMPLATE_ID, element.templateId) - .set(CLOUD_SERVERS.IP, element.ip) - .set(CLOUD_SERVERS.PORT, element.port.toInt()) - .set(CLOUD_SERVERS.MINIMUM_MEMORY, element.minMemory.toInt()) - .set(CLOUD_SERVERS.MAXIMUM_MEMORY, element.maxMemory.toInt()) - .set(CLOUD_SERVERS.PLAYER_COUNT, element.playerCount.toInt()) - .set(CLOUD_SERVERS.STATE, element.state.toString()) - .set(CLOUD_SERVERS.UPDATED_AT, currentTimestamp) - .executeAsync() - element.properties.forEach { - database.context.insertInto( - CLOUD_SERVER_PROPERTIES, + val currentTimestamp = LocalDateTime.now() + database.context.insertInto( + CLOUD_SERVERS, - CLOUD_SERVER_PROPERTIES.SERVER_ID, - CLOUD_SERVER_PROPERTIES.KEY, - CLOUD_SERVER_PROPERTIES.VALUE - ) - .values( - element.uniqueId, - it.key, - it.value + CLOUD_SERVERS.UNIQUE_ID, + CLOUD_SERVERS.TYPE, + CLOUD_SERVERS.GROUP_NAME, + CLOUD_SERVERS.HOST_ID, + CLOUD_SERVERS.NUMERICAL_ID, + CLOUD_SERVERS.TEMPLATE_ID, + CLOUD_SERVERS.IP, + CLOUD_SERVERS.PORT, + CLOUD_SERVERS.MINIMUM_MEMORY, + CLOUD_SERVERS.MAXIMUM_MEMORY, + CLOUD_SERVERS.PLAYER_COUNT, + CLOUD_SERVERS.STATE, + CLOUD_SERVERS.CREATED_AT, + CLOUD_SERVERS.UPDATED_AT ) - .onDuplicateKeyUpdate() - .set(CLOUD_SERVER_PROPERTIES.SERVER_ID, element.uniqueId) - .set(CLOUD_SERVER_PROPERTIES.KEY, it.key) - .set(CLOUD_SERVER_PROPERTIES.VALUE, it.value) - .executeAsync() + .values( + element.uniqueId, + element.type.toString(), + element.group, + element.host, + element.numericalId, + element.templateId, + element.ip, + element.port.toInt(), + element.minMemory.toInt(), + element.maxMemory.toInt(), + element.playerCount.toInt(), + element.state.toString(), + currentTimestamp, + currentTimestamp + ) + .onDuplicateKeyUpdate() + .set(CLOUD_SERVERS.UNIQUE_ID, element.uniqueId) + .set(CLOUD_SERVERS.TYPE, element.type.toString()) + .set(CLOUD_SERVERS.GROUP_NAME, element.group) + .set(CLOUD_SERVERS.HOST_ID, element.host) + .set(CLOUD_SERVERS.NUMERICAL_ID, element.numericalId) + .set(CLOUD_SERVERS.TEMPLATE_ID, element.templateId) + .set(CLOUD_SERVERS.IP, element.ip) + .set(CLOUD_SERVERS.PORT, element.port.toInt()) + .set(CLOUD_SERVERS.MINIMUM_MEMORY, element.minMemory.toInt()) + .set(CLOUD_SERVERS.MAXIMUM_MEMORY, element.maxMemory.toInt()) + .set(CLOUD_SERVERS.PLAYER_COUNT, element.playerCount.toInt()) + .set(CLOUD_SERVERS.STATE, element.state.toString()) + .set(CLOUD_SERVERS.UPDATED_AT, currentTimestamp) + .executeAsync() + element.properties.forEach { + database.context.insertInto( + CLOUD_SERVER_PROPERTIES, + + CLOUD_SERVER_PROPERTIES.SERVER_ID, + CLOUD_SERVER_PROPERTIES.KEY, + CLOUD_SERVER_PROPERTIES.VALUE + ) + .values( + element.uniqueId, + it.key, + it.value + ) + .onDuplicateKeyUpdate() + .set(CLOUD_SERVER_PROPERTIES.SERVER_ID, element.uniqueId) + .set(CLOUD_SERVER_PROPERTIES.KEY, it.key) + .set(CLOUD_SERVER_PROPERTIES.VALUE, it.value) + .executeAsync() + } } - } } \ No newline at end of file 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 6cf00a0..0ec8e80 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 @@ -14,146 +14,146 @@ import io.grpc.stub.StreamObserver import org.apache.logging.log4j.LogManager class ServerService( - private val numericalIdRepository: ServerNumericalIdRepository, - private val serverRepository: ServerRepository, - private val hostRepository: ServerHostRepository, - private val groupRepository: GroupRepository, + private val numericalIdRepository: ServerNumericalIdRepository, + private val serverRepository: ServerRepository, + private val hostRepository: ServerHostRepository, + private val groupRepository: GroupRepository, ) : ControllerServerServiceGrpc.ControllerServerServiceImplBase() { - private val logger = LogManager.getLogger(ServerService::class.java) + private val logger = LogManager.getLogger(ServerService::class.java) - override fun attachServerHost(request: ServerHostDefinition, responseObserver: StreamObserver) { - val serverHost = ServerHost.fromDefinition(request) - hostRepository.remove(serverHost) - hostRepository.add(serverHost) - logger.info("Successfully registered ServerHost ${serverHost.id}.") - responseObserver.onNext(ApiResponse("success").toDefinition()) - responseObserver.onCompleted() - Context.current().fork().run { - val channel = serverHost.createChannel() - val stub = ServerHostServiceGrpc.newFutureStub(channel) - serverRepository.filter { it.host == serverHost.id }.forEach { - logger.info("Reattaching Server ${it.uniqueId} of group ${it.group}...") - stub.reattachServer(it.toDefinition()).toCompletable().thenApply { response -> - val status = ApiResponse.fromDefinition(response) - if (status.status == "success") { - logger.info("Success!") - } else { - logger.error("Server was found to be offline, unregistering...") - serverRepository.delete(it) - } - channel.shutdown() - } - } - } - } - - override fun updateServer(request: ServerUpdateRequest, responseObserver: StreamObserver) { - val deleted = request.deleted - val server = Server.fromDefinition(request.server) - if (!deleted) { - serverRepository.save(server) - responseObserver.onNext(ApiResponse("success").toDefinition()) - responseObserver.onCompleted() - } else { - logger.info("Deleting server ${server.uniqueId} of group ${request.server.groupName}...") - serverRepository.delete(server).thenApply { - logger.info("Deleted server ${server.uniqueId} of group ${request.server.groupName}.") + override fun attachServerHost(request: ServerHostDefinition, responseObserver: StreamObserver) { + val serverHost = ServerHost.fromDefinition(request) + hostRepository.remove(serverHost) + hostRepository.add(serverHost) + logger.info("Successfully registered ServerHost ${serverHost.id}.") responseObserver.onNext(ApiResponse("success").toDefinition()) responseObserver.onCompleted() - }.exceptionally { - responseObserver.onNext(ApiResponse("error").toDefinition()) - responseObserver.onCompleted() - } + Context.current().fork().run { + val channel = serverHost.createChannel() + val stub = ServerHostServiceGrpc.newFutureStub(channel) + serverRepository.filter { it.host == serverHost.id }.forEach { + logger.info("Reattaching Server ${it.uniqueId} of group ${it.group}...") + stub.reattachServer(it.toDefinition()).toCompletable().thenApply { response -> + val status = ApiResponse.fromDefinition(response) + if (status.status == "success") { + logger.info("Success!") + } else { + logger.error("Server was found to be offline, unregistering...") + serverRepository.delete(it) + } + channel.shutdown() + } + } + } } - } - - override fun getServerById(request: ServerIdRequest, responseObserver: StreamObserver) { - val server = serverRepository.findServerById(request.id)?.toDefinition() - responseObserver.onNext(server) - responseObserver.onCompleted() - } - override fun getServersByGroup( - request: GroupNameRequest, - responseObserver: StreamObserver - ) { - val servers = serverRepository.findServersByGroup(request.name).map { it.toDefinition() } - val response = GetServersByGroupResponse.newBuilder().addAllServers(servers).build() - responseObserver.onNext(response) - responseObserver.onCompleted() - } - - override fun getServersByType( - request: ServerTypeRequest, - responseObserver: StreamObserver - ) { - val servers = serverRepository.findServersByType(request.type).map { it.toDefinition() } - val response = GetServersByGroupResponse.newBuilder().addAllServers(servers).build() - responseObserver.onNext(response) - responseObserver.onCompleted() - } - - override fun startServer(request: GroupNameRequest, responseObserver: StreamObserver) { - val host = hostRepository.findLaziestServerHost(serverRepository) - if (host == null) { - responseObserver.onError(ServerHostException("No server host found, could not start server.")) - return + override fun updateServer(request: ServerUpdateRequest, responseObserver: StreamObserver) { + val deleted = request.deleted + val server = Server.fromDefinition(request.server) + if (!deleted) { + serverRepository.save(server) + responseObserver.onNext(ApiResponse("success").toDefinition()) + responseObserver.onCompleted() + } else { + logger.info("Deleting server ${server.uniqueId} of group ${request.server.groupName}...") + serverRepository.delete(server).thenApply { + logger.info("Deleted server ${server.uniqueId} of group ${request.server.groupName}.") + responseObserver.onNext(ApiResponse("success").toDefinition()) + responseObserver.onCompleted() + }.exceptionally { + responseObserver.onNext(ApiResponse("error").toDefinition()) + responseObserver.onCompleted() + } + } } - val channel = host.createChannel() - val stub = ServerHostServiceGrpc.newFutureStub(channel) - val group = groupRepository.find(request.name) - if (group == null) { - responseObserver.onError(IllegalArgumentException("No group was found matching the group name.")) - return + + override fun getServerById(request: ServerIdRequest, responseObserver: StreamObserver) { + val server = serverRepository.findServerById(request.id)?.toDefinition() + responseObserver.onNext(server) + responseObserver.onCompleted() } - val numericalId = numericalIdRepository.findNextNumericalId(group.name) - val server = ServerFactory.builder() - .setGroup(group) - .setNumericalId(numericalId.toLong()) - .build() - serverRepository.save(server) - stub.startServer( - StartServerRequest.newBuilder() - .setGroup(group.toDefinition()) - .setServer(server.toDefinition()) - .build() - ).toCompletable().thenApply { - serverRepository.save(Server.fromDefinition(it)) - responseObserver.onNext(it) - responseObserver.onCompleted() - channel.shutdown() - return@thenApply - }.exceptionally { - serverRepository.delete(server) - numericalIdRepository.removeNumericalId(group.name, numericalId) - responseObserver.onError(ServerHostException("Could not start server, aborting.")) - channel.shutdown() + override fun getServersByGroup( + request: GroupNameRequest, + responseObserver: StreamObserver + ) { + val servers = serverRepository.findServersByGroup(request.name).map { it.toDefinition() } + val response = GetServersByGroupResponse.newBuilder().addAllServers(servers).build() + responseObserver.onNext(response) + responseObserver.onCompleted() } - } - override fun stopServer(request: ServerIdRequest, responseObserver: StreamObserver) { - val server = serverRepository.findServerById(request.id)?.toDefinition() - if (server == null) { - responseObserver.onError(IllegalArgumentException("No server was found matching this id.")) - return + override fun getServersByType( + request: ServerTypeRequest, + responseObserver: StreamObserver + ) { + val servers = serverRepository.findServersByType(request.type).map { it.toDefinition() } + val response = GetServersByGroupResponse.newBuilder().addAllServers(servers).build() + responseObserver.onNext(response) + responseObserver.onCompleted() } - val host = hostRepository.findServerHostById(server.hostId) - if (host == null) { - responseObserver.onError(ServerHostException("No server host was found matching this server.")) - return + + override fun startServer(request: GroupNameRequest, responseObserver: StreamObserver) { + val host = hostRepository.findLaziestServerHost(serverRepository) + if (host == null) { + responseObserver.onError(ServerHostException("No server host found, could not start server.")) + return + } + val channel = host.createChannel() + val stub = ServerHostServiceGrpc.newFutureStub(channel) + val group = groupRepository.find(request.name) + if (group == null) { + responseObserver.onError(IllegalArgumentException("No group was found matching the group name.")) + return + } + + val numericalId = numericalIdRepository.findNextNumericalId(group.name) + val server = ServerFactory.builder() + .setGroup(group) + .setNumericalId(numericalId.toLong()) + .build() + serverRepository.save(server) + stub.startServer( + StartServerRequest.newBuilder() + .setGroup(group.toDefinition()) + .setServer(server.toDefinition()) + .build() + ).toCompletable().thenApply { + serverRepository.save(Server.fromDefinition(it)) + responseObserver.onNext(it) + responseObserver.onCompleted() + channel.shutdown() + return@thenApply + }.exceptionally { + serverRepository.delete(server) + numericalIdRepository.removeNumericalId(group.name, numericalId) + responseObserver.onError(ServerHostException("Could not start server, aborting.")) + channel.shutdown() + } } - val channel = host.createChannel() - val stub = ServerHostServiceGrpc.newFutureStub(channel) - stub.stopServer(server).toCompletable().thenApply { - if (it.status == "success") { - serverRepository.delete(Server.fromDefinition(server)) - } - responseObserver.onNext(it) - responseObserver.onCompleted() - channel.shutdown() + + override fun stopServer(request: ServerIdRequest, responseObserver: StreamObserver) { + val server = serverRepository.findServerById(request.id)?.toDefinition() + if (server == null) { + responseObserver.onError(IllegalArgumentException("No server was found matching this id.")) + return + } + val host = hostRepository.findServerHostById(server.hostId) + if (host == null) { + responseObserver.onError(ServerHostException("No server host was found matching this server.")) + return + } + val channel = host.createChannel() + val stub = ServerHostServiceGrpc.newFutureStub(channel) + stub.stopServer(server).toCompletable().thenApply { + if (it.status == "success") { + serverRepository.delete(Server.fromDefinition(server)) + } + responseObserver.onNext(it) + responseObserver.onCompleted() + channel.shutdown() + } } - } } \ No newline at end of file diff --git a/controller-runtime/src/main/resources/groups.yml b/controller-runtime/src/main/resources/groups.yml index 8dca3ee..4ecbba0 100644 --- a/controller-runtime/src/main/resources/groups.yml +++ b/controller-runtime/src/main/resources/groups.yml @@ -1,14 +1,14 @@ -- name: lobby - type: SERVER - server-url: https://api.papermc.io/v2/projects/paper/versions/1.20.4/builds/450/downloads/paper-1.20.4-450.jar - template-id: default - min-memory: 1024 - max-memory: 1024 - start-port: 25565 - min-online-count: 1 - max-online-count: 2 - properties: - fallback-server: true - configurator: spigot - max-players: 20 - max-startup-seconds: 20 +- name: lobby + type: SERVER + server-url: https://api.papermc.io/v2/projects/paper/versions/1.20.4/builds/450/downloads/paper-1.20.4-450.jar + template-id: default + min-memory: 1024 + max-memory: 1024 + start-port: 25565 + min-online-count: 1 + max-online-count: 2 + properties: + fallback-server: true + configurator: spigot + max-players: 20 + max-startup-seconds: 20 diff --git a/controller-runtime/src/main/resources/hosts.yml b/controller-runtime/src/main/resources/hosts.yml index b05f829..43549af 100644 --- a/controller-runtime/src/main/resources/hosts.yml +++ b/controller-runtime/src/main/resources/hosts.yml @@ -1,3 +1,3 @@ -- id: "my-local-server-host" - host: "localhost" - port: "5820" \ No newline at end of file +- id: "my-local-server-host" + host: "localhost" + port: "5820" \ No newline at end of file diff --git a/controller-runtime/src/main/resources/log4j2.xml b/controller-runtime/src/main/resources/log4j2.xml index e32762f..8391720 100644 --- a/controller-runtime/src/main/resources/log4j2.xml +++ b/controller-runtime/src/main/resources/log4j2.xml @@ -2,7 +2,8 @@ - + diff --git a/controller-shared/build.gradle.kts b/controller-shared/build.gradle.kts index 7ab956b..94544d6 100644 --- a/controller-shared/build.gradle.kts +++ b/controller-shared/build.gradle.kts @@ -1,4 +1,4 @@ -import com.google.protobuf.gradle.* +import com.google.protobuf.gradle.id plugins { alias(libs.plugins.protobuf) 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 45a98ee..61c3089 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 @@ -6,49 +6,49 @@ import org.spongepowered.configurate.objectmapping.ConfigSerializable @ConfigSerializable data class Group( - val name: String = "", - val type: ServerType = ServerType.OTHER, - val serverUrl: String = "", - val minMemory: Long = 0, - val maxMemory: Long = 0, - val startPort: Long = 0, - @Transient val onlineServers: Long? = 0, - val minOnlineCount: Long = 0, - val maxOnlineCount: Long = 0, - val properties: Map = mapOf() + val name: String = "", + val type: ServerType = ServerType.OTHER, + val serverUrl: String = "", + val minMemory: Long = 0, + val maxMemory: Long = 0, + val startPort: Long = 0, + @Transient val onlineServers: Long? = 0, + val minOnlineCount: Long = 0, + val maxOnlineCount: Long = 0, + val properties: Map = mapOf() ) { - fun toDefinition(): GroupDefinition { - return GroupDefinition.newBuilder() - .setName(name) - .setType(type) - .setServerUrl(serverUrl) - .setMinimumMemory(minMemory) - .setMaximumMemory(maxMemory) - .setStartPort(startPort) - .setOnlineServers(onlineServers ?: 0) - .setMinimumOnlineCount(minOnlineCount) - .setMaximumOnlineCount(maxOnlineCount) - .putAllProperties(properties) - .build() - } + fun toDefinition(): GroupDefinition { + return GroupDefinition.newBuilder() + .setName(name) + .setType(type) + .setServerUrl(serverUrl) + .setMinimumMemory(minMemory) + .setMaximumMemory(maxMemory) + .setStartPort(startPort) + .setOnlineServers(onlineServers ?: 0) + .setMinimumOnlineCount(minOnlineCount) + .setMaximumOnlineCount(maxOnlineCount) + .putAllProperties(properties) + .build() + } - companion object { - @JvmStatic - fun fromDefinition(groupDefinition: GroupDefinition): Group { - return Group( - groupDefinition.name, - groupDefinition.type, - groupDefinition.serverUrl, - groupDefinition.minimumMemory, - groupDefinition.maximumMemory, - groupDefinition.startPort, - groupDefinition.onlineServers, - groupDefinition.minimumOnlineCount, - groupDefinition.maximumOnlineCount, - groupDefinition.propertiesMap - ) + companion object { + @JvmStatic + fun fromDefinition(groupDefinition: GroupDefinition): Group { + return Group( + groupDefinition.name, + groupDefinition.type, + groupDefinition.serverUrl, + groupDefinition.minimumMemory, + groupDefinition.maximumMemory, + groupDefinition.startPort, + groupDefinition.onlineServers, + groupDefinition.minimumOnlineCount, + groupDefinition.maximumOnlineCount, + groupDefinition.propertiesMap + ) + } } - } } diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt index 7f9cf74..cd371ca 100644 --- a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt @@ -35,6 +35,7 @@ class ServerFactory { this.numericalId = numericalId return this } + private var port: Long? = null fun setPort(port: Long): ServerFactory { this.port = port diff --git a/readme.md b/readme.md index a8c789c..753042d 100644 --- a/readme.md +++ b/readme.md @@ -1,20 +1,27 @@ # SimpleCloud v3 Controller + Process that (automatically) manages minecraft server deployments (across multiple root-servers). At least one [ServerHost](#serverhosts) is needed to actually start servers. > You can take a look at the [controller structure](structure.png) to learn how exactly it works ## Features + - [x] Reconciler (auto-deploying for servers) - [x] [API](#api-usage) using [gRPC](https://grpc.io/) - [x] Server [SQL](https://en.wikipedia.org/wiki/SQL)-Database (any dialect) ## ServerHosts -ServerHosts are processes, that directly handle minecraft server deployments. Each root-server should have exactly one ServerHost online. We provide a [default implementation](), + +ServerHosts are processes, that directly handle minecraft server deployments. Each root-server should have exactly one +ServerHost online. We provide a [default implementation](), however, you can write your [own implementation](). You can have as many ServerHost instances as you like. ## API usage + The SimpleCloud v3 Controller provides API for both server groups and actual servers. -The group API is used for [CRUD-Operations](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) of server groups, whereas the server API is used to manage running servers or starting new ones. +The group API is used for [CRUD-Operations](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) of server +groups, whereas the server API is used to manage running servers or starting new ones. + ````kotlin //Initializing a Controller connection Controller.connect() @@ -27,57 +34,60 @@ val groupApi = Controller.groupApi ```` ### Group API functions + > Group update functionality is yet to be implemented + ````kotlin /** - * @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 a [CompletableFuture] with the [Group]. + */ +fun getGroupByName(name: String): CompletableFuture - /** - * @param name the name of the group. - * @return a status [ApiResponse] of the delete state. - */ - fun deleteGroup(name: String): CompletableFuture +/** + * @param name the name of the group. + * @return a status [ApiResponse] of the delete state. + */ +fun deleteGroup(name: String): CompletableFuture - /** - * @param group the [Group] to create. - * @return a status [ApiResponse] of the creation state. - */ - fun createGroup(group: Group): CompletableFuture +/** + * @param group the [Group] to create. + * @return a status [ApiResponse] of the creation state. + */ +fun createGroup(group: Group): CompletableFuture ```` ### Server API functions + ````kotlin /** - * @param id the id of the server. - * @return a [CompletableFuture] with the [Server]. - */ - fun getServerById(id: String): 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. + * @return a [CompletableFuture] with a [List] of [Server]s of that group. + */ +fun getServersByGroup(groupName: String): CompletableFuture> - /** - * @param group The server group. - * @return a [CompletableFuture] with a [List] of [Server]s of that group. - */ - fun getServersByGroup(group: Group): CompletableFuture> +/** + * @param group The server group. + * @return a [CompletableFuture] with a [List] of [Server]s of that group. + */ +fun getServersByGroup(group: Group): 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): 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): CompletableFuture - /** - * @param id the id of the server. - * @return a [CompletableFuture] with a [ApiResponse]. - */ - fun stopServer(id: String): CompletableFuture +/** + * @param id the id of the server. + * @return a [CompletableFuture] with a [ApiResponse]. + */ +fun stopServer(id: String): CompletableFuture ```` diff --git a/settings.gradle.kts b/settings.gradle.kts index 3b5cc01..e5c8b31 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,7 +14,7 @@ plugins { rootProject.name = "simplecloud-controller" include( - "controller-shared", - "controller-api", - "controller-runtime" + "controller-shared", + "controller-api", + "controller-runtime" ) \ No newline at end of file From 8810f0d17174dac97267054a0cee81a715c9c6d8 Mon Sep 17 00:00:00 2001 From: Philipp Date: Tue, 2 Apr 2024 15:44:16 +0200 Subject: [PATCH 59/94] fix: gradle libs version naming --- gradle/libs.versions.toml | 42 +++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ed25e38..70baa0f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,13 +5,13 @@ shadow = "8.1.1" log4j = "2.20.0" protobuf = "3.25.2" protobufPlugin = "0.9.4" -grpcVersion = "1.61.0" -grpcKotlinVersion = "1.4.1" -grpcGenKotlinVersion = "1.3.0:jdk8@jar" -jooqVersion = "3.19.3" -configurateVersion = "4.1.2" -sqliteJdbcVersion = "3.44.1.0" -cliktVersion = "4.3.0" +grpc = "1.61.0" +grpcKotlin = "1.4.1" +grpcGenKotlin = "1.3.0:jdk8@jar" +jooq = "3.19.3" +configurate = "4.1.2" +sqliteJdbc = "3.44.1.0" +clikt = "4.3.0" [libraries] kotlinJvm = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } @@ -24,24 +24,24 @@ log4jSlf4j = { module = "org.apache.logging.log4j:log4j-slf4j-impl", version.ref protobufKotlin = { module = "com.google.protobuf:protobuf-kotlin", version.ref = "protobuf" } protobufProtoc = { module = "com.google.protobuf:protoc", version.ref = "protobuf" } -protobufGenGrpc = { module = "io.grpc:protoc-gen-grpc-java", version.ref = "grpcVersion" } -protobufGenGrpcKotlin = { module = "io.grpc:protoc-gen-grpc-kotlin", version.ref = "grpcGenKotlinVersion" } +protobufGenGrpc = { module = "io.grpc:protoc-gen-grpc-java", version.ref = "grpc" } +protobufGenGrpcKotlin = { module = "io.grpc:protoc-gen-grpc-kotlin", version.ref = "grpcGenKotlin" } -grpcStub = { module = "io.grpc:grpc-stub", version.ref = "grpcVersion" } -grpcKotlinStub = { module = "io.grpc:grpc-kotlin-stub", version.ref = "grpcKotlinVersion" } -grpcProtobuf = { module = "io.grpc:grpc-protobuf", version.ref = "grpcVersion" } -grpcNettyShaded = { module = "io.grpc:grpc-netty-shaded", version.ref = "grpcVersion" } +grpcStub = { module = "io.grpc:grpc-stub", version.ref = "grpc" } +grpcKotlinStub = { module = "io.grpc:grpc-kotlin-stub", version.ref = "grpcKotlin" } +grpcProtobuf = { module = "io.grpc:grpc-protobuf", version.ref = "grpc" } +grpcNettyShaded = { module = "io.grpc:grpc-netty-shaded", version.ref = "grpc" } -qooq = { module = "org.jooq:jooq", version.ref = "jooqVersion" } -qooqMeta = { module = "org.jooq:jooq-meta", version.ref = "jooqVersion" } -jooqMetaExtensions = { module = "org.jooq:jooq-meta-extensions", version.ref = "jooqVersion" } +qooq = { module = "org.jooq:jooq", version.ref = "jooq" } +qooqMeta = { module = "org.jooq:jooq-meta", version.ref = "jooq" } +jooqMetaExtensions = { module = "org.jooq:jooq-meta-extensions", version.ref = "jooq" } -configurateYaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurateVersion" } -configurateExtraKotlin = { module = "org.spongepowered:configurate-extra-kotlin", version.ref = "configurateVersion" } +configurateYaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurate" } +configurateExtraKotlin = { module = "org.spongepowered:configurate-extra-kotlin", version.ref = "configurate" } -sqliteJdbc = { module = "org.xerial:sqlite-jdbc", version.ref = "sqliteJdbcVersion" } +sqliteJdbc = { module = "org.xerial:sqlite-jdbc", version.ref = "sqliteJdbc" } -clikt = { module = "com.github.ajalt.clikt:clikt", version.ref = "cliktVersion" } +clikt = { module = "com.github.ajalt.clikt:clikt", version.ref = "clikt" } [bundles] log4j = [ @@ -69,4 +69,4 @@ configurate = [ kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" } protobuf = { id = "com.google.protobuf", version.ref = "protobufPlugin" } -jooqCodegen = { id = "org.jooq.jooq-codegen-gradle", version.ref = "jooqVersion" } \ No newline at end of file +jooqCodegen = { id = "org.jooq.jooq-codegen-gradle", version.ref = "jooq" } \ No newline at end of file From 7f289e621b501226a33f5072e97560c66f5ce8d3 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Tue, 2 Apr 2024 17:33:14 +0200 Subject: [PATCH 60/94] refactor: cleanup annoying log --- .../kotlin/app/simplecloud/controller/runtime/Reconciler.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt index fd91ddc..f7d9a5c 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt @@ -34,9 +34,6 @@ class Reconciler( || server.state == ServerState.STARTING || server.state == ServerState.PREPARING } - - logger.info("Reconciling group ${group.name} with ${servers.size} servers, $availableServerCount available servers") - cleanupServers(group, servers, availableServerCount) startServers(group, availableServerCount, servers.size) } From fed43b961ee45e987ebc68cbd3eb82456b7ad953 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Tue, 2 Apr 2024 17:35:37 +0200 Subject: [PATCH 61/94] refactor: cleanup annoying log --- .../kotlin/app/simplecloud/controller/runtime/Reconciler.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt index 6a6f117..6f9ae7c 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt @@ -21,7 +21,7 @@ class Reconciler( managedChannel: ManagedChannel, ) { - val INACTIVE_SERVER_TIME = 5L + private val INACTIVE_SERVER_TIME = 5L private val serverStub = ControllerServerServiceGrpc.newFutureStub(managedChannel) private val logger = LogManager.getLogger(Reconciler::class.java) @@ -35,8 +35,6 @@ class Reconciler( || server.state == ServerState.PREPARING } - logger.info("Reconciling group ${group.name} with ${servers.size} servers, $availableServerCount available servers") - cleanupServers(group, servers, availableServerCount) startServers(group, availableServerCount, servers.size) } From 1dd61630eb07af358936665c5d44d5561cc14d54 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Tue, 2 Apr 2024 18:03:47 +0200 Subject: [PATCH 62/94] fix: array exceptions --- .../controller/runtime/server/ServerRepository.kt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) 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 8509539..9e94ff5 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 @@ -85,16 +85,13 @@ class ServerRepository( } } + @Synchronized override fun save(element: Server) { val server = firstOrNull { it.uniqueId == element.uniqueId } if (server != null) { - val index = indexOf(server) - removeAt(index) - add(index, element) - } else { - add(element) + remove(server) } - + add(element) numericalIdRepository.saveNumericalId(element.group, element.numericalId) val currentTimestamp = LocalDateTime.now() From dd4eaa459d42317be6b1752b3197a4df29ab7888 Mon Sep 17 00:00:00 2001 From: Philipp Date: Tue, 2 Apr 2024 18:20:52 +0200 Subject: [PATCH 63/94] refactor: add cleanup for numerical ids --- .../controller/runtime/ControllerRuntime.kt | 8 +++++++- .../simplecloud/controller/runtime/Reconciler.kt | 14 ++++++++++++++ .../runtime/server/ServerNumericalIdRepository.kt | 8 ++++++-- 3 files changed, 27 insertions(+), 3 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 a39ef33..a772335 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 @@ -28,7 +28,13 @@ class ControllerRuntime( private val numericalIdRepository = ServerNumericalIdRepository() private val serverRepository = ServerRepository(database, numericalIdRepository) private val hostRepository = ServerHostRepository() - private val reconciler = Reconciler(groupRepository, serverRepository, hostRepository, createManagedChannel()) + private val reconciler = Reconciler( + groupRepository, + serverRepository, + hostRepository, + numericalIdRepository, + createManagedChannel() + ) private val server = createGrpcServerFromEnv() fun start() { diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt index 6a6f117..a1a270e 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt @@ -2,6 +2,7 @@ package app.simplecloud.controller.runtime import app.simplecloud.controller.runtime.group.GroupRepository import app.simplecloud.controller.runtime.host.ServerHostRepository +import app.simplecloud.controller.runtime.server.ServerNumericalIdRepository import app.simplecloud.controller.runtime.server.ServerRepository import app.simplecloud.controller.shared.future.toCompletable import app.simplecloud.controller.shared.group.Group @@ -18,6 +19,7 @@ class Reconciler( private val groupRepository: GroupRepository, private val serverRepository: ServerRepository, private val serverHostRepository: ServerHostRepository, + private val numericalIdRepository: ServerNumericalIdRepository, managedChannel: ManagedChannel, ) { @@ -38,6 +40,8 @@ class Reconciler( logger.info("Reconciling group ${group.name} with ${servers.size} servers, $availableServerCount available servers") cleanupServers(group, servers, availableServerCount) + cleanupNumericalIds(group, servers) + startServers(group, availableServerCount, servers.size) } } @@ -63,6 +67,16 @@ class Reconciler( } } + private fun cleanupNumericalIds(group: Group, servers: List) { + val groupName = group.name + val usedNumericalIds = servers.map { it.numericalId } + val numericalIds = numericalIdRepository.findNumericalIds(groupName) + + val unusedNumericalIds = numericalIds.filter { !usedNumericalIds.contains(it) }.toSet() + numericalIdRepository.removeNumericalIds(groupName, unusedNumericalIds) + logger.info("Removed ${unusedNumericalIds.size} unused numerical ids for group $groupName") + } + private fun wasUpdatedRecently(server: Server): Boolean { return server.updatedAt.isAfter(LocalDateTime.now().minusMinutes(INACTIVE_SERVER_TIME)) } 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 208c6f6..c9685aa 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 @@ -26,8 +26,12 @@ class ServerNumericalIdRepository { return numericalIds.computeIfPresent(group) { _, v -> v.minus(id) } != null } - private fun findNumericalIds(group: String): List { - return numericalIds[group]?.toList() ?: emptyList() + fun removeNumericalIds(group: String, ids: Set) { + numericalIds.remove(group, ids) + } + + fun findNumericalIds(group: String): Set { + return numericalIds[group] ?: emptySet() } } From 2c4f52822aa3522e452c27a063779a8fc47e4d63 Mon Sep 17 00:00:00 2001 From: Philipp Date: Tue, 2 Apr 2024 20:16:58 +0200 Subject: [PATCH 64/94] fix: only log numerical id cleanup if there are unused ids --- .../kotlin/app/simplecloud/controller/runtime/Reconciler.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt index de92946..8776be3 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt @@ -72,7 +72,10 @@ class Reconciler( val unusedNumericalIds = numericalIds.filter { !usedNumericalIds.contains(it) }.toSet() numericalIdRepository.removeNumericalIds(groupName, unusedNumericalIds) - logger.info("Removed ${unusedNumericalIds.size} unused numerical ids for group $groupName") + + if (unusedNumericalIds.isNotEmpty()) { + logger.info("Removed unused numerical ids $unusedNumericalIds of group $groupName") + } } private fun wasUpdatedRecently(server: Server): Boolean { From 3c04db338e147619d6e5428b7dd72624215dbc72 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Tue, 2 Apr 2024 20:43:22 +0200 Subject: [PATCH 65/94] fix: garbage collection error --- .../simplecloud/controller/runtime/server/ServerService.kt | 5 +++-- 1 file changed, 3 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 0ec8e80..3d80dc9 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 @@ -42,9 +42,10 @@ class ServerService( logger.error("Server was found to be offline, unregistering...") serverRepository.delete(it) } - channel.shutdown() - } + }.get() } + + channel.shutdown() } } From 388d1fe28d889d4957d432bd904dbe113d27a2b4 Mon Sep 17 00:00:00 2001 From: Philipp Date: Sat, 6 Apr 2024 10:11:40 +0200 Subject: [PATCH 66/94] refactor: move proto files to buf --- build.gradle.kts | 1 + .../controller/api/group/impl/GroupApiImpl.kt | 4 +- .../controller/api/server/ServerApi.kt | 2 +- .../api/server/impl/ServerApiImpl.kt | 2 +- .../controller/runtime/Reconciler.kt | 8 +-- .../controller/runtime/group/GroupService.kt | 2 +- .../runtime/server/ServerRepository.kt | 4 +- .../runtime/server/ServerService.kt | 2 +- controller-shared/build.gradle.kts | 36 ----------- .../controller/shared/group/Group.kt | 4 +- .../controller/shared/host/ServerHost.kt | 2 +- .../controller/shared/server/Server.kt | 6 +- .../controller/shared/server/ServerFactory.kt | 2 +- .../controller/shared/status/ApiResponse.kt | 2 +- .../src/main/proto/controller_group_api.proto | 22 ------- .../main/proto/controller_server_api.proto | 39 ------------ .../src/main/proto/controller_types.proto | 62 ------------------- .../src/main/proto/server_host_api.proto | 20 ------ gradle/libs.versions.toml | 12 ++-- 19 files changed, 26 insertions(+), 206 deletions(-) delete mode 100644 controller-shared/src/main/proto/controller_group_api.proto delete mode 100644 controller-shared/src/main/proto/controller_server_api.proto delete mode 100644 controller-shared/src/main/proto/controller_types.proto delete mode 100644 controller-shared/src/main/proto/server_host_api.proto diff --git a/build.gradle.kts b/build.gradle.kts index aeb2d79..218c837 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,6 +14,7 @@ allprojects { repositories { mavenCentral() + maven("https://buf.build/gen/maven") } } diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/impl/GroupApiImpl.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/impl/GroupApiImpl.kt index 7ee5bb5..7eb9ed2 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/impl/GroupApiImpl.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/impl/GroupApiImpl.kt @@ -4,8 +4,8 @@ import app.simplecloud.controller.api.Controller import app.simplecloud.controller.api.group.GroupApi import app.simplecloud.controller.shared.future.toCompletable import app.simplecloud.controller.shared.group.Group -import app.simplecloud.controller.shared.proto.ControllerGroupServiceGrpc -import app.simplecloud.controller.shared.proto.GetGroupByNameRequest +import build.buf.gen.simplecloud.controller.v1.ControllerGroupServiceGrpc +import build.buf.gen.simplecloud.controller.v1.GetGroupByNameRequest import app.simplecloud.controller.shared.status.ApiResponse import java.util.concurrent.CompletableFuture diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/ServerApi.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/ServerApi.kt index 428cec9..454c693 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/ServerApi.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/ServerApi.kt @@ -1,7 +1,7 @@ package app.simplecloud.controller.api.server import app.simplecloud.controller.shared.group.Group -import app.simplecloud.controller.shared.proto.ServerType +import build.buf.gen.simplecloud.controller.v1.ServerType import app.simplecloud.controller.shared.server.Server import app.simplecloud.controller.shared.status.ApiResponse import java.util.concurrent.CompletableFuture diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt index 6d1d058..d90dc8e 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt @@ -4,7 +4,7 @@ import app.simplecloud.controller.api.Controller import app.simplecloud.controller.api.server.ServerApi import app.simplecloud.controller.shared.future.toCompletable import app.simplecloud.controller.shared.group.Group -import app.simplecloud.controller.shared.proto.* +import build.buf.gen.simplecloud.controller.v1.* import app.simplecloud.controller.shared.server.Server import app.simplecloud.controller.shared.status.ApiResponse import java.util.concurrent.CompletableFuture diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt index 8776be3..ca03f4f 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt @@ -6,10 +6,10 @@ import app.simplecloud.controller.runtime.server.ServerNumericalIdRepository 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.proto.ControllerServerServiceGrpc -import app.simplecloud.controller.shared.proto.GroupNameRequest -import app.simplecloud.controller.shared.proto.ServerIdRequest -import app.simplecloud.controller.shared.proto.ServerState +import build.buf.gen.simplecloud.controller.v1.ControllerServerServiceGrpc +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 app.simplecloud.controller.shared.server.Server import io.grpc.ManagedChannel import org.apache.logging.log4j.LogManager 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 8211459..4a51d7b 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 @@ -1,7 +1,7 @@ package app.simplecloud.controller.runtime.group import app.simplecloud.controller.shared.group.Group -import app.simplecloud.controller.shared.proto.* +import build.buf.gen.simplecloud.controller.v1.* import app.simplecloud.controller.shared.status.ApiResponse import io.grpc.stub.StreamObserver 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 9e94ff5..3609b2c 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 @@ -4,8 +4,8 @@ import app.simplecloud.controller.runtime.Repository 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.proto.ServerState -import app.simplecloud.controller.shared.proto.ServerType +import build.buf.gen.simplecloud.controller.v1.ServerState +import build.buf.gen.simplecloud.controller.v1.ServerType import app.simplecloud.controller.shared.server.Server import java.time.LocalDateTime import java.util.concurrent.CompletableFuture 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 0ec8e80..27ad9d0 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 @@ -5,7 +5,7 @@ import app.simplecloud.controller.runtime.host.ServerHostException import app.simplecloud.controller.runtime.host.ServerHostRepository import app.simplecloud.controller.shared.future.toCompletable import app.simplecloud.controller.shared.host.ServerHost -import app.simplecloud.controller.shared.proto.* +import build.buf.gen.simplecloud.controller.v1.* import app.simplecloud.controller.shared.server.Server import app.simplecloud.controller.shared.server.ServerFactory import app.simplecloud.controller.shared.status.ApiResponse diff --git a/controller-shared/build.gradle.kts b/controller-shared/build.gradle.kts index 94544d6..5f65cc6 100644 --- a/controller-shared/build.gradle.kts +++ b/controller-shared/build.gradle.kts @@ -1,7 +1,4 @@ -import com.google.protobuf.gradle.id - plugins { - alias(libs.plugins.protobuf) alias(libs.plugins.jooqCodegen) } @@ -15,16 +12,8 @@ dependencies { sourceSets { main { - kotlin { - srcDirs( - "build/generated/source/proto/main/grpckt", - "build/generated/source/proto/main/kotlin", - ) - } java { srcDirs( - "build/generated/source/proto/main/grpc", - "build/generated/source/proto/main/java", "build/generated/source/db/main/java", ) } @@ -36,31 +25,6 @@ sourceSets { } } -protobuf { - protoc { - artifact = libs.protobufProtoc.get().toString() - } - plugins { - id("grpc") { - artifact = libs.protobufGenGrpc.get().toString() - } - id("grpckt") { - artifact = libs.protobufGenGrpcKotlin.get().toString() - } - } - generateProtoTasks { - all().forEach { - it.plugins { - id("grpc") - id("grpckt") - } - it.builtins { - id("kotlin") - } - } - } -} - tasks.named("compileKotlin") { dependsOn(tasks.jooqCodegen) } 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 61c3089..1dba832 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 @@ -1,7 +1,7 @@ package app.simplecloud.controller.shared.group -import app.simplecloud.controller.shared.proto.GroupDefinition -import app.simplecloud.controller.shared.proto.ServerType +import build.buf.gen.simplecloud.controller.v1.GroupDefinition +import build.buf.gen.simplecloud.controller.v1.ServerType import org.spongepowered.configurate.objectmapping.ConfigSerializable @ConfigSerializable 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 c1a08a9..c712a22 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,6 @@ package app.simplecloud.controller.shared.host -import app.simplecloud.controller.shared.proto.ServerHostDefinition +import build.buf.gen.simplecloud.controller.v1.ServerHostDefinition import io.grpc.ManagedChannel import io.grpc.ManagedChannelBuilder import org.spongepowered.configurate.objectmapping.ConfigSerializable 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 61cd75a..fa2d9b4 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,8 +1,8 @@ package app.simplecloud.controller.shared.server -import app.simplecloud.controller.shared.proto.ServerDefinition -import app.simplecloud.controller.shared.proto.ServerState -import app.simplecloud.controller.shared.proto.ServerType +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.LocalDateTime data class Server( diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt index cd371ca..4119517 100644 --- a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt @@ -2,7 +2,7 @@ package app.simplecloud.controller.shared.server import app.simplecloud.controller.shared.group.Group import app.simplecloud.controller.shared.host.ServerHost -import app.simplecloud.controller.shared.proto.ServerState +import build.buf.gen.simplecloud.controller.v1.ServerState import java.time.LocalDateTime import java.util.* import kotlin.properties.Delegates diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/status/ApiResponse.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/status/ApiResponse.kt index fdfd97e..8cdd9e2 100644 --- a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/status/ApiResponse.kt +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/status/ApiResponse.kt @@ -1,6 +1,6 @@ package app.simplecloud.controller.shared.status -import app.simplecloud.controller.shared.proto.StatusResponse +import build.buf.gen.simplecloud.controller.v1.StatusResponse data class ApiResponse( val status: String diff --git a/controller-shared/src/main/proto/controller_group_api.proto b/controller-shared/src/main/proto/controller_group_api.proto deleted file mode 100644 index 308a939..0000000 --- a/controller-shared/src/main/proto/controller_group_api.proto +++ /dev/null @@ -1,22 +0,0 @@ -syntax = "proto3"; - -package app.simplecloud.controller.shared.proto; - -option java_package = "app.simplecloud.controller.shared.proto"; -option java_multiple_files = true; - -import "controller_types.proto"; - -message GetGroupByNameRequest { - string name = 1; -} - -message GetGroupByNameResponse { - GroupDefinition group = 2; -} - -service ControllerGroupService { - rpc getGroupByName(GetGroupByNameRequest) returns (GetGroupByNameResponse); - rpc deleteGroupByName(GetGroupByNameRequest) returns (StatusResponse); - rpc createGroup(GroupDefinition) returns (StatusResponse); -} \ No newline at end of file diff --git a/controller-shared/src/main/proto/controller_server_api.proto b/controller-shared/src/main/proto/controller_server_api.proto deleted file mode 100644 index 9e9b2b0..0000000 --- a/controller-shared/src/main/proto/controller_server_api.proto +++ /dev/null @@ -1,39 +0,0 @@ -syntax = "proto3"; - -package app.simplecloud.controller.shared.proto; - -option java_package = "app.simplecloud.controller.shared.proto"; -option java_multiple_files = true; - -import "controller_types.proto"; - -message GroupNameRequest { - string name = 1; -} - -message ServerIdRequest { - string id = 1; -} - -message ServerTypeRequest { - ServerType type = 1; -} - -message GetServersByGroupResponse { - repeated ServerDefinition servers = 1; -} - -message ServerUpdateRequest { - ServerDefinition server = 1; - bool deleted = 2; -} - -service ControllerServerService { - rpc getServersByType(ServerTypeRequest) returns (GetServersByGroupResponse); - rpc getServersByGroup(GroupNameRequest) returns (GetServersByGroupResponse); - rpc getServerById(ServerIdRequest) returns (ServerDefinition); - rpc startServer(GroupNameRequest) returns (ServerDefinition); - rpc stopServer(ServerIdRequest) returns (StatusResponse); - rpc updateServer(ServerUpdateRequest) returns (StatusResponse); - rpc attachServerHost(ServerHostDefinition) returns (StatusResponse); -} \ No newline at end of file diff --git a/controller-shared/src/main/proto/controller_types.proto b/controller-shared/src/main/proto/controller_types.proto deleted file mode 100644 index 8e12846..0000000 --- a/controller-shared/src/main/proto/controller_types.proto +++ /dev/null @@ -1,62 +0,0 @@ -syntax = "proto3"; - -package app.simplecloud.controller.shared.proto; - -option java_package = "app.simplecloud.controller.shared.proto"; -option java_multiple_files = true; - -message GroupDefinition { - string name = 1; - ServerType type = 2; - string server_url = 3; - string template_id = 4; - uint64 minimum_memory = 5; - uint64 maximum_memory = 6; - uint64 start_port = 7; - uint64 online_servers = 8; - uint64 minimum_online_count = 9; - uint64 maximum_online_count = 10; - map properties = 11; -} - -message ServerDefinition { - string unique_id = 1; - ServerType type = 2; - string group_name = 3; - string host_id = 4; - uint32 numerical_id = 5; - string template_id = 6; - string ip = 7; - uint64 port = 8; - uint64 minimum_memory = 9; - uint64 maximum_memory = 10; - uint64 player_count = 11; - map properties = 12; - ServerState state = 13; - string created_at = 14; - string updated_at = 15; -} - -message ServerHostDefinition { - string unique_id = 1; - string host = 2; - uint32 port = 3; -} - -enum ServerState { - PREPARING = 0; - STARTING = 1; - AVAILABLE = 2; - INGAME = 3; - STOPPING = 4; -} - -enum ServerType { - SERVER = 0; - PROXY = 1; - OTHER = 2; -} - -message StatusResponse { - string status = 1; -} \ No newline at end of file diff --git a/controller-shared/src/main/proto/server_host_api.proto b/controller-shared/src/main/proto/server_host_api.proto deleted file mode 100644 index 22e1b6d..0000000 --- a/controller-shared/src/main/proto/server_host_api.proto +++ /dev/null @@ -1,20 +0,0 @@ -syntax = "proto3"; - -package app.simplecloud.controller.shared.proto; - -option java_package = "app.simplecloud.controller.shared.proto"; -option java_multiple_files = true; - -import "controller_types.proto"; -import "controller_server_api.proto"; - -message StartServerRequest { - GroupDefinition group = 1; - ServerDefinition server = 2; -} - -service ServerHostService { - rpc startServer(StartServerRequest) returns (ServerDefinition); - rpc stopServer(ServerDefinition) returns (StatusResponse); - rpc reattachServer(ServerDefinition) returns (StatusResponse); -} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 70baa0f..0e60d0e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,10 +4,9 @@ kotlinCoroutines = "1.4.2" shadow = "8.1.1" log4j = "2.20.0" protobuf = "3.25.2" -protobufPlugin = "0.9.4" grpc = "1.61.0" grpcKotlin = "1.4.1" -grpcGenKotlin = "1.3.0:jdk8@jar" +simpleCloudProtoSpecs = "1.4.1.1.20240406080148.cbc66e7054b0" jooq = "3.19.3" configurate = "4.1.2" sqliteJdbc = "3.44.1.0" @@ -23,15 +22,14 @@ log4jApi = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j log4jSlf4j = { module = "org.apache.logging.log4j:log4j-slf4j-impl", version.ref = "log4j" } protobufKotlin = { module = "com.google.protobuf:protobuf-kotlin", version.ref = "protobuf" } -protobufProtoc = { module = "com.google.protobuf:protoc", version.ref = "protobuf" } -protobufGenGrpc = { module = "io.grpc:protoc-gen-grpc-java", version.ref = "grpc" } -protobufGenGrpcKotlin = { module = "io.grpc:protoc-gen-grpc-kotlin", version.ref = "grpcGenKotlin" } grpcStub = { module = "io.grpc:grpc-stub", version.ref = "grpc" } grpcKotlinStub = { module = "io.grpc:grpc-kotlin-stub", version.ref = "grpcKotlin" } 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" } + qooq = { module = "org.jooq:jooq", version.ref = "jooq" } qooqMeta = { module = "org.jooq:jooq-meta", version.ref = "jooq" } jooqMetaExtensions = { module = "org.jooq:jooq-meta-extensions", version.ref = "jooq" } @@ -54,7 +52,8 @@ proto = [ "grpcStub", "grpcKotlinStub", "grpcProtobuf", - "grpcNettyShaded" + "grpcNettyShaded", + "simpleCloudProtoSpecs" ] jooq = [ "qooq", @@ -68,5 +67,4 @@ configurate = [ [plugins] kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" } -protobuf = { id = "com.google.protobuf", version.ref = "protobufPlugin" } jooqCodegen = { id = "org.jooq.jooq-codegen-gradle", version.ref = "jooq" } \ No newline at end of file From 9917ad497319606865c04f2e41537e2d35d7d8de Mon Sep 17 00:00:00 2001 From: Philipp Date: Sat, 6 Apr 2024 10:24:09 +0200 Subject: [PATCH 67/94] refactor: update version to 1.0.20 --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 218c837..36d32bf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "1.0.19-EXPERIMENTAL" + version = "1.0.20-EXPERIMENTAL" repositories { mavenCentral() From 2b8070ebdba6a89620948be2aad891ede56bbe00 Mon Sep 17 00:00:00 2001 From: Philipp Date: Sat, 6 Apr 2024 10:27:25 +0200 Subject: [PATCH 68/94] refactor: move jooq codegen to runtime --- controller-runtime/build.gradle.kts | 81 +++++++++++++++++ .../src/main/db/schema.sql | 0 controller-shared/build.gradle.kts | 86 ------------------- 3 files changed, 81 insertions(+), 86 deletions(-) rename {controller-shared => controller-runtime}/src/main/db/schema.sql (100%) diff --git a/controller-runtime/build.gradle.kts b/controller-runtime/build.gradle.kts index 38cd259..c3a57e7 100644 --- a/controller-runtime/build.gradle.kts +++ b/controller-runtime/build.gradle.kts @@ -1,10 +1,14 @@ plugins { application + alias(libs.plugins.jooqCodegen) } dependencies { api(project(":controller-shared")) api(rootProject.libs.kotlinCoroutines) + api(rootProject.libs.bundles.jooq) + api(rootProject.libs.sqliteJdbc) + jooqCodegen(rootProject.libs.jooqMetaExtensions) implementation(rootProject.libs.bundles.log4j) implementation(rootProject.libs.clikt) } @@ -13,3 +17,80 @@ application { mainClass.set("app.simplecloud.controller.runtime.launcher.LauncherKt") } +sourceSets { + main { + java { + srcDirs( + "build/generated/source/db/main/java", + ) + } + resources { + srcDirs( + "src/main/db" + ) + } + } +} + +tasks.named("compileKotlin") { + dependsOn(tasks.jooqCodegen) +} + +jooq { + configuration { + generator { + target { + directory = "build/generated/source/db/main/java" + packageName = "app.simplecloud.controller.shared.db" + } + database { + name = "org.jooq.meta.extensions.ddl.DDLDatabase" + properties { + // Specify the location of your SQL script. + // You may use ant-style file matching, e.g. /path/**/to/*.sql + // + // Where: + // - ** matches any directory subtree + // - * matches any number of characters in a directory / file name + // - ? matches a single character in a directory / file name + property { + key = "scripts" + value = "src/main/db/schema.sql" + } + + // The sort order of the scripts within a directory, where: + // + // - semantic: sorts versions, e.g. v-3.10.0 is after v-3.9.0 (default) + // - alphanumeric: sorts strings, e.g. v-3.10.0 is before v-3.9.0 + // - flyway: sorts files the same way as flyway does + // - none: doesn't sort directory contents after fetching them from the directory + property { + key = "sort" + value = "semantic" + } + + // The default schema for unqualified objects: + // + // - public: all unqualified objects are located in the PUBLIC (upper case) schema + // - none: all unqualified objects are located in the default schema (default) + // + // This configuration can be overridden with the schema mapping feature + property { + key = "unqualifiedSchema" + value = "none" + } + + // The default name case for unquoted objects: + // + // - as_is: unquoted object names are kept unquoted + // - upper: unquoted object names are turned into upper case (most databases) + // - lower: unquoted object names are turned into lower case (e.g. PostgreSQL) + property { + key = "defaultNameCase" + value = "lower" + } + } + } + } + } +} \ No newline at end of file diff --git a/controller-shared/src/main/db/schema.sql b/controller-runtime/src/main/db/schema.sql similarity index 100% rename from controller-shared/src/main/db/schema.sql rename to controller-runtime/src/main/db/schema.sql diff --git a/controller-shared/build.gradle.kts b/controller-shared/build.gradle.kts index 5f65cc6..56a56b8 100644 --- a/controller-shared/build.gradle.kts +++ b/controller-shared/build.gradle.kts @@ -1,90 +1,4 @@ -plugins { - alias(libs.plugins.jooqCodegen) -} - dependencies { api(rootProject.libs.bundles.proto) - api(rootProject.libs.bundles.jooq) api(rootProject.libs.bundles.configurate) - api(rootProject.libs.sqliteJdbc) - jooqCodegen(rootProject.libs.jooqMetaExtensions) -} - -sourceSets { - main { - java { - srcDirs( - "build/generated/source/db/main/java", - ) - } - resources { - srcDirs( - "src/main/db" - ) - } - } -} - -tasks.named("compileKotlin") { - dependsOn(tasks.jooqCodegen) -} - -jooq { - configuration { - generator { - target { - directory = "build/generated/source/db/main/java" - packageName = "app.simplecloud.controller.shared.db" - } - database { - name = "org.jooq.meta.extensions.ddl.DDLDatabase" - properties { - // Specify the location of your SQL script. - // You may use ant-style file matching, e.g. /path/**/to/*.sql - // - // Where: - // - ** matches any directory subtree - // - * matches any number of characters in a directory / file name - // - ? matches a single character in a directory / file name - property { - key = "scripts" - value = "src/main/db/schema.sql" - } - - // The sort order of the scripts within a directory, where: - // - // - semantic: sorts versions, e.g. v-3.10.0 is after v-3.9.0 (default) - // - alphanumeric: sorts strings, e.g. v-3.10.0 is before v-3.9.0 - // - flyway: sorts files the same way as flyway does - // - none: doesn't sort directory contents after fetching them from the directory - property { - key = "sort" - value = "semantic" - } - - // The default schema for unqualified objects: - // - // - public: all unqualified objects are located in the PUBLIC (upper case) schema - // - none: all unqualified objects are located in the default schema (default) - // - // This configuration can be overridden with the schema mapping feature - property { - key = "unqualifiedSchema" - value = "none" - } - - // The default name case for unquoted objects: - // - // - as_is: unquoted object names are kept unquoted - // - upper: unquoted object names are turned into upper case (most databases) - // - lower: unquoted object names are turned into lower case (e.g. PostgreSQL) - property { - key = "defaultNameCase" - value = "lower" - } - } - } - } - } } - From 4521cd69b56ea93d0e8891181f5f067267d1c27b Mon Sep 17 00:00:00 2001 From: Philipp Date: Sat, 6 Apr 2024 10:33:21 +0200 Subject: [PATCH 69/94] feat: grpc config in start arguments and env --- .../controller/runtime/ControllerRuntime.kt | 14 +++++++------- .../runtime/launcher/ControllerStartCommand.kt | 8 ++++++-- 2 files changed, 13 insertions(+), 9 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 a772335..4afdd4b 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 @@ -17,7 +17,7 @@ import org.apache.logging.log4j.LogManager import kotlin.concurrent.thread class ControllerRuntime( - controllerStartCommand: ControllerStartCommand + private val controllerStartCommand: ControllerStartCommand ) { private val logger = LogManager.getLogger(ControllerRuntime::class.java) @@ -35,7 +35,7 @@ class ControllerRuntime( numericalIdRepository, createManagedChannel() ) - private val server = createGrpcServerFromEnv() + private val server = createGrpcServer() fun start() { setupDatabase() @@ -74,17 +74,17 @@ class ControllerRuntime( logger.info("Loaded groups: ${loadedGroups.joinToString(",")}") } - private fun createGrpcServerFromEnv(): Server { - val port = System.getenv("GRPC_PORT")?.toInt() ?: 5816 - return ServerBuilder.forPort(port) + private fun createGrpcServer(): Server { + return ServerBuilder.forPort(controllerStartCommand.grpcPort) .addService(GroupService(groupRepository)) .addService(ServerService(numericalIdRepository, serverRepository, hostRepository, groupRepository)) .build() } private fun createManagedChannel(): ManagedChannel { - val port = System.getenv("GRPC_PORT")?.toInt() ?: 5816 - return ManagedChannelBuilder.forAddress("localhost", port).usePlaintext().build() + return ManagedChannelBuilder.forAddress(controllerStartCommand.grpcHost, controllerStartCommand.grpcPort) + .usePlaintext() + .build() } @OptIn(InternalCoroutinesApi::class) 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 82b1a0d..4658911 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 @@ -4,6 +4,7 @@ import app.simplecloud.controller.runtime.ControllerRuntime import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.parameters.options.default 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 java.nio.file.Path @@ -11,12 +12,15 @@ class ControllerStartCommand : CliktCommand() { private val defaultDatabaseUrl = "jdbc:sqlite:database.db" - val groupPath: Path by option(help = "Path to the group files (groups)", envvar = "GROUPS_PATH") + val groupPath: Path by option(help = "Path to the group files (default: groups)", envvar = "GROUPS_PATH") .path() .default(Path.of("groups")) - val databaseUrl: String by option(help = "Database URL (${defaultDatabaseUrl})", envvar = "DATABASE_URL") + val databaseUrl: String by option(help = "Database URL (default: ${defaultDatabaseUrl})", envvar = "DATABASE_URL") .default(defaultDatabaseUrl) + 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) + override fun run() { val controllerRuntime = ControllerRuntime(this) controllerRuntime.start() From cd3c3ebd77b3d8d8144297eb679744a931f8cc95 Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 17 Apr 2024 22:42:25 +0200 Subject: [PATCH 70/94] refactor: some logger improvements --- .../controller/runtime/ControllerRuntime.kt | 4 +++ .../runtime/YamlDirectoryRepository.kt | 30 ++++++++++++++----- .../controller/runtime/launcher/Launcher.kt | 13 +++++++- .../src/main/resources/log4j2.xml | 18 +++++++++-- 4 files changed, 54 insertions(+), 11 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 4afdd4b..656b4e7 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 @@ -71,6 +71,10 @@ class ControllerRuntime( private fun loadGroups() { logger.info("Loading groups...") val loadedGroups = groupRepository.loadAll() + if (loadedGroups.isEmpty()) { + logger.warn("No groups found.") + } + logger.info("Loaded groups: ${loadedGroups.joinToString(",")}") } 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 79b7ca6..a6ef2bc 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 @@ -4,6 +4,7 @@ import kotlinx.coroutines.* import org.apache.logging.log4j.LogManager import org.spongepowered.configurate.ConfigurationOptions import org.spongepowered.configurate.kotlin.objectMapperFactory +import org.spongepowered.configurate.loader.ParsingException import org.spongepowered.configurate.yaml.NodeStyle import org.spongepowered.configurate.yaml.YamlConfigurationLoader import java.io.File @@ -47,19 +48,34 @@ abstract class YamlDirectoryRepository( Files.list(directory) .filter { !it.toFile().isDirectory && it.toString().endsWith(".yml") } .forEach { - load(it.toFile()) - fileNames.add(it.name) + val successFullyLoaded = load(it.toFile()) + if (successFullyLoaded) { + fileNames.add(it.name) + } } registerWatcher() return fileNames } - private fun load(file: File) { - val loader = getOrCreateLoader(file) - val node = loader.load(ConfigurationOptions.defaults()) - val entity = node.get(clazz) ?: return - entities[file] = entity + private fun load(file: File): Boolean { + try { + val loader = getOrCreateLoader(file) + val node = loader.load(ConfigurationOptions.defaults()) + val entity = node.get(clazz) ?: return false + entities[file] = entity + } catch (ex: ParsingException) { + val existedBefore = entities.containsKey(file) + if (existedBefore) { + logger.error("Could not load file ${file.name}. Switching back to an older version.") + return false + } + + logger.error("Could not load file ${file.name}. Make sure it's correctly formatted!") + return false + } + + return true } private fun delete(file: File): Boolean { diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/Launcher.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/Launcher.kt index abb4b5d..10c2f99 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/Launcher.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/Launcher.kt @@ -1,5 +1,16 @@ package app.simplecloud.controller.runtime.launcher +import org.apache.logging.log4j.LogManager + + fun main(args: Array) { + configureLog4j() ControllerStartCommand().main(args) -} \ No newline at end of file +} + +fun configureLog4j() { + val globalExceptionHandlerLogger = LogManager.getLogger("GlobalExceptionHandler") + Thread.setDefaultUncaughtExceptionHandler { thread, throwable -> + globalExceptionHandlerLogger.error("Uncaught exception in thread ${thread.name}", throwable) + } +} diff --git a/controller-runtime/src/main/resources/log4j2.xml b/controller-runtime/src/main/resources/log4j2.xml index 8391720..462153c 100644 --- a/controller-runtime/src/main/resources/log4j2.xml +++ b/controller-runtime/src/main/resources/log4j2.xml @@ -2,13 +2,25 @@ - + + + + + + + + + + + + + + - \ No newline at end of file + From 45f881f203936fe976503f5990cde9543d6ccc55 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Thu, 18 Apr 2024 19:27:23 +0200 Subject: [PATCH 71/94] impl: velocity secret file, group api crud, cleanup definitions --- build.gradle.kts | 2 +- controller-runtime/src/main/db/schema.sql | 2 +- .../controller/runtime/ControllerRuntime.kt | 8 ++--- .../runtime/YamlDirectoryRepository.kt | 3 +- .../runtime/group/GroupRepository.kt | 4 +++ .../controller/runtime/group/GroupService.kt | 22 +++++++++++++ .../launcher/ControllerStartCommand.kt | 4 +++ .../runtime/secret/ForwardingSecretHandler.kt | 29 ++++++++++++++++ .../runtime/server/ServerRepository.kt | 8 ++--- .../runtime/server/ServerService.kt | 33 +++++++++++++++++++ .../controller/shared/group/Group.kt | 9 ++--- .../controller/shared/server/Server.kt | 10 +++--- .../controller/shared/server/ServerFactory.kt | 11 +++++-- gradle/libs.versions.toml | 2 +- 14 files changed, 120 insertions(+), 27 deletions(-) create mode 100644 controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/secret/ForwardingSecretHandler.kt diff --git a/build.gradle.kts b/build.gradle.kts index 36d32bf..53781ae 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "1.0.20-EXPERIMENTAL" + version = "1.0.21-EXPERIMENTAL" repositories { mavenCentral() diff --git a/controller-runtime/src/main/db/schema.sql b/controller-runtime/src/main/db/schema.sql index 4d54da0..d779577 100644 --- a/controller-runtime/src/main/db/schema.sql +++ b/controller-runtime/src/main/db/schema.sql @@ -8,11 +8,11 @@ CREATE TABLE IF NOT EXISTS cloud_servers( group_name varchar NOT NULL, host_id varchar NOT NULL, numerical_id int NOT NULL, - template_id varchar NOT NULL, ip varchar NOT NULL, port int NOT NULL, minimum_memory int NOT NULL, maximum_memory int NOT NULL, + max_players int NOT NULL, player_count int NOT NULL, state varchar NOT NULL, type varchar NOT NULL, 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 656b4e7..881a8c4 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 @@ -8,6 +8,7 @@ import app.simplecloud.controller.runtime.launcher.ControllerStartCommand import app.simplecloud.controller.runtime.server.ServerNumericalIdRepository import app.simplecloud.controller.runtime.server.ServerRepository import app.simplecloud.controller.runtime.server.ServerService +import app.simplecloud.controller.runtime.secret.ForwardingSecretHandler import io.grpc.ManagedChannel import io.grpc.ManagedChannelBuilder import io.grpc.Server @@ -21,7 +22,7 @@ class ControllerRuntime( ) { private val logger = LogManager.getLogger(ControllerRuntime::class.java) - + private val forwardingSecretHandler = ForwardingSecretHandler(controllerStartCommand.velocitySecretPath) private val database = DatabaseFactory.createDatabase(controllerStartCommand.databaseUrl) private val groupRepository = GroupRepository(controllerStartCommand.groupPath) @@ -81,7 +82,7 @@ class ControllerRuntime( private fun createGrpcServer(): Server { return ServerBuilder.forPort(controllerStartCommand.grpcPort) .addService(GroupService(groupRepository)) - .addService(ServerService(numericalIdRepository, serverRepository, hostRepository, groupRepository)) + .addService(ServerService(numericalIdRepository, serverRepository, hostRepository, groupRepository, forwardingSecretHandler)) .build() } @@ -91,10 +92,9 @@ class ControllerRuntime( .build() } - @OptIn(InternalCoroutinesApi::class) private fun startReconcilerJob(): Job { return CoroutineScope(Dispatchers.Default).launch { - while (NonCancellable.isActive) { + while (isActive) { reconciler.reconcile() delay(2000L) } 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 a6ef2bc..d125a53 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 @@ -106,7 +106,6 @@ abstract class YamlDirectoryRepository( } } - @OptIn(InternalCoroutinesApi::class) private fun registerWatcher(): Job { directory.register( watchService, @@ -116,7 +115,7 @@ abstract class YamlDirectoryRepository( ) return CoroutineScope(Dispatchers.Default).launch { - while (NonCancellable.isActive) { + while (isActive) { val key = watchService.take() for (event in key.pollEvents()) { val path = event.context() as? Path ?: continue 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 1aac9c8..ca8226d 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 @@ -18,4 +18,8 @@ class GroupRepository( override fun save(entity: Group) { save(getFileName(entity.name), entity) } + + 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 4a51d7b..7b9ab8f 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 @@ -27,6 +27,28 @@ class GroupService( responseObserver.onCompleted() } + override fun getAllGroups(request: GetAllGroupsRequest, responseObserver: StreamObserver) { + val response = GetAllGroupsResponse.newBuilder().addAllGroups(groupRepository.getAll().map { it.toDefinition() }).build() + responseObserver.onNext(response) + responseObserver.onCompleted() + } + + override fun getGroupsByType( + request: GetGroupsByTypeRequest, + responseObserver: StreamObserver + ) { + val type = request.type + val response = GetGroupsByTypeResponse.newBuilder().addAllGroups(groupRepository.getAll().filter { it.type == type }.map { it.toDefinition() }).build() + responseObserver.onNext(response) + responseObserver.onCompleted() + } + + override fun updateGroup(request: GroupDefinition, responseObserver: StreamObserver) { + groupRepository.save(Group.fromDefinition(request)) + responseObserver.onNext(ApiResponse("success").toDefinition()) + responseObserver.onCompleted() + } + override fun createGroup(request: GroupDefinition, responseObserver: StreamObserver) { val group = Group.fromDefinition(request) try { 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 4658911..867989c 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 @@ -21,6 +21,10 @@ 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 velocitySecretPath: Path by option(help = "Path to the velocity secret (default: forwarding.secret)", envvar = "VELOCITY_SECRET_PATH") + .path() + .default(Path.of("forwarding.secret")) + override fun run() { val controllerRuntime = ControllerRuntime(this) controllerRuntime.start() diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/secret/ForwardingSecretHandler.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/secret/ForwardingSecretHandler.kt new file mode 100644 index 0000000..6cdb913 --- /dev/null +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/secret/ForwardingSecretHandler.kt @@ -0,0 +1,29 @@ +package app.simplecloud.controller.runtime.secret + +import java.io.FileReader +import java.io.FileWriter +import java.nio.file.Files +import java.nio.file.Path +import java.util.* + +class ForwardingSecretHandler( + path: Path +) { + private val secret: String + init { + val secretFile = path.toFile() + if(!secretFile.exists()) { + Files.createDirectories(secretFile.parentFile.toPath()) + Files.createFile(secretFile.toPath()) + val writer = FileWriter(secretFile) + writer.write(UUID.randomUUID().toString()) + writer.close() + } + val reader = FileReader(secretFile) + secret = reader.readText() + reader.close() + } + fun getSecret(): String { + return secret + } +} \ No newline at end of file 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 3609b2c..b62dcdb 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 @@ -45,11 +45,11 @@ class ServerRepository( it.groupName, it.hostId, it.numericalId, - it.templateId, 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 @@ -103,11 +103,11 @@ class ServerRepository( CLOUD_SERVERS.GROUP_NAME, CLOUD_SERVERS.HOST_ID, CLOUD_SERVERS.NUMERICAL_ID, - CLOUD_SERVERS.TEMPLATE_ID, CLOUD_SERVERS.IP, CLOUD_SERVERS.PORT, CLOUD_SERVERS.MINIMUM_MEMORY, CLOUD_SERVERS.MAXIMUM_MEMORY, + CLOUD_SERVERS.MAX_PLAYERS, CLOUD_SERVERS.PLAYER_COUNT, CLOUD_SERVERS.STATE, CLOUD_SERVERS.CREATED_AT, @@ -119,11 +119,11 @@ class ServerRepository( element.group, element.host, element.numericalId, - element.templateId, element.ip, element.port.toInt(), element.minMemory.toInt(), element.maxMemory.toInt(), + element.maxPlayers.toInt(), element.playerCount.toInt(), element.state.toString(), currentTimestamp, @@ -135,11 +135,11 @@ class ServerRepository( .set(CLOUD_SERVERS.GROUP_NAME, element.group) .set(CLOUD_SERVERS.HOST_ID, element.host) .set(CLOUD_SERVERS.NUMERICAL_ID, element.numericalId) - .set(CLOUD_SERVERS.TEMPLATE_ID, element.templateId) .set(CLOUD_SERVERS.IP, element.ip) .set(CLOUD_SERVERS.PORT, element.port.toInt()) .set(CLOUD_SERVERS.MINIMUM_MEMORY, element.minMemory.toInt()) .set(CLOUD_SERVERS.MAXIMUM_MEMORY, element.maxMemory.toInt()) + .set(CLOUD_SERVERS.MAX_PLAYERS, element.maxPlayers.toInt()) .set(CLOUD_SERVERS.PLAYER_COUNT, element.playerCount.toInt()) .set(CLOUD_SERVERS.STATE, element.state.toString()) .set(CLOUD_SERVERS.UPDATED_AT, currentTimestamp) 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 c7c6203..c74b209 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,6 +3,7 @@ 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.runtime.secret.ForwardingSecretHandler import app.simplecloud.controller.shared.future.toCompletable import app.simplecloud.controller.shared.host.ServerHost import build.buf.gen.simplecloud.controller.v1.* @@ -18,6 +19,7 @@ class ServerService( private val serverRepository: ServerRepository, private val hostRepository: ServerHostRepository, private val groupRepository: GroupRepository, + private val secretHandler: ForwardingSecretHandler ) : ControllerServerServiceGrpc.ControllerServerServiceImplBase() { private val logger = LogManager.getLogger(ServerService::class.java) @@ -113,6 +115,7 @@ class ServerService( val server = ServerFactory.builder() .setGroup(group) .setNumericalId(numericalId.toLong()) + .setForwardingSecret(secretHandler.getSecret()) .build() serverRepository.save(server) stub.startServer( @@ -157,4 +160,34 @@ class ServerService( } } + override fun updateServerProperty( + request: ServerUpdatePropertyRequest, + responseObserver: StreamObserver + ) { + val server = serverRepository.findServerById(request.id) + if(server == null) { + responseObserver.onError(NullPointerException("Server with id ${request.id} does not exist.")) + return + } + server.properties[request.key] = request.value + serverRepository.save(server) + responseObserver.onNext(ApiResponse("success").toDefinition()) + responseObserver.onCompleted() + } + + override fun updateServerState( + request: ServerUpdateStateRequest, + responseObserver: StreamObserver + ) { + val server = serverRepository.findServerById(request.id) + if(server == null) { + responseObserver.onError(NullPointerException("Server with id ${request.id} does not exist.")) + return + } + server.state = request.state + serverRepository.save(server) + responseObserver.onNext(ApiResponse("success").toDefinition()) + responseObserver.onCompleted() + } + } \ No newline at end of file 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 1dba832..f2e25fd 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 @@ -8,13 +8,12 @@ import org.spongepowered.configurate.objectmapping.ConfigSerializable data class Group( val name: String = "", val type: ServerType = ServerType.OTHER, - val serverUrl: String = "", val minMemory: Long = 0, val maxMemory: Long = 0, val startPort: Long = 0, - @Transient val onlineServers: Long? = 0, val minOnlineCount: Long = 0, val maxOnlineCount: Long = 0, + val maxPlayers: Long = 0, val properties: Map = mapOf() ) { @@ -22,13 +21,12 @@ data class Group( return GroupDefinition.newBuilder() .setName(name) .setType(type) - .setServerUrl(serverUrl) .setMinimumMemory(minMemory) .setMaximumMemory(maxMemory) .setStartPort(startPort) - .setOnlineServers(onlineServers ?: 0) .setMinimumOnlineCount(minOnlineCount) .setMaximumOnlineCount(maxOnlineCount) + .setMaxPlayers(maxPlayers) .putAllProperties(properties) .build() } @@ -39,13 +37,12 @@ data class Group( return Group( groupDefinition.name, groupDefinition.type, - groupDefinition.serverUrl, groupDefinition.minimumMemory, groupDefinition.maximumMemory, groupDefinition.startPort, - groupDefinition.onlineServers, groupDefinition.minimumOnlineCount, groupDefinition.maximumOnlineCount, + groupDefinition.maxPlayers, groupDefinition.propertiesMap ) } 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 fa2d9b4..94c5398 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 @@ -11,14 +11,14 @@ data class Server( val group: String, val host: String?, val numericalId: Int, - val templateId: String, val ip: String, val port: Long, val minMemory: Long, val maxMemory: Long, - val playerCount: Long, + val maxPlayers: Long, + var playerCount: Long, val properties: MutableMap, - val state: ServerState, + var state: ServerState, val createdAt: LocalDateTime, val updatedAt: LocalDateTime ) { @@ -34,8 +34,8 @@ data class Server( .setMinimumMemory(minMemory) .setMaximumMemory(maxMemory) .setPlayerCount(playerCount) + .setMaxPlayers(maxPlayers) .putAllProperties(properties) - .setTemplateId(templateId) .setNumericalId(numericalId) .setCreatedAt(createdAt.toString()) .setUpdatedAt(updatedAt.toString()) @@ -51,11 +51,11 @@ data class Server( serverDefinition.groupName, serverDefinition.hostId, serverDefinition.numericalId, - serverDefinition.templateId, serverDefinition.ip, serverDefinition.port, serverDefinition.minimumMemory, serverDefinition.maximumMemory, + serverDefinition.maxPlayers, serverDefinition.playerCount, serverDefinition.propertiesMap, serverDefinition.state, diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt index 4119517..ceaffa5 100644 --- a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt @@ -42,6 +42,11 @@ class ServerFactory { return this } + private var forwardingSecret: String? = null + fun setForwardingSecret(secret: String): ServerFactory { + this.forwardingSecret = secret + return this + } fun build(): Server { return Server( uniqueId = UUID.randomUUID().toString().replace("-", ""), @@ -54,11 +59,11 @@ class ServerFactory { ip = host?.host ?: "unknown", state = ServerState.PREPARING, numericalId = numericalId.toInt(), + maxPlayers = group.maxPlayers, playerCount = 0, - templateId = "", properties = mutableMapOf( - "serverUrl" to group.serverUrl, - *group.properties.entries.map { it.key to it.value }.toTypedArray() + *group.properties.entries.map { it.key to it.value }.toTypedArray(), + "forwarding-secret" to (forwardingSecret ?: ""), ), createdAt = LocalDateTime.now(), updatedAt = LocalDateTime.now() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0e60d0e..b5742c1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ log4j = "2.20.0" protobuf = "3.25.2" grpc = "1.61.0" grpcKotlin = "1.4.1" -simpleCloudProtoSpecs = "1.4.1.1.20240406080148.cbc66e7054b0" +simpleCloudProtoSpecs = "1.4.1.1.20240418160946.ecdcc966f1cb" jooq = "3.19.3" configurate = "4.1.2" sqliteJdbc = "3.44.1.0" From d334c25415a69cc5afe2a86e99f27920cddae5ac Mon Sep 17 00:00:00 2001 From: Philipp Date: Sat, 20 Apr 2024 13:08:46 +0200 Subject: [PATCH 72/94] refactor: improve forwarding secret --- .../controller/runtime/YamlDirectoryRepository.kt | 2 +- .../runtime/secret/ForwardingSecretHandler.kt | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) 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 d125a53..86335ef 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 @@ -40,7 +40,7 @@ abstract class YamlDirectoryRepository( fun loadAll(): List { if (!directory.toFile().exists()) { - directory.toFile().mkdir() + directory.toFile().mkdirs() } val fileNames = mutableListOf() diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/secret/ForwardingSecretHandler.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/secret/ForwardingSecretHandler.kt index 6cdb913..a669e80 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/secret/ForwardingSecretHandler.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/secret/ForwardingSecretHandler.kt @@ -12,9 +12,13 @@ class ForwardingSecretHandler( private val secret: String init { val secretFile = path.toFile() - if(!secretFile.exists()) { - Files.createDirectories(secretFile.parentFile.toPath()) - Files.createFile(secretFile.toPath()) + if(!Files.exists(path)) { + val parent = path.parent + if (parent != null) { + Files.createDirectories(parent) + } + + Files.createFile(path) val writer = FileWriter(secretFile) writer.write(UUID.randomUUID().toString()) writer.close() From c616727e089cba88aea4fea728d05fbe341e5a01 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Sat, 20 Apr 2024 13:16:05 +0200 Subject: [PATCH 73/94] refactor: remove unused resources --- controller-runtime/src/main/resources/database.yml | 2 -- controller-runtime/src/main/resources/groups.yml | 14 -------------- controller-runtime/src/main/resources/hosts.yml | 3 --- 3 files changed, 19 deletions(-) delete mode 100644 controller-runtime/src/main/resources/database.yml delete mode 100644 controller-runtime/src/main/resources/groups.yml delete mode 100644 controller-runtime/src/main/resources/hosts.yml diff --git a/controller-runtime/src/main/resources/database.yml b/controller-runtime/src/main/resources/database.yml deleted file mode 100644 index 382bd3b..0000000 --- a/controller-runtime/src/main/resources/database.yml +++ /dev/null @@ -1,2 +0,0 @@ -driver: 'jdbc:sqlite' -url: '/database.db' \ No newline at end of file diff --git a/controller-runtime/src/main/resources/groups.yml b/controller-runtime/src/main/resources/groups.yml deleted file mode 100644 index 4ecbba0..0000000 --- a/controller-runtime/src/main/resources/groups.yml +++ /dev/null @@ -1,14 +0,0 @@ -- name: lobby - type: SERVER - server-url: https://api.papermc.io/v2/projects/paper/versions/1.20.4/builds/450/downloads/paper-1.20.4-450.jar - template-id: default - min-memory: 1024 - max-memory: 1024 - start-port: 25565 - min-online-count: 1 - max-online-count: 2 - properties: - fallback-server: true - configurator: spigot - max-players: 20 - max-startup-seconds: 20 diff --git a/controller-runtime/src/main/resources/hosts.yml b/controller-runtime/src/main/resources/hosts.yml deleted file mode 100644 index 43549af..0000000 --- a/controller-runtime/src/main/resources/hosts.yml +++ /dev/null @@ -1,3 +0,0 @@ -- id: "my-local-server-host" - host: "localhost" - port: "5820" \ No newline at end of file From 55d345f0c0ddbd7cbe033fe8e44753054deca67f Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Sun, 21 Apr 2024 18:59:35 +0200 Subject: [PATCH 74/94] refactor: remove arraylist inheritance from repositories --- .../controller/runtime/Repository.kt | 4 +- .../controller/runtime/YamlRepository.kt | 13 ++--- .../runtime/host/ServerHostRepository.kt | 22 ++++++-- .../runtime/server/ServerRepository.kt | 51 +++++++++++-------- .../runtime/server/ServerService.kt | 2 +- 5 files changed, 58 insertions(+), 34 deletions(-) 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 2a0c13d..fd4f024 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 @@ -2,8 +2,8 @@ package app.simplecloud.controller.runtime import java.util.concurrent.CompletableFuture -abstract class Repository : ArrayList() { - abstract fun load() +abstract class Repository { + open fun load() {} abstract fun save(element: T) abstract fun delete(element: T): CompletableFuture } \ No newline at end of file diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt index 9dc6762..69d57c5 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt @@ -13,6 +13,7 @@ abstract class YamlRepository(path: String, private var clazz: Class) : Re private lateinit var node: ConfigurationNode private lateinit var loader: YamlConfigurationLoader private var destination: File = File(path.substring(1, path.length)) + private val list = mutableListOf() init { if (!destination.exists()) { @@ -35,7 +36,7 @@ abstract class YamlRepository(path: String, private var clazz: Class) : Re }.build() this.loader = loader node = loader.load() - addAll(node.getList(clazz) ?: ArrayList()) + list.addAll(node.getList(clazz) ?: ArrayList()) } override fun delete(element: T): CompletableFuture { @@ -43,7 +44,7 @@ abstract class YamlRepository(path: String, private var clazz: Class) : Re if (index == -1) { return CompletableFuture.completedFuture(false) } - removeAt(index) + list.removeAt(index) node.set(clazz, this) return CompletableFuture.completedFuture(true) } @@ -51,12 +52,12 @@ abstract class YamlRepository(path: String, private var clazz: Class) : Re override fun save(element: T) { val index = findIndex(element) if (index != -1) { - removeAt(index) - add(index, element) + list.removeAt(index) + list.add(index, element) } else { - add(element) + list.add(element) } - node.setList(clazz, this) + node.setList(clazz, list) loader.save(node) } 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 bc53230..9da82fa 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 @@ -5,16 +5,28 @@ import app.simplecloud.controller.runtime.server.ServerRepository import app.simplecloud.controller.shared.host.ServerHost import io.grpc.ConnectivityState import java.util.concurrent.CompletableFuture +import java.util.concurrent.ConcurrentHashMap class ServerHostRepository : Repository() { + + private val hosts: ConcurrentHashMap = ConcurrentHashMap() + fun findServerHostById(id: String): ServerHost? { - return firstOrNull { it.id == id } + return hosts.getOrDefault(hosts.keys.firstOrNull { it == id }, null) + } + + fun add(serverHost: ServerHost) { + hosts[serverHost.id] = serverHost + } + + fun remove(serverHost: ServerHost) { + hosts.remove(serverHost.id, serverHost) } fun findLaziestServerHost(serverRepository: ServerRepository): ServerHost? { var lastAmount = Int.MAX_VALUE var lastHost: ServerHost? = null - for (host: ServerHost in this) { + for (host: ServerHost in hosts.values) { val amount = serverRepository.findServersByHostId(host.id).size if (amount < lastAmount) { lastAmount = amount @@ -25,8 +37,8 @@ class ServerHostRepository : Repository() { } fun areServerHostsAvailable(): Boolean { - return any { - val channel = it.createChannel() + return hosts.any { + val channel = it.value.createChannel() val state = channel.getState(true) channel.shutdown() state == ConnectivityState.IDLE || state == ConnectivityState.READY @@ -38,7 +50,7 @@ class ServerHostRepository : Repository() { } override fun delete(element: ServerHost): CompletableFuture { - return CompletableFuture.completedFuture(add(element)) + return CompletableFuture.completedFuture(hosts.remove(element.id, element)) } override fun save(element: ServerHost) { 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 b62dcdb..a999ea3 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 @@ -4,9 +4,11 @@ import app.simplecloud.controller.runtime.Repository 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 build.buf.gen.simplecloud.controller.v1.ServerState import build.buf.gen.simplecloud.controller.v1.ServerType import app.simplecloud.controller.shared.server.Server +import org.jooq.Result import java.time.LocalDateTime import java.util.concurrent.CompletableFuture @@ -15,30 +17,36 @@ class ServerRepository( private val numericalIdRepository: ServerNumericalIdRepository ) : Repository() { + fun findServerById(id: String): Server? { - return firstOrNull { it.uniqueId == id } + val query = database.context.select().from(CLOUD_SERVERS).where(CLOUD_SERVERS.UNIQUE_ID.eq(id)).fetchInto( + CLOUD_SERVERS) + return toList(query).firstOrNull() } fun findServersByHostId(id: String): List { - return filter { it.host == id } + val query = database.context.select().from(CLOUD_SERVERS).where(CLOUD_SERVERS.HOST_ID.eq(id)).fetchInto( + CLOUD_SERVERS) + return toList(query) } fun findServersByGroup(group: String): List { - return filter { server -> server.group == group } + val query = database.context.select().from(CLOUD_SERVERS).where(CLOUD_SERVERS.GROUP_NAME.eq(group)).fetchInto( + CLOUD_SERVERS) + return toList(query) } fun findServersByType(type: ServerType): List { - return filter { server -> server.type == type } + val query = database.context.select().from(CLOUD_SERVERS).where(CLOUD_SERVERS.TYPE.eq(type.toString())).fetchInto(CLOUD_SERVERS) + return toList(query) } - override fun load() { - clear() - val query = database.context.select().from(CLOUD_SERVERS).fetchInto(CLOUD_SERVERS) + private fun toList(query: Result): List { + val result = mutableListOf() query.map { val propertiesQuery = - database.context.select().from(CLOUD_SERVER_PROPERTIES).fetchInto(CLOUD_SERVER_PROPERTIES) - numericalIdRepository.saveNumericalId(it.groupName, it.numericalId) - add( + 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), @@ -60,13 +68,13 @@ class ServerRepository( ) ) } + return result } override fun delete(element: Server): CompletableFuture { - val server = firstOrNull { it.uniqueId == element.uniqueId } ?: return CompletableFuture.completedFuture(false) val canDelete = database.context.deleteFrom(CLOUD_SERVER_PROPERTIES) - .where(CLOUD_SERVER_PROPERTIES.SERVER_ID.eq(server.uniqueId)) + .where(CLOUD_SERVER_PROPERTIES.SERVER_ID.eq(element.uniqueId)) .executeAsync().toCompletableFuture().thenApply { return@thenApply true }.exceptionally { @@ -74,11 +82,11 @@ class ServerRepository( return@exceptionally false }.get() if (!canDelete) return CompletableFuture.completedFuture(false) - numericalIdRepository.removeNumericalId(server.group, server.numericalId) - return database.context.deleteFrom(CLOUD_SERVERS).where(CLOUD_SERVERS.UNIQUE_ID.eq(server.uniqueId)) + 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 && remove(server) + return@thenApply it > 0 }.exceptionally { it.printStackTrace() return@exceptionally false @@ -87,11 +95,6 @@ class ServerRepository( @Synchronized override fun save(element: Server) { - val server = firstOrNull { it.uniqueId == element.uniqueId } - if (server != null) { - remove(server) - } - add(element) numericalIdRepository.saveNumericalId(element.group, element.numericalId) val currentTimestamp = LocalDateTime.now() @@ -165,4 +168,12 @@ class ServerRepository( } } + override fun load() { + val query = database.context.select().from(CLOUD_SERVERS).fetchInto(CLOUD_SERVERS) + val list = toList(query) + list.forEach { + numericalIdRepository.saveNumericalId(it.group, it.numericalId) + } + } + } \ No newline at end of file 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 c74b209..f6602c5 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 @@ -34,7 +34,7 @@ class ServerService( Context.current().fork().run { val channel = serverHost.createChannel() val stub = ServerHostServiceGrpc.newFutureStub(channel) - serverRepository.filter { it.host == serverHost.id }.forEach { + serverRepository.findServersByHostId(serverHost.id).forEach { logger.info("Reattaching Server ${it.uniqueId} of group ${it.group}...") stub.reattachServer(it.toDefinition()).toCompletable().thenApply { response -> val status = ApiResponse.fromDefinition(response) From 8029d333bebc103793b9ed0f5dbc1dc672c1b282 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Sun, 21 Apr 2024 20:02:57 +0200 Subject: [PATCH 75/94] impl: new api methods in buf bump --- build.gradle.kts | 3 +- .../runtime/server/ServerRepository.kt | 12 +++++ .../runtime/server/ServerService.kt | 48 +++++++++++++++++++ gradle/libs.versions.toml | 2 +- 4 files changed, 62 insertions(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 53781ae..341a2ee 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,9 +8,8 @@ plugins { } allprojects { - group = "app.simplecloud.controller" - version = "1.0.21-EXPERIMENTAL" + version = "1.0.22-EXPERIMENTAL" repositories { mavenCentral() 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 a999ea3..24d4727 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 @@ -24,6 +24,18 @@ class ServerRepository( return toList(query).firstOrNull() } + fun findServerByNumerical(group: String, id: Int): Server? { + val query = database.context.select().from(CLOUD_SERVERS).where(CLOUD_SERVERS.GROUP_NAME.eq(group) + .and(CLOUD_SERVERS.NUMERICAL_ID.eq(id))) + .fetchInto(CLOUD_SERVERS) + return toList(query).firstOrNull() + } + + fun getAll(): List { + val query = database.context.select().from(CLOUD_SERVERS).fetchInto(CLOUD_SERVERS) + return toList(query) + } + fun findServersByHostId(id: String): List { val query = database.context.select().from(CLOUD_SERVERS).where(CLOUD_SERVERS.HOST_ID.eq(id)).fetchInto( CLOUD_SERVERS) 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 f6602c5..d798d0d 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 @@ -51,6 +51,54 @@ class ServerService( } } + override fun getAllServers( + request: GetAllServersRequest, + responseObserver: StreamObserver + ) { + val servers = serverRepository.getAll().map { it.toDefinition() } + responseObserver.onNext(GetAllServersResponse.newBuilder().addAllServers(servers).build()) + } + + override fun getServerByNumerical( + request: GetServerByNumericalRequest, + responseObserver: StreamObserver + ) { + val server = serverRepository.findServerByNumerical(request.group, request.numericalId.toInt())?.toDefinition() + if(server == null) { + responseObserver.onError(IllegalArgumentException("No server was found matching this group and numerical id.")) + return + } + responseObserver.onNext(server) + responseObserver.onCompleted() + } + + override fun stopServerByNumerical( + request: StopServerByNumericalRequest, + responseObserver: StreamObserver + ) { + + val server = serverRepository.findServerByNumerical(request.group, request.numericalId.toInt())?.toDefinition() + if (server == null) { + responseObserver.onError(IllegalArgumentException("No server was found matching this group and numerical id.")) + return + } + val host = hostRepository.findServerHostById(server.hostId) + if (host == null) { + responseObserver.onError(ServerHostException("No server host was found matching this server.")) + return + } + val channel = host.createChannel() + val stub = ServerHostServiceGrpc.newFutureStub(channel) + stub.stopServer(server).toCompletable().thenApply { + if (it.status == "success") { + serverRepository.delete(Server.fromDefinition(server)) + } + responseObserver.onNext(it) + responseObserver.onCompleted() + channel.shutdown() + } + } + override fun updateServer(request: ServerUpdateRequest, responseObserver: StreamObserver) { val deleted = request.deleted val server = Server.fromDefinition(request.server) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b5742c1..4353572 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ log4j = "2.20.0" protobuf = "3.25.2" grpc = "1.61.0" grpcKotlin = "1.4.1" -simpleCloudProtoSpecs = "1.4.1.1.20240418160946.ecdcc966f1cb" +simpleCloudProtoSpecs = "1.4.1.1.20240421175249.675f866e4da4" jooq = "3.19.3" configurate = "4.1.2" sqliteJdbc = "3.44.1.0" From 5fe6d2538dd62bab8556151041172ee47eab4ac4 Mon Sep 17 00:00:00 2001 From: Philipp Date: Mon, 22 Apr 2024 11:45:53 +0200 Subject: [PATCH 76/94] feat: authentication & improve forwarding secret --- build.gradle.kts | 2 +- .../controller/runtime/ControllerRuntime.kt | 16 ++++----- .../controller/runtime/Reconciler.kt | 3 ++ .../launcher/ControllerStartCommand.kt | 22 +++++++++++-- .../runtime/secret/ForwardingSecretHandler.kt | 33 ------------------- .../runtime/server/ServerService.kt | 5 ++- .../controller/shared/MetadataKeys.kt | 9 +++++ .../shared/auth/AuthCallCredentials.kt | 24 ++++++++++++++ .../shared/auth/AuthSecretInterceptor.kt | 24 ++++++++++++++ .../shared/secret/AuthFileSecretFactory.kt | 28 ++++++++++++++++ .../shared/secret/SecretGenerator.kt | 15 +++++++++ 11 files changed, 134 insertions(+), 47 deletions(-) delete mode 100644 controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/secret/ForwardingSecretHandler.kt create mode 100644 controller-shared/src/main/kotlin/app/simplecloud/controller/shared/MetadataKeys.kt create mode 100644 controller-shared/src/main/kotlin/app/simplecloud/controller/shared/auth/AuthCallCredentials.kt create mode 100644 controller-shared/src/main/kotlin/app/simplecloud/controller/shared/auth/AuthSecretInterceptor.kt create mode 100644 controller-shared/src/main/kotlin/app/simplecloud/controller/shared/secret/AuthFileSecretFactory.kt create mode 100644 controller-shared/src/main/kotlin/app/simplecloud/controller/shared/secret/SecretGenerator.kt diff --git a/build.gradle.kts b/build.gradle.kts index 341a2ee..f0dee57 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "1.0.22-EXPERIMENTAL" + version = "1.0.23-EXPERIMENTAL" repositories { mavenCentral() 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 881a8c4..5f86978 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 @@ -8,11 +8,9 @@ import app.simplecloud.controller.runtime.launcher.ControllerStartCommand import app.simplecloud.controller.runtime.server.ServerNumericalIdRepository import app.simplecloud.controller.runtime.server.ServerRepository import app.simplecloud.controller.runtime.server.ServerService -import app.simplecloud.controller.runtime.secret.ForwardingSecretHandler -import io.grpc.ManagedChannel -import io.grpc.ManagedChannelBuilder -import io.grpc.Server -import io.grpc.ServerBuilder +import app.simplecloud.controller.shared.auth.AuthCallCredentials +import app.simplecloud.controller.shared.auth.AuthSecretInterceptor +import io.grpc.* import kotlinx.coroutines.* import org.apache.logging.log4j.LogManager import kotlin.concurrent.thread @@ -22,8 +20,8 @@ class ControllerRuntime( ) { private val logger = LogManager.getLogger(ControllerRuntime::class.java) - private val forwardingSecretHandler = ForwardingSecretHandler(controllerStartCommand.velocitySecretPath) private val database = DatabaseFactory.createDatabase(controllerStartCommand.databaseUrl) + private val authCallCredentials = AuthCallCredentials(controllerStartCommand.authSecret) private val groupRepository = GroupRepository(controllerStartCommand.groupPath) private val numericalIdRepository = ServerNumericalIdRepository() @@ -34,7 +32,8 @@ class ControllerRuntime( serverRepository, hostRepository, numericalIdRepository, - createManagedChannel() + createManagedChannel(), + authCallCredentials ) private val server = createGrpcServer() @@ -82,7 +81,8 @@ class ControllerRuntime( private fun createGrpcServer(): Server { return ServerBuilder.forPort(controllerStartCommand.grpcPort) .addService(GroupService(groupRepository)) - .addService(ServerService(numericalIdRepository, serverRepository, hostRepository, groupRepository, forwardingSecretHandler)) + .addService(ServerService(numericalIdRepository, serverRepository, hostRepository, groupRepository, controllerStartCommand.forwardingSecret)) + .intercept(AuthSecretInterceptor(controllerStartCommand.authSecret)) .build() } diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt index ca03f4f..7db4d16 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt @@ -4,6 +4,7 @@ import app.simplecloud.controller.runtime.group.GroupRepository import app.simplecloud.controller.runtime.host.ServerHostRepository import app.simplecloud.controller.runtime.server.ServerNumericalIdRepository import app.simplecloud.controller.runtime.server.ServerRepository +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.ControllerServerServiceGrpc @@ -21,11 +22,13 @@ class Reconciler( private val serverHostRepository: ServerHostRepository, private val numericalIdRepository: ServerNumericalIdRepository, managedChannel: ManagedChannel, + authCallCredentials: AuthCallCredentials, ) { private val INACTIVE_SERVER_TIME = 5L private val serverStub = ControllerServerServiceGrpc.newFutureStub(managedChannel) + .withCallCredentials(authCallCredentials) private val logger = LogManager.getLogger(Reconciler::class.java) fun reconcile() { 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 867989c..85f5558 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 @@ -1,8 +1,10 @@ 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.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 @@ -21,9 +23,25 @@ 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 velocitySecretPath: Path by option(help = "Path to the velocity secret (default: forwarding.secret)", envvar = "VELOCITY_SECRET_PATH") + private val authSecretPath: Path by option( + help = "Path to auth secret file (default: .auth.secret)", + envvar = "AUTH_SECRET_PATH" + ) .path() - .default(Path.of("forwarding.secret")) + .default(Path.of(".secrets", "auth.secret")) + + val authSecret: String by option(help = "Auth secret", envvar = "AUTH_SECRET_KEY") + .defaultLazy { AuthFileSecretFactory.loadOrCreate(authSecretPath) } + + private val forwardingSecretPath: Path by option( + help = "Path to the forwarding secret (default: .forwarding.secret)", + envvar = "FORWARDING_SECRET_PATH" + ) + .path() + .default(Path.of(".secrets", "forwarding.secret")) + + val forwardingSecret: String by option(help = "Forwarding secrewt", envvar = "FORWARDING_SECRET") + .defaultLazy { AuthFileSecretFactory.loadOrCreate(forwardingSecretPath) } override fun run() { val controllerRuntime = ControllerRuntime(this) diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/secret/ForwardingSecretHandler.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/secret/ForwardingSecretHandler.kt deleted file mode 100644 index a669e80..0000000 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/secret/ForwardingSecretHandler.kt +++ /dev/null @@ -1,33 +0,0 @@ -package app.simplecloud.controller.runtime.secret - -import java.io.FileReader -import java.io.FileWriter -import java.nio.file.Files -import java.nio.file.Path -import java.util.* - -class ForwardingSecretHandler( - path: Path -) { - private val secret: String - init { - val secretFile = path.toFile() - if(!Files.exists(path)) { - val parent = path.parent - if (parent != null) { - Files.createDirectories(parent) - } - - Files.createFile(path) - val writer = FileWriter(secretFile) - writer.write(UUID.randomUUID().toString()) - writer.close() - } - val reader = FileReader(secretFile) - secret = reader.readText() - reader.close() - } - fun getSecret(): String { - return secret - } -} \ No newline at end of file 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 d798d0d..b044375 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,7 +3,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.runtime.secret.ForwardingSecretHandler import app.simplecloud.controller.shared.future.toCompletable import app.simplecloud.controller.shared.host.ServerHost import build.buf.gen.simplecloud.controller.v1.* @@ -19,7 +18,7 @@ class ServerService( private val serverRepository: ServerRepository, private val hostRepository: ServerHostRepository, private val groupRepository: GroupRepository, - private val secretHandler: ForwardingSecretHandler + private val forwardingSecret: String ) : ControllerServerServiceGrpc.ControllerServerServiceImplBase() { private val logger = LogManager.getLogger(ServerService::class.java) @@ -163,7 +162,7 @@ class ServerService( val server = ServerFactory.builder() .setGroup(group) .setNumericalId(numericalId.toLong()) - .setForwardingSecret(secretHandler.getSecret()) + .setForwardingSecret(forwardingSecret) .build() serverRepository.save(server) stub.startServer( diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/MetadataKeys.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/MetadataKeys.kt new file mode 100644 index 0000000..bf9c277 --- /dev/null +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/MetadataKeys.kt @@ -0,0 +1,9 @@ +package app.simplecloud.controller.shared + +import io.grpc.Metadata + +object MetadataKeys { + + val AUTH_SECRET_KEY = Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER) + +} \ No newline at end of file diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/auth/AuthCallCredentials.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/auth/AuthCallCredentials.kt new file mode 100644 index 0000000..8ffa976 --- /dev/null +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/auth/AuthCallCredentials.kt @@ -0,0 +1,24 @@ +package app.simplecloud.controller.shared.auth + +import app.simplecloud.controller.shared.MetadataKeys +import io.grpc.CallCredentials +import io.grpc.Metadata +import java.util.concurrent.Executor + +class AuthCallCredentials( + private val secretKey: String +): CallCredentials() { + + override fun applyRequestMetadata( + requestInfo: RequestInfo, + appExecutor: Executor, + applier: MetadataApplier + ) { + appExecutor.execute { + val headers = Metadata() + headers.put(MetadataKeys.AUTH_SECRET_KEY, secretKey) + applier.apply(headers) + } + } + +} \ No newline at end of file diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/auth/AuthSecretInterceptor.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/auth/AuthSecretInterceptor.kt new file mode 100644 index 0000000..290c710 --- /dev/null +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/auth/AuthSecretInterceptor.kt @@ -0,0 +1,24 @@ +package app.simplecloud.controller.shared.auth + +import app.simplecloud.controller.shared.MetadataKeys +import io.grpc.* + +class AuthSecretInterceptor( + private val secretKey: String +) : ServerInterceptor { + + override fun interceptCall( + call: ServerCall, + headers: Metadata, + next: ServerCallHandler + ): ServerCall.Listener { + val secretKey = headers.get(MetadataKeys.AUTH_SECRET_KEY) + if (this.secretKey != secretKey) { + call.close(Status.UNAUTHENTICATED, headers) + return object : ServerCall.Listener() {} + } + + return Contexts.interceptCall(Context.current(), call, headers, next) + } + +} \ No newline at end of file diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/secret/AuthFileSecretFactory.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/secret/AuthFileSecretFactory.kt new file mode 100644 index 0000000..bd0d540 --- /dev/null +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/secret/AuthFileSecretFactory.kt @@ -0,0 +1,28 @@ +package app.simplecloud.controller.shared.secret + +import java.nio.file.Files +import java.nio.file.Path + +object AuthFileSecretFactory { + + fun loadOrCreate(path: Path): String { + if (!Files.exists(path)) { + return create(path) + } + + return Files.readString(path) + } + + + private fun create(path: Path): String { + val secret = SecretGenerator.generate() + + if (!Files.exists(path)) { + path.parent?.let { Files.createDirectories(it) } + Files.writeString(path, secret) + } + + return secret + } + +} \ No newline at end of file diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/secret/SecretGenerator.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/secret/SecretGenerator.kt new file mode 100644 index 0000000..8c18bd3 --- /dev/null +++ b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/secret/SecretGenerator.kt @@ -0,0 +1,15 @@ +package app.simplecloud.controller.shared.secret + +import java.security.SecureRandom +import java.util.* + +object SecretGenerator { + + fun generate(size: Int = 64): String { + val random = SecureRandom() + val bytes = ByteArray(size) + random.nextBytes(bytes) + return Base64.getEncoder().encodeToString(bytes) + } + +} \ No newline at end of file From b95a124f7212a6c457e84965ffa4f294129b6fa9 Mon Sep 17 00:00:00 2001 From: Philipp Date: Mon, 22 Apr 2024 17:54:26 +0200 Subject: [PATCH 77/94] fix: authentication --- .../controller/runtime/ControllerRuntime.kt | 16 ++++++++++++++-- .../controller/runtime/server/ServerService.kt | 16 +++++++++++----- 2 files changed, 25 insertions(+), 7 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 5f86978..0170937 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 @@ -10,7 +10,10 @@ 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 io.grpc.* +import io.grpc.ManagedChannel +import io.grpc.ManagedChannelBuilder +import io.grpc.Server +import io.grpc.ServerBuilder import kotlinx.coroutines.* import org.apache.logging.log4j.LogManager import kotlin.concurrent.thread @@ -81,7 +84,16 @@ class ControllerRuntime( private fun createGrpcServer(): Server { return ServerBuilder.forPort(controllerStartCommand.grpcPort) .addService(GroupService(groupRepository)) - .addService(ServerService(numericalIdRepository, serverRepository, hostRepository, groupRepository, controllerStartCommand.forwardingSecret)) + .addService( + ServerService( + numericalIdRepository, + serverRepository, + hostRepository, + groupRepository, + controllerStartCommand.forwardingSecret, + authCallCredentials + ) + ) .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 b044375..2b87165 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,12 +3,13 @@ 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 import app.simplecloud.controller.shared.host.ServerHost -import build.buf.gen.simplecloud.controller.v1.* import app.simplecloud.controller.shared.server.Server import app.simplecloud.controller.shared.server.ServerFactory import app.simplecloud.controller.shared.status.ApiResponse +import build.buf.gen.simplecloud.controller.v1.* import io.grpc.Context import io.grpc.stub.StreamObserver import org.apache.logging.log4j.LogManager @@ -18,7 +19,8 @@ class ServerService( private val serverRepository: ServerRepository, private val hostRepository: ServerHostRepository, private val groupRepository: GroupRepository, - private val forwardingSecret: String + private val forwardingSecret: String, + private val authCallCredentials: AuthCallCredentials ) : ControllerServerServiceGrpc.ControllerServerServiceImplBase() { private val logger = LogManager.getLogger(ServerService::class.java) @@ -33,6 +35,7 @@ class ServerService( Context.current().fork().run { val channel = serverHost.createChannel() val stub = ServerHostServiceGrpc.newFutureStub(channel) + .withCallCredentials(authCallCredentials) serverRepository.findServersByHostId(serverHost.id).forEach { logger.info("Reattaching Server ${it.uniqueId} of group ${it.group}...") stub.reattachServer(it.toDefinition()).toCompletable().thenApply { response -> @@ -63,7 +66,7 @@ class ServerService( responseObserver: StreamObserver ) { val server = serverRepository.findServerByNumerical(request.group, request.numericalId.toInt())?.toDefinition() - if(server == null) { + if (server == null) { responseObserver.onError(IllegalArgumentException("No server was found matching this group and numerical id.")) return } @@ -88,6 +91,7 @@ class ServerService( } val channel = host.createChannel() val stub = ServerHostServiceGrpc.newFutureStub(channel) + .withCallCredentials(authCallCredentials) stub.stopServer(server).toCompletable().thenApply { if (it.status == "success") { serverRepository.delete(Server.fromDefinition(server)) @@ -152,6 +156,7 @@ class ServerService( } val channel = host.createChannel() val stub = ServerHostServiceGrpc.newFutureStub(channel) + .withCallCredentials(authCallCredentials) val group = groupRepository.find(request.name) if (group == null) { responseObserver.onError(IllegalArgumentException("No group was found matching the group name.")) @@ -197,6 +202,7 @@ class ServerService( } val channel = host.createChannel() val stub = ServerHostServiceGrpc.newFutureStub(channel) + .withCallCredentials(authCallCredentials) stub.stopServer(server).toCompletable().thenApply { if (it.status == "success") { serverRepository.delete(Server.fromDefinition(server)) @@ -212,7 +218,7 @@ class ServerService( responseObserver: StreamObserver ) { val server = serverRepository.findServerById(request.id) - if(server == null) { + if (server == null) { responseObserver.onError(NullPointerException("Server with id ${request.id} does not exist.")) return } @@ -227,7 +233,7 @@ class ServerService( responseObserver: StreamObserver ) { val server = serverRepository.findServerById(request.id) - if(server == null) { + if (server == null) { responseObserver.onError(NullPointerException("Server with id ${request.id} does not exist.")) return } From 0408da40d8a5e2c07694754cb1226ea2e29147af Mon Sep 17 00:00:00 2001 From: Philipp Date: Mon, 22 Apr 2024 17:59:01 +0200 Subject: [PATCH 78/94] feat: authentication in controller api --- .../app/simplecloud/controller/api/Controller.kt | 11 +++++++++-- .../app/simplecloud/controller/api/group/GroupApi.kt | 7 +++++++ .../controller/api/group/impl/GroupApiImpl.kt | 10 +++++++++- .../simplecloud/controller/api/server/ServerApi.kt | 1 + .../controller/api/server/impl/ServerApiImpl.kt | 6 +++++- 5 files changed, 31 insertions(+), 4 deletions(-) diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/Controller.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/Controller.kt index ff423e3..13c0da9 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/Controller.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/Controller.kt @@ -4,6 +4,7 @@ import app.simplecloud.controller.api.group.GroupApi import app.simplecloud.controller.api.group.impl.GroupApiImpl import app.simplecloud.controller.api.server.ServerApi import app.simplecloud.controller.api.server.impl.ServerApiImpl +import app.simplecloud.controller.shared.auth.AuthCallCredentials import io.grpc.ManagedChannel import io.grpc.ManagedChannelBuilder @@ -18,8 +19,14 @@ class Controller { private set fun connect() { - initGroupApi(GroupApiImpl()) - initServerApi(ServerApiImpl()) + val authSecret = System.getenv("GRPC_SECRET") + connect(authSecret) + } + + fun connect(authSecret: String) { + val authCallCredentials = AuthCallCredentials(authSecret) + initGroupApi(GroupApiImpl(authCallCredentials)) + initServerApi(ServerApiImpl(authCallCredentials)) } private fun initGroupApi(groupApi: GroupApi) { diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/GroupApi.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/GroupApi.kt index 2a74d38..4b76adc 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/GroupApi.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/GroupApi.kt @@ -5,6 +5,7 @@ import app.simplecloud.controller.shared.status.ApiResponse import java.util.concurrent.CompletableFuture interface GroupApi { + /** * @param name the name of the group. * @return a [CompletableFuture] with the [Group]. @@ -22,4 +23,10 @@ interface GroupApi { * @return a status [ApiResponse] of the creation state. */ fun createGroup(group: Group): CompletableFuture + + /** + * @return a [CompletableFuture] with a list of all groups + */ + fun getAllGroups(): CompletableFuture> + } \ No newline at end of file diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/impl/GroupApiImpl.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/impl/GroupApiImpl.kt index 7eb9ed2..26b7159 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/impl/GroupApiImpl.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/impl/GroupApiImpl.kt @@ -2,6 +2,7 @@ package app.simplecloud.controller.api.group.impl import app.simplecloud.controller.api.Controller import app.simplecloud.controller.api.group.GroupApi +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 @@ -9,12 +10,15 @@ import build.buf.gen.simplecloud.controller.v1.GetGroupByNameRequest import app.simplecloud.controller.shared.status.ApiResponse import java.util.concurrent.CompletableFuture -class GroupApiImpl : GroupApi { +class GroupApiImpl( + authCallCredentials: AuthCallCredentials +) : GroupApi { private val managedChannel = Controller.createManagedChannelFromEnv() private val groupServiceStub: ControllerGroupServiceGrpc.ControllerGroupServiceFutureStub = ControllerGroupServiceGrpc.newFutureStub(managedChannel) + .withCallCredentials(authCallCredentials) override fun getGroupByName(name: String): CompletableFuture { return groupServiceStub.getGroupByName( @@ -47,4 +51,8 @@ class GroupApiImpl : GroupApi { } } + override fun getAllGroups(): CompletableFuture> { + TODO("Not yet implemented") + } + } \ No newline at end of file diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/ServerApi.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/ServerApi.kt index 454c693..1d7cf97 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/ServerApi.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/ServerApi.kt @@ -42,4 +42,5 @@ interface ServerApi { * @return a [CompletableFuture] with a [ApiResponse]. */ fun stopServer(id: String): CompletableFuture + } \ No newline at end of file diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt index d90dc8e..466ce76 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt @@ -2,6 +2,7 @@ package app.simplecloud.controller.api.server.impl import app.simplecloud.controller.api.Controller import app.simplecloud.controller.api.server.ServerApi +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.* @@ -9,12 +10,15 @@ import app.simplecloud.controller.shared.server.Server import app.simplecloud.controller.shared.status.ApiResponse import java.util.concurrent.CompletableFuture -class ServerApiImpl : ServerApi { +class ServerApiImpl( + authCallCredentials: AuthCallCredentials +) : ServerApi { private val messageChannel = Controller.createManagedChannelFromEnv() private val serverServiceStub: ControllerServerServiceGrpc.ControllerServerServiceFutureStub = ControllerServerServiceGrpc.newFutureStub(messageChannel) + .withCallCredentials(authCallCredentials) override fun getServerById(id: String): CompletableFuture { return serverServiceStub.getServerById( From 13ef965e24195afe3ef7420f3d37db562cc67cd3 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Mon, 22 Apr 2024 18:29:47 +0200 Subject: [PATCH 79/94] impl: all grpc methods in controller api --- build.gradle.kts | 2 +- .../simplecloud/controller/api/Controller.kt | 2 +- .../controller/api/group/GroupApi.kt | 15 +++- .../controller/api/group/impl/GroupApiImpl.kt | 19 +++- .../controller/api/server/ServerApi.kt | 31 ++++++- .../api/server/impl/ServerApiImpl.kt | 88 +++++++++++-------- 6 files changed, 115 insertions(+), 42 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index f0dee57..a7c02fa 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "1.0.23-EXPERIMENTAL" + version = "1.0.24-EXPERIMENTAL" repositories { mavenCentral() diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/Controller.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/Controller.kt index 13c0da9..4640e49 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/Controller.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/Controller.kt @@ -19,7 +19,7 @@ class Controller { private set fun connect() { - val authSecret = System.getenv("GRPC_SECRET") + val authSecret = System.getenv("CONTROLLER_SECRET") connect(authSecret) } diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/GroupApi.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/GroupApi.kt index 4b76adc..c3a9dbf 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/GroupApi.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/GroupApi.kt @@ -2,6 +2,7 @@ package app.simplecloud.controller.api.group import app.simplecloud.controller.shared.group.Group import app.simplecloud.controller.shared.status.ApiResponse +import build.buf.gen.simplecloud.controller.v1.ServerType import java.util.concurrent.CompletableFuture interface GroupApi { @@ -25,8 +26,20 @@ interface GroupApi { fun createGroup(group: Group): CompletableFuture /** - * @return a [CompletableFuture] with a list of all groups + * @param group the [Group] to create. + * @return a status [ApiResponse] of the update state. + */ + 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> + } \ No newline at end of file diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/impl/GroupApiImpl.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/impl/GroupApiImpl.kt index 26b7159..18e880d 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/impl/GroupApiImpl.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/impl/GroupApiImpl.kt @@ -8,6 +8,9 @@ import app.simplecloud.controller.shared.group.Group import build.buf.gen.simplecloud.controller.v1.ControllerGroupServiceGrpc import build.buf.gen.simplecloud.controller.v1.GetGroupByNameRequest import app.simplecloud.controller.shared.status.ApiResponse +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 java.util.concurrent.CompletableFuture class GroupApiImpl( @@ -51,8 +54,22 @@ class GroupApiImpl( } } + override fun updateGroup(group: Group): CompletableFuture { + return groupServiceStub.updateGroup(group.toDefinition()).toCompletable().thenApply { + return@thenApply ApiResponse.fromDefinition(it) + } + } + override fun getAllGroups(): CompletableFuture> { - TODO("Not yet implemented") + return groupServiceStub.getAllGroups(GetAllGroupsRequest.newBuilder().build()).toCompletable().thenApply { + return@thenApply it.groupsList.map { group -> Group.fromDefinition(group) } + } + } + + override fun getGroupsByType(type: ServerType): CompletableFuture> { + return groupServiceStub.getGroupsByType(GetGroupsByTypeRequest.newBuilder().setType(type).build()).toCompletable().thenApply { + return@thenApply it.groupsList.map { group -> Group.fromDefinition(group) } + } } } \ No newline at end of file diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/ServerApi.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/ServerApi.kt index 1d7cf97..17674b1 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/ServerApi.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/ServerApi.kt @@ -4,10 +4,16 @@ import app.simplecloud.controller.shared.group.Group import build.buf.gen.simplecloud.controller.v1.ServerType import app.simplecloud.controller.shared.server.Server import app.simplecloud.controller.shared.status.ApiResponse +import build.buf.gen.simplecloud.controller.v1.ServerState 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]. @@ -27,7 +33,8 @@ interface ServerApi { fun getServersByGroup(group: Group): CompletableFuture> /** - * @return a [CompletableFuture] with a [List] of all [Server]s + * @param type The servers type + * @return a [CompletableFuture] with a [List] of all [Server]s with this type */ fun getServersByType(type: ServerType): CompletableFuture> @@ -37,10 +44,32 @@ interface ServerApi { */ fun startServer(groupName: String): CompletableFuture + /** + * @param groupName the group name of the servers group. + * @param numericalId the numerical id of the server. + * @return a [CompletableFuture] with a [ApiResponse]. + */ + fun stopServer(groupName: String, numericalId: Long): CompletableFuture + /** * @param id the id of the server. * @return a [CompletableFuture] with a [ApiResponse]. */ fun stopServer(id: String): CompletableFuture + /** + * @param id the id of the server. + * @param state the new state of the server. + * @return a [CompletableFuture] with a [ApiResponse]. + */ + 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 a [ApiResponse]. + */ + fun updateServerProperty(id: String, key: String, value: Any): CompletableFuture + } \ No newline at end of file diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt index 466ce76..fffd697 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt @@ -17,29 +17,28 @@ class ServerApiImpl( private val messageChannel = Controller.createManagedChannelFromEnv() private val serverServiceStub: ControllerServerServiceGrpc.ControllerServerServiceFutureStub = - ControllerServerServiceGrpc.newFutureStub(messageChannel) - .withCallCredentials(authCallCredentials) + ControllerServerServiceGrpc.newFutureStub(messageChannel).withCallCredentials(authCallCredentials) + + override fun getAllServers(): CompletableFuture> { + return serverServiceStub.getAllServers(GetAllServersRequest.newBuilder().build()).toCompletable().thenApply { + Server.fromDefinition(it.serversList) + } + } override fun getServerById(id: String): CompletableFuture { return serverServiceStub.getServerById( - ServerIdRequest.newBuilder() - .setId(id) - .build() - ).toCompletable() - .thenApply { - Server.fromDefinition(it) - } + ServerIdRequest.newBuilder().setId(id).build() + ).toCompletable().thenApply { + Server.fromDefinition(it) + } } override fun getServersByGroup(groupName: String): CompletableFuture> { return serverServiceStub.getServersByGroup( - GroupNameRequest.newBuilder() - .setName(groupName) - .build() - ).toCompletable() - .thenApply { - Server.fromDefinition(it.serversList) - } + GroupNameRequest.newBuilder().setName(groupName).build() + ).toCompletable().thenApply { + Server.fromDefinition(it.serversList) + } } override fun getServersByGroup(group: Group): CompletableFuture> { @@ -48,34 +47,49 @@ class ServerApiImpl( override fun getServersByType(type: ServerType): CompletableFuture> { return serverServiceStub.getServersByType( - ServerTypeRequest.newBuilder() - .setType(type) - .build() - ).toCompletable() - .thenApply { - Server.fromDefinition(it.serversList) - } + ServerTypeRequest.newBuilder().setType(type).build() + ).toCompletable().thenApply { + Server.fromDefinition(it.serversList) + } } override fun startServer(groupName: String): CompletableFuture { return serverServiceStub.startServer( - GroupNameRequest.newBuilder() - .setName(groupName) - .build() - ).toCompletable() - .thenApply { - Server.fromDefinition(it) - } + GroupNameRequest.newBuilder().setName(groupName).build() + ).toCompletable().thenApply { + Server.fromDefinition(it) + } + } + + override fun stopServer(groupName: String, numericalId: Long): CompletableFuture { + return serverServiceStub.stopServerByNumerical( + StopServerByNumericalRequest.newBuilder().setGroup(groupName).setNumericalId(numericalId).build() + ).toCompletable().thenApply { + ApiResponse.fromDefinition(it) + } } override fun stopServer(id: String): CompletableFuture { return serverServiceStub.stopServer( - ServerIdRequest.newBuilder() - .setId(id) - .build() - ).toCompletable() - .thenApply { - ApiResponse.fromDefinition(it) - } + ServerIdRequest.newBuilder().setId(id).build() + ).toCompletable().thenApply { + ApiResponse.fromDefinition(it) + } + } + + override fun updateServerState(id: String, state: ServerState): CompletableFuture { + return serverServiceStub.updateServerState( + ServerUpdateStateRequest.newBuilder().setState(state).setId(id).build() + ).toCompletable().thenApply { + return@thenApply ApiResponse.fromDefinition(it) + } + } + + override fun updateServerProperty(id: String, key: String, value: Any): CompletableFuture { + return serverServiceStub.updateServerProperty( + ServerUpdatePropertyRequest.newBuilder().setKey(key).setValue(value.toString()).setId(id).build() + ).toCompletable().thenApply { + return@thenApply ApiResponse.fromDefinition(it) + } } } \ No newline at end of file From 137eb0d058db026d0f57bc6f308d2faf5226c32d Mon Sep 17 00:00:00 2001 From: Frederick Baier Date: Mon, 22 Apr 2024 20:53:13 +0200 Subject: [PATCH 80/94] refactor: reconciler --- .../controller/runtime/ControllerRuntime.kt | 3 +- .../controller/runtime/Reconciler.kt | 134 ------------------ .../runtime/reconciler/GroupReconciler.kt | 132 +++++++++++++++++ .../runtime/reconciler/Reconciler.kt | 27 ++++ .../server/ServerNumericalIdRepository.kt | 4 - 5 files changed, 161 insertions(+), 139 deletions(-) delete mode 100644 controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt create mode 100644 controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/reconciler/GroupReconciler.kt create mode 100644 controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/reconciler/Reconciler.kt 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 881a8c4..0b7b06b 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 @@ -5,10 +5,11 @@ import app.simplecloud.controller.runtime.group.GroupRepository import app.simplecloud.controller.runtime.group.GroupService import app.simplecloud.controller.runtime.host.ServerHostRepository import app.simplecloud.controller.runtime.launcher.ControllerStartCommand +import app.simplecloud.controller.runtime.reconciler.Reconciler +import app.simplecloud.controller.runtime.secret.ForwardingSecretHandler import app.simplecloud.controller.runtime.server.ServerNumericalIdRepository import app.simplecloud.controller.runtime.server.ServerRepository import app.simplecloud.controller.runtime.server.ServerService -import app.simplecloud.controller.runtime.secret.ForwardingSecretHandler import io.grpc.ManagedChannel import io.grpc.ManagedChannelBuilder import io.grpc.Server diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt deleted file mode 100644 index ca03f4f..0000000 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/Reconciler.kt +++ /dev/null @@ -1,134 +0,0 @@ -package app.simplecloud.controller.runtime - -import app.simplecloud.controller.runtime.group.GroupRepository -import app.simplecloud.controller.runtime.host.ServerHostRepository -import app.simplecloud.controller.runtime.server.ServerNumericalIdRepository -import app.simplecloud.controller.runtime.server.ServerRepository -import app.simplecloud.controller.shared.future.toCompletable -import app.simplecloud.controller.shared.group.Group -import build.buf.gen.simplecloud.controller.v1.ControllerServerServiceGrpc -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 app.simplecloud.controller.shared.server.Server -import io.grpc.ManagedChannel -import org.apache.logging.log4j.LogManager -import java.time.LocalDateTime - -class Reconciler( - private val groupRepository: GroupRepository, - private val serverRepository: ServerRepository, - private val serverHostRepository: ServerHostRepository, - private val numericalIdRepository: ServerNumericalIdRepository, - managedChannel: ManagedChannel, -) { - - private val INACTIVE_SERVER_TIME = 5L - - private val serverStub = ControllerServerServiceGrpc.newFutureStub(managedChannel) - private val logger = LogManager.getLogger(Reconciler::class.java) - - fun reconcile() { - groupRepository.findAll().forEach { group -> - val servers = serverRepository.findServersByGroup(group.name) - val availableServerCount = servers.count { server -> - server.state == ServerState.AVAILABLE - || server.state == ServerState.STARTING - || server.state == ServerState.PREPARING - } - - cleanupServers(group, servers, availableServerCount) - cleanupNumericalIds(group, servers) - - startServers(group, availableServerCount, servers.size) - } - } - - private fun cleanupServers(group: Group, servers: List, availableServerCount: Int) { - val hasMoreServersThenNeeded = availableServerCount > group.minOnlineCount - servers - .filter { it.state == ServerState.AVAILABLE } - .forEach { server -> - if (hasMoreServersThenNeeded && !wasUpdatedRecently(server)) { - logger.info("Stopping server ${server.uniqueId} of group ${group.name}") - serverStub.stopServer( - ServerIdRequest.newBuilder() - .setId(server.uniqueId) - .build() - ).toCompletable() - .thenApply { - logger.info("Stopped server ${server.uniqueId} of group ${group.name}") - }.exceptionally { - logger.error("Could not stop server ${server.uniqueId} of group ${group.name}: ${it.message}") - } - } - } - } - - private fun cleanupNumericalIds(group: Group, servers: List) { - val groupName = group.name - val usedNumericalIds = servers.map { it.numericalId } - val numericalIds = numericalIdRepository.findNumericalIds(groupName) - - val unusedNumericalIds = numericalIds.filter { !usedNumericalIds.contains(it) }.toSet() - numericalIdRepository.removeNumericalIds(groupName, unusedNumericalIds) - - if (unusedNumericalIds.isNotEmpty()) { - logger.info("Removed unused numerical ids $unusedNumericalIds of group $groupName") - } - } - - private fun wasUpdatedRecently(server: Server): Boolean { - return server.updatedAt.isAfter(LocalDateTime.now().minusMinutes(INACTIVE_SERVER_TIME)) - } - - private fun startServers( - group: Group, - availableServerCount: Int, - serverCount: Int - ) { - if (!checkIfNewServerCanBeStarted(group, availableServerCount, serverCount) || - !serverHostRepository.areServerHostsAvailable() - ) { - return - } - - startServer(group) - } - - private fun startServer(group: Group) { - logger.info("Starting new instance of group ${group.name}") - serverStub.startServer(GroupNameRequest.newBuilder().setName(group.name).build()).toCompletable() - .thenApply { - logger.info("Started new instance ${it.groupName}-${it.numericalId}/${it.uniqueId} of group ${group.name} on ${it.ip}:${it.port}") - }.exceptionally { - it.printStackTrace() - logger.error("Could not start a new instance of group ${group.name}: ${it.message}") - } - } - - private fun checkIfNewServerCanBeStarted( - group: Group, - availableServerCount: Int, - serverCount: Int - ): Boolean { - return getNeededServerCount(group, availableServerCount, serverCount) > 0 - } - - private fun getNeededServerCount( - group: Group, - availableServerCount: Int, - serverCount: Int - ): Int { - if (!checkIfServersAreNeeded(group, availableServerCount, serverCount)) { - return 0 - } - - return (group.minOnlineCount - availableServerCount).toInt() - } - - private fun checkIfServersAreNeeded(group: Group, availableServerCount: Int, serverCount: Int): Boolean { - return availableServerCount < group.minOnlineCount && serverCount < group.maxOnlineCount - } - -} \ No newline at end of file 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 new file mode 100644 index 0000000..6d3b476 --- /dev/null +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/reconciler/GroupReconciler.kt @@ -0,0 +1,132 @@ +package app.simplecloud.controller.runtime.reconciler + +import app.simplecloud.controller.runtime.host.ServerHostRepository +import app.simplecloud.controller.runtime.server.ServerNumericalIdRepository +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.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 + +/** + * Date: 20.04.24 + * Time: 16:37 + * @author Frederick Baier + * + */ +class GroupReconciler( + private val serverRepository: ServerRepository, + private val serverHostRepository: ServerHostRepository, + private val numericalIdRepository: ServerNumericalIdRepository, + private val serverStub: ControllerServerServiceFutureStub, + private val group: Group, +) { + + private val logger = LogManager.getLogger(Reconciler::class.java) + private val servers = this.serverRepository.findServersByGroup(this.group.name) + + private val availableServerCount = calculateAvailableServerCount() + + fun reconcile() { + cleanupServers() + cleanupNumericalIds() + startServers() + } + + private fun calculateAvailableServerCount(): Int { + return servers.count { server -> + server.state == ServerState.AVAILABLE + || server.state == ServerState.STARTING + || server.state == ServerState.PREPARING + } + } + + private fun cleanupServers() { + val fullyStartedServers = this.servers.filter { it.state == ServerState.AVAILABLE } + val hasMoreServersThenNeeded = fullyStartedServers.size > this.group.minOnlineCount + if (!hasMoreServersThenNeeded) + return + + val serverCountToStop = fullyStartedServers.size - this.group.minOnlineCount + fullyStartedServers + .filter { !wasUpdatedRecently(it) } + .shuffled() + .take(serverCountToStop.toInt()) + .forEach { stopServer(it) } + } + + private fun stopServer(server: Server) { + logger.info("Stopping server ${server.uniqueId} of group ${server.group}") + serverStub.stopServer( + ServerIdRequest.newBuilder() + .setId(server.uniqueId) + .build() + ).toCompletable() + .thenApply { + logger.info("Stopped server ${server.uniqueId} of group ${server.group}") + }.exceptionally { + logger.error("Could not stop server ${server.uniqueId} of group ${server.group}: ${it.message}") + } + } + + private fun cleanupNumericalIds() { + val usedNumericalIds = this.servers.map { it.numericalId } + val numericalIds = this.numericalIdRepository.findNumericalIds(this.group.name) + + val unusedNumericalIds = numericalIds.filter { !usedNumericalIds.contains(it) } + + unusedNumericalIds.forEach { this.numericalIdRepository.removeNumericalId(this.group.name, it) } + + if (unusedNumericalIds.isNotEmpty()) { + logger.info("Removed unused numerical ids $unusedNumericalIds of group ${this.group.name}") + } + } + + private fun wasUpdatedRecently(server: Server): Boolean { + return server.updatedAt.isAfter(LocalDateTime.now().minusMinutes(INACTIVE_SERVER_TIME)) + } + + private fun startServers() { + if (!serverHostRepository.areServerHostsAvailable()) + return + + if (isNewServerNeeded()) + startServer() + } + + private fun startServer() { + logger.info("Starting new instance of group ${this.group.name}") + serverStub.startServer(GroupNameRequest.newBuilder().setName(this.group.name).build()).toCompletable() + .thenApply { + logger.info("Started new instance ${it.groupName}-${it.numericalId}/${it.uniqueId} of group ${this.group.name} on ${it.ip}:${it.port}") + }.exceptionally { + it.printStackTrace() + logger.error("Could not start a new instance of group ${this.group.name}: ${it.message}") + } + } + + private fun isNewServerNeeded(): Boolean { + return calculateServerCountToStart() > 0 + } + + private fun calculateServerCountToStart(): Int { + val currentServerCount = this.servers.size + val neededServerCount = this.group.minOnlineCount - this.availableServerCount + val maxNewServers = this.group.maxOnlineCount - currentServerCount + + if (neededServerCount > 0) + return min(neededServerCount, maxNewServers).toInt() + return 0 + } + + companion object { + private const val INACTIVE_SERVER_TIME = 5L + } + +} \ No newline at end of file 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 new file mode 100644 index 0000000..66c87bb --- /dev/null +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/reconciler/Reconciler.kt @@ -0,0 +1,27 @@ +package app.simplecloud.controller.runtime.reconciler + +import app.simplecloud.controller.runtime.group.GroupRepository +import app.simplecloud.controller.runtime.host.ServerHostRepository +import app.simplecloud.controller.runtime.server.ServerNumericalIdRepository +import app.simplecloud.controller.runtime.server.ServerRepository +import build.buf.gen.simplecloud.controller.v1.ControllerServerServiceGrpc +import io.grpc.ManagedChannel + +class Reconciler( + private val groupRepository: GroupRepository, + private val serverRepository: ServerRepository, + private val serverHostRepository: ServerHostRepository, + private val numericalIdRepository: ServerNumericalIdRepository, + managedChannel: ManagedChannel, +) { + + private val serverStub = ControllerServerServiceGrpc.newFutureStub(managedChannel) + + fun reconcile() { + this.groupRepository.findAll().forEach { group -> + GroupReconciler(serverRepository, serverHostRepository, numericalIdRepository, serverStub, group) + .reconcile() + } + } + +} \ No newline at end of file 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 c9685aa..d8962cd 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 @@ -26,10 +26,6 @@ class ServerNumericalIdRepository { return numericalIds.computeIfPresent(group) { _, v -> v.minus(id) } != null } - fun removeNumericalIds(group: String, ids: Set) { - numericalIds.remove(group, ids) - } - fun findNumericalIds(group: String): Set { return numericalIds[group] ?: emptySet() } From 03d12e1ae368afca33c98db09c14b0a0495dacc4 Mon Sep 17 00:00:00 2001 From: Frederick Baier Date: Mon, 22 Apr 2024 21:41:41 +0200 Subject: [PATCH 81/94] fix: merge conflicts --- .gitignore | 4 +++- .../app/simplecloud/controller/runtime/ControllerRuntime.kt | 1 + .../controller/runtime/reconciler/GroupReconciler.kt | 2 +- .../simplecloud/controller/runtime/reconciler/Reconciler.kt | 3 +++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index b63da45..973dab3 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,6 @@ bin/ .vscode/ ### Mac OS ### -.DS_Store \ No newline at end of file +.DS_Store + +run/ \ 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 0170937..e15a654 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 @@ -5,6 +5,7 @@ import app.simplecloud.controller.runtime.group.GroupRepository import app.simplecloud.controller.runtime.group.GroupService import app.simplecloud.controller.runtime.host.ServerHostRepository import app.simplecloud.controller.runtime.launcher.ControllerStartCommand +import app.simplecloud.controller.runtime.reconciler.Reconciler import app.simplecloud.controller.runtime.server.ServerNumericalIdRepository import app.simplecloud.controller.runtime.server.ServerRepository import app.simplecloud.controller.runtime.server.ServerService 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 6d3b476..1d7aaf6 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 @@ -28,7 +28,7 @@ class GroupReconciler( private val group: Group, ) { - private val logger = LogManager.getLogger(Reconciler::class.java) + private val logger = LogManager.getLogger(GroupReconciler::class.java) private val servers = this.serverRepository.findServersByGroup(this.group.name) private val availableServerCount = calculateAvailableServerCount() 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 66c87bb..73c2d04 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 @@ -4,6 +4,7 @@ import app.simplecloud.controller.runtime.group.GroupRepository import app.simplecloud.controller.runtime.host.ServerHostRepository import app.simplecloud.controller.runtime.server.ServerNumericalIdRepository import app.simplecloud.controller.runtime.server.ServerRepository +import app.simplecloud.controller.shared.auth.AuthCallCredentials import build.buf.gen.simplecloud.controller.v1.ControllerServerServiceGrpc import io.grpc.ManagedChannel @@ -13,9 +14,11 @@ class Reconciler( private val serverHostRepository: ServerHostRepository, private val numericalIdRepository: ServerNumericalIdRepository, managedChannel: ManagedChannel, + authCallCredentials: AuthCallCredentials, ) { private val serverStub = ControllerServerServiceGrpc.newFutureStub(managedChannel) + .withCallCredentials(authCallCredentials) fun reconcile() { this.groupRepository.findAll().forEach { group -> From b7f42807fea95fe73ec2c60f3840982d0919c269 Mon Sep 17 00:00:00 2001 From: Philipp Date: Sat, 27 Apr 2024 09:37:23 +0200 Subject: [PATCH 82/94] fix: ServerService#getAllServers has not completed the responseObserver --- .../app/simplecloud/controller/runtime/server/ServerService.kt | 1 + 1 file changed, 1 insertion(+) 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 2b87165..b756742 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 @@ -59,6 +59,7 @@ class ServerService( ) { val servers = serverRepository.getAll().map { it.toDefinition() } responseObserver.onNext(GetAllServersResponse.newBuilder().addAllServers(servers).build()) + responseObserver.onCompleted() } override fun getServerByNumerical( From cbbdbab663c7fd9ddc8a054a6a03b2f2357df194 Mon Sep 17 00:00:00 2001 From: Philipp Date: Sun, 28 Apr 2024 17:59:48 +0200 Subject: [PATCH 83/94] refactor: improve controller api --- build.gradle.kts | 2 +- .../simplecloud/controller/api/Controller.kt | 46 ------------------- .../controller/api/ControllerApi.kt | 26 +++++++++++ .../controller/api/{group => }/GroupApi.kt | 2 +- .../controller/api/{server => }/ServerApi.kt | 2 +- .../controller/api/impl/ControllerApiImpl.kt | 34 ++++++++++++++ .../api/{group => }/impl/GroupApiImpl.kt | 9 ++-- .../api/{server => }/impl/ServerApiImpl.kt | 11 ++--- 8 files changed, 72 insertions(+), 60 deletions(-) delete mode 100644 controller-api/src/main/kotlin/app/simplecloud/controller/api/Controller.kt create mode 100644 controller-api/src/main/kotlin/app/simplecloud/controller/api/ControllerApi.kt rename controller-api/src/main/kotlin/app/simplecloud/controller/api/{group => }/GroupApi.kt (96%) rename controller-api/src/main/kotlin/app/simplecloud/controller/api/{server => }/ServerApi.kt (98%) create mode 100644 controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ControllerApiImpl.kt rename controller-api/src/main/kotlin/app/simplecloud/controller/api/{group => }/impl/GroupApiImpl.kt (92%) rename controller-api/src/main/kotlin/app/simplecloud/controller/api/{server => }/impl/ServerApiImpl.kt (92%) diff --git a/build.gradle.kts b/build.gradle.kts index a7c02fa..1a4b1a9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "1.0.24-EXPERIMENTAL" + version = "1.0.25-EXPERIMENTAL" repositories { mavenCentral() diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/Controller.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/Controller.kt deleted file mode 100644 index 4640e49..0000000 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/Controller.kt +++ /dev/null @@ -1,46 +0,0 @@ -package app.simplecloud.controller.api - -import app.simplecloud.controller.api.group.GroupApi -import app.simplecloud.controller.api.group.impl.GroupApiImpl -import app.simplecloud.controller.api.server.ServerApi -import app.simplecloud.controller.api.server.impl.ServerApiImpl -import app.simplecloud.controller.shared.auth.AuthCallCredentials -import io.grpc.ManagedChannel -import io.grpc.ManagedChannelBuilder - -class Controller { - companion object { - @JvmStatic - lateinit var groupApi: GroupApi - private set - - @JvmStatic - lateinit var serverApi: ServerApi - private set - - fun connect() { - val authSecret = System.getenv("CONTROLLER_SECRET") - connect(authSecret) - } - - fun connect(authSecret: String) { - val authCallCredentials = AuthCallCredentials(authSecret) - initGroupApi(GroupApiImpl(authCallCredentials)) - initServerApi(ServerApiImpl(authCallCredentials)) - } - - private fun initGroupApi(groupApi: GroupApi) { - this.groupApi = groupApi - } - - private fun initServerApi(serverApi: ServerApi) { - this.serverApi = serverApi - } - - 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/ControllerApi.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/ControllerApi.kt new file mode 100644 index 0000000..bdac7fd --- /dev/null +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/ControllerApi.kt @@ -0,0 +1,26 @@ +package app.simplecloud.controller.api + +import app.simplecloud.controller.api.impl.ControllerApiImpl + +interface ControllerApi { + + fun getGroups(): GroupApi + + fun getServers(): ServerApi + + companion object { + + @JvmStatic + fun create(): ControllerApi { + val authSecret = System.getenv("CONTROLLER_SECRET") + return create(authSecret) + } + + @JvmStatic + fun create(authSecret: String): ControllerApi { + return ControllerApiImpl(authSecret) + } + + } + +} \ No newline at end of file diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/GroupApi.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/GroupApi.kt similarity index 96% rename from controller-api/src/main/kotlin/app/simplecloud/controller/api/group/GroupApi.kt rename to controller-api/src/main/kotlin/app/simplecloud/controller/api/GroupApi.kt index c3a9dbf..f180bd1 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/GroupApi.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/GroupApi.kt @@ -1,4 +1,4 @@ -package app.simplecloud.controller.api.group +package app.simplecloud.controller.api import app.simplecloud.controller.shared.group.Group import app.simplecloud.controller.shared.status.ApiResponse diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/ServerApi.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/ServerApi.kt similarity index 98% rename from controller-api/src/main/kotlin/app/simplecloud/controller/api/server/ServerApi.kt rename to controller-api/src/main/kotlin/app/simplecloud/controller/api/ServerApi.kt index 17674b1..e3a494f 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/ServerApi.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/ServerApi.kt @@ -1,4 +1,4 @@ -package app.simplecloud.controller.api.server +package app.simplecloud.controller.api import app.simplecloud.controller.shared.group.Group import build.buf.gen.simplecloud.controller.v1.ServerType 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 new file mode 100644 index 0000000..f95a496 --- /dev/null +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ControllerApiImpl.kt @@ -0,0 +1,34 @@ +package app.simplecloud.controller.api.impl + +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 io.grpc.ManagedChannel +import io.grpc.ManagedChannelBuilder + +class ControllerApiImpl( + authSecret: String +): ControllerApi { + + private val authCallCredentials = AuthCallCredentials(authSecret) + + private val managedChannel = createManagedChannelFromEnv() + private val groups: GroupApi = GroupApiImpl(managedChannel, authCallCredentials) + private val servers: ServerApi = ServerApiImpl(managedChannel, authCallCredentials) + + override fun getGroups(): GroupApi { + return groups + } + + override fun getServers(): ServerApi { + return servers + } + + 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/group/impl/GroupApiImpl.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/GroupApiImpl.kt similarity index 92% rename from controller-api/src/main/kotlin/app/simplecloud/controller/api/group/impl/GroupApiImpl.kt rename to controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/GroupApiImpl.kt index 18e880d..fe01a83 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/group/impl/GroupApiImpl.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/GroupApiImpl.kt @@ -1,7 +1,6 @@ -package app.simplecloud.controller.api.group.impl +package app.simplecloud.controller.api.impl -import app.simplecloud.controller.api.Controller -import app.simplecloud.controller.api.group.GroupApi +import app.simplecloud.controller.api.GroupApi import app.simplecloud.controller.shared.auth.AuthCallCredentials import app.simplecloud.controller.shared.future.toCompletable import app.simplecloud.controller.shared.group.Group @@ -11,14 +10,14 @@ import app.simplecloud.controller.shared.status.ApiResponse 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 io.grpc.ManagedChannel import java.util.concurrent.CompletableFuture class GroupApiImpl( + managedChannel: ManagedChannel, authCallCredentials: AuthCallCredentials ) : GroupApi { - private val managedChannel = Controller.createManagedChannelFromEnv() - private val groupServiceStub: ControllerGroupServiceGrpc.ControllerGroupServiceFutureStub = ControllerGroupServiceGrpc.newFutureStub(managedChannel) .withCallCredentials(authCallCredentials) diff --git a/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ServerApiImpl.kt similarity index 92% rename from controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt rename to controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ServerApiImpl.kt index fffd697..0df8c52 100644 --- a/controller-api/src/main/kotlin/app/simplecloud/controller/api/server/impl/ServerApiImpl.kt +++ b/controller-api/src/main/kotlin/app/simplecloud/controller/api/impl/ServerApiImpl.kt @@ -1,23 +1,22 @@ -package app.simplecloud.controller.api.server.impl +package app.simplecloud.controller.api.impl -import app.simplecloud.controller.api.Controller -import app.simplecloud.controller.api.server.ServerApi +import app.simplecloud.controller.api.ServerApi 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.* import app.simplecloud.controller.shared.server.Server import app.simplecloud.controller.shared.status.ApiResponse +import io.grpc.ManagedChannel import java.util.concurrent.CompletableFuture class ServerApiImpl( + managedChannel: ManagedChannel, authCallCredentials: AuthCallCredentials ) : ServerApi { - private val messageChannel = Controller.createManagedChannelFromEnv() - private val serverServiceStub: ControllerServerServiceGrpc.ControllerServerServiceFutureStub = - ControllerServerServiceGrpc.newFutureStub(messageChannel).withCallCredentials(authCallCredentials) + ControllerServerServiceGrpc.newFutureStub(managedChannel).withCallCredentials(authCallCredentials) override fun getAllServers(): CompletableFuture> { return serverServiceStub.getAllServers(GetAllServersRequest.newBuilder().build()).toCompletable().thenApply { From 57787eb33a0060b4a28defa082d44ebdd269db12 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Wed, 1 May 2024 19:05:27 +0200 Subject: [PATCH 84/94] refactor: resolve requested changes --- build.gradle.kts | 2 +- .../controller/api/ControllerApi.kt | 6 + .../controller/api/impl/ServerApiImpl.kt | 36 ++- .../controller/runtime/ControllerRuntime.kt | 2 +- .../controller/runtime/LoadableRepository.kt | 5 + .../controller/runtime/Repository.kt | 10 +- .../runtime/YamlDirectoryRepository.kt | 31 +-- .../controller/runtime/YamlRepository.kt | 65 ----- .../runtime/group/GroupRepository.kt | 15 +- .../controller/runtime/group/GroupService.kt | 69 +++-- .../runtime/host/ServerHostRepository.kt | 45 ++-- .../runtime/reconciler/GroupReconciler.kt | 14 +- .../runtime/reconciler/Reconciler.kt | 13 +- .../runtime/server/ServerRepository.kt | 98 +++++-- .../runtime/server/ServerService.kt | 255 +++++++++++------- .../controller/shared/server/ServerFactory.kt | 73 ----- 16 files changed, 364 insertions(+), 375 deletions(-) create mode 100644 controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/LoadableRepository.kt delete mode 100644 controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt delete mode 100644 controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt diff --git a/build.gradle.kts b/build.gradle.kts index 1a4b1a9..1bdaff7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "1.0.25-EXPERIMENTAL" + version = "1.0.26-EXPERIMENTAL" repositories { mavenCentral() 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 bdac7fd..d7fa2cf 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 @@ -4,8 +4,14 @@ import app.simplecloud.controller.api.impl.ControllerApiImpl interface ControllerApi { + /** + * @return the Controller [GroupApi] + */ fun getGroups(): GroupApi + /** + * @return the Controller [ServerApi] + */ fun getServers(): ServerApi companion object { 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 0df8c52..07264c9 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 @@ -26,7 +26,9 @@ class ServerApiImpl( override fun getServerById(id: String): CompletableFuture { return serverServiceStub.getServerById( - ServerIdRequest.newBuilder().setId(id).build() + ServerIdRequest.newBuilder() + .setId(id) + .build() ).toCompletable().thenApply { Server.fromDefinition(it) } @@ -34,7 +36,9 @@ class ServerApiImpl( override fun getServersByGroup(groupName: String): CompletableFuture> { return serverServiceStub.getServersByGroup( - GroupNameRequest.newBuilder().setName(groupName).build() + GroupNameRequest.newBuilder() + .setName(groupName) + .build() ).toCompletable().thenApply { Server.fromDefinition(it.serversList) } @@ -46,7 +50,9 @@ class ServerApiImpl( override fun getServersByType(type: ServerType): CompletableFuture> { return serverServiceStub.getServersByType( - ServerTypeRequest.newBuilder().setType(type).build() + ServerTypeRequest.newBuilder() + .setType(type) + .build() ).toCompletable().thenApply { Server.fromDefinition(it.serversList) } @@ -54,7 +60,9 @@ class ServerApiImpl( override fun startServer(groupName: String): CompletableFuture { return serverServiceStub.startServer( - GroupNameRequest.newBuilder().setName(groupName).build() + GroupNameRequest.newBuilder() + .setName(groupName) + .build() ).toCompletable().thenApply { Server.fromDefinition(it) } @@ -62,7 +70,10 @@ class ServerApiImpl( override fun stopServer(groupName: String, numericalId: Long): CompletableFuture { return serverServiceStub.stopServerByNumerical( - StopServerByNumericalRequest.newBuilder().setGroup(groupName).setNumericalId(numericalId).build() + StopServerByNumericalRequest.newBuilder() + .setGroup(groupName) + .setNumericalId(numericalId) + .build() ).toCompletable().thenApply { ApiResponse.fromDefinition(it) } @@ -70,7 +81,9 @@ class ServerApiImpl( override fun stopServer(id: String): CompletableFuture { return serverServiceStub.stopServer( - ServerIdRequest.newBuilder().setId(id).build() + ServerIdRequest.newBuilder() + .setId(id) + .build() ).toCompletable().thenApply { ApiResponse.fromDefinition(it) } @@ -78,7 +91,10 @@ class ServerApiImpl( override fun updateServerState(id: String, state: ServerState): CompletableFuture { return serverServiceStub.updateServerState( - ServerUpdateStateRequest.newBuilder().setState(state).setId(id).build() + ServerUpdateStateRequest.newBuilder() + .setState(state) + .setId(id) + .build() ).toCompletable().thenApply { return@thenApply ApiResponse.fromDefinition(it) } @@ -86,7 +102,11 @@ class ServerApiImpl( override fun updateServerProperty(id: String, key: String, value: Any): CompletableFuture { return serverServiceStub.updateServerProperty( - ServerUpdatePropertyRequest.newBuilder().setKey(key).setValue(value.toString()).setId(id).build() + ServerUpdatePropertyRequest.newBuilder() + .setKey(key) + .setValue(value.toString()) + .setId(id) + .build() ).toCompletable().thenApply { return@thenApply ApiResponse.fromDefinition(it) } 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 e15a654..a239500 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 @@ -74,7 +74,7 @@ class ControllerRuntime( private fun loadGroups() { logger.info("Loading groups...") - val loadedGroups = groupRepository.loadAll() + val loadedGroups = groupRepository.load() if (loadedGroups.isEmpty()) { logger.warn("No groups found.") } diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/LoadableRepository.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/LoadableRepository.kt new file mode 100644 index 0000000..8f3cc2a --- /dev/null +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/LoadableRepository.kt @@ -0,0 +1,5 @@ +package app.simplecloud.controller.runtime + +interface LoadableRepository : Repository { + fun load(): L +} \ No newline at end of file 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 fd4f024..1785fa2 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 @@ -2,8 +2,10 @@ package app.simplecloud.controller.runtime import java.util.concurrent.CompletableFuture -abstract class Repository { - open fun load() {} - abstract fun save(element: T) - abstract fun delete(element: T): CompletableFuture + +interface Repository { + fun delete(element: E): CompletableFuture + fun save(element: E) + fun find(identifier: I): CompletableFuture + fun getAll(): CompletableFuture> } \ 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 86335ef..223428b 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,36 +9,33 @@ 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 import kotlin.io.path.name -abstract class YamlDirectoryRepository( +abstract class YamlDirectoryRepository( private val directory: Path, - private val clazz: Class, -) { + private val clazz: Class, +): LoadableRepository> { private val logger = LogManager.getLogger(this::class.java) private val watchService = FileSystems.getDefault().newWatchService() private val loaders = mutableMapOf() - protected val entities = mutableMapOf() + protected val entities = mutableMapOf() abstract fun getFileName(identifier: I): String - abstract fun find(identifier: I): T? - - abstract fun save(entity: T) - - fun delete(entity: T): Boolean { - val file = entities.keys.find { entities[it] == entity } ?: return false - return delete(file) + override fun delete(element: E): CompletableFuture { + val file = entities.keys.find { entities[it] == element } ?: return CompletableFuture.completedFuture(false) + return CompletableFuture.completedFuture(deleteFile(file)) } - fun findAll(): List { - return entities.values.toList() + override fun getAll(): CompletableFuture> { + return CompletableFuture.completedFuture(entities.values.toList()) } - fun loadAll(): List { + override fun load(): List { if (!directory.toFile().exists()) { directory.toFile().mkdirs() } @@ -78,13 +75,13 @@ abstract class YamlDirectoryRepository( return true } - private fun delete(file: File): Boolean { + private fun deleteFile(file: File): Boolean { val deletedSuccessfully = file.delete() val removedSuccessfully = entities.remove(file) != null return deletedSuccessfully && removedSuccessfully } - protected fun save(fileName: String, entity: T) { + protected fun save(fileName: String, entity: E) { val file = directory.resolve(fileName).toFile() val loader = getOrCreateLoader(file) val node = loader.createNode(ConfigurationOptions.defaults()) @@ -133,7 +130,7 @@ abstract class YamlDirectoryRepository( } StandardWatchEventKinds.ENTRY_DELETE -> { - delete(resolvedPath.toFile()) + deleteFile(resolvedPath.toFile()) } } } diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt deleted file mode 100644 index 69d57c5..0000000 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/YamlRepository.kt +++ /dev/null @@ -1,65 +0,0 @@ -package app.simplecloud.controller.runtime - -import org.spongepowered.configurate.ConfigurationNode -import org.spongepowered.configurate.kotlin.objectMapperFactory -import org.spongepowered.configurate.yaml.YamlConfigurationLoader -import java.io.File -import java.nio.file.Files -import java.nio.file.StandardCopyOption -import java.util.concurrent.CompletableFuture - -abstract class YamlRepository(path: String, private var clazz: Class) : Repository() { - - private lateinit var node: ConfigurationNode - private lateinit var loader: YamlConfigurationLoader - private var destination: File = File(path.substring(1, path.length)) - private val list = mutableListOf() - - init { - if (!destination.exists()) { - Files.copy( - YamlRepository::class.java.getResourceAsStream(path)!!, - destination.toPath(), - StandardCopyOption.REPLACE_EXISTING - ) - } - load() - } - - final override fun load() { - val loader = YamlConfigurationLoader.builder() - .path(destination.toPath()) - .defaultOptions { options -> - options.serializers { builder -> - builder.registerAnnotatedObjects(objectMapperFactory()) - } - }.build() - this.loader = loader - node = loader.load() - list.addAll(node.getList(clazz) ?: ArrayList()) - } - - override fun delete(element: T): CompletableFuture { - val index = findIndex(element) - if (index == -1) { - return CompletableFuture.completedFuture(false) - } - list.removeAt(index) - node.set(clazz, this) - return CompletableFuture.completedFuture(true) - } - - override fun save(element: T) { - val index = findIndex(element) - if (index != -1) { - list.removeAt(index) - list.add(index, element) - } else { - list.add(element) - } - node.setList(clazz, list) - loader.save(node) - } - - abstract fun findIndex(element: T): Int -} \ No newline at end of file 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 ca8226d..73669fe 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 @@ -3,23 +3,24 @@ package app.simplecloud.controller.runtime.group import app.simplecloud.controller.runtime.YamlDirectoryRepository import app.simplecloud.controller.shared.group.Group import java.nio.file.Path +import java.util.concurrent.CompletableFuture class GroupRepository( path: Path -) : YamlDirectoryRepository(path, Group::class.java) { +) : YamlDirectoryRepository(path, Group::class.java) { override fun getFileName(identifier: String): String { return "$identifier.yml" } - override fun find(identifier: String): Group? { - return entities.values.find { it.name == identifier } + override fun find(identifier: String): CompletableFuture { + return CompletableFuture.completedFuture(entities.values.find { it.name == identifier }) } - override fun save(entity: Group) { - save(getFileName(entity.name), entity) + override fun save(element: Group) { + save(getFileName(element.name), element) } - fun getAll(): List { - return entities.values.toList() + override fun getAll(): CompletableFuture> { + return CompletableFuture.completedFuture(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 7b9ab8f..d754592 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,24 +13,31 @@ class GroupService( request: GetGroupByNameRequest, responseObserver: StreamObserver ) { - val group = groupRepository.find(request.name) - if (group == null) { - responseObserver.onError(Exception("Group not found")) - return - } + groupRepository.find(request.name).thenApply { group -> + if (group == null) { + responseObserver.onError(Exception("Group not found")) + return@thenApply + } - val response = GetGroupByNameResponse.newBuilder() - .setGroup(group.toDefinition()) - .build() + val response = GetGroupByNameResponse.newBuilder() + .setGroup(group.toDefinition()) + .build() + + responseObserver.onNext(response) + responseObserver.onCompleted() + } - responseObserver.onNext(response) - responseObserver.onCompleted() } override fun getAllGroups(request: GetAllGroupsRequest, responseObserver: StreamObserver) { - val response = GetAllGroupsResponse.newBuilder().addAllGroups(groupRepository.getAll().map { it.toDefinition() }).build() - responseObserver.onNext(response) - responseObserver.onCompleted() + groupRepository.getAll().thenApply { groups -> + val response = GetAllGroupsResponse.newBuilder() + .addAllGroups(groups.map { it.toDefinition() }) + .build() + responseObserver.onNext(response) + responseObserver.onCompleted() + } + } override fun getGroupsByType( @@ -38,9 +45,14 @@ class GroupService( responseObserver: StreamObserver ) { val type = request.type - val response = GetGroupsByTypeResponse.newBuilder().addAllGroups(groupRepository.getAll().filter { it.type == type }.map { it.toDefinition() }).build() - responseObserver.onNext(response) - responseObserver.onCompleted() + groupRepository.getAll().thenApply { groups -> + val response = GetGroupsByTypeResponse.newBuilder() + .addAllGroups(groups.filter { it.type == type }.map { it.toDefinition() }) + .build() + responseObserver.onNext(response) + responseObserver.onCompleted() + } + } override fun updateGroup(request: GroupDefinition, responseObserver: StreamObserver) { @@ -61,19 +73,20 @@ class GroupService( } override fun deleteGroupByName(request: GetGroupByNameRequest, responseObserver: StreamObserver) { - val group = groupRepository.find(request.name) - if (group == null) { - responseObserver.onNext(ApiResponse(status = "error").toDefinition()) - responseObserver.onCompleted() - return - } - try { - val successfullyDeleted = groupRepository.delete(group) - responseObserver.onNext(ApiResponse(status = if (successfullyDeleted) "success" else "error").toDefinition()) - responseObserver.onCompleted() - } catch (e: Exception) { - responseObserver.onError(e) + groupRepository.find(request.name).thenApply { group -> + if (group == null) { + responseObserver.onNext(ApiResponse(status = "error").toDefinition()) + responseObserver.onCompleted() + return@thenApply + } + groupRepository.delete(group).thenApply { successfullyDeleted -> + responseObserver.onNext(ApiResponse(status = if (successfullyDeleted) "success" else "error").toDefinition()) + responseObserver.onCompleted() + }.exceptionally { + responseObserver.onError(it) + } } + } } \ 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 9da82fa..df8ffbf 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 @@ -7,7 +7,7 @@ import io.grpc.ConnectivityState import java.util.concurrent.CompletableFuture import java.util.concurrent.ConcurrentHashMap -class ServerHostRepository : Repository() { +class ServerHostRepository : Repository { private val hosts: ConcurrentHashMap = ConcurrentHashMap() @@ -15,46 +15,35 @@ class ServerHostRepository : Repository() { return hosts.getOrDefault(hosts.keys.firstOrNull { it == id }, null) } - fun add(serverHost: ServerHost) { - hosts[serverHost.id] = serverHost - } - - fun remove(serverHost: ServerHost) { - hosts.remove(serverHost.id, serverHost) + override fun save(element: ServerHost) { + hosts[element.id] = element } - fun findLaziestServerHost(serverRepository: ServerRepository): ServerHost? { - var lastAmount = Int.MAX_VALUE - var lastHost: ServerHost? = null - for (host: ServerHost in hosts.values) { - val amount = serverRepository.findServersByHostId(host.id).size - if (amount < lastAmount) { - lastAmount = amount - lastHost = host + override fun find(identifier: ServerRepository): CompletableFuture { + return CompletableFuture.supplyAsync { + return@supplyAsync hosts.values.minByOrNull { host -> + identifier.findServersByHostId(host.id).thenApply { return@thenApply it.size }.get() } } - return lastHost } - fun areServerHostsAvailable(): Boolean { - return hosts.any { - val channel = it.value.createChannel() - val state = channel.getState(true) - channel.shutdown() - state == ConnectivityState.IDLE || state == ConnectivityState.READY + fun areServerHostsAvailable(): CompletableFuture { + return CompletableFuture.supplyAsync { + hosts.any { + val channel = it.value.createChannel() + val state = channel.getState(true) + channel.shutdown() + state == ConnectivityState.IDLE || state == ConnectivityState.READY + } } } - override fun load() { - throw UnsupportedOperationException("This method is not implemented.") - } - override fun delete(element: ServerHost): CompletableFuture { return CompletableFuture.completedFuture(hosts.remove(element.id, element)) } - override fun save(element: ServerHost) { - throw UnsupportedOperationException("This method is not implemented.") + override fun getAll(): CompletableFuture> { + return CompletableFuture.completedFuture(hosts.values.toList()) } } 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 1d7aaf6..6e7e17e 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 @@ -18,7 +18,7 @@ import kotlin.math.min * Date: 20.04.24 * Time: 16:37 * @author Frederick Baier - * + *s */ class GroupReconciler( private val serverRepository: ServerRepository, @@ -29,7 +29,7 @@ class GroupReconciler( ) { private val logger = LogManager.getLogger(GroupReconciler::class.java) - private val servers = this.serverRepository.findServersByGroup(this.group.name) + private val servers = this.serverRepository.findServersByGroup(this.group.name).get() private val availableServerCount = calculateAvailableServerCount() @@ -93,11 +93,11 @@ class GroupReconciler( } private fun startServers() { - if (!serverHostRepository.areServerHostsAvailable()) - return - - if (isNewServerNeeded()) - startServer() + serverHostRepository.areServerHostsAvailable().thenApply { + if (!it) return@thenApply + if (isNewServerNeeded()) + startServer() + } } private fun startServer() { 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 73c2d04..93525d2 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 @@ -21,9 +21,16 @@ class Reconciler( .withCallCredentials(authCallCredentials) fun reconcile() { - this.groupRepository.findAll().forEach { group -> - GroupReconciler(serverRepository, serverHostRepository, numericalIdRepository, serverStub, group) - .reconcile() + this.groupRepository.getAll().thenApply { + it.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 24d4727..eac2cd8 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 @@ -1,13 +1,13 @@ package app.simplecloud.controller.runtime.server -import app.simplecloud.controller.runtime.Repository +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.server.Server import build.buf.gen.simplecloud.controller.v1.ServerState import build.buf.gen.simplecloud.controller.v1.ServerType -import app.simplecloud.controller.shared.server.Server import org.jooq.Result import java.time.LocalDateTime import java.util.concurrent.CompletableFuture @@ -15,49 +15,86 @@ import java.util.concurrent.CompletableFuture class ServerRepository( private val database: Database, private val numericalIdRepository: ServerNumericalIdRepository -) : Repository() { +) : LoadableRepository { - fun findServerById(id: String): Server? { - val query = database.context.select().from(CLOUD_SERVERS).where(CLOUD_SERVERS.UNIQUE_ID.eq(id)).fetchInto( - CLOUD_SERVERS) - return toList(query).firstOrNull() + override fun find(identifier: String): CompletableFuture { + return CompletableFuture.supplyAsync { + val query = database.context.select() + .from(CLOUD_SERVERS) + .where(CLOUD_SERVERS.UNIQUE_ID.eq(identifier)) + .fetchInto( + CLOUD_SERVERS + ) + return@supplyAsync toList(query).firstOrNull() + } } - fun findServerByNumerical(group: String, id: Int): Server? { - val query = database.context.select().from(CLOUD_SERVERS).where(CLOUD_SERVERS.GROUP_NAME.eq(group) - .and(CLOUD_SERVERS.NUMERICAL_ID.eq(id))) - .fetchInto(CLOUD_SERVERS) - return toList(query).firstOrNull() + + fun findServerByNumerical(group: String, id: Int): CompletableFuture { + return CompletableFuture.supplyAsync { + val query = database.context.select().from(CLOUD_SERVERS) + .where( + CLOUD_SERVERS.GROUP_NAME.eq(group) + .and(CLOUD_SERVERS.NUMERICAL_ID.eq(id)) + ) + .fetchInto(CLOUD_SERVERS) + return@supplyAsync toList(query).firstOrNull() + } } - fun getAll(): List { - val query = database.context.select().from(CLOUD_SERVERS).fetchInto(CLOUD_SERVERS) - return toList(query) + override fun getAll(): CompletableFuture> { + return CompletableFuture.supplyAsync { + val query = database.context.select() + .from(CLOUD_SERVERS) + .fetchInto(CLOUD_SERVERS) + return@supplyAsync toList(query) + } } - fun findServersByHostId(id: String): List { - val query = database.context.select().from(CLOUD_SERVERS).where(CLOUD_SERVERS.HOST_ID.eq(id)).fetchInto( - CLOUD_SERVERS) - return toList(query) + fun findServersByHostId(id: String): CompletableFuture> { + return CompletableFuture.supplyAsync { + val query = database.context.select() + .from(CLOUD_SERVERS) + .where(CLOUD_SERVERS.HOST_ID.eq(id)) + .fetchInto( + CLOUD_SERVERS + ) + return@supplyAsync toList(query) + } } - fun findServersByGroup(group: String): List { - val query = database.context.select().from(CLOUD_SERVERS).where(CLOUD_SERVERS.GROUP_NAME.eq(group)).fetchInto( - CLOUD_SERVERS) - return toList(query) + fun findServersByGroup(group: String): CompletableFuture> { + return CompletableFuture.supplyAsync { + val query = database.context.select() + .from(CLOUD_SERVERS) + .where(CLOUD_SERVERS.GROUP_NAME.eq(group)) + .fetchInto( + CLOUD_SERVERS + ) + return@supplyAsync toList(query) + } + } - fun findServersByType(type: ServerType): List { - val query = database.context.select().from(CLOUD_SERVERS).where(CLOUD_SERVERS.TYPE.eq(type.toString())).fetchInto(CLOUD_SERVERS) - return toList(query) + fun findServersByType(type: ServerType): CompletableFuture> { + return CompletableFuture.supplyAsync { + val query = database.context.select() + .from(CLOUD_SERVERS) + .where(CLOUD_SERVERS.TYPE.eq(type.toString())) + .fetchInto(CLOUD_SERVERS) + return@supplyAsync toList(query) + } } 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) + 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, @@ -95,7 +132,8 @@ class ServerRepository( }.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)) + return database.context.deleteFrom(CLOUD_SERVERS) + .where(CLOUD_SERVERS.UNIQUE_ID.eq(element.uniqueId)) .executeAsync() .toCompletableFuture().thenApply { return@thenApply it > 0 @@ -181,7 +219,9 @@ class ServerRepository( } override fun load() { - val query = database.context.select().from(CLOUD_SERVERS).fetchInto(CLOUD_SERVERS) + val query = database.context.select() + .from(CLOUD_SERVERS) + .fetchInto(CLOUD_SERVERS) val list = toList(query) list.forEach { numericalIdRepository.saveNumericalId(it.group, it.numericalId) 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 b756742..616601d 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 @@ -5,14 +5,17 @@ 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 +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.server.ServerFactory import app.simplecloud.controller.shared.status.ApiResponse import build.buf.gen.simplecloud.controller.v1.* import io.grpc.Context import io.grpc.stub.StreamObserver import org.apache.logging.log4j.LogManager +import java.time.LocalDateTime +import java.util.* +import java.util.concurrent.CompletableFuture class ServerService( private val numericalIdRepository: ServerNumericalIdRepository, @@ -27,8 +30,8 @@ class ServerService( override fun attachServerHost(request: ServerHostDefinition, responseObserver: StreamObserver) { val serverHost = ServerHost.fromDefinition(request) - hostRepository.remove(serverHost) - hostRepository.add(serverHost) + hostRepository.delete(serverHost) + hostRepository.save(serverHost) logger.info("Successfully registered ServerHost ${serverHost.id}.") responseObserver.onNext(ApiResponse("success").toDefinition()) responseObserver.onCompleted() @@ -36,20 +39,21 @@ class ServerService( val channel = serverHost.createChannel() val stub = ServerHostServiceGrpc.newFutureStub(channel) .withCallCredentials(authCallCredentials) - serverRepository.findServersByHostId(serverHost.id).forEach { - logger.info("Reattaching Server ${it.uniqueId} of group ${it.group}...") - stub.reattachServer(it.toDefinition()).toCompletable().thenApply { response -> - val status = ApiResponse.fromDefinition(response) - if (status.status == "success") { - logger.info("Success!") - } else { - logger.error("Server was found to be offline, unregistering...") - serverRepository.delete(it) - } - }.get() + serverRepository.findServersByHostId(serverHost.id).thenApply { + it.forEach { server -> + logger.info("Reattaching Server ${server.uniqueId} of group ${server.group}...") + stub.reattachServer(server.toDefinition()).toCompletable().thenApply { response -> + val status = ApiResponse.fromDefinition(response) + if (status.status == "success") { + logger.info("Success!") + } else { + logger.error("Server was found to be offline, unregistering...") + serverRepository.delete(server) + } + }.get() + } + channel.shutdown() } - - channel.shutdown() } } @@ -57,22 +61,28 @@ class ServerService( request: GetAllServersRequest, responseObserver: StreamObserver ) { - val servers = serverRepository.getAll().map { it.toDefinition() } - responseObserver.onNext(GetAllServersResponse.newBuilder().addAllServers(servers).build()) - responseObserver.onCompleted() + serverRepository.getAll().thenApply { servers -> + responseObserver.onNext( + GetAllServersResponse.newBuilder() + .addAllServers(servers.map { it.toDefinition() }) + .build() + ) + responseObserver.onCompleted() + } } override fun getServerByNumerical( request: GetServerByNumericalRequest, responseObserver: StreamObserver ) { - val server = serverRepository.findServerByNumerical(request.group, request.numericalId.toInt())?.toDefinition() - if (server == null) { - responseObserver.onError(IllegalArgumentException("No server was found matching this group and numerical id.")) - return + serverRepository.findServerByNumerical(request.group, request.numericalId.toInt()).thenApply { server -> + if (server == null) { + responseObserver.onError(IllegalArgumentException("No server was found matching this group and numerical id.")) + return@thenApply + } + responseObserver.onNext(server.toDefinition()) + responseObserver.onCompleted() } - responseObserver.onNext(server) - responseObserver.onCompleted() } override fun stopServerByNumerical( @@ -80,27 +90,19 @@ class ServerService( responseObserver: StreamObserver ) { - val server = serverRepository.findServerByNumerical(request.group, request.numericalId.toInt())?.toDefinition() - if (server == null) { - responseObserver.onError(IllegalArgumentException("No server was found matching this group and numerical id.")) - return - } - val host = hostRepository.findServerHostById(server.hostId) - if (host == null) { - responseObserver.onError(ServerHostException("No server host was found matching this server.")) - return - } - val channel = host.createChannel() - val stub = ServerHostServiceGrpc.newFutureStub(channel) - .withCallCredentials(authCallCredentials) - stub.stopServer(server).toCompletable().thenApply { - if (it.status == "success") { - serverRepository.delete(Server.fromDefinition(server)) + serverRepository.findServerByNumerical(request.group, request.numericalId.toInt()).thenApply { server -> + if (server == null) { + responseObserver.onError(IllegalArgumentException("No server was found matching this group and numerical id.")) + return@thenApply + } + stopServer(server.toDefinition()).thenApply { + responseObserver.onNext(it) + responseObserver.onCompleted() + }.exceptionally { + responseObserver.onError(it) } - responseObserver.onNext(it) - responseObserver.onCompleted() - channel.shutdown() } + } override fun updateServer(request: ServerUpdateRequest, responseObserver: StreamObserver) { @@ -124,93 +126,134 @@ class ServerService( } override fun getServerById(request: ServerIdRequest, responseObserver: StreamObserver) { - val server = serverRepository.findServerById(request.id)?.toDefinition() - responseObserver.onNext(server) - responseObserver.onCompleted() + serverRepository.find(request.id).thenApply { server -> + if (server == null) { + responseObserver.onError(IllegalArgumentException("No server was found matching this unique id.")) + return@thenApply + } + responseObserver.onNext(server.toDefinition()) + responseObserver.onCompleted() + } + } override fun getServersByGroup( request: GroupNameRequest, responseObserver: StreamObserver ) { - val servers = serverRepository.findServersByGroup(request.name).map { it.toDefinition() } - val response = GetServersByGroupResponse.newBuilder().addAllServers(servers).build() - responseObserver.onNext(response) - responseObserver.onCompleted() + serverRepository.findServersByGroup(request.name).thenApply { servers -> + val response = GetServersByGroupResponse.newBuilder() + .addAllServers(servers.map { it.toDefinition() }) + .build() + responseObserver.onNext(response) + responseObserver.onCompleted() + } } override fun getServersByType( request: ServerTypeRequest, responseObserver: StreamObserver ) { - val servers = serverRepository.findServersByType(request.type).map { it.toDefinition() } - val response = GetServersByGroupResponse.newBuilder().addAllServers(servers).build() - responseObserver.onNext(response) - responseObserver.onCompleted() + serverRepository.findServersByType(request.type).thenApply { servers -> + val response = GetServersByGroupResponse.newBuilder() + .addAllServers(servers.map { it.toDefinition() }) + .build() + responseObserver.onNext(response) + responseObserver.onCompleted() + } } override fun startServer(request: GroupNameRequest, responseObserver: StreamObserver) { - val host = hostRepository.findLaziestServerHost(serverRepository) - if (host == null) { - responseObserver.onError(ServerHostException("No server host found, could not start server.")) - return + hostRepository.find(serverRepository).thenApply { host -> + if (host == null) { + responseObserver.onError(ServerHostException("No server host found, could not start server.")) + return@thenApply + } + groupRepository.find(request.name).thenApply { group -> + if (group == null) { + responseObserver.onError(IllegalArgumentException("No group was found matching the group name.")) + } else { + startServer(host, group) + } + } } + } + + private fun startServer(host: ServerHost, group: Group): CompletableFuture { + val numericalId = numericalIdRepository.findNextNumericalId(group.name) + val server = buildServer(group, numericalId, forwardingSecret) + serverRepository.save(server) val channel = host.createChannel() val stub = ServerHostServiceGrpc.newFutureStub(channel) .withCallCredentials(authCallCredentials) - val group = groupRepository.find(request.name) - if (group == null) { - responseObserver.onError(IllegalArgumentException("No group was found matching the group name.")) - return - } - - val numericalId = numericalIdRepository.findNextNumericalId(group.name) - val server = ServerFactory.builder() - .setGroup(group) - .setNumericalId(numericalId.toLong()) - .setForwardingSecret(forwardingSecret) - .build() serverRepository.save(server) - stub.startServer( + return stub.startServer( StartServerRequest.newBuilder() .setGroup(group.toDefinition()) .setServer(server.toDefinition()) .build() ).toCompletable().thenApply { serverRepository.save(Server.fromDefinition(it)) - responseObserver.onNext(it) - responseObserver.onCompleted() channel.shutdown() - return@thenApply + return@thenApply it }.exceptionally { serverRepository.delete(server) - numericalIdRepository.removeNumericalId(group.name, numericalId) - responseObserver.onError(ServerHostException("Could not start server, aborting.")) + numericalIdRepository.removeNumericalId(group.name, server.numericalId) channel.shutdown() + throw ServerHostException("Could not start server, aborting.") } } + private fun buildServer(group: Group, numericalId: Int, forwardingSecret: String): Server { + return Server.fromDefinition( + ServerDefinition.newBuilder() + .setNumericalId(numericalId) + .setType(group.type) + .setGroupName(group.name) + .setMinimumMemory(group.minMemory) + .setMaximumMemory(group.maxMemory) + .setState(ServerState.PREPARING) + .setMaxPlayers(group.maxPlayers) + .setCreatedAt(LocalDateTime.now().toString()) + .setUpdatedAt(LocalDateTime.now().toString()) + .setPlayerCount(0) + .setUniqueId(UUID.randomUUID().toString().replace("-", "")).putAllProperties( + mapOf( + *group.properties.entries.map { it.key to it.value }.toTypedArray(), + "forwarding-secret" to forwardingSecret, + ) + ).build() + ) + } + override fun stopServer(request: ServerIdRequest, responseObserver: StreamObserver) { - val server = serverRepository.findServerById(request.id)?.toDefinition() - if (server == null) { - responseObserver.onError(IllegalArgumentException("No server was found matching this id.")) - return + serverRepository.find(request.id).thenApply { server -> + if (server == null) { + throw IllegalArgumentException("No server was found matching this id.") + } + stopServer(server.toDefinition()).thenApply { + responseObserver.onNext(it) + responseObserver.onCompleted() + }.exceptionally { + responseObserver.onError(it) + }.get() + }.exceptionally { + responseObserver.onError(it) } + } + + private fun stopServer(server: ServerDefinition): CompletableFuture { val host = hostRepository.findServerHostById(server.hostId) - if (host == null) { - responseObserver.onError(ServerHostException("No server host was found matching this server.")) - return - } + ?: throw ServerHostException("No server host was found matching this server.") val channel = host.createChannel() val stub = ServerHostServiceGrpc.newFutureStub(channel) .withCallCredentials(authCallCredentials) - stub.stopServer(server).toCompletable().thenApply { + return stub.stopServer(server).toCompletable().thenApply { if (it.status == "success") { serverRepository.delete(Server.fromDefinition(server)) } - responseObserver.onNext(it) - responseObserver.onCompleted() channel.shutdown() + return@thenApply it } } @@ -218,30 +261,34 @@ class ServerService( request: ServerUpdatePropertyRequest, responseObserver: StreamObserver ) { - val server = serverRepository.findServerById(request.id) - if (server == null) { - responseObserver.onError(NullPointerException("Server with id ${request.id} does not exist.")) - return + serverRepository.find(request.id).thenApply { server -> + if (server == null) { + throw NullPointerException("Server with id ${request.id} does not exist.") + } + server.properties[request.key] = request.value + serverRepository.save(server) + responseObserver.onNext(ApiResponse("success").toDefinition()) + responseObserver.onCompleted() + }.exceptionally { + responseObserver.onError(it) } - server.properties[request.key] = request.value - serverRepository.save(server) - responseObserver.onNext(ApiResponse("success").toDefinition()) - responseObserver.onCompleted() } override fun updateServerState( request: ServerUpdateStateRequest, responseObserver: StreamObserver ) { - val server = serverRepository.findServerById(request.id) - if (server == null) { - responseObserver.onError(NullPointerException("Server with id ${request.id} does not exist.")) - return + serverRepository.find(request.id).thenApply { server -> + if (server == null) { + throw NullPointerException("Server with id ${request.id} does not exist.") + } + server.state = request.state + serverRepository.save(server) + responseObserver.onNext(ApiResponse("success").toDefinition()) + responseObserver.onCompleted() + }.exceptionally { + responseObserver.onError(it) } - server.state = request.state - serverRepository.save(server) - responseObserver.onNext(ApiResponse("success").toDefinition()) - responseObserver.onCompleted() } } \ No newline at end of file diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt deleted file mode 100644 index ceaffa5..0000000 --- a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/server/ServerFactory.kt +++ /dev/null @@ -1,73 +0,0 @@ -package app.simplecloud.controller.shared.server - -import app.simplecloud.controller.shared.group.Group -import app.simplecloud.controller.shared.host.ServerHost -import build.buf.gen.simplecloud.controller.v1.ServerState -import java.time.LocalDateTime -import java.util.* -import kotlin.properties.Delegates - -class ServerFactory { - - companion object { - fun builder(): ServerFactory { - return ServerFactory() - } - } - - private lateinit var group: Group - - fun setGroup(group: Group): ServerFactory { - this.group = group - return this - } - - private var host: ServerHost? = null - - fun setHost(host: ServerHost): ServerFactory { - this.host = host - return this - } - - private var numericalId by Delegates.notNull() - - fun setNumericalId(numericalId: Long): ServerFactory { - this.numericalId = numericalId - return this - } - - private var port: Long? = null - fun setPort(port: Long): ServerFactory { - this.port = port - return this - } - - private var forwardingSecret: String? = null - fun setForwardingSecret(secret: String): ServerFactory { - this.forwardingSecret = secret - return this - } - fun build(): Server { - return Server( - uniqueId = UUID.randomUUID().toString().replace("-", ""), - port = port ?: -1, - type = group.type, - group = group.name, - minMemory = group.minMemory, - maxMemory = group.maxMemory, - host = host?.id ?: "unknown", - ip = host?.host ?: "unknown", - state = ServerState.PREPARING, - numericalId = numericalId.toInt(), - maxPlayers = group.maxPlayers, - playerCount = 0, - properties = mutableMapOf( - *group.properties.entries.map { it.key to it.value }.toTypedArray(), - "forwarding-secret" to (forwardingSecret ?: ""), - ), - createdAt = LocalDateTime.now(), - updatedAt = LocalDateTime.now() - ) - } - -} \ No newline at end of file From 4c5b24be244ca6e10574af5eb4542eedccf0bfa4 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Wed, 1 May 2024 19:06:59 +0200 Subject: [PATCH 85/94] refactor: add javadoc to group and server api getters --- .../simplecloud/controller/api/impl/ControllerApiImpl.kt | 6 ++++++ 1 file changed, 6 insertions(+) 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 f95a496..2550e78 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 @@ -17,10 +17,16 @@ class ControllerApiImpl( private val groups: GroupApi = GroupApiImpl(managedChannel, authCallCredentials) private val servers: ServerApi = ServerApiImpl(managedChannel, authCallCredentials) + /** + * @return The controllers [GroupApi] + */ override fun getGroups(): GroupApi { return groups } + /** + * @return The controllers [ServerApi] + */ override fun getServers(): ServerApi { return servers } From 8015a52d10af948ca72d57c8aa2537cff93bc870 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Thu, 2 May 2024 22:22:36 +0200 Subject: [PATCH 86/94] refactor: remove author comment --- .../controller/runtime/reconciler/GroupReconciler.kt | 6 ------ 1 file changed, 6 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 6e7e17e..1c7cd56 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 @@ -14,12 +14,6 @@ import org.apache.logging.log4j.LogManager import java.time.LocalDateTime import kotlin.math.min -/** - * Date: 20.04.24 - * Time: 16:37 - * @author Frederick Baier - *s - */ class GroupReconciler( private val serverRepository: ServerRepository, private val serverHostRepository: ServerHostRepository, From dda8744c55d29cb8920a80909cdc695130c8b9be Mon Sep 17 00:00:00 2001 From: Philipp Date: Thu, 2 May 2024 23:12:49 +0200 Subject: [PATCH 87/94] feat: maven central publish --- build.gradle.kts | 51 +++++++++++++++++++++--- gradle/libs.versions.toml | 7 +++- gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 1bdaff7..7f3a9ba 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,12 +4,13 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { alias(libs.plugins.kotlin) alias(libs.plugins.shadow) + alias(libs.plugins.sonatypeCentralPortalPublisher) `maven-publish` } allprojects { group = "app.simplecloud.controller" - version = "1.0.26-EXPERIMENTAL" + version = "0.0.26-EXPERIMENTAL" repositories { mavenCentral() @@ -20,6 +21,7 @@ allprojects { subprojects { apply(plugin = "org.jetbrains.kotlin.jvm") apply(plugin = "com.github.johnrengelman.shadow") + apply(plugin = "net.thebugmc.gradle.sonatype-central-portal-publisher") apply(plugin = "maven-publish") dependencies { @@ -27,10 +29,6 @@ subprojects { implementation(rootProject.libs.kotlinJvm) } - kotlin { - jvmToolchain(17) - } - publishing { publications { create("mavenJava") { @@ -39,6 +37,14 @@ subprojects { } } + java { + toolchain.languageVersion.set(JavaLanguageVersion.of(21)) + } + + kotlin { + jvmToolchain(17) + } + tasks.withType { kotlinOptions.jvmTarget = "17" } @@ -51,4 +57,39 @@ subprojects { tasks.test { useJUnitPlatform() } + + centralPortal { + name = project.name + + username = project.findProperty("sonatypeUsername") as String + password = project.findProperty("sonatypePassword") as String + + pom { + name.set("SimpleCloud controller") + description.set("The heart of SimpleCloud v3") + url.set("https://github.com/theSimpleCloud/simplecloud-controller") + + developers { + developer { + id.set("fllipeis") + email.set("p.eistrach@gmail.com") + } + } + licenses { + license { + name.set("Apache-2.0") + url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") + } + } + scm { + url.set("https://github.com/theSimpleCloud/simplecloud-controller.git") + connection.set("git:git@github.com:theSimpleCloud/simplecloud-controller.git") + } + } + } + + signing { + sign(publishing.publications) + useGpgCmd() + } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4353572..64a4dd1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,6 +11,8 @@ jooq = "3.19.3" configurate = "4.1.2" sqliteJdbc = "3.44.1.0" clikt = "4.3.0" +sonatypeCentralPortalPublisher = "1.2.3" +spotifyCompletableFutures = "0.3.6" [libraries] kotlinJvm = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } @@ -41,6 +43,8 @@ sqliteJdbc = { module = "org.xerial:sqlite-jdbc", version.ref = "sqliteJdbc" } clikt = { module = "com.github.ajalt.clikt:clikt", version.ref = "clikt" } +spotifyCompletableFutures = { module = "com.spotify:completable-futures", version.ref = "spotifyCompletableFutures" } + [bundles] log4j = [ "log4jCore", @@ -67,4 +71,5 @@ configurate = [ [plugins] kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" } -jooqCodegen = { id = "org.jooq.jooq-codegen-gradle", version.ref = "jooq" } \ No newline at end of file +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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0832ac8..2f2ebf5 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.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 1fde0a57523cad41bbcd059fb8eb34d768ec8e94 Mon Sep 17 00:00:00 2001 From: Philipp Date: Thu, 2 May 2024 23:13:18 +0200 Subject: [PATCH 88/94] refactor: improve ServerHostRepository#find --- controller-runtime/build.gradle.kts | 1 + .../runtime/host/ServerHostRepository.kt | 18 ++++++++++++++---- .../runtime/host/ServerHostWithServerCount.kt | 8 ++++++++ 3 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/host/ServerHostWithServerCount.kt diff --git a/controller-runtime/build.gradle.kts b/controller-runtime/build.gradle.kts index c3a57e7..7b4bdb5 100644 --- a/controller-runtime/build.gradle.kts +++ b/controller-runtime/build.gradle.kts @@ -11,6 +11,7 @@ dependencies { jooqCodegen(rootProject.libs.jooqMetaExtensions) implementation(rootProject.libs.bundles.log4j) implementation(rootProject.libs.clikt) + implementation(rootProject.libs.spotifyCompletableFutures) } application { 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 df8ffbf..14ddb8d 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,6 +3,7 @@ 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 java.util.concurrent.ConcurrentHashMap @@ -20,10 +21,9 @@ class ServerHostRepository : Repository { } override fun find(identifier: ServerRepository): CompletableFuture { - return CompletableFuture.supplyAsync { - return@supplyAsync hosts.values.minByOrNull { host -> - identifier.findServersByHostId(host.id).thenApply { return@thenApply it.size }.get() - } + return mapHostsToServerHostWithServerCount(identifier).thenApply { + val serverHostWithServerCount = it.minByOrNull { it.serverCount } + serverHostWithServerCount?.serverHost } } @@ -46,5 +46,15 @@ class ServerHostRepository : Repository { return CompletableFuture.completedFuture(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) + } + } + ) + } + } diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/host/ServerHostWithServerCount.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/host/ServerHostWithServerCount.kt new file mode 100644 index 0000000..30006b9 --- /dev/null +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/host/ServerHostWithServerCount.kt @@ -0,0 +1,8 @@ +package app.simplecloud.controller.runtime.host + +import app.simplecloud.controller.shared.host.ServerHost + +data class ServerHostWithServerCount( + val serverHost: ServerHost, + val serverCount: Int +) From 9a8dcca6f233cea19c1a862a52e10bc8461f7f1e Mon Sep 17 00:00:00 2001 From: Philipp Date: Thu, 2 May 2024 23:14:11 +0200 Subject: [PATCH 89/94] refactor: change return type of LoadableRepository#load to list of elements instead of generic --- .../controller/runtime/ControllerRuntime.kt | 10 ++++-- .../controller/runtime/LoadableRepository.kt | 4 +-- .../runtime/YamlDirectoryRepository.kt | 31 +++++++------------ .../runtime/server/ServerRepository.kt | 5 +-- 4 files changed, 24 insertions(+), 26 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 a239500..c5abe3e 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 @@ -56,7 +56,12 @@ class ControllerRuntime( private fun loadServers() { logger.info("Loading servers...") - serverRepository.load() + val loadedServers = serverRepository.load() + if (loadedServers.isEmpty()) { + return + } + + logger.info("Loaded servers from cache: ${loadedServers.joinToString { "${it.group}-${it.numericalId}" }}") } private fun startGrpcServer() { @@ -77,9 +82,10 @@ class ControllerRuntime( val loadedGroups = groupRepository.load() if (loadedGroups.isEmpty()) { logger.warn("No groups found.") + return } - logger.info("Loaded groups: ${loadedGroups.joinToString(",")}") + logger.info("Loaded groups: ${loadedGroups.joinToString { it.name }}") } private fun createGrpcServer(): Server { diff --git a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/LoadableRepository.kt b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/LoadableRepository.kt index 8f3cc2a..64de502 100644 --- a/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/LoadableRepository.kt +++ b/controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/LoadableRepository.kt @@ -1,5 +1,5 @@ package app.simplecloud.controller.runtime -interface LoadableRepository : Repository { - fun load(): L +interface LoadableRepository : Repository { + fun load(): 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 223428b..34e627c 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 @@ -10,13 +10,12 @@ import org.spongepowered.configurate.yaml.YamlConfigurationLoader import java.io.File import java.nio.file.* import java.util.concurrent.CompletableFuture -import kotlin.io.path.name abstract class YamlDirectoryRepository( private val directory: Path, private val clazz: Class, -): LoadableRepository> { +) : LoadableRepository { private val logger = LogManager.getLogger(this::class.java) @@ -35,44 +34,36 @@ abstract class YamlDirectoryRepository( return CompletableFuture.completedFuture(entities.values.toList()) } - override fun load(): List { + override fun load(): List { if (!directory.toFile().exists()) { directory.toFile().mkdirs() } - val fileNames = mutableListOf() + registerWatcher() - Files.list(directory) + return Files.list(directory) + .toList() .filter { !it.toFile().isDirectory && it.toString().endsWith(".yml") } - .forEach { - val successFullyLoaded = load(it.toFile()) - if (successFullyLoaded) { - fileNames.add(it.name) - } - } - - registerWatcher() - return fileNames + .mapNotNull { load(it.toFile()) } } - private fun load(file: File): Boolean { + private fun load(file: File): E? { try { val loader = getOrCreateLoader(file) val node = loader.load(ConfigurationOptions.defaults()) - val entity = node.get(clazz) ?: return false + val entity = node.get(clazz) ?: return null entities[file] = entity + return entity } catch (ex: ParsingException) { val existedBefore = entities.containsKey(file) if (existedBefore) { logger.error("Could not load file ${file.name}. Switching back to an older version.") - return false + return null } logger.error("Could not load file ${file.name}. Make sure it's correctly formatted!") - return false + return null } - - return true } private fun deleteFile(file: File): Boolean { 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 eac2cd8..7b3dea6 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 @@ -15,7 +15,7 @@ import java.util.concurrent.CompletableFuture class ServerRepository( private val database: Database, private val numericalIdRepository: ServerNumericalIdRepository -) : LoadableRepository { +) : LoadableRepository { override fun find(identifier: String): CompletableFuture { @@ -218,7 +218,7 @@ class ServerRepository( } } - override fun load() { + override fun load(): List { val query = database.context.select() .from(CLOUD_SERVERS) .fetchInto(CLOUD_SERVERS) @@ -226,6 +226,7 @@ class ServerRepository( list.forEach { numericalIdRepository.saveNumericalId(it.group, it.numericalId) } + return list } } \ No newline at end of file From 39f669af9946e80911376335de08b31e20398420 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Fri, 3 May 2024 00:51:57 +0200 Subject: [PATCH 90/94] fix: make error handling way better --- build.gradle.kts | 6 +- .../simplecloud/controller/api/GroupApi.kt | 16 +-- .../simplecloud/controller/api/ServerApi.kt | 17 ++- .../controller/api/impl/GroupApiImpl.kt | 13 +- .../controller/api/impl/ServerApiImpl.kt | 17 ++- .../controller/runtime/group/GroupService.kt | 70 +++++++-- .../runtime/server/ServerService.kt | 135 ++++++++++++------ .../controller/shared/status/ApiResponse.kt | 22 --- gradle/libs.versions.toml | 2 +- 9 files changed, 185 insertions(+), 113 deletions(-) delete mode 100644 controller-shared/src/main/kotlin/app/simplecloud/controller/shared/status/ApiResponse.kt diff --git a/build.gradle.kts b/build.gradle.kts index 7f3a9ba..b2febd7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { allprojects { group = "app.simplecloud.controller" - version = "0.0.26-EXPERIMENTAL" + version = "0.0.27-EXPERIMENTAL" repositories { mavenCentral() @@ -74,6 +74,10 @@ subprojects { id.set("fllipeis") email.set("p.eistrach@gmail.com") } + developer { + id.set("dayyeeet") + email.set("david@cappell.net") + } } licenses { license { 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 f180bd1..8289c1f 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 @@ -1,7 +1,6 @@ package app.simplecloud.controller.api import app.simplecloud.controller.shared.group.Group -import app.simplecloud.controller.shared.status.ApiResponse import build.buf.gen.simplecloud.controller.v1.ServerType import java.util.concurrent.CompletableFuture @@ -15,22 +14,21 @@ interface GroupApi { /** * @param name the name of the group. - * @return a status [ApiResponse] of the delete state. + * @return the deleted [Group]. */ - fun deleteGroup(name: String): CompletableFuture + fun deleteGroup(name: String): CompletableFuture /** * @param group the [Group] to create. - * @return a status [ApiResponse] of the creation state. + * @return the created [Group]. */ - fun createGroup(group: Group): CompletableFuture + fun createGroup(group: Group): CompletableFuture /** - * @param group the [Group] to create. - * @return a status [ApiResponse] of the update state. + * @param group the [Group] to update. + * @return the updated [Group]. */ - fun updateGroup(group: Group): CompletableFuture - + fun updateGroup(group: Group): CompletableFuture /** * @return a [CompletableFuture] with a list of all groups. */ 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 e3a494f..af0bb38 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,6 @@ 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 app.simplecloud.controller.shared.status.ApiResponse import build.buf.gen.simplecloud.controller.v1.ServerState import java.util.concurrent.CompletableFuture @@ -47,29 +46,29 @@ interface ServerApi { /** * @param groupName the group name of the servers group. * @param numericalId the numerical id of the server. - * @return a [CompletableFuture] with a [ApiResponse]. + * @return a [CompletableFuture] with the stopped [Server]. */ - fun stopServer(groupName: String, numericalId: Long): CompletableFuture + fun stopServer(groupName: String, numericalId: Long): CompletableFuture /** * @param id the id of the server. - * @return a [CompletableFuture] with a [ApiResponse]. + * @return a [CompletableFuture] with the stopped [Server]. */ - fun stopServer(id: String): CompletableFuture + fun stopServer(id: String): CompletableFuture /** * @param id the id of the server. * @param state the new state of the server. - * @return a [CompletableFuture] with a [ApiResponse]. + * @return a [CompletableFuture] with the updated [Server]. */ - fun updateServerState(id: String, state: ServerState): CompletableFuture + 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 a [ApiResponse]. + * @return a [CompletableFuture] with the updated [Server]. */ - fun updateServerProperty(id: String, key: String, value: Any): CompletableFuture + fun updateServerProperty(id: String, key: String, value: Any): CompletableFuture } \ 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/GroupApiImpl.kt index fe01a83..25b4fc6 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 @@ -6,7 +6,6 @@ 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.GetGroupByNameRequest -import app.simplecloud.controller.shared.status.ApiResponse import build.buf.gen.simplecloud.controller.v1.GetAllGroupsRequest import build.buf.gen.simplecloud.controller.v1.GetGroupsByTypeRequest import build.buf.gen.simplecloud.controller.v1.ServerType @@ -33,29 +32,29 @@ class GroupApiImpl( } } - override fun deleteGroup(name: String): CompletableFuture { + override fun deleteGroup(name: String): CompletableFuture { return groupServiceStub.deleteGroupByName( GetGroupByNameRequest.newBuilder() .setName(name) .build() ).toCompletable() .thenApply { - ApiResponse.fromDefinition(it) + Group.fromDefinition(it) } } - override fun createGroup(group: Group): CompletableFuture { + override fun createGroup(group: Group): CompletableFuture { return groupServiceStub.createGroup( group.toDefinition() ).toCompletable() .thenApply { - ApiResponse.fromDefinition(it) + Group.fromDefinition(it) } } - override fun updateGroup(group: Group): CompletableFuture { + override fun updateGroup(group: Group): CompletableFuture { return groupServiceStub.updateGroup(group.toDefinition()).toCompletable().thenApply { - return@thenApply ApiResponse.fromDefinition(it) + return@thenApply Group.fromDefinition(it) } } 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 07264c9..189429c 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 @@ -6,7 +6,6 @@ import app.simplecloud.controller.shared.future.toCompletable import app.simplecloud.controller.shared.group.Group import build.buf.gen.simplecloud.controller.v1.* import app.simplecloud.controller.shared.server.Server -import app.simplecloud.controller.shared.status.ApiResponse import io.grpc.ManagedChannel import java.util.concurrent.CompletableFuture @@ -68,39 +67,39 @@ class ServerApiImpl( } } - override fun stopServer(groupName: String, numericalId: Long): CompletableFuture { + override fun stopServer(groupName: String, numericalId: Long): CompletableFuture { return serverServiceStub.stopServerByNumerical( StopServerByNumericalRequest.newBuilder() .setGroup(groupName) .setNumericalId(numericalId) .build() ).toCompletable().thenApply { - ApiResponse.fromDefinition(it) + Server.fromDefinition(it) } } - override fun stopServer(id: String): CompletableFuture { + override fun stopServer(id: String): CompletableFuture { return serverServiceStub.stopServer( ServerIdRequest.newBuilder() .setId(id) .build() ).toCompletable().thenApply { - ApiResponse.fromDefinition(it) + Server.fromDefinition(it) } } - override fun updateServerState(id: String, state: ServerState): CompletableFuture { + override fun updateServerState(id: String, state: ServerState): CompletableFuture { return serverServiceStub.updateServerState( ServerUpdateStateRequest.newBuilder() .setState(state) .setId(id) .build() ).toCompletable().thenApply { - return@thenApply ApiResponse.fromDefinition(it) + return@thenApply Server.fromDefinition(it) } } - override fun updateServerProperty(id: String, key: String, value: Any): CompletableFuture { + override fun updateServerProperty(id: String, key: String, value: Any): CompletableFuture { return serverServiceStub.updateServerProperty( ServerUpdatePropertyRequest.newBuilder() .setKey(key) @@ -108,7 +107,7 @@ class ServerApiImpl( .setId(id) .build() ).toCompletable().thenApply { - return@thenApply ApiResponse.fromDefinition(it) + return@thenApply Server.fromDefinition(it) } } } \ 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 d754592..5fd259a 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 @@ -2,7 +2,7 @@ package app.simplecloud.controller.runtime.group import app.simplecloud.controller.shared.group.Group import build.buf.gen.simplecloud.controller.v1.* -import app.simplecloud.controller.shared.status.ApiResponse +import io.grpc.Status import io.grpc.stub.StreamObserver class GroupService( @@ -15,7 +15,11 @@ class GroupService( ) { groupRepository.find(request.name).thenApply { group -> if (group == null) { - responseObserver.onError(Exception("Group not found")) + responseObserver.onError( + Status.NOT_FOUND + .withDescription("This group does not exist") + .asRuntimeException() + ) return@thenApply } @@ -55,35 +59,71 @@ class GroupService( } - override fun updateGroup(request: GroupDefinition, responseObserver: StreamObserver) { - groupRepository.save(Group.fromDefinition(request)) - responseObserver.onNext(ApiResponse("success").toDefinition()) + override fun updateGroup(request: GroupDefinition, responseObserver: StreamObserver) { + val group = Group.fromDefinition(request) + try { + groupRepository.save(group) + } catch (e: Exception) { + responseObserver.onError( + Status.INTERNAL + .withDescription("Error whilst updating group") + .withCause(e) + .asRuntimeException() + ) + return + } + + responseObserver.onNext(group.toDefinition()) responseObserver.onCompleted() } - override fun createGroup(request: GroupDefinition, responseObserver: StreamObserver) { + override fun createGroup(request: GroupDefinition, responseObserver: StreamObserver) { val group = Group.fromDefinition(request) try { groupRepository.save(group) - responseObserver.onNext(ApiResponse(status = "success").toDefinition()) - responseObserver.onCompleted() } catch (e: Exception) { - responseObserver.onError(e) + responseObserver.onError( + Status.INTERNAL + .withDescription("Error whilst creating group") + .withCause(e) + .asRuntimeException() + ) + return } + + responseObserver.onNext(group.toDefinition()) + responseObserver.onCompleted() } - override fun deleteGroupByName(request: GetGroupByNameRequest, responseObserver: StreamObserver) { + override fun deleteGroupByName(request: GetGroupByNameRequest, responseObserver: StreamObserver) { groupRepository.find(request.name).thenApply { group -> if (group == null) { - responseObserver.onNext(ApiResponse(status = "error").toDefinition()) - responseObserver.onCompleted() + responseObserver.onError( + Status.NOT_FOUND + .withDescription("This group does not exist") + .asRuntimeException() + ) return@thenApply } - groupRepository.delete(group).thenApply { successfullyDeleted -> - responseObserver.onNext(ApiResponse(status = if (successfullyDeleted) "success" else "error").toDefinition()) + 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(it) + responseObserver.onError( + Status.INTERNAL + .withDescription("Could not delete group") + .withCause(it) + .asRuntimeException() + ) } } 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 616601d..c0b6dfd 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,9 +8,9 @@ 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.status.ApiResponse import build.buf.gen.simplecloud.controller.v1.* import io.grpc.Context +import io.grpc.Status import io.grpc.stub.StreamObserver import org.apache.logging.log4j.LogManager import java.time.LocalDateTime @@ -28,12 +28,22 @@ class ServerService( private val logger = LogManager.getLogger(ServerService::class.java) - override fun attachServerHost(request: ServerHostDefinition, responseObserver: StreamObserver) { + override fun attachServerHost(request: ServerHostDefinition, responseObserver: StreamObserver) { val serverHost = ServerHost.fromDefinition(request) - hostRepository.delete(serverHost) - hostRepository.save(serverHost) + try { + hostRepository.delete(serverHost) + hostRepository.save(serverHost) + }catch (e: Exception) { + responseObserver.onError( + Status.INTERNAL + .withDescription("Could not save serverhost") + .withCause(e) + .asRuntimeException() + ) + return + } logger.info("Successfully registered ServerHost ${serverHost.id}.") - responseObserver.onNext(ApiResponse("success").toDefinition()) + responseObserver.onNext(serverHost.toDefinition()) responseObserver.onCompleted() Context.current().fork().run { val channel = serverHost.createChannel() @@ -42,14 +52,11 @@ class ServerService( serverRepository.findServersByHostId(serverHost.id).thenApply { it.forEach { server -> logger.info("Reattaching Server ${server.uniqueId} of group ${server.group}...") - stub.reattachServer(server.toDefinition()).toCompletable().thenApply { response -> - val status = ApiResponse.fromDefinition(response) - if (status.status == "success") { - logger.info("Success!") - } else { - logger.error("Server was found to be offline, unregistering...") - serverRepository.delete(server) - } + stub.reattachServer(server.toDefinition()).toCompletable().thenApply { + logger.info("Success!") + }.exceptionally { + logger.error("Server was found to be offline, unregistering...") + serverRepository.delete(server) }.get() } channel.shutdown() @@ -77,7 +84,11 @@ class ServerService( ) { serverRepository.findServerByNumerical(request.group, request.numericalId.toInt()).thenApply { server -> if (server == null) { - responseObserver.onError(IllegalArgumentException("No server was found matching this group and numerical id.")) + responseObserver.onError( + Status.NOT_FOUND + .withDescription("No server was found matching this group and numerical id") + .asRuntimeException() + ) return@thenApply } responseObserver.onNext(server.toDefinition()) @@ -87,12 +98,16 @@ class ServerService( override fun stopServerByNumerical( request: StopServerByNumericalRequest, - responseObserver: StreamObserver + responseObserver: StreamObserver ) { serverRepository.findServerByNumerical(request.group, request.numericalId.toInt()).thenApply { server -> if (server == null) { - responseObserver.onError(IllegalArgumentException("No server was found matching this group and numerical id.")) + responseObserver.onError( + Status.NOT_FOUND + .withDescription("No server was found matching this group and numerical id") + .asRuntimeException() + ) return@thenApply } stopServer(server.toDefinition()).thenApply { @@ -105,22 +120,44 @@ class ServerService( } - override fun updateServer(request: ServerUpdateRequest, responseObserver: StreamObserver) { + override fun updateServer(request: ServerUpdateRequest, responseObserver: StreamObserver) { val deleted = request.deleted val server = Server.fromDefinition(request.server) if (!deleted) { - serverRepository.save(server) - responseObserver.onNext(ApiResponse("success").toDefinition()) + try { + serverRepository.save(server) + }catch (e: Exception) { + responseObserver.onError( + 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 { + 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}.") - responseObserver.onNext(ApiResponse("success").toDefinition()) + responseObserver.onNext(server.toDefinition()) responseObserver.onCompleted() }.exceptionally { - responseObserver.onNext(ApiResponse("error").toDefinition()) - responseObserver.onCompleted() + responseObserver.onError( + Status.INTERNAL + .withDescription("Could not delete server") + .withCause(it) + .asRuntimeException() + ) } } } @@ -128,7 +165,11 @@ class ServerService( override fun getServerById(request: ServerIdRequest, responseObserver: StreamObserver) { serverRepository.find(request.id).thenApply { server -> if (server == null) { - responseObserver.onError(IllegalArgumentException("No server was found matching this unique id.")) + responseObserver.onError( + Status.NOT_FOUND + .withDescription("No server was found matching this unique id") + .asRuntimeException() + ) return@thenApply } responseObserver.onNext(server.toDefinition()) @@ -166,12 +207,20 @@ class ServerService( override fun startServer(request: GroupNameRequest, responseObserver: StreamObserver) { hostRepository.find(serverRepository).thenApply { host -> if (host == null) { - responseObserver.onError(ServerHostException("No server host found, could not start server.")) + responseObserver.onError( + Status.NOT_FOUND + .withDescription("No server host found, could not start server") + .asRuntimeException() + ) return@thenApply } groupRepository.find(request.name).thenApply { group -> if (group == null) { - responseObserver.onError(IllegalArgumentException("No group was found matching the group name.")) + responseObserver.onError( + Status.NOT_FOUND + .withDescription("No group was found matching this name") + .asRuntimeException() + ) } else { startServer(host, group) } @@ -200,7 +249,7 @@ class ServerService( serverRepository.delete(server) numericalIdRepository.removeNumericalId(group.name, server.numericalId) channel.shutdown() - throw ServerHostException("Could not start server, aborting.") + throw it } } @@ -226,10 +275,12 @@ class ServerService( ) } - override fun stopServer(request: ServerIdRequest, responseObserver: StreamObserver) { + override fun stopServer(request: ServerIdRequest, responseObserver: StreamObserver) { serverRepository.find(request.id).thenApply { server -> if (server == null) { - throw IllegalArgumentException("No server was found matching this id.") + throw Status.NOT_FOUND + .withDescription("No server was found matching this id.") + .asRuntimeException() } stopServer(server.toDefinition()).thenApply { responseObserver.onNext(it) @@ -242,16 +293,16 @@ class ServerService( } } - private fun stopServer(server: ServerDefinition): CompletableFuture { + private fun stopServer(server: ServerDefinition): CompletableFuture { val host = hostRepository.findServerHostById(server.hostId) - ?: throw ServerHostException("No server host was found matching this server.") + ?: throw Status.NOT_FOUND + .withDescription("No server host was found matching this server.") + .asRuntimeException() val channel = host.createChannel() val stub = ServerHostServiceGrpc.newFutureStub(channel) .withCallCredentials(authCallCredentials) return stub.stopServer(server).toCompletable().thenApply { - if (it.status == "success") { - serverRepository.delete(Server.fromDefinition(server)) - } + serverRepository.delete(Server.fromDefinition(server)) channel.shutdown() return@thenApply it } @@ -259,15 +310,17 @@ class ServerService( override fun updateServerProperty( request: ServerUpdatePropertyRequest, - responseObserver: StreamObserver + responseObserver: StreamObserver ) { serverRepository.find(request.id).thenApply { server -> if (server == null) { - throw NullPointerException("Server with id ${request.id} does not exist.") + throw Status.NOT_FOUND + .withDescription("Server with id ${request.id} does not exist.") + .asRuntimeException() } server.properties[request.key] = request.value serverRepository.save(server) - responseObserver.onNext(ApiResponse("success").toDefinition()) + responseObserver.onNext(server.toDefinition()) responseObserver.onCompleted() }.exceptionally { responseObserver.onError(it) @@ -276,15 +329,17 @@ class ServerService( override fun updateServerState( request: ServerUpdateStateRequest, - responseObserver: StreamObserver + responseObserver: StreamObserver ) { serverRepository.find(request.id).thenApply { server -> if (server == null) { - throw NullPointerException("Server with id ${request.id} does not exist.") + throw Status.NOT_FOUND + .withDescription("Server with id ${request.id} does not exist.") + .asRuntimeException() } server.state = request.state serverRepository.save(server) - responseObserver.onNext(ApiResponse("success").toDefinition()) + responseObserver.onNext(server.toDefinition()) responseObserver.onCompleted() }.exceptionally { responseObserver.onError(it) diff --git a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/status/ApiResponse.kt b/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/status/ApiResponse.kt deleted file mode 100644 index 8cdd9e2..0000000 --- a/controller-shared/src/main/kotlin/app/simplecloud/controller/shared/status/ApiResponse.kt +++ /dev/null @@ -1,22 +0,0 @@ -package app.simplecloud.controller.shared.status - -import build.buf.gen.simplecloud.controller.v1.StatusResponse - -data class ApiResponse( - val status: String -) { - fun toDefinition(): StatusResponse { - return StatusResponse.newBuilder() - .setStatus(status) - .build() - } - - companion object { - @JvmStatic - fun fromDefinition(statusResponse: StatusResponse): ApiResponse { - return ApiResponse( - statusResponse.status - ) - } - } -} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 64a4dd1..4c121d6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ log4j = "2.20.0" protobuf = "3.25.2" grpc = "1.61.0" grpcKotlin = "1.4.1" -simpleCloudProtoSpecs = "1.4.1.1.20240421175249.675f866e4da4" +simpleCloudProtoSpecs = "1.4.1.1.20240502213813.9b1f676c235f" jooq = "3.19.3" configurate = "4.1.2" sqliteJdbc = "3.44.1.0" From 52ee5b269a7dd2678c4ebece539ee8fb7a076eb2 Mon Sep 17 00:00:00 2001 From: Dayeeet Date: Fri, 3 May 2024 14:54:29 +0200 Subject: [PATCH 91/94] refactor: update javadocs and readme --- .../controller/api/ControllerApi.kt | 9 +++ readme.md | 74 +------------------ 2 files changed, 13 insertions(+), 70 deletions(-) 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 d7fa2cf..d1a9f91 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 @@ -16,12 +16,21 @@ interface ControllerApi { companion object { + /** + * Creates a new [ControllerApi] instance + * @return the created [ControllerApi] + */ @JvmStatic fun create(): ControllerApi { val authSecret = System.getenv("CONTROLLER_SECRET") return create(authSecret) } + /** + * Creates a new [ControllerApi] instance + * @param authSecret the authentication key used by the Controller + * @return the created [ControllerApi] + */ @JvmStatic fun create(authSecret: String): ControllerApi { return ControllerApiImpl(authSecret) diff --git a/readme.md b/readme.md index 753042d..e7394f5 100644 --- a/readme.md +++ b/readme.md @@ -2,13 +2,13 @@ Process that (automatically) manages minecraft server deployments (across multiple root-servers). At least one [ServerHost](#serverhosts) is needed to actually start servers. -> You can take a look at the [controller structure](structure.png) to learn how exactly it works +> Please visit [our documentation](https://docs.simplecloud.app/controller) to learn how exactly it works ## Features - [x] Reconciler (auto-deploying for servers) - [x] [API](#api-usage) using [gRPC](https://grpc.io/) -- [x] Server [SQL](https://en.wikipedia.org/wiki/SQL)-Database (any dialect) +- [x] Server cache [SQL](https://en.wikipedia.org/wiki/SQL)-Database (any dialect) ## ServerHosts @@ -18,76 +18,10 @@ however, you can write your [own implementation](). You can have as many ServerH ## API usage +> If you are searching for documentation, please visit our [official documentation](https://docs.simplecloud.app/api) + The SimpleCloud v3 Controller provides API for both server groups and actual servers. The group API is used for [CRUD-Operations](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) of server groups, whereas the server API is used to manage running servers or starting new ones. -````kotlin -//Initializing a Controller connection -Controller.connect() - -//Getting the Server API -val serverApi = Controller.serverApi - -//Getting the Group API -val groupApi = Controller.groupApi -```` - -### Group API functions - -> Group update functionality is yet to be implemented - -````kotlin - /** - * @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 a status [ApiResponse] of the delete state. - */ -fun deleteGroup(name: String): CompletableFuture - -/** - * @param group the [Group] to create. - * @return a status [ApiResponse] of the creation state. - */ -fun createGroup(group: Group): CompletableFuture -```` - -### Server API functions - -````kotlin - /** - * @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 group The server group. - * @return a [CompletableFuture] with a [List] of [Server]s of that group. - */ -fun getServersByGroup(group: Group): 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): CompletableFuture - -/** - * @param id the id of the server. - * @return a [CompletableFuture] with a [ApiResponse]. - */ -fun stopServer(id: String): CompletableFuture -```` From 4de9772f50020bd5f28367a6b77670f9462a2eb7 Mon Sep 17 00:00:00 2001 From: Philipp Date: Sat, 4 May 2024 14:22:32 +0200 Subject: [PATCH 92/94] update: proto specs --- .../kotlin/app/simplecloud/controller/shared/group/Group.kt | 3 +++ gradle/libs.versions.toml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) 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 f2e25fd..711880e 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 @@ -14,6 +14,7 @@ data class Group( val minOnlineCount: Long = 0, val maxOnlineCount: Long = 0, val maxPlayers: Long = 0, + val newServerPlayerRatio: Long = 100, val properties: Map = mapOf() ) { @@ -27,6 +28,7 @@ data class Group( .setMinimumOnlineCount(minOnlineCount) .setMaximumOnlineCount(maxOnlineCount) .setMaxPlayers(maxPlayers) + .setNewServerPlayerRatio(newServerPlayerRatio) .putAllProperties(properties) .build() } @@ -43,6 +45,7 @@ data class Group( groupDefinition.minimumOnlineCount, groupDefinition.maximumOnlineCount, groupDefinition.maxPlayers, + groupDefinition.newServerPlayerRatio, groupDefinition.propertiesMap ) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 64a4dd1..c6e6e36 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ log4j = "2.20.0" protobuf = "3.25.2" grpc = "1.61.0" grpcKotlin = "1.4.1" -simpleCloudProtoSpecs = "1.4.1.1.20240421175249.675f866e4da4" +simpleCloudProtoSpecs = "1.4.1.1.20240504121939.09af2f3cc691" jooq = "3.19.3" configurate = "4.1.2" sqliteJdbc = "3.44.1.0" From 13c74b8db5137e2c5f8cfccae745741181d050b2 Mon Sep 17 00:00:00 2001 From: Frederick Baier Date: Sat, 4 May 2024 15:11:53 +0200 Subject: [PATCH 93/94] feat: adjust reconciler to start new servers based on player ratio --- .../controller/runtime/reconciler/GroupReconciler.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 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 1c7cd56..466b1b0 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 @@ -35,10 +35,18 @@ class GroupReconciler( private fun calculateAvailableServerCount(): Int { return servers.count { server -> - server.state == ServerState.AVAILABLE + hasAvailableState(server) && isOnlineCountBelowPlayerRatio(server) + } + } + + private fun isOnlineCountBelowPlayerRatio(server: Server): Boolean { + return (server.playerCount / server.maxPlayers) * 100 < this.group.newServerPlayerRatio + } + + private fun hasAvailableState(server: Server): Boolean { + return server.state == ServerState.AVAILABLE || server.state == ServerState.STARTING || server.state == ServerState.PREPARING - } } private fun cleanupServers() { From 2f7a6b28600a8f77dfd339daf5267db814439f3a Mon Sep 17 00:00:00 2001 From: Frederick Baier Date: Sat, 4 May 2024 15:27:19 +0200 Subject: [PATCH 94/94] feat: make new server player ratio optional --- .../controller/runtime/reconciler/GroupReconciler.kt | 7 +++++-- .../app/simplecloud/controller/shared/group/Group.kt | 2 +- 2 files changed, 6 insertions(+), 3 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 466b1b0..26de957 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 @@ -40,13 +40,16 @@ class GroupReconciler( } private fun isOnlineCountBelowPlayerRatio(server: Server): Boolean { + if (this.group.newServerPlayerRatio <= 0) { + return true + } return (server.playerCount / server.maxPlayers) * 100 < this.group.newServerPlayerRatio } private fun hasAvailableState(server: Server): Boolean { return server.state == ServerState.AVAILABLE - || server.state == ServerState.STARTING - || server.state == ServerState.PREPARING + || server.state == ServerState.STARTING + || server.state == ServerState.PREPARING } private fun cleanupServers() { 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 711880e..014f507 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 @@ -14,7 +14,7 @@ data class Group( val minOnlineCount: Long = 0, val maxOnlineCount: Long = 0, val maxPlayers: Long = 0, - val newServerPlayerRatio: Long = 100, + val newServerPlayerRatio: Long = -1, val properties: Map = mapOf() ) {