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