Skip to content

Commit b6fbedf

Browse files
authored
Merge pull request #9 from theSimpleCloud/feat/droplet-registry
feat/droplet-registry
2 parents 87b4f69 + 46944a0 commit b6fbedf

File tree

11 files changed

+429
-3
lines changed

11 files changed

+429
-3
lines changed

controller-runtime/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ dependencies {
1414
implementation(libs.clikt)
1515
implementation(libs.spring.crypto)
1616
implementation(libs.spotify.completablefutures)
17+
implementation(libs.envoy.controlplane)
1718
}
1819

1920
application {

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

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

33
import app.simplecloud.controller.runtime.database.DatabaseFactory
4+
import app.simplecloud.controller.runtime.droplet.ControllerDropletService
5+
import app.simplecloud.controller.runtime.droplet.DropletRepository
6+
import app.simplecloud.controller.runtime.envoy.ControlPlaneServer
47
import app.simplecloud.controller.runtime.group.GroupRepository
58
import app.simplecloud.controller.runtime.group.GroupService
69
import app.simplecloud.controller.runtime.host.ServerHostRepository
@@ -29,11 +32,13 @@ class ControllerRuntime(
2932
private val database = DatabaseFactory.createDatabase(controllerStartCommand.databaseUrl)
3033
private val authCallCredentials = AuthCallCredentials(controllerStartCommand.authSecret)
3134

35+
private val dropletRepository = DropletRepository()
3236
private val groupRepository = GroupRepository(controllerStartCommand.groupPath)
3337
private val numericalIdRepository = ServerNumericalIdRepository()
3438
private val serverRepository = ServerRepository(database, numericalIdRepository)
3539
private val hostRepository = ServerHostRepository()
3640
private val pubSubService = PubSubService()
41+
private val controlPlaneServer = ControlPlaneServer(controllerStartCommand, dropletRepository)
3742
private val authServer = OAuthServer(controllerStartCommand, database)
3843
private val reconciler = Reconciler(
3944
groupRepository,
@@ -50,6 +55,7 @@ class ControllerRuntime(
5055
logger.info("Starting controller")
5156
setupDatabase()
5257
startAuthServer()
58+
startControlPlaneServer()
5359
startPubSubGrpcServer()
5460
startGrpcServer()
5561
startReconciler()
@@ -80,6 +86,11 @@ class ControllerRuntime(
8086

8187
}
8288

89+
private fun startControlPlaneServer() {
90+
logger.info("Starting envoy control plane...")
91+
controlPlaneServer.start()
92+
}
93+
8394
private fun setupDatabase() {
8495
logger.info("Setting up database...")
8596
database.setup()
@@ -154,6 +165,7 @@ class ControllerRuntime(
154165
)
155166
)
156167
)
168+
.addService(ControllerDropletService(dropletRepository))
157169
.intercept(AuthSecretInterceptor(controllerStartCommand.grpcHost, controllerStartCommand.authorizationPort))
158170
.build()
159171
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package app.simplecloud.controller.runtime.droplet
2+
3+
import app.simplecloud.droplet.api.droplet.Droplet
4+
import build.buf.gen.simplecloud.controller.v1.*
5+
import io.grpc.Status
6+
import io.grpc.StatusException
7+
8+
class ControllerDropletService(private val dropletRepository: DropletRepository) :
9+
ControllerDropletServiceGrpcKt.ControllerDropletServiceCoroutineImplBase() {
10+
override suspend fun getDroplet(request: GetDropletRequest): GetDropletResponse {
11+
val droplet = dropletRepository.find(request.type, request.id)
12+
?: throw StatusException(Status.NOT_FOUND.withDescription("This Droplet does not exist"))
13+
return getDropletResponse { this.definition = droplet.toDefinition() }
14+
}
15+
16+
override suspend fun getAllDroplets(request: GetAllDropletsRequest): GetAllDropletsResponse {
17+
val allDroplets = dropletRepository.getAll()
18+
return getAllDropletsResponse {
19+
definition.addAll(allDroplets.map { it.toDefinition() })
20+
}
21+
}
22+
23+
override suspend fun getDropletsByType(request: GetDropletsByTypeRequest): GetDropletsByTypeResponse {
24+
val type = request.type
25+
val typedDroplets = dropletRepository.getAll().filter { it.type == type }
26+
return getDropletsByTypeResponse {
27+
definition.addAll(typedDroplets.map { it.toDefinition() })
28+
}
29+
}
30+
31+
override suspend fun registerDroplet(request: RegisterDropletRequest): RegisterDropletResponse {
32+
dropletRepository.find(request.definition.type, request.definition.id)
33+
?: throw StatusException(Status.NOT_FOUND.withDescription("This Droplet does not exist"))
34+
val droplet = Droplet.fromDefinition(request.definition)
35+
36+
try {
37+
dropletRepository.save(droplet)
38+
} catch (e: Exception) {
39+
throw StatusException(Status.INTERNAL.withDescription("Error whilst updating Droplet").withCause(e))
40+
}
41+
return registerDropletResponse { this.definition = droplet.toDefinition() }
42+
}
43+
44+
override suspend fun unregisterDroplet(request: UnregisterDropletRequest): UnregisterDropletResponse {
45+
val droplet = dropletRepository.find(request.id)
46+
?: throw StatusException(Status.NOT_FOUND.withDescription("This Droplet does not exist"))
47+
val deleted = dropletRepository.delete(droplet)
48+
if (!deleted) throw StatusException(Status.NOT_FOUND.withDescription("Could not delete this Droplet"))
49+
return unregisterDropletResponse { }
50+
}
51+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package app.simplecloud.controller.runtime.droplet
2+
3+
import app.simplecloud.controller.runtime.Repository
4+
import app.simplecloud.controller.runtime.envoy.DropletCache
5+
import app.simplecloud.droplet.api.droplet.Droplet
6+
import kotlinx.coroutines.CoroutineScope
7+
import kotlinx.coroutines.Dispatchers
8+
import kotlinx.coroutines.launch
9+
10+
class DropletRepository : Repository<Droplet, String> {
11+
12+
private val currentDroplets = mutableListOf<Droplet>()
13+
private val dropletCache = DropletCache(this)
14+
15+
override suspend fun getAll(): List<Droplet> {
16+
return currentDroplets
17+
}
18+
19+
override suspend fun find(identifier: String): Droplet? {
20+
return currentDroplets.firstOrNull { it.id == identifier }
21+
}
22+
23+
fun find(type: String, identifier: String): Droplet? {
24+
return currentDroplets.firstOrNull { it.type == type && it.id == identifier }
25+
}
26+
27+
override fun save(element: Droplet) {
28+
val updated = managePortRange(element)
29+
val droplet = find(element.type, element.id)
30+
if (droplet != null) {
31+
currentDroplets[currentDroplets.indexOf(droplet)] = updated
32+
return
33+
}
34+
currentDroplets.add(updated)
35+
CoroutineScope(Dispatchers.IO).launch {
36+
dropletCache.update()
37+
}
38+
}
39+
40+
private fun managePortRange(element: Droplet): Droplet {
41+
if (!currentDroplets.any { it.envoyPort == element.envoyPort }) return element
42+
return managePortRange(element.copy(envoyPort = element.envoyPort + 1))
43+
}
44+
45+
override suspend fun delete(element: Droplet): Boolean {
46+
val found = find(element.type, element.id) ?: return false
47+
if (!currentDroplets.remove(found)) return false
48+
dropletCache.update()
49+
return true
50+
}
51+
52+
fun getAsDropletCache(): DropletCache {
53+
return dropletCache
54+
}
55+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package app.simplecloud.controller.runtime.envoy
2+
3+
import app.simplecloud.controller.runtime.droplet.DropletRepository
4+
import app.simplecloud.controller.runtime.launcher.ControllerStartCommand
5+
import app.simplecloud.droplet.api.droplet.Droplet
6+
import io.envoyproxy.controlplane.server.V3DiscoveryServer
7+
import io.grpc.ServerBuilder
8+
import kotlinx.coroutines.CoroutineScope
9+
import kotlinx.coroutines.Dispatchers
10+
import kotlinx.coroutines.launch
11+
import org.apache.logging.log4j.LogManager
12+
13+
/**
14+
* @see <a href="https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol#xds-protocol-ads">ADS Documentation</a>
15+
*/
16+
class ControlPlaneServer(private val args: ControllerStartCommand, private val dropletRepository: DropletRepository) {
17+
private val server = V3DiscoveryServer(dropletRepository.getAsDropletCache().getCache())
18+
private val logger = LogManager.getLogger(ControlPlaneServer::class.java)
19+
20+
fun start() {
21+
val serverBuilder = ServerBuilder.forPort(args.envoyDiscoveryPort)
22+
register(serverBuilder)
23+
val server = serverBuilder.build()
24+
CoroutineScope(Dispatchers.IO).launch {
25+
try {
26+
server.start()
27+
server.awaitTermination()
28+
} catch (e: Exception) {
29+
logger.warn("Error in envoy control server server", e)
30+
throw e
31+
}
32+
}
33+
registerSelf()
34+
}
35+
36+
private fun registerSelf() {
37+
dropletRepository.save(
38+
Droplet(
39+
type = "controller",
40+
id = "internal-controller",
41+
host = args.grpcHost,
42+
port = args.grpcPort,
43+
envoyPort = 8080,
44+
)
45+
)
46+
}
47+
48+
private fun register(builder: ServerBuilder<*>) {
49+
logger.info("Registering envoy ADS...")
50+
builder.addService(server.aggregatedDiscoveryServiceImpl)
51+
}
52+
}

0 commit comments

Comments
 (0)