From bb701ccf83eaf4fe47dba5291da526eaeb4d70b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Duarte?= Date: Tue, 23 Nov 2021 13:23:27 +0000 Subject: [PATCH] Clean up --- .gitignore | 5 +- .../MyApplicationActivationListener.kt | 4 +- .../services/ControllerStatusProvider.kt | 66 ----------------- .../services/ControllerStatusService.kt | 71 +++++++++++++++++++ .../backend/services/HeartbeatService.kt | 53 ++++++++------ .../backend/services/SupervisorInfoService.kt | 4 +- 6 files changed, 112 insertions(+), 91 deletions(-) delete mode 100644 components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/ide/jetbrains/backend/services/ControllerStatusProvider.kt create mode 100644 components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/ide/jetbrains/backend/services/ControllerStatusService.kt diff --git a/.gitignore b/.gitignore index 6e16af9f98aea8..931b2f761aa949 100644 --- a/.gitignore +++ b/.gitignore @@ -59,4 +59,7 @@ components/ws-scheduler/ws-scheduler components/ws-proxy/ws-proxy # Logs -components/server/*.log \ No newline at end of file +components/server/*.log + +# IntelliJ +.idea/ diff --git a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/ide/jetbrains/backend/listeners/MyApplicationActivationListener.kt b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/ide/jetbrains/backend/listeners/MyApplicationActivationListener.kt index 658278b0250312..6c65457e97f669 100644 --- a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/ide/jetbrains/backend/listeners/MyApplicationActivationListener.kt +++ b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/ide/jetbrains/backend/listeners/MyApplicationActivationListener.kt @@ -5,9 +5,9 @@ package io.gitpod.ide.jetbrains.backend.listeners import com.intellij.openapi.application.ApplicationActivationListener -import io.gitpod.ide.jetbrains.backend.services.HeartbeatService -import com.intellij.openapi.wm.IdeFrame import com.intellij.openapi.components.service +import com.intellij.openapi.wm.IdeFrame +import io.gitpod.ide.jetbrains.backend.services.HeartbeatService class MyApplicationActivationListener : ApplicationActivationListener { override fun applicationActivated(ideFrame: IdeFrame) { diff --git a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/ide/jetbrains/backend/services/ControllerStatusProvider.kt b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/ide/jetbrains/backend/services/ControllerStatusProvider.kt deleted file mode 100644 index 4f1a0aa46cd982..00000000000000 --- a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/ide/jetbrains/backend/services/ControllerStatusProvider.kt +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) 2021 Gitpod GmbH. All rights reserved. -// Licensed under the GNU Affero General Public License (AGPL). -// See License-AGPL.txt in the project root for license information. - -package io.gitpod.ide.jetbrains.backend.services - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties -import com.intellij.openapi.diagnostic.logger -import io.ktor.client.HttpClient -import io.ktor.client.features.HttpTimeout -import io.ktor.client.features.json.JsonFeature -import io.ktor.client.request.get -import io.ktor.client.features.json.JacksonSerializer - -class ControllerStatusProvider { - private val logger = logger() - - private val client: HttpClient = HttpClient() { - install(HttpTimeout) { - @Suppress("MagicNumber") - requestTimeoutMillis = 2000 - } - install(JsonFeature) { - serializer = JacksonSerializer() - } - } - private val cwmToken = System.getenv("CWM_HOST_STATUS_OVER_HTTP_TOKEN") - - suspend fun fetch(): ControllerStatus { - @Suppress("TooGenericExceptionCaught") // Unsure what exceptions Ktor might throw - val response: Response = try { - client.get("http://localhost:$PORT/codeWithMe/unattendedHostStatus?token=$cwmToken") - } catch (e: Exception) { - logger.error("Error fetching host status: $e") - return ControllerStatus(false, 0) - } - - if (response.projects.isEmpty()) { - logger.error("response.projects is empty") - return ControllerStatus(false, 0) - } - - return ControllerStatus( - response.projects[0].controllerConnected, - response.projects[0].secondsSinceLastControllerActivity - ) - } - - companion object { - private const val PORT = 63342 - - data class ControllerStatus(val connected: Boolean, val secondsSinceLastActivity: Int) - - @JsonIgnoreProperties(ignoreUnknown = true) - private data class Response( - val appPid: Int, - val projects: List - ) { - @JsonIgnoreProperties(ignoreUnknown = true) - data class Project( - val controllerConnected: Boolean, - val secondsSinceLastControllerActivity: Int - ) - } - } -} diff --git a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/ide/jetbrains/backend/services/ControllerStatusService.kt b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/ide/jetbrains/backend/services/ControllerStatusService.kt new file mode 100644 index 00000000000000..ae99d3c43abac3 --- /dev/null +++ b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/ide/jetbrains/backend/services/ControllerStatusService.kt @@ -0,0 +1,71 @@ +// Copyright (c) 2021 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. + +package io.gitpod.ide.jetbrains.backend.services + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.intellij.openapi.diagnostic.logger +import io.gitpod.ide.jetbrains.backend.utils.Retrier.retry +import io.ktor.client.HttpClient +import io.ktor.client.features.HttpTimeout +import io.ktor.client.features.json.JacksonSerializer +import io.ktor.client.features.json.JsonFeature +import io.ktor.client.request.get +import java.io.IOException + +object ControllerStatusService { + private val logger = logger() + + private const val PORT = 63342 + private val cwmToken = System.getenv("CWM_HOST_STATUS_OVER_HTTP_TOKEN") + + private val client: HttpClient by lazy { + HttpClient { + install(HttpTimeout) { + @Suppress("MagicNumber") + requestTimeoutMillis = 2000 + } + install(JsonFeature) { + serializer = JacksonSerializer() + } + } + } + + data class ControllerStatus(val connected: Boolean, val secondsSinceLastActivity: Int) + + /** + * @throws IOException + */ + suspend fun fetch(): ControllerStatus = + @Suppress("MagicNumber") + retry(3, logger) { + @Suppress("TooGenericExceptionCaught") // Unsure what exceptions Ktor might throw + val response: Response = try { + client.get("http://localhost:$PORT/codeWithMe/unattendedHostStatus?token=$cwmToken") + } catch (e: Exception) { + throw IOException("Failed to retrieve controller status.", e) + } + + if (response.projects.isEmpty()) { + throw IOException("Failed to fetch controller status as project list is empty.") + } + + ControllerStatus( + response.projects[0].controllerConnected, + response.projects[0].secondsSinceLastControllerActivity + ) + } + + @JsonIgnoreProperties(ignoreUnknown = true) + private data class Response( + val appPid: Int, + val projects: List + ) { + @JsonIgnoreProperties(ignoreUnknown = true) + data class Project( + val controllerConnected: Boolean, + val secondsSinceLastControllerActivity: Int + ) + } +} diff --git a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/ide/jetbrains/backend/services/HeartbeatService.kt b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/ide/jetbrains/backend/services/HeartbeatService.kt index a387a0affcf6e4..5a0aa85307775b 100644 --- a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/ide/jetbrains/backend/services/HeartbeatService.kt +++ b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/ide/jetbrains/backend/services/HeartbeatService.kt @@ -9,27 +9,26 @@ import com.intellij.openapi.components.Service import com.intellij.openapi.diagnostic.logger import io.gitpod.gitpodprotocol.api.ConnectionHelper import io.gitpod.gitpodprotocol.api.entities.SendHeartBeatOptions -import io.gitpod.ide.jetbrains.backend.services.ControllerStatusProvider.Companion.ControllerStatus +import io.gitpod.ide.jetbrains.backend.services.ControllerStatusService.ControllerStatus import io.gitpod.ide.jetbrains.backend.utils.Retrier.retry import kotlinx.coroutines.delay import kotlinx.coroutines.future.await import kotlinx.coroutines.runBlocking +import java.io.IOException +import java.util.concurrent.CompletableFuture import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicReference import kotlin.concurrent.thread import kotlin.random.Random.Default.nextInt -import java.util.concurrent.CompletableFuture @Service -class HeartbeatService() : Disposable { +class HeartbeatService : Disposable { private val logger = logger() - private val fetchInfo: suspend () -> SupervisorInfoService.Info = { SupervisorInfoService.fetch() } - private val controllerStatusProvider = ControllerStatusProvider() @Suppress("MagicNumber") private val intervalInSeconds = 30 - private val client = AtomicReference() + private val heartbeatClient = AtomicReference() private val status = AtomicReference( ControllerStatus( connected = false, @@ -54,15 +53,27 @@ class HeartbeatService() : Disposable { private suspend fun checkActivity(maxIntervalInSeconds: Int) { logger.info("Checking activity") - val status = controllerStatusProvider.fetch() + val status = try { + ControllerStatusService.fetch() + } catch (e: IOException) { + logger.error(e.message, e) + return@checkActivity + } val previousStatus = this.status.getAndSet(status) - if (status.connected != previousStatus.connected) { - return sendHeartbeat(wasClosed = !status.connected) + val wasClosed: Boolean? = when { + status.connected != previousStatus.connected -> !status.connected + status.connected && status.secondsSinceLastActivity <= maxIntervalInSeconds -> false + else -> null } - if (status.connected && status.secondsSinceLastActivity <= maxIntervalInSeconds) { - return sendHeartbeat(wasClosed = false) + if (wasClosed != null) { + @Suppress("TooGenericExceptionCaught") + return try { + sendHeartbeat(wasClosed) + } catch (e: Exception) { + logger.error("Failed to send heartbeat with wasClosed=$wasClosed", e) + } } } @@ -74,18 +85,18 @@ class HeartbeatService() : Disposable { @Synchronized private suspend fun sendHeartbeat(wasClosed: Boolean = false) { retry(2, logger) { - if (client.get() == null) { - client.set(createHeartbeatClient()) + if (heartbeatClient.get() == null) { + heartbeatClient.set(createHeartbeatClient()) } @Suppress("TooGenericExceptionCaught") // Unsure what exceptions might be thrown try { - client.get()!!(wasClosed).await() + heartbeatClient.get()!!(wasClosed).await() logger.info("Heartbeat sent with wasClosed=$wasClosed") } catch (e: Exception) { // If connection fails for some reason, // remove the reference to the existing server. - client.set(null) + heartbeatClient.set(null) throw e } } @@ -98,15 +109,17 @@ class HeartbeatService() : Disposable { */ private suspend fun createHeartbeatClient(): HeartbeatClient { logger.info("Creating HeartbeatClient") - val info = fetchInfo() + val supervisorInfo = SupervisorInfoService.fetch() val server = ConnectionHelper().connect( - "wss://${info.host.split("//").last()}/api/v1", - info.workspaceUrl, - info.authToken + "wss://${supervisorInfo.host.split("//").last()}/api/v1", + supervisorInfo.workspaceUrl, + supervisorInfo.authToken ).server() - return { wasClosed: Boolean -> server.sendHeartBeat(SendHeartBeatOptions(info.instanceId, wasClosed)) } + return { wasClosed: Boolean -> + server.sendHeartBeat(SendHeartBeatOptions(supervisorInfo.instanceId, wasClosed)) + } } override fun dispose() = closed.set(true) diff --git a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/ide/jetbrains/backend/services/SupervisorInfoService.kt b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/ide/jetbrains/backend/services/SupervisorInfoService.kt index 482a82adfdd9c8..414b304354949f 100644 --- a/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/ide/jetbrains/backend/services/SupervisorInfoService.kt +++ b/components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/ide/jetbrains/backend/services/SupervisorInfoService.kt @@ -7,10 +7,10 @@ package io.gitpod.ide.jetbrains.backend.services import com.intellij.openapi.diagnostic.logger import io.gitpod.ide.jetbrains.backend.utils.Retrier.retry import io.gitpod.supervisor.api.Info -import io.gitpod.supervisor.api.Token.GetTokenRequest import io.gitpod.supervisor.api.Info.WorkspaceInfoRequest -import io.gitpod.supervisor.api.TokenServiceGrpc import io.gitpod.supervisor.api.InfoServiceGrpc +import io.gitpod.supervisor.api.Token.GetTokenRequest +import io.gitpod.supervisor.api.TokenServiceGrpc import io.grpc.ManagedChannelBuilder import kotlinx.coroutines.guava.asDeferred