Skip to content

Commit 3a3a6b1

Browse files
authored
Merge pull request #2411 from digma-ai/prevent-multiple-local-engine-install
prevent parallel and multiple engine installation and start Closes #2410
2 parents 5791f19 + 26ba229 commit 3a3a6b1

File tree

12 files changed

+342
-142
lines changed

12 files changed

+342
-142
lines changed

ide-common/src/main/kotlin/org/digma/intellij/plugin/docker/DigmaInstallationStatus.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@ enum class DigmaInstallationType {
99
data class DigmaInstallationStatus(
1010
val connection: ConnectionStatus,
1111
val runningDigmaInstances: List<DigmaInstallationType>,
12-
)
12+
) {
13+
14+
companion object {
15+
val NOT_RUNNING = DigmaInstallationStatus(ConnectionStatus(null, false), listOf())
16+
}
17+
18+
}
1319

1420
enum class ConnectionType { local, remote }
1521

ide-common/src/main/kotlin/org/digma/intellij/plugin/docker/DockerService.kt

Lines changed: 65 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,31 @@ import com.intellij.openapi.project.Project
1212
import com.intellij.openapi.ui.MessageConstants
1313
import com.intellij.openapi.ui.Messages
1414
import com.intellij.openapi.util.SystemInfo
15-
import org.digma.intellij.plugin.analytics.BackendConnectionMonitor
1615
import org.digma.intellij.plugin.common.Backgroundable
1716
import org.digma.intellij.plugin.errorreporting.ErrorReporter
1817
import org.digma.intellij.plugin.log.Log
1918
import org.digma.intellij.plugin.persistence.PersistenceService
2019
import org.digma.intellij.plugin.posthog.ActivityMonitor
20+
import org.jetbrains.annotations.ApiStatus.Internal
2121
import java.util.function.Consumer
2222
import java.util.function.Supplier
2323

