Skip to content

Commit baf5999

Browse files
committed
feat: make envoy start port configurable
1 parent 864aa58 commit baf5999

File tree

8 files changed

+127
-14
lines changed

8 files changed

+127
-14
lines changed

controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/ControllerRuntime.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class ControllerRuntime(
4343
private val serverRepository = ServerRepository(database, numericalIdRepository)
4444
private val hostRepository = ServerHostRepository()
4545
private val serverHostAttacher = ServerHostAttacher(hostRepository, serverRepository)
46-
private val dropletRepository = DropletRepository(authCallCredentials, serverHostAttacher, hostRepository)
46+
private val dropletRepository = DropletRepository(authCallCredentials, serverHostAttacher, controllerStartCommand.envoyStartPort, hostRepository)
4747
private val pubSubService = PubSubService()
4848
private val controlPlaneServer = ControlPlaneServer(controllerStartCommand, dropletRepository)
4949
private val authServer = OAuthServer(controllerStartCommand, database)

controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/droplet/ControllerDropletService.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package app.simplecloud.controller.runtime.droplet
22

3-
import app.simplecloud.controller.runtime.MetricsEventNames
43
import app.simplecloud.droplet.api.droplet.Droplet
54
import build.buf.gen.simplecloud.controller.v1.*
65
import io.grpc.Status

controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/droplet/DropletRepository.kt

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package app.simplecloud.controller.runtime.droplet
22

33
import app.simplecloud.controller.runtime.Repository
44
import app.simplecloud.controller.runtime.envoy.DropletCache
5+
import app.simplecloud.controller.runtime.hack.PortProcessHandle
56
import app.simplecloud.controller.runtime.host.ServerHostRepository
67
import app.simplecloud.controller.runtime.server.ServerHostAttacher
78
import app.simplecloud.controller.shared.host.ServerHost
@@ -15,11 +16,12 @@ import kotlinx.coroutines.launch
1516
class DropletRepository(
1617
private val authCallCredentials: AuthCallCredentials,
1718
private val serverHostAttacher: ServerHostAttacher,
19+
private val envoyStartPort: Int,
1820
private val serverHostRepository: ServerHostRepository,
1921
) : Repository<Droplet, String> {
2022

2123
private val currentDroplets = mutableListOf<Droplet>()
22-
private val dropletCache = DropletCache(this)
24+
private val dropletCache = DropletCache()
2325

2426
override suspend fun getAll(): List<Droplet> {
2527
return currentDroplets
@@ -33,8 +35,9 @@ class DropletRepository(
3335
return currentDroplets.firstOrNull { it.type == type && it.id == identifier }
3436
}
3537

38+
@Synchronized
3639
override fun save(element: Droplet) {
37-
val updated = managePortRange(element)
40+
val updated = managePortRange(element.copy(envoyPort = envoyStartPort))
3841
val droplet = find(element.type, element.id)
3942
if (droplet != null) {
4043
currentDroplets[currentDroplets.indexOf(droplet)] = updated
@@ -45,10 +48,11 @@ class DropletRepository(
4548
postUpdate(updated)
4649
}
4750

51+
@Synchronized
4852
private fun postUpdate(droplet: Droplet) {
53+
dropletCache.update(currentDroplets)
54+
if (droplet.type != "serverhost") return
4955
CoroutineScope(Dispatchers.IO).launch {
50-
dropletCache.update()
51-
if (droplet.type != "serverhost") return@launch
5256
serverHostAttacher.attach(
5357
ServerHost(
5458
droplet.id, droplet.host, droplet.port, ServerHostServiceGrpcKt.ServerHostServiceCoroutineStub(
@@ -63,14 +67,14 @@ class DropletRepository(
6367
}
6468

6569
private fun managePortRange(element: Droplet): Droplet {
66-
if (!currentDroplets.any { it.envoyPort == element.envoyPort }) return element
70+
if (!currentDroplets.any { it.envoyPort == element.envoyPort } && PortProcessHandle.of(element.envoyPort).isEmpty) return element
6771
return managePortRange(element.copy(envoyPort = element.envoyPort + 1))
6872
}
6973

7074
override suspend fun delete(element: Droplet): Boolean {
7175
val found = find(element.type, element.id) ?: return false
7276
if (!currentDroplets.remove(found)) return false
73-
dropletCache.update()
77+
dropletCache.update(currentDroplets)
7478
if (element.type == "serverhost") {
7579
val host = serverHostRepository.findServerHostById(element.id) ?: return true
7680
serverHostRepository.delete(host)

controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/envoy/ControlPlaneServer.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ class ControlPlaneServer(private val args: ControllerStartCommand, private val d
4343
envoyPort = 8080,
4444
)
4545
)
46+
dropletRepository.save(
47+
Droplet(
48+
type = "auth",
49+
id = "internal-auth",
50+
host = args.grpcHost,
51+
port = args.authorizationPort,
52+
envoyPort = 8080
53+
)
54+
)
4655
}
4756

4857
private fun register(builder: ServerBuilder<*>) {

controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/envoy/DropletCache.kt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,21 @@ import java.util.*
3131
/**
3232
* This class handles the remapping of the [DropletRepository] to a [SimpleCache] of [Snapshot]s, which are used by the envoy ADS service.
3333
*/
34-
class DropletCache(private val dropletRepository: DropletRepository) {
34+
class DropletCache {
3535
private val cache = SimpleCache(SimpleCloudNodeGroup())
3636
private val logger = LogManager.getLogger(DropletCache::class.java)
3737

3838
//Create a new Snapshot by the droplet repository's data
39-
suspend fun update() {
39+
fun update(droplets: List<Droplet>) {
4040
logger.info("Detected new droplets in DropletRepository, adding to ADS...")
4141
val clusters = mutableListOf<Cluster>()
4242
val listeners = mutableListOf<Listener>()
4343
val clas = mutableListOf<ClusterLoadAssignment>()
44-
45-
dropletRepository.getAll().forEach {
44+
droplets.forEach {
4645
clusters.add(createCluster(it))
4746
listeners.add(createListener(it))
4847
clas.add(createCLA(it))
4948
}
50-
5149
cache.setSnapshot(
5250
SimpleCloudNodeGroup.GROUP,
5351
Snapshot.create(
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package app.simplecloud.controller.runtime.hack
2+
3+
enum class OS(val names: List<String>) {
4+
WINDOWS(listOf("windows")),
5+
LINUX(listOf("linux")),
6+
MAC(listOf("mac"));
7+
8+
companion object {
9+
fun get(): OS? {
10+
val name = System.getProperty("os.name").lowercase()
11+
entries.forEach {
12+
if (it.names.any { osName -> name.contains(osName) }) {
13+
return it
14+
}
15+
}
16+
return null
17+
}
18+
}
19+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package app.simplecloud.controller.runtime.hack
2+
3+
import app.simplecloud.controller.shared.server.Server
4+
import build.buf.gen.simplecloud.controller.v1.ServerDefinition
5+
import java.time.LocalDateTime
6+
import java.util.*
7+
import java.util.concurrent.ConcurrentHashMap
8+
import java.util.regex.Pattern
9+
10+
object PortProcessHandle {
11+
12+
private const val WINDOWS_PID_INDEX = 3
13+
private val windowsPattern = Pattern.compile("\\s*TCP\\s+\\S+:(\\d+)\\s+\\S+:(\\d+)\\s+\\S+\\s+(\\d+)")
14+
15+
private val preBindPorts = ConcurrentHashMap<Int, LocalDateTime>()
16+
17+
fun of(port: Int): Optional<ProcessHandle> {
18+
val os = OS.get() ?: return Optional.empty()
19+
val command = when (os) {
20+
OS.LINUX, OS.MAC -> arrayOf("sh", "-c", "lsof -i :$port | awk '{print \$2}'")
21+
OS.WINDOWS -> arrayOf("cmd", "/c", "netstat -ano | findstr $port")
22+
}
23+
24+
val process = Runtime.getRuntime().exec(command)
25+
26+
val bufferedReader = process.inputReader()
27+
val processId = bufferedReader.useLines { lines ->
28+
lines.firstNotNullOfOrNull { parseProcessIdOrNull(os, it) }
29+
}
30+
31+
if (processId == null) {
32+
return Optional.empty()
33+
}
34+
35+
return ProcessHandle.of(processId)
36+
}
37+
38+
private fun parseProcessIdOrNull(os: OS, line: String): Long? {
39+
return when (os) {
40+
OS.LINUX, OS.MAC -> line.toLongOrNull()
41+
OS.WINDOWS -> {
42+
val matcher = windowsPattern.matcher(line)
43+
if (!matcher.matches()) {
44+
return null
45+
}
46+
47+
return matcher.group(WINDOWS_PID_INDEX).toLongOrNull()
48+
}
49+
}
50+
}
51+
52+
@Synchronized
53+
fun findNextFreePort(startPort: Int, serverDefinition: ServerDefinition): Int {
54+
val server = Server.fromDefinition(serverDefinition)
55+
var port = startPort
56+
val time = LocalDateTime.now()
57+
while (isPortBound(port)) {
58+
port++
59+
}
60+
addPreBind(port, time, server.properties.getOrDefault("max-startup-seconds", "120").toLong())
61+
return port
62+
}
63+
64+
private fun addPreBind(port: Int, time: LocalDateTime, duration: Long) {
65+
preBindPorts[port] = time.plusSeconds(duration)
66+
}
67+
68+
fun isPortBound(port: Int): Boolean {
69+
return !of(port).isEmpty || LocalDateTime.now().isBefore(preBindPorts.getOrDefault(port, LocalDateTime.MIN))
70+
}
71+
72+
fun removePreBind(port: Int, forced: Boolean = false) {
73+
// Check if max-startup-seconds is reached and return if not
74+
if (!forced && LocalDateTime.now().isBefore(preBindPorts.getOrDefault(port, LocalDateTime.MAX))) {
75+
return
76+
}
77+
78+
preBindPorts.remove(port)
79+
}
80+
81+
}

controller-runtime/src/main/kotlin/app/simplecloud/controller/runtime/launcher/ControllerStartCommand.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,13 @@ class ControllerStartCommand(
4646
).int().default(5818)
4747

4848
val envoyDiscoveryPort: Int by option(
49-
help = "Authorization port (default: 5814)",
49+
help = "Envoy Discovery port (default: 5814)",
5050
envvar = "ENVOY_DISCOVERY_PORT"
5151
).int().default(5814)
5252

53+
val envoyStartPort: Int by option(help = "Envoy start port (default: 8080)", envvar = "ENVOY_START_PORT").int()
54+
.default(8080)
55+
5356
private val authSecretPath: Path by option(
5457
help = "Path to auth secret file (default: .auth.secret)",
5558
envvar = "AUTH_SECRET_PATH"

0 commit comments

Comments
 (0)