24-
24+
/**
25+
* this service is a front end to docker, it can install,upgrade,stop,start,remove and also provide
26+
* information about docker installation and running instances.
27+
* this service is a kind of CRUD service, the core operation of install,upgrade,stop,start,remove
28+
* should never be called directly because it does not manage single access and locking, meaning if install is called
29+
* multiple times concurrently it may cause failures. please never call these operations directly, always call
30+
* LocalInstallationFacade for these CRUD operations,LocalInstallationFacade manages single access and logical flows.
31+
* information services may be called directly
32+
*/
2533
@Service(Service.Level.APP)
2634
class DockerService {
2735

2836
private val logger = Logger.getInstance(this::class.java)
2937

3038
private val engine = Engine()
3139
private val downloader = Downloader()
32-
private var installationInProgress: Boolean = false
3340

3441
companion object {
3542

@@ -44,58 +51,24 @@ class DockerService {
4451

4552
init {
4653

47-
if (isEngineInstalled()) {
54+
if (PersistenceService.getInstance().isLocalEngineInstalled()) {
4855
//this will happen on IDE start,
4956
// DockerService is an application service so Downloader will be singleton per application
5057
downloader.downloadComposeFile()
5158
}
5259
}
5360

5461

55-
fun getActualRunningEngine(project: Project): DigmaInstallationStatus {
56-
return discoverActualRunningEngine(project)
57-
}
58-
59-
60-
fun getCurrentDigmaInstallationStatusOnConnectionLost(): DigmaInstallationStatus {
61-
return discoverActualRunningEngine(false)
62-
}
63-
64-
65-
fun getCurrentDigmaInstallationStatusOnConnectionGained(): DigmaInstallationStatus {
66-
return discoverActualRunningEngine(true)
67-
}
68-
69-
70-
fun isInstallationInProgress(): Boolean {
71-
return installationInProgress
72-
}
7362

7463
fun isDockerInstalled(): Boolean {
75-
return isInstalled(DOCKER_COMMAND) || isInstalled(DOCKER_COMPOSE_COMMAND)
64+
return isInstalled(DOCKER_COMMAND)
7665
}
7766

78-
79-
fun isEngineInstalled(): Boolean {
80-
return PersistenceService.getInstance().isLocalEngineInstalled()
81-
}
82-
83-
84-
fun isEngineRunning(project: Project): Boolean {
85-
return isEngineInstalled() && BackendConnectionMonitor.getInstance(project).isConnectionOk()
67+
fun isDockerComposeInstalled(): Boolean {
68+
return isInstalled(DOCKER_COMPOSE_COMMAND)
8669
}
8770

8871

89-
private fun isDockerDaemonDownExitValue(exitValue: String): Boolean {
90-
return exitValue.contains("Cannot connect to the Docker daemon", true) ||//mac, linux
91-
exitValue.contains("docker daemon is not running", true) || //win
92-
//this is an error on windows with docker desktop that will be solved by starting docker desktop
93-
(exitValue.contains("error during connect", true) && exitValue.contains("The system cannot find the file specified", true)) || //win
94-
exitValue.contains("Error while fetching server API version", true)
95-
}
96-
97-
98-
9972
fun collectDigmaContainerLog(): String {
10073
try {
10174

@@ -113,7 +86,7 @@ class DockerService {
11386
val getLogCommand = GeneralCommandLine(dockerCmd, "logs", "--tail", "1000", "$containerId")
11487
.withParentEnvironmentType(GeneralCommandLine.ParentEnvironmentType.CONSOLE)
11588

116-
val processOutput: ProcessOutput = ExecUtil.execAndGetOutput(getLogCommand)
89+
val processOutput: ProcessOutput = ExecUtil.execAndGetOutput(getLogCommand)
11790
return processOutput.toString()
11891

11992
} catch (ex: Exception) {
@@ -124,14 +97,8 @@ class DockerService {
12497
}
12598

12699

100+
@Internal
127101
fun installEngine(project: Project, resultTask: Consumer<String>) {
128-
installationInProgress = true
129-
130-
PersistenceService.getInstance().setLocalEngineInstalled(true)
131-
132-
val onCompleted = Consumer { _: String ->
133-
installationInProgress = false
134-
}.andThen(resultTask)
135102

136103
ActivityMonitor.getInstance(project).registerDigmaEngineEventStart("installEngine", mapOf())
137104

@@ -155,65 +122,85 @@ class DockerService {
155122
}
156123
}
157124

158-
notifyResult(exitValue, onCompleted)
125+
notifyResult(exitValue, resultTask)
159126
} else {
160127
ActivityMonitor.getInstance(project).registerDigmaEngineEventError("installEngine", "could not find docker compose command")
161128
Log.log(logger::warn, "could not find docker compose command")
162-
notifyResult(NO_DOCKER_COMPOSE_COMMAND, onCompleted)
129+
notifyResult(NO_DOCKER_COMPOSE_COMMAND, resultTask)
163130
}
164131
} else {
165132
ActivityMonitor.getInstance(project).registerDigmaEngineEventError("installEngine", "Failed to download compose file")
166133
Log.log(logger::warn, "Failed to download compose file")
167-
notifyResult("Failed to download compose file", onCompleted)
134+
notifyResult("Failed to download compose file", resultTask)
168135
}
169136

170137
} catch (e: Throwable) {
171138
ErrorReporter.getInstance().reportError(project, "DockerService.installEngine", e)
172139
ActivityMonitor.getInstance(project).registerDigmaEngineEventError("installEngine", "Failed in installEngine $e")
173140
Log.warnWithException(logger, e, "Failed install docker engine {}", e)
174-
notifyResult("Failed to install docker engine: $e", onCompleted)
141+
notifyResult("Failed to install docker engine: $e", resultTask)
175142
} finally {
176143
ActivityMonitor.getInstance(project).registerDigmaEngineEventEnd("installEngine", mapOf())
177144
}
178145
}
179146
}
180147

181-
182-
fun upgradeEngine(project: Project) {
148+
@Internal
149+
fun upgradeEngine(project: Project, resultTask: Consumer<String>) {
183150

184151
ActivityMonitor.getInstance(project).registerDigmaEngineEventStart("upgradeEngine", mapOf())
185152

186153
Backgroundable.runInNewBackgroundThread(project, "upgrading digma engine") {
187154

155+
//stop the engine before upgrade using the current compose file, before downloading a new file.
156+
//the engine should be up otherwise the upgrade would not be triggered.
157+
//ignore errors, we'll try to upgrade anyway after that.
158+
try {
159+
val dockerComposeCmd = getDockerComposeCommand()
160+
dockerComposeCmd?.let {
161+
engine.down(project, downloader.composeFile, it, false)
162+
}
163+
} catch (e: Throwable) {
164+
ErrorReporter.getInstance().reportError(project, "DockerService.upgradeEngine", e)
165+
Log.warnWithException(logger, e, "Failed to stop docker engine {}", e)
166+
}
167+
168+
188169
try {
189170
if (downloader.downloadComposeFile(true)) {
190171
val dockerComposeCmd = getDockerComposeCommand()
191172

192173
if (dockerComposeCmd != null) {
193174
val exitValue = engine.up(project, downloader.composeFile, dockerComposeCmd)
175+
//in upgrade there is no need to check if daemon is down because upgrade will not be triggered if the engine is not running.
194176
if (exitValue != "0") {
195177
ActivityMonitor.getInstance(project).registerDigmaEngineEventError("upgradeEngine", exitValue)
196178
Log.log(logger::warn, "error upgrading engine {}", exitValue)
197179
}
180+
181+
notifyResult(exitValue, resultTask)
198182
} else {
199183
ActivityMonitor.getInstance(project).registerDigmaEngineEventError("upgradeEngine", "could not find docker compose command")
200184
Log.log(logger::warn, "could not find docker compose command")
185+
notifyResult(NO_DOCKER_COMPOSE_COMMAND, resultTask)
201186
}
202187
} else {
203188
ActivityMonitor.getInstance(project).registerDigmaEngineEventError("upgradeEngine", "Failed to download compose file")
204189
Log.log(logger::warn, "Failed to download compose file")
190+
notifyResult("Failed to download compose file", resultTask)
205191
}
206192
} catch (e: Exception) {
207193
ErrorReporter.getInstance().reportError(project, "DockerService.upgradeEngine", e)
208194
ActivityMonitor.getInstance(project).registerDigmaEngineEventError("upgradeEngine", "Failed in upgradeEngine $e")
209195
Log.warnWithException(logger, e, "Failed install docker engine {}", e)
196+
notifyResult("Failed to upgrade docker engine: $e", resultTask)
210197
} finally {
211198
ActivityMonitor.getInstance(project).registerDigmaEngineEventEnd("upgradeEngine", mapOf())
212199
}
213200
}
214201
}
215202

216-
203+
@Internal
217204
fun stopEngine(project: Project, resultTask: Consumer<String>) {
218205

219206
ActivityMonitor.getInstance(project).registerDigmaEngineEventStart("stopEngine", mapOf())
@@ -254,6 +241,7 @@ class DockerService {
254241
}
255242
}
256243

244+
@Internal
257245
fun startEngine(project: Project, resultTask: Consumer<String>) {
258246

259247
ActivityMonitor.getInstance(project).registerDigmaEngineEventStart("startEngine", mapOf())
@@ -265,15 +253,19 @@ class DockerService {
265253
val dockerComposeCmd = getDockerComposeCommand()
266254

267255
if (dockerComposeCmd != null) {
268-
//we try to detect errors when running the docker command. engine.start executes docker-compose up,
269-
// if executing docker-compose up while containers exist it will print many errors that are ok but
270-
// that interferes with our attempt to detect errors.
271-
//so running down and then up solves it
272-
engine.down(project, downloader.composeFile, dockerComposeCmd, false)
256+
273257
try {
258+
//we try to detect errors when running the docker command. engine.start executes docker-compose up,
259+
// if executing docker-compose up while containers exist it will print many errors that are ok but
260+
// that interferes with our attempt to detect errors.
261+
//so running down and then up solves it.
262+
//in any case it's better to stop before start because we don't know the state of the engine, maybe its
263+
// partially up and start will fail.
264+
engine.down(project, downloader.composeFile, dockerComposeCmd, false)
274265
Thread.sleep(2000)
275266
} catch (e: Exception) {
276-
//ignore
267+
ErrorReporter.getInstance().reportError(project, "DockerService.startEngine", e)
268+
Log.warnWithException(logger, e, "Failed to stop docker engine {}", e)
277269
}
278270

279271
var exitValue = engine.start(project, downloader.composeFile, dockerComposeCmd)
@@ -310,7 +302,7 @@ class DockerService {
310302

311303
}
312304

313-
305+
@Internal
314306
fun removeEngine(project: Project, resultTask: Consumer<String>) {
315307

316308
ActivityMonitor.getInstance(project).registerDigmaEngineEventStart("removeEngine", mapOf())
@@ -363,6 +355,15 @@ class DockerService {
363355
}
364356

365357

358+
private fun isDockerDaemonDownExitValue(exitValue: String): Boolean {
359+
return exitValue.contains("Cannot connect to the Docker daemon", true) ||//mac, linux
360+
exitValue.contains("docker daemon is not running", true) || //win
361+
//this is an error on windows with docker desktop that will be solved by starting docker desktop
362+
(exitValue.contains("error during connect", true) && exitValue.contains("The system cannot find the file specified", true)) || //win
363+
exitValue.contains("Error while fetching server API version", true)
364+
}
365+
366+
366367
private fun doRetryFlowWhenDockerDaemonIsDown(project: Project, prevExitValue: String, runCommand: Supplier<String>): String {
367368

368369
val eventName = "docker-daemon-is-down"
@@ -413,6 +414,7 @@ class DockerService {
413414
return exitValue
414415
}
415416

417+
416418
private fun tryStartDockerDaemon(project: Project) {
417419

418420
Log.log(logger::info, "Trying to start docker daemon")
@@ -468,5 +470,4 @@ class DockerService {
468470
}
469471

470472

471-
472473
}

0 commit comments

Comments
 (0)