From 6bbfbd7aeb99475308a140222316f3e9006aeec3 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Tue, 1 Nov 2022 10:38:26 +0100 Subject: refactor(compute/api): Do not suspend in compute API This change updates the API interface of the OpenDC Compute service to not suspend execution using Kotlin Coroutines. The suspending modifiers were introduced in case the ComputeClient would communicate with the service over a network connection. However, the main use-case has been together with the ComputeService, where the suspending modifiers only frustrate the user experience when writing experiments. Furthermore, with the advent of Project Loom, it is not necessarily a problem to block the (virtual) thread during network communications. --- .../kotlin/org/opendc/compute/api/ComputeClient.kt | 18 +- .../main/kotlin/org/opendc/compute/api/Flavor.kt | 5 - .../main/kotlin/org/opendc/compute/api/Image.kt | 7 +- .../main/kotlin/org/opendc/compute/api/Resource.kt | 9 +- .../main/kotlin/org/opendc/compute/api/Server.kt | 18 +- .../kotlin/org/opendc/compute/api/ServerWatcher.kt | 3 - .../compute/service/internal/ClientFlavor.kt | 6 +- .../opendc/compute/service/internal/ClientImage.kt | 8 +- .../compute/service/internal/ClientServer.kt | 16 +- .../compute/service/internal/ComputeServiceImpl.kt | 229 +++++++++++---------- .../compute/service/internal/InternalFlavor.kt | 4 +- .../compute/service/internal/InternalImage.kt | 4 +- .../compute/service/internal/InternalServer.kt | 8 +- .../opendc/compute/service/ComputeServiceTest.kt | 20 +- .../org/opendc/compute/simulator/SimHostTest.kt | 16 +- 15 files changed, 177 insertions(+), 194 deletions(-) diff --git a/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ComputeClient.kt b/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ComputeClient.kt index 577fbc73..c26d0b8b 100644 --- a/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ComputeClient.kt +++ b/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ComputeClient.kt @@ -31,14 +31,14 @@ public interface ComputeClient : AutoCloseable { /** * Obtain the list of [Flavor]s accessible by the requesting user. */ - public suspend fun queryFlavors(): List + public fun queryFlavors(): List /** * Obtain a [Flavor] by its unique identifier. * * @param id The identifier of the flavor. */ - public suspend fun findFlavor(id: UUID): Flavor? + public fun findFlavor(id: UUID): Flavor? /** * Create a new [Flavor] instance at this compute service. @@ -49,7 +49,7 @@ public interface ComputeClient : AutoCloseable { * @param labels The identifying labels of the image. * @param meta The non-identifying meta-data of the image. */ - public suspend fun newFlavor( + public fun newFlavor( name: String, cpuCount: Int, memorySize: Long, @@ -60,14 +60,14 @@ public interface ComputeClient : AutoCloseable { /** * Obtain the list of [Image]s accessible by the requesting user. */ - public suspend fun queryImages(): List + public fun queryImages(): List /** * Obtain a [Image] by its unique identifier. * * @param id The identifier of the image. */ - public suspend fun findImage(id: UUID): Image? + public fun findImage(id: UUID): Image? /** * Create a new [Image] instance at this compute service. @@ -76,7 +76,7 @@ public interface ComputeClient : AutoCloseable { * @param labels The identifying labels of the image. * @param meta The non-identifying meta-data of the image. */ - public suspend fun newImage( + public fun newImage( name: String, labels: Map = emptyMap(), meta: Map = emptyMap() @@ -85,14 +85,14 @@ public interface ComputeClient : AutoCloseable { /** * Obtain the list of [Server]s accessible by the requesting user. */ - public suspend fun queryServers(): List + public fun queryServers(): List /** * Obtain a [Server] by its unique identifier. * * @param id The identifier of the server. */ - public suspend fun findServer(id: UUID): Server? + public fun findServer(id: UUID): Server? /** * Create a new [Server] instance at this compute service. @@ -104,7 +104,7 @@ public interface ComputeClient : AutoCloseable { * @param meta The non-identifying meta-data of the server. * @param start A flag to indicate that the server should be started immediately. */ - public suspend fun newServer( + public fun newServer( name: String, image: Image, flavor: Flavor, diff --git a/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Flavor.kt b/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Flavor.kt index 5f511f91..d76e0fba 100644 --- a/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Flavor.kt +++ b/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Flavor.kt @@ -36,9 +36,4 @@ public interface Flavor : Resource { * The amount of RAM available to the server (in MB). */ public val memorySize: Long - - /** - * Delete the flavor instance. - */ - public suspend fun delete() } diff --git a/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Image.kt b/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Image.kt index 83e63b81..c4a04b96 100644 --- a/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Image.kt +++ b/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Image.kt @@ -25,9 +25,4 @@ package org.opendc.compute.api /** * An image containing a bootable operating system that can directly be executed by physical or virtual server. */ -public interface Image : Resource { - /** - * Delete the image instance. - */ - public suspend fun delete() -} +public interface Image : Resource diff --git a/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Resource.kt b/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Resource.kt index 08120848..58082130 100644 --- a/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Resource.kt +++ b/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Resource.kt @@ -49,7 +49,12 @@ public interface Resource { public val meta: Map /** - * Refresh the local state of the resource. + * Reload the attributes of the resource. */ - public suspend fun refresh() + public fun reload() + + /** + * Delete the resource. + */ + public fun delete() } diff --git a/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Server.kt b/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Server.kt index 64b73d0b..b4cc5129 100644 --- a/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Server.kt +++ b/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Server.kt @@ -50,27 +50,13 @@ public interface Server : Resource { /** * Request the server to be started. - * - * This method is guaranteed to return after the request was acknowledged, but might return before the server was - * started. */ - public suspend fun start() + public fun start() /** * Request the server to be stopped. - * - * This method is guaranteed to return after the request was acknowledged, but might return before the server was - * stopped. - */ - public suspend fun stop() - - /** - * Request the server to be deleted. - * - * This method is guaranteed to return after the request was acknowledged, but might return before the server was - * deleted. */ - public suspend fun delete() + public fun stop() /** * Register the specified [ServerWatcher] to watch the state of the server. diff --git a/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ServerWatcher.kt b/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ServerWatcher.kt index 48a17b30..cf995fc3 100644 --- a/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ServerWatcher.kt +++ b/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ServerWatcher.kt @@ -29,9 +29,6 @@ public interface ServerWatcher { /** * This method is invoked when the state of a [Server] changes. * - * Note that the state of [server] might not reflect the state as reported by the invocation, as a call to - * [Server.refresh] is required to update its state. - * * @param server The server whose state has changed. * @param newState The new state of the server. */ diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientFlavor.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientFlavor.kt index 4a8d3046..45a3d472 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientFlavor.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientFlavor.kt @@ -46,12 +46,12 @@ internal class ClientFlavor(private val delegate: Flavor) : Flavor { override var meta: Map = delegate.meta.toMap() private set - override suspend fun delete() { + override fun delete() { delegate.delete() } - override suspend fun refresh() { - delegate.refresh() + override fun reload() { + delegate.reload() name = delegate.name cpuCount = delegate.cpuCount diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientImage.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientImage.kt index f0032acf..eb963f0e 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientImage.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientImage.kt @@ -40,13 +40,13 @@ internal class ClientImage(private val delegate: Image) : Image { override var meta: Map = delegate.meta.toMap() private set - override suspend fun delete() { + override fun delete() { delegate.delete() - refresh() + reload() } - override suspend fun refresh() { - delegate.refresh() + override fun reload() { + delegate.reload() name = delegate.name labels = delegate.labels diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientServer.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientServer.kt index 6cd7d30f..f2248466 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientServer.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientServer.kt @@ -59,19 +59,19 @@ internal class ClientServer(private val delegate: Server) : Server, ServerWatche override var launchedAt: Instant? = null private set - override suspend fun start() { + override fun start() { delegate.start() - refresh() + reload() } - override suspend fun stop() { + override fun stop() { delegate.stop() - refresh() + reload() } - override suspend fun delete() { + override fun delete() { delegate.delete() - refresh() + reload() } override fun watch(watcher: ServerWatcher) { @@ -90,8 +90,8 @@ internal class ClientServer(private val delegate: Server) : Server, ServerWatche } } - override suspend fun refresh() { - delegate.refresh() + override fun reload() { + delegate.reload() name = delegate.name flavor = delegate.flavor diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt index 77932545..ffebd5fa 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt @@ -122,118 +122,7 @@ internal class ComputeServiceImpl( override fun newClient(): ComputeClient { check(!isClosed) { "Service is already closed" } - return object : ComputeClient { - private var isClosed: Boolean = false - - override suspend fun queryFlavors(): List { - check(!isClosed) { "Client is already closed" } - - return flavorById.values.map { ClientFlavor(it) } - } - - override suspend fun findFlavor(id: UUID): Flavor? { - check(!isClosed) { "Client is already closed" } - - return flavorById[id]?.let { ClientFlavor(it) } - } - - override suspend fun newFlavor( - name: String, - cpuCount: Int, - memorySize: Long, - labels: Map, - meta: Map - ): Flavor { - check(!isClosed) { "Client is already closed" } - - val uid = UUID(clock.millis(), random.nextLong()) - val flavor = InternalFlavor( - this@ComputeServiceImpl, - uid, - name, - cpuCount, - memorySize, - labels, - meta - ) - - flavorById[uid] = flavor - - return ClientFlavor(flavor) - } - - override suspend fun queryImages(): List { - check(!isClosed) { "Client is already closed" } - - return imageById.values.map { ClientImage(it) } - } - - override suspend fun findImage(id: UUID): Image? { - check(!isClosed) { "Client is already closed" } - - return imageById[id]?.let { ClientImage(it) } - } - - override suspend fun newImage(name: String, labels: Map, meta: Map): Image { - check(!isClosed) { "Client is already closed" } - - val uid = UUID(clock.millis(), random.nextLong()) - val image = InternalImage(this@ComputeServiceImpl, uid, name, labels, meta) - - imageById[uid] = image - - return ClientImage(image) - } - - override suspend fun newServer( - name: String, - image: Image, - flavor: Flavor, - labels: Map, - meta: Map, - start: Boolean - ): Server { - check(!isClosed) { "Client is closed" } - - val uid = UUID(clock.millis(), random.nextLong()) - val server = InternalServer( - this@ComputeServiceImpl, - uid, - name, - requireNotNull(flavorById[flavor.uid]) { "Unknown flavor" }, - requireNotNull(imageById[image.uid]) { "Unknown image" }, - labels.toMutableMap(), - meta.toMutableMap() - ) - - serverById[uid] = server - servers.add(server) - - if (start) { - server.start() - } - - return ClientServer(server) - } - - override suspend fun findServer(id: UUID): Server? { - check(!isClosed) { "Client is already closed" } - - return serverById[id]?.let { ClientServer(it) } - } - - override suspend fun queryServers(): List { - check(!isClosed) { "Client is already closed" } - - return serverById.values.map { ClientServer(it) } - } - - override fun close() { - isClosed = true - } - - override fun toString(): String = "ComputeClient" - } + return Client(this) } override fun addHost(host: Host) { @@ -460,4 +349,120 @@ internal class ComputeServiceImpl( requestSchedulingCycle() } } + + /** + * Implementation of [ComputeClient] using a [ComputeService]. + */ + private class Client(private val service: ComputeServiceImpl) : ComputeClient { + private var isClosed: Boolean = false + + override fun queryFlavors(): List { + check(!isClosed) { "Client is already closed" } + + return service.flavorById.values.map { ClientFlavor(it) } + } + + override fun findFlavor(id: UUID): Flavor? { + check(!isClosed) { "Client is already closed" } + + return service.flavorById[id]?.let { ClientFlavor(it) } + } + + override fun newFlavor( + name: String, + cpuCount: Int, + memorySize: Long, + labels: Map, + meta: Map + ): Flavor { + check(!isClosed) { "Client is already closed" } + + val uid = UUID(service.clock.millis(), service.random.nextLong()) + val flavor = InternalFlavor( + service, + uid, + name, + cpuCount, + memorySize, + labels, + meta + ) + + service.flavorById[uid] = flavor + + return ClientFlavor(flavor) + } + + override fun queryImages(): List { + check(!isClosed) { "Client is already closed" } + + return service.imageById.values.map { ClientImage(it) } + } + + override fun findImage(id: UUID): Image? { + check(!isClosed) { "Client is already closed" } + + return service.imageById[id]?.let { ClientImage(it) } + } + + override fun newImage(name: String, labels: Map, meta: Map): Image { + check(!isClosed) { "Client is already closed" } + + val uid = UUID(service.clock.millis(), service.random.nextLong()) + val image = InternalImage(service, uid, name, labels, meta) + + service.imageById[uid] = image + + return ClientImage(image) + } + + override fun newServer( + name: String, + image: Image, + flavor: Flavor, + labels: Map, + meta: Map, + start: Boolean + ): Server { + check(!isClosed) { "Client is closed" } + + val uid = UUID(service.clock.millis(), service.random.nextLong()) + val server = InternalServer( + service, + uid, + name, + requireNotNull(service.flavorById[flavor.uid]) { "Unknown flavor" }, + requireNotNull(service.imageById[image.uid]) { "Unknown image" }, + labels.toMutableMap(), + meta.toMutableMap() + ) + + service.serverById[uid] = server + service.servers.add(server) + + if (start) { + server.start() + } + + return ClientServer(server) + } + + override fun findServer(id: UUID): Server? { + check(!isClosed) { "Client is already closed" } + + return service.serverById[id]?.let { ClientServer(it) } + } + + override fun queryServers(): List { + check(!isClosed) { "Client is already closed" } + + return service.serverById.values.map { ClientServer(it) } + } + + override fun close() { + isClosed = true + } + + override fun toString(): String = "ComputeService.Client" + } } diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalFlavor.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalFlavor.kt index acd87dfc..077ec865 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalFlavor.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalFlavor.kt @@ -50,11 +50,11 @@ internal class InternalFlavor( override val meta: MutableMap = meta.toMutableMap() - override suspend fun refresh() { + override fun reload() { // No-op: this object is the source-of-truth } - override suspend fun delete() { + override fun delete() { service.delete(this) } diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalImage.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalImage.kt index a0a35a55..b27ef33a 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalImage.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalImage.kt @@ -40,11 +40,11 @@ internal class InternalImage( override val meta: MutableMap = meta.toMutableMap() - override suspend fun refresh() { + override fun reload() { // No-op: this object is the source-of-truth } - override suspend fun delete() { + override fun delete() { service.delete(this) } diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt index e3bae405..c1353ba1 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt @@ -67,7 +67,7 @@ internal class InternalServer( */ private var request: ComputeServiceImpl.SchedulingRequest? = null - override suspend fun start() { + override fun start() { when (state) { ServerState.RUNNING -> { logger.debug { "User tried to start server but server is already running" } @@ -90,7 +90,7 @@ internal class InternalServer( } } - override suspend fun stop() { + override fun stop() { when (state) { ServerState.PROVISIONING -> { cancelProvisioningRequest() @@ -104,7 +104,7 @@ internal class InternalServer( } } - override suspend fun delete() { + override fun delete() { when (state) { ServerState.PROVISIONING, ServerState.TERMINATED -> { cancelProvisioningRequest() @@ -129,7 +129,7 @@ internal class InternalServer( watchers -= watcher } - override suspend fun refresh() { + override fun reload() { // No-op: this object is the source-of-truth } diff --git a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt index b5685aba..13b926e8 100644 --- a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt +++ b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt @@ -170,7 +170,7 @@ internal class ComputeServiceTest { server.start() delay(5L * 60 * 1000) - server.refresh() + server.reload() assertEquals(ServerState.TERMINATED, server.state) } @@ -183,7 +183,7 @@ internal class ComputeServiceTest { server.start() delay(5L * 60 * 1000) - server.refresh() + server.reload() assertEquals(ServerState.TERMINATED, server.state) } @@ -196,7 +196,7 @@ internal class ComputeServiceTest { server.start() delay(5L * 60 * 1000) - server.refresh() + server.reload() assertEquals(ServerState.TERMINATED, server.state) } @@ -210,7 +210,7 @@ internal class ComputeServiceTest { server.start() server.stop() delay(5L * 60 * 1000) - server.refresh() + server.reload() assertEquals(ServerState.TERMINATED, server.state) } @@ -231,7 +231,7 @@ internal class ComputeServiceTest { server.start() delay(10L * 60 * 1000) - server.refresh() + server.reload() assertEquals(ServerState.PROVISIONING, server.state) verify { host.canFit(server) } @@ -262,7 +262,7 @@ internal class ComputeServiceTest { listeners.forEach { it.onStateChanged(host, HostState.UP) } delay(5L * 60 * 1000) - server.refresh() + server.reload() assertEquals(ServerState.PROVISIONING, server.state) verify { host.canFit(server) } @@ -293,7 +293,7 @@ internal class ComputeServiceTest { server.start() delay(5L * 60 * 1000) - server.refresh() + server.reload() assertEquals(ServerState.PROVISIONING, server.state) verify(exactly = 0) { host.canFit(server) } @@ -351,7 +351,7 @@ internal class ComputeServiceTest { listeners.forEach { it.onStateChanged(host, slot.captured, ServerState.RUNNING) } - server.refresh() + server.reload() assertEquals(ServerState.RUNNING, server.state) verify { watcher.onStateChanged(server, ServerState.RUNNING) } @@ -359,7 +359,7 @@ internal class ComputeServiceTest { // Stop server listeners.forEach { it.onStateChanged(host, slot.captured, ServerState.TERMINATED) } - server.refresh() + server.reload() assertEquals(ServerState.TERMINATED, server.state) verify { watcher.onStateChanged(server, ServerState.TERMINATED) } @@ -387,7 +387,7 @@ internal class ComputeServiceTest { server.start() delay(5L * 60 * 1000) - server.refresh() + server.reload() assertEquals(ServerState.PROVISIONING, server.state) } } diff --git a/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimHostTest.kt b/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimHostTest.kt index a496cc99..1734daf5 100644 --- a/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimHostTest.kt +++ b/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimHostTest.kt @@ -305,11 +305,11 @@ internal class SimHostTest { override val labels: Map = emptyMap() override val meta: Map = emptyMap() - override suspend fun delete() { + override fun delete() { throw NotImplementedError() } - override suspend fun refresh() { + override fun reload() { throw NotImplementedError() } } @@ -320,11 +320,11 @@ internal class SimHostTest { override val labels: Map, override val meta: Map ) : Image { - override suspend fun delete() { + override fun delete() { throw NotImplementedError() } - override suspend fun refresh() { + override fun reload() { throw NotImplementedError() } } @@ -343,16 +343,16 @@ internal class SimHostTest { override val launchedAt: Instant? = null - override suspend fun start() {} + override fun start() {} - override suspend fun stop() {} + override fun stop() {} - override suspend fun delete() {} + override fun delete() {} override fun watch(watcher: ServerWatcher) {} override fun unwatch(watcher: ServerWatcher) {} - override suspend fun refresh() {} + override fun reload() {} } } -- cgit v1.2.3 From e0856b26c3e1961e7ff4bb3ca038adc4892bbc22 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Tue, 1 Nov 2022 10:52:46 +0100 Subject: refactor(compute/service): Expose state directly to clients This change updates the implementation of the compute service to expose state to clients created by the compute service. --- .../compute/service/internal/ClientFlavor.kt | 68 ------------ .../opendc/compute/service/internal/ClientImage.kt | 61 ----------- .../compute/service/internal/ClientServer.kt | 118 --------------------- .../compute/service/internal/ComputeServiceImpl.kt | 27 +++-- .../opendc/compute/service/ComputeServiceTest.kt | 6 +- 5 files changed, 19 insertions(+), 261 deletions(-) delete mode 100644 opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientFlavor.kt delete mode 100644 opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientImage.kt delete mode 100644 opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientServer.kt diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientFlavor.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientFlavor.kt deleted file mode 100644 index 45a3d472..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientFlavor.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.internal - -import org.opendc.compute.api.Flavor -import java.util.UUID - -/** - * A [Flavor] implementation that is passed to clients but delegates its implementation to another class. - */ -internal class ClientFlavor(private val delegate: Flavor) : Flavor { - override val uid: UUID = delegate.uid - - override var name: String = delegate.name - private set - - override var cpuCount: Int = delegate.cpuCount - private set - - override var memorySize: Long = delegate.memorySize - private set - - override var labels: Map = delegate.labels.toMap() - private set - - override var meta: Map = delegate.meta.toMap() - private set - - override fun delete() { - delegate.delete() - } - - override fun reload() { - delegate.reload() - - name = delegate.name - cpuCount = delegate.cpuCount - memorySize = delegate.memorySize - labels = delegate.labels - meta = delegate.meta - } - - override fun equals(other: Any?): Boolean = other is Flavor && other.uid == uid - - override fun hashCode(): Int = uid.hashCode() - - override fun toString(): String = "Flavor[uid=$uid,name=$name]" -} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientImage.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientImage.kt deleted file mode 100644 index eb963f0e..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientImage.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.internal - -import org.opendc.compute.api.Image -import java.util.UUID - -/** - * An [Image] implementation that is passed to clients but delegates its implementation to another class. - */ -internal class ClientImage(private val delegate: Image) : Image { - override val uid: UUID = delegate.uid - - override var name: String = delegate.name - private set - - override var labels: Map = delegate.labels.toMap() - private set - - override var meta: Map = delegate.meta.toMap() - private set - - override fun delete() { - delegate.delete() - reload() - } - - override fun reload() { - delegate.reload() - - name = delegate.name - labels = delegate.labels - meta = delegate.meta - } - - override fun equals(other: Any?): Boolean = other is Image && other.uid == uid - - override fun hashCode(): Int = uid.hashCode() - - override fun toString(): String = "Image[uid=$uid,name=$name]" -} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientServer.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientServer.kt deleted file mode 100644 index f2248466..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientServer.kt +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.internal - -import org.opendc.compute.api.Flavor -import org.opendc.compute.api.Image -import org.opendc.compute.api.Server -import org.opendc.compute.api.ServerState -import org.opendc.compute.api.ServerWatcher -import java.time.Instant -import java.util.UUID - -/** - * A [Server] implementation that is passed to clients but delegates its implementation to another class. - */ -internal class ClientServer(private val delegate: Server) : Server, ServerWatcher { - private val watchers = mutableListOf() - - override val uid: UUID = delegate.uid - - override var name: String = delegate.name - private set - - override var flavor: Flavor = delegate.flavor - private set - - override var image: Image = delegate.image - private set - - override var labels: Map = delegate.labels.toMap() - private set - - override var meta: Map = delegate.meta.toMap() - private set - - override var state: ServerState = delegate.state - private set - - override var launchedAt: Instant? = null - private set - - override fun start() { - delegate.start() - reload() - } - - override fun stop() { - delegate.stop() - reload() - } - - override fun delete() { - delegate.delete() - reload() - } - - override fun watch(watcher: ServerWatcher) { - if (watchers.isEmpty()) { - delegate.watch(this) - } - - watchers += watcher - } - - override fun unwatch(watcher: ServerWatcher) { - watchers += watcher - - if (watchers.isEmpty()) { - delegate.unwatch(this) - } - } - - override fun reload() { - delegate.reload() - - name = delegate.name - flavor = delegate.flavor - image = delegate.image - labels = delegate.labels - meta = delegate.meta - state = delegate.state - launchedAt = delegate.launchedAt - } - - override fun onStateChanged(server: Server, newState: ServerState) { - val watchers = watchers - - for (watcher in watchers) { - watcher.onStateChanged(this, newState) - } - } - - override fun equals(other: Any?): Boolean = other is Server && other.uid == uid - - override fun hashCode(): Int = uid.hashCode() - - override fun toString(): String = "Server[uid=$uid,name=$name,state=$state]" -} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt index ffebd5fa..ac66ff8b 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt @@ -90,11 +90,13 @@ internal class ComputeServiceImpl( * The registered flavors for this compute service. */ internal val flavorById = mutableMapOf() + private val flavors: MutableList = mutableListOf() /** * The registered images for this compute service. */ internal val imageById = mutableMapOf() + private val images: MutableList = mutableListOf() /** * The registered servers for this compute service. @@ -198,10 +200,12 @@ internal class ComputeServiceImpl( internal fun delete(flavor: InternalFlavor) { flavorById.remove(flavor.uid) + flavors.remove(flavor) } internal fun delete(image: InternalImage) { imageById.remove(image.uid) + images.remove(image) } internal fun delete(server: InternalServer) { @@ -359,13 +363,13 @@ internal class ComputeServiceImpl( override fun queryFlavors(): List { check(!isClosed) { "Client is already closed" } - return service.flavorById.values.map { ClientFlavor(it) } + return service.flavors.toList() } override fun findFlavor(id: UUID): Flavor? { check(!isClosed) { "Client is already closed" } - return service.flavorById[id]?.let { ClientFlavor(it) } + return service.flavorById[id] } override fun newFlavor( @@ -377,6 +381,7 @@ internal class ComputeServiceImpl( ): Flavor { check(!isClosed) { "Client is already closed" } + val service = service val uid = UUID(service.clock.millis(), service.random.nextLong()) val flavor = InternalFlavor( service, @@ -389,31 +394,34 @@ internal class ComputeServiceImpl( ) service.flavorById[uid] = flavor + service.flavors.add(flavor) - return ClientFlavor(flavor) + return flavor } override fun queryImages(): List { check(!isClosed) { "Client is already closed" } - return service.imageById.values.map { ClientImage(it) } + return service.images.toList() } override fun findImage(id: UUID): Image? { check(!isClosed) { "Client is already closed" } - return service.imageById[id]?.let { ClientImage(it) } + return service.imageById[id] } override fun newImage(name: String, labels: Map, meta: Map): Image { check(!isClosed) { "Client is already closed" } + val service = service val uid = UUID(service.clock.millis(), service.random.nextLong()) val image = InternalImage(service, uid, name, labels, meta) service.imageById[uid] = image + service.images.add(image) - return ClientImage(image) + return image } override fun newServer( @@ -426,6 +434,7 @@ internal class ComputeServiceImpl( ): Server { check(!isClosed) { "Client is closed" } + val service = service val uid = UUID(service.clock.millis(), service.random.nextLong()) val server = InternalServer( service, @@ -444,19 +453,19 @@ internal class ComputeServiceImpl( server.start() } - return ClientServer(server) + return server } override fun findServer(id: UUID): Server? { check(!isClosed) { "Client is already closed" } - return service.serverById[id]?.let { ClientServer(it) } + return service.serverById[id] } override fun queryServers(): List { check(!isClosed) { "Client is already closed" } - return service.serverById.values.map { ClientServer(it) } + return service.servers.toList() } override fun close() { diff --git a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt index 13b926e8..a64c0885 100644 --- a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt +++ b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt @@ -302,6 +302,7 @@ internal class ComputeServiceTest { @Test fun testServerInvalidType() = scope.runSimulation { val host = mockk(relaxUnitFun = true) + val server = mockk(relaxUnitFun = true) val listeners = mutableListOf() every { host.uid } returns UUID.randomUUID() @@ -312,11 +313,6 @@ internal class ComputeServiceTest { service.addHost(host) - val client = service.newClient() - val flavor = client.newFlavor("test", 1, 1024) - val image = client.newImage("test") - val server = client.newServer("test", image, flavor, start = false) - assertThrows { listeners.forEach { it.onStateChanged(host, server, ServerState.RUNNING) } } -- cgit v1.2.3 From 4dfae28c5bd656806a7baf7855c95770f4ad0ed8 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Wed, 2 Nov 2022 17:20:00 +0100 Subject: refactor(compute/service): Do not split interface and implementation This change inlines the implementation of the compute service into the `ComputeService` interface. We do not intend to provide multiple implementations of the service. In addition, this approach makes more sense for a Java implementation. --- .../org/opendc/compute/service/ComputeService.java | 601 +++++++++++++++++++++ .../java/org/opendc/compute/service/HostView.java | 78 +++ .../org/opendc/compute/service/ServiceFlavor.java | 122 +++++ .../org/opendc/compute/service/ServiceImage.java | 101 ++++ .../org/opendc/compute/service/ServiceServer.java | 246 +++++++++ .../org/opendc/compute/service/driver/Host.java | 137 +++++ .../compute/service/driver/HostListener.java | 41 ++ .../opendc/compute/service/driver/HostModel.java | 32 ++ .../opendc/compute/service/driver/HostState.java | 43 ++ .../service/driver/telemetry/GuestCpuStats.java | 43 ++ .../service/driver/telemetry/GuestSystemStats.java | 35 ++ .../service/driver/telemetry/HostCpuStats.java | 46 ++ .../service/driver/telemetry/HostSystemStats.java | 50 ++ .../compute/service/telemetry/SchedulerStats.java | 45 ++ .../org/opendc/compute/service/ComputeService.kt | 94 ---- .../org/opendc/compute/service/driver/Host.kt | 135 ----- .../opendc/compute/service/driver/HostListener.kt | 41 -- .../org/opendc/compute/service/driver/HostModel.kt | 36 -- .../org/opendc/compute/service/driver/HostState.kt | 43 -- .../service/driver/telemetry/GuestCpuStats.kt | 44 -- .../service/driver/telemetry/GuestSystemStats.kt | 39 -- .../service/driver/telemetry/HostCpuStats.kt | 47 -- .../service/driver/telemetry/HostSystemStats.kt | 51 -- .../compute/service/internal/ComputeServiceImpl.kt | 477 ---------------- .../opendc/compute/service/internal/HostView.kt | 44 -- .../compute/service/internal/InternalFlavor.kt | 66 --- .../compute/service/internal/InternalImage.kt | 56 -- .../compute/service/internal/InternalServer.kt | 161 ------ .../compute/service/scheduler/ComputeScheduler.kt | 2 +- .../compute/service/scheduler/FilterScheduler.kt | 2 +- .../compute/service/scheduler/ReplayScheduler.kt | 2 +- .../service/scheduler/filters/ComputeFilter.kt | 2 +- .../scheduler/filters/DifferentHostFilter.kt | 2 +- .../service/scheduler/filters/HostFilter.kt | 2 +- .../scheduler/filters/InstanceCountFilter.kt | 2 +- .../compute/service/scheduler/filters/RamFilter.kt | 2 +- .../service/scheduler/filters/SameHostFilter.kt | 2 +- .../scheduler/filters/VCpuCapacityFilter.kt | 2 +- .../service/scheduler/filters/VCpuFilter.kt | 2 +- .../service/scheduler/weights/CoreRamWeigher.kt | 2 +- .../service/scheduler/weights/HostWeigher.kt | 2 +- .../scheduler/weights/InstanceCountWeigher.kt | 2 +- .../service/scheduler/weights/RamWeigher.kt | 2 +- .../scheduler/weights/VCpuCapacityWeigher.kt | 2 +- .../service/scheduler/weights/VCpuWeigher.kt | 2 +- .../compute/service/telemetry/SchedulerStats.kt | 48 -- .../opendc/compute/service/ComputeServiceTest.kt | 22 +- .../opendc/compute/service/InternalFlavorTest.kt | 81 --- .../opendc/compute/service/InternalImageTest.kt | 82 --- .../opendc/compute/service/InternalServerTest.kt | 305 ----------- .../opendc/compute/service/ServiceFlavorTest.kt | 67 +++ .../org/opendc/compute/service/ServiceImageTest.kt | 67 +++ .../opendc/compute/service/ServiceServerTest.kt | 286 ++++++++++ .../service/scheduler/FilterSchedulerTest.kt | 2 +- .../kotlin/org/opendc/compute/simulator/SimHost.kt | 37 +- .../compute/ComputeServiceProvisioningStep.kt | 4 +- 56 files changed, 2091 insertions(+), 1898 deletions(-) create mode 100644 opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ComputeService.java create mode 100644 opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/HostView.java create mode 100644 opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ServiceFlavor.java create mode 100644 opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ServiceImage.java create mode 100644 opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ServiceServer.java create mode 100644 opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/Host.java create mode 100644 opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/HostListener.java create mode 100644 opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/HostModel.java create mode 100644 opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/HostState.java create mode 100644 opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/GuestCpuStats.java create mode 100644 opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/GuestSystemStats.java create mode 100644 opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/HostCpuStats.java create mode 100644 opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/HostSystemStats.java create mode 100644 opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/telemetry/SchedulerStats.java delete mode 100644 opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/ComputeService.kt delete mode 100644 opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/Host.kt delete mode 100644 opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/HostListener.kt delete mode 100644 opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/HostModel.kt delete mode 100644 opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/HostState.kt delete mode 100644 opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/telemetry/GuestCpuStats.kt delete mode 100644 opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/telemetry/GuestSystemStats.kt delete mode 100644 opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/telemetry/HostCpuStats.kt delete mode 100644 opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/telemetry/HostSystemStats.kt delete mode 100644 opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt delete mode 100644 opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/HostView.kt delete mode 100644 opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalFlavor.kt delete mode 100644 opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalImage.kt delete mode 100644 opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt delete mode 100644 opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/telemetry/SchedulerStats.kt delete mode 100644 opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalFlavorTest.kt delete mode 100644 opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalImageTest.kt delete mode 100644 opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalServerTest.kt create mode 100644 opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ServiceFlavorTest.kt create mode 100644 opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ServiceImageTest.kt create mode 100644 opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ServiceServerTest.kt diff --git a/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ComputeService.java b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ComputeService.java new file mode 100644 index 00000000..eda9a79f --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ComputeService.java @@ -0,0 +1,601 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service; + +import java.time.Duration; +import java.time.Instant; +import java.time.InstantSource; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.SplittableRandom; +import java.util.UUID; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.opendc.common.Dispatcher; +import org.opendc.common.util.Pacer; +import org.opendc.compute.api.ComputeClient; +import org.opendc.compute.api.Flavor; +import org.opendc.compute.api.Image; +import org.opendc.compute.api.Server; +import org.opendc.compute.api.ServerState; +import org.opendc.compute.service.driver.Host; +import org.opendc.compute.service.driver.HostListener; +import org.opendc.compute.service.driver.HostModel; +import org.opendc.compute.service.driver.HostState; +import org.opendc.compute.service.scheduler.ComputeScheduler; +import org.opendc.compute.service.telemetry.SchedulerStats; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link ComputeService} hosts the API implementation of the OpenDC Compute Engine. + */ +public final class ComputeService implements AutoCloseable { + private static final Logger LOGGER = LoggerFactory.getLogger(ComputeService.class); + + /** + * The {@link InstantSource} representing the clock tracking the (simulation) time. + */ + private final InstantSource clock; + + /** + * The {@link ComputeScheduler} responsible for placing the servers onto hosts. + */ + private final ComputeScheduler scheduler; + + /** + * The {@link Pacer} used to pace the scheduling requests. + */ + private final Pacer pacer; + + /** + * The {@link SplittableRandom} used to generate the unique identifiers for the service resources. + */ + private final SplittableRandom random = new SplittableRandom(0); + + /** + * A flag to indicate that the service is closed. + */ + private boolean isClosed; + + /** + * A mapping from host to host view. + */ + private final Map hostToView = new HashMap<>(); + + /** + * The available hypervisors. + */ + private final Set availableHosts = new HashSet<>(); + + /** + * The servers that should be launched by the service. + */ + private final Deque queue = new ArrayDeque<>(); + + /** + * The active servers in the system. + */ + private final Map activeServers = new HashMap<>(); + + /** + * The registered flavors for this compute service. + */ + private final Map flavorById = new HashMap<>(); + + private final List flavors = new ArrayList<>(); + + /** + * The registered images for this compute service. + */ + private final Map imageById = new HashMap<>(); + + private final List images = new ArrayList<>(); + + /** + * The registered servers for this compute service. + */ + private final Map serverById = new HashMap<>(); + + private final List servers = new ArrayList<>(); + + /** + * A [HostListener] used to track the active servers. + */ + private final HostListener hostListener = new HostListener() { + @Override + public void onStateChanged(@NotNull Host host, @NotNull HostState newState) { + LOGGER.debug("Host {} state changed: {}", host, newState); + + final HostView hv = hostToView.get(host); + + if (hv != null) { + if (newState == HostState.UP) { + availableHosts.add(hv); + } else { + availableHosts.remove(hv); + } + } + + // Re-schedule on the new machine + requestSchedulingCycle(); + } + + @Override + public void onStateChanged(@NotNull Host host, @NotNull Server server, @NotNull ServerState newState) { + final ServiceServer serviceServer = (ServiceServer) server; + + if (serviceServer.getHost() != host) { + // This can happen when a server is rescheduled and started on another machine, while being deleted from + // the old machine. + return; + } + + serviceServer.setState(newState); + + if (newState == ServerState.TERMINATED || newState == ServerState.DELETED) { + LOGGER.info("Server {} {} {} finished", server.getUid(), server.getName(), server.getFlavor()); + + if (activeServers.remove(server) != null) { + serversActive--; + } + + HostView hv = hostToView.get(host); + final ServiceFlavor flavor = serviceServer.getFlavor(); + if (hv != null) { + hv.provisionedCores -= flavor.getCpuCount(); + hv.instanceCount--; + hv.availableMemory += flavor.getMemorySize(); + } else { + LOGGER.error("Unknown host {}", host); + } + + // Try to reschedule if needed + requestSchedulingCycle(); + } + } + }; + + private int maxCores = 0; + private long maxMemory = 0L; + private long attemptsSuccess = 0L; + private long attemptsFailure = 0L; + private long attemptsError = 0L; + private int serversPending = 0; + private int serversActive = 0; + + /** + * Construct a {@link ComputeService} instance. + */ + ComputeService(Dispatcher dispatcher, ComputeScheduler scheduler, Duration quantum) { + this.clock = dispatcher.getTimeSource(); + this.scheduler = scheduler; + this.pacer = new Pacer(dispatcher, quantum.toMillis(), (time) -> doSchedule()); + } + + /** + * Create a new {@link Builder} instance. + */ + public static Builder builder(Dispatcher dispatcher, ComputeScheduler scheduler) { + return new Builder(dispatcher, scheduler); + } + + /** + * Create a new {@link ComputeClient} to control the compute service. + */ + public ComputeClient newClient() { + if (isClosed) { + throw new IllegalStateException("Service is closed"); + } + return new Client(this); + } + + /** + * Return the {@link Server}s hosted by this service. + */ + public List getServers() { + return Collections.unmodifiableList(servers); + } + + /** + * Add a {@link Host} to the scheduling pool of the compute service. + */ + public void addHost(Host host) { + // Check if host is already known + if (hostToView.containsKey(host)) { + return; + } + + HostView hv = new HostView(host); + HostModel model = host.getModel(); + + maxCores = Math.max(maxCores, model.cpuCount()); + maxMemory = Math.max(maxMemory, model.memoryCapacity()); + hostToView.put(host, hv); + + if (host.getState() == HostState.UP) { + availableHosts.add(hv); + } + + scheduler.addHost(hv); + host.addListener(hostListener); + } + + /** + * Remove a {@link Host} from the scheduling pool of the compute service. + */ + public void removeHost(Host host) { + HostView view = hostToView.remove(host); + if (view != null) { + availableHosts.remove(view); + scheduler.removeHost(view); + host.removeListener(hostListener); + } + } + + /** + * Lookup the {@link Host} that currently hosts the specified {@link Server}. + */ + public Host lookupHost(Server server) { + if (server instanceof ServiceServer) { + return ((ServiceServer) server).getHost(); + } + + ServiceServer internal = + Objects.requireNonNull(serverById.get(server.getUid()), "Invalid server passed to lookupHost"); + return internal.getHost(); + } + + /** + * Return the {@link Host}s that are registered with this service. + */ + public Set getHosts() { + return Collections.unmodifiableSet(hostToView.keySet()); + } + + /** + * Collect the statistics about the scheduler component of this service. + */ + public SchedulerStats getSchedulerStats() { + return new SchedulerStats( + availableHosts.size(), + hostToView.size() - availableHosts.size(), + attemptsSuccess, + attemptsFailure, + attemptsError, + servers.size(), + serversPending, + serversActive); + } + + @Override + public void close() { + if (isClosed) { + return; + } + + isClosed = true; + pacer.cancel(); + } + + /** + * Enqueue the specified [server] to be scheduled onto a host. + */ + SchedulingRequest schedule(ServiceServer server) { + LOGGER.debug("Enqueueing server {} to be assigned to host", server.getUid()); + + long now = clock.millis(); + SchedulingRequest request = new SchedulingRequest(server, now); + + server.launchedAt = Instant.ofEpochMilli(now); + queue.add(request); + serversPending++; + requestSchedulingCycle(); + return request; + } + + void delete(ServiceFlavor flavor) { + flavorById.remove(flavor.getUid()); + flavors.remove(flavor); + } + + void delete(ServiceImage image) { + imageById.remove(image.getUid()); + images.remove(image); + } + + void delete(ServiceServer server) { + serverById.remove(server.getUid()); + servers.remove(server); + } + + /** + * Indicate that a new scheduling cycle is needed due to a change to the service's state. + */ + private void requestSchedulingCycle() { + // Bail out in case the queue is empty. + if (queue.isEmpty()) { + return; + } + + pacer.enqueue(); + } + + /** + * Run a single scheduling iteration. + */ + private void doSchedule() { + while (!queue.isEmpty()) { + SchedulingRequest request = queue.peek(); + + if (request.isCancelled) { + queue.poll(); + serversPending--; + continue; + } + + final ServiceServer server = request.server; + final ServiceFlavor flavor = server.getFlavor(); + final HostView hv = scheduler.select(request.server); + + if (hv == null || !hv.getHost().canFit(server)) { + LOGGER.trace( + "Server {} selected for scheduling but no capacity available for it at the moment", server); + + if (flavor.getMemorySize() > maxMemory || flavor.getCpuCount() > maxCores) { + // Remove the incoming image + queue.poll(); + serversPending--; + attemptsFailure++; + + LOGGER.warn("Failed to spawn {}: does not fit", server); + + server.setState(ServerState.TERMINATED); + continue; + } else { + break; + } + } + + Host host = hv.getHost(); + + // Remove request from queue + queue.poll(); + serversPending--; + + LOGGER.info("Assigned server {} to host {}", server, host); + + try { + server.host = host; + + host.spawn(server); + host.start(server); + + serversActive++; + attemptsSuccess++; + + hv.instanceCount++; + hv.provisionedCores += flavor.getCpuCount(); + hv.availableMemory -= flavor.getMemorySize(); + + activeServers.put(server, host); + } catch (Exception cause) { + LOGGER.error("Failed to deploy VM", cause); + attemptsError++; + } + } + } + + /** + * Builder class for a {@link ComputeService}. + */ + public static class Builder { + private final Dispatcher dispatcher; + private final ComputeScheduler computeScheduler; + private Duration quantum = Duration.ofMinutes(5); + + Builder(Dispatcher dispatcher, ComputeScheduler computeScheduler) { + this.dispatcher = dispatcher; + this.computeScheduler = computeScheduler; + } + + /** + * Set the scheduling quantum of the service. + */ + public Builder withQuantum(Duration quantum) { + this.quantum = quantum; + return this; + } + + /** + * Build a {@link ComputeService}. + */ + public ComputeService build() { + return new ComputeService(dispatcher, computeScheduler, quantum); + } + } + + /** + * Implementation of {@link ComputeClient} using a {@link ComputeService}. + */ + private static class Client implements ComputeClient { + private final ComputeService service; + private boolean isClosed; + + Client(ComputeService service) { + this.service = service; + } + + /** + * Method to check if the client is still open and throw an exception if it is not. + */ + private void checkOpen() { + if (isClosed) { + throw new IllegalStateException("Client is already closed"); + } + } + + @NotNull + @Override + public List queryFlavors() { + checkOpen(); + return new ArrayList<>(service.flavors); + } + + @Override + public Flavor findFlavor(@NotNull UUID id) { + checkOpen(); + + return service.flavorById.get(id); + } + + @NotNull + @Override + public Flavor newFlavor( + @NotNull String name, + int cpuCount, + long memorySize, + @NotNull Map labels, + @NotNull Map meta) { + checkOpen(); + + final ComputeService service = this.service; + UUID uid = new UUID(service.clock.millis(), service.random.nextLong()); + ServiceFlavor flavor = new ServiceFlavor(service, uid, name, cpuCount, memorySize, labels, meta); + + service.flavorById.put(uid, flavor); + service.flavors.add(flavor); + + return flavor; + } + + @NotNull + @Override + public List queryImages() { + checkOpen(); + + return new ArrayList<>(service.images); + } + + @Override + public Image findImage(@NotNull UUID id) { + checkOpen(); + + return service.imageById.get(id); + } + + @NotNull + public Image newImage(@NotNull String name, @NotNull Map labels, @NotNull Map meta) { + checkOpen(); + + final ComputeService service = this.service; + UUID uid = new UUID(service.clock.millis(), service.random.nextLong()); + + ServiceImage image = new ServiceImage(service, uid, name, labels, meta); + + service.imageById.put(uid, image); + service.images.add(image); + + return image; + } + + @NotNull + @Override + public Server newServer( + @NotNull String name, + @NotNull Image image, + @NotNull Flavor flavor, + @NotNull Map labels, + @NotNull Map meta, + boolean start) { + checkOpen(); + + final ComputeService service = this.service; + UUID uid = new UUID(service.clock.millis(), service.random.nextLong()); + + final ServiceFlavor internalFlavor = + Objects.requireNonNull(service.flavorById.get(flavor.getUid()), "Unknown flavor"); + final ServiceImage internalImage = + Objects.requireNonNull(service.imageById.get(image.getUid()), "Unknown image"); + + ServiceServer server = new ServiceServer(service, uid, name, internalFlavor, internalImage, labels, meta); + + service.serverById.put(uid, server); + service.servers.add(server); + + if (start) { + server.start(); + } + + return server; + } + + @Nullable + @Override + public Server findServer(@NotNull UUID id) { + checkOpen(); + return service.serverById.get(id); + } + + @NotNull + @Override + public List queryServers() { + checkOpen(); + + return new ArrayList<>(service.servers); + } + + @Override + public void close() { + isClosed = true; + } + + @Override + public String toString() { + return "ComputeService.Client"; + } + } + + /** + * A request to schedule a {@link ServiceServer} onto one of the {@link Host}s. + */ + static class SchedulingRequest { + final ServiceServer server; + final long submitTime; + + boolean isCancelled; + + SchedulingRequest(ServiceServer server, long submitTime) { + this.server = server; + this.submitTime = submitTime; + } + } +} diff --git a/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/HostView.java b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/HostView.java new file mode 100644 index 00000000..6e2cdcb4 --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/HostView.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service; + +import org.opendc.compute.service.driver.Host; + +/** + * A view of a {@link Host} as seen from the {@link ComputeService}. + */ +public class HostView { + private final Host host; + int instanceCount; + long availableMemory; + int provisionedCores; + + /** + * Construct a {@link HostView} instance. + * + * @param host The host to create a view of. + */ + public HostView(Host host) { + this.host = host; + this.availableMemory = host.getModel().memoryCapacity(); + } + + /** + * The {@link Host} this is a view of. + */ + public Host getHost() { + return host; + } + + /** + * Return the number of instances on this host. + */ + public int getInstanceCount() { + return instanceCount; + } + + /** + * Return the available memory of the host. + */ + public long getAvailableMemory() { + return availableMemory; + } + + /** + * Return the provisioned cores on the host. + */ + public int getProvisionedCores() { + return provisionedCores; + } + + @Override + public String toString() { + return "HostView[host=" + host + "]"; + } +} diff --git a/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ServiceFlavor.java b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ServiceFlavor.java new file mode 100644 index 00000000..dba87e2c --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ServiceFlavor.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service; + +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import org.jetbrains.annotations.NotNull; +import org.opendc.compute.api.Flavor; + +/** + * Implementation of {@link Flavor} provided by {@link ComputeService}. + */ +public final class ServiceFlavor implements Flavor { + private final ComputeService service; + private final UUID uid; + private final String name; + private final int cpuCount; + private final long memorySize; + private final Map labels; + private final Map meta; + + ServiceFlavor( + ComputeService service, + UUID uid, + String name, + int cpuCount, + long memorySize, + Map labels, + Map meta) { + this.service = service; + this.uid = uid; + this.name = name; + this.cpuCount = cpuCount; + this.memorySize = memorySize; + this.labels = labels; + this.meta = meta; + } + + @Override + public int getCpuCount() { + return cpuCount; + } + + @Override + public long getMemorySize() { + return memorySize; + } + + @NotNull + @Override + public UUID getUid() { + return uid; + } + + @NotNull + @Override + public String getName() { + return name; + } + + @NotNull + @Override + public Map getLabels() { + return Collections.unmodifiableMap(labels); + } + + @NotNull + @Override + public Map getMeta() { + return Collections.unmodifiableMap(meta); + } + + @Override + public void reload() { + // No-op: this object is the source-of-truth + } + + @Override + public void delete() { + service.delete(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ServiceFlavor flavor = (ServiceFlavor) o; + return service.equals(flavor.service) && uid.equals(flavor.uid); + } + + @Override + public int hashCode() { + return Objects.hash(service, uid); + } + + @Override + public String toString() { + return "Flavor[uid=" + uid + ",name=" + name + "]"; + } +} diff --git a/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ServiceImage.java b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ServiceImage.java new file mode 100644 index 00000000..706be483 --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ServiceImage.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service; + +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import org.jetbrains.annotations.NotNull; +import org.opendc.compute.api.Image; + +/** + * Implementation of {@link Image} provided by {@link ComputeService}. + */ +public final class ServiceImage implements Image { + private final ComputeService service; + private final UUID uid; + private final String name; + private final Map labels; + private final Map meta; + + ServiceImage(ComputeService service, UUID uid, String name, Map labels, Map meta) { + this.service = service; + this.uid = uid; + this.name = name; + this.labels = labels; + this.meta = meta; + } + + @NotNull + @Override + public UUID getUid() { + return uid; + } + + @NotNull + @Override + public String getName() { + return name; + } + + @NotNull + @Override + public Map getLabels() { + return Collections.unmodifiableMap(labels); + } + + @NotNull + @Override + public Map getMeta() { + return Collections.unmodifiableMap(meta); + } + + @Override + public void reload() { + // No-op: this object is the source-of-truth + } + + @Override + public void delete() { + service.delete(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ServiceImage image = (ServiceImage) o; + return service.equals(image.service) && uid.equals(image.uid); + } + + @Override + public int hashCode() { + return Objects.hash(service, uid); + } + + @Override + public String toString() { + return "Image[uid=" + uid + ",name=" + name + "]"; + } +} diff --git a/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ServiceServer.java b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ServiceServer.java new file mode 100644 index 00000000..265feac0 --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/ServiceServer.java @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.opendc.compute.api.Server; +import org.opendc.compute.api.ServerState; +import org.opendc.compute.api.ServerWatcher; +import org.opendc.compute.service.driver.Host; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation of {@link Server} provided by {@link ComputeService}. + */ +public final class ServiceServer implements Server { + private static final Logger LOGGER = LoggerFactory.getLogger(ServiceServer.class); + + private final ComputeService service; + private final UUID uid; + private final String name; + private final ServiceFlavor flavor; + private final ServiceImage image; + private final Map labels; + private final Map meta; + + private final List watchers = new ArrayList<>(); + private ServerState state = ServerState.TERMINATED; + Instant launchedAt = null; + Host host = null; + private ComputeService.SchedulingRequest request = null; + + ServiceServer( + ComputeService service, + UUID uid, + String name, + ServiceFlavor flavor, + ServiceImage image, + Map labels, + Map meta) { + this.service = service; + this.uid = uid; + this.name = name; + this.flavor = flavor; + this.image = image; + this.labels = labels; + this.meta = meta; + } + + @NotNull + @Override + public UUID getUid() { + return uid; + } + + @NotNull + @Override + public String getName() { + return name; + } + + @NotNull + @Override + public ServiceFlavor getFlavor() { + return flavor; + } + + @NotNull + @Override + public ServiceImage getImage() { + return image; + } + + @NotNull + @Override + public Map getLabels() { + return Collections.unmodifiableMap(labels); + } + + @NotNull + @Override + public Map getMeta() { + return Collections.unmodifiableMap(meta); + } + + @NotNull + @Override + public ServerState getState() { + return state; + } + + @Nullable + @Override + public Instant getLaunchedAt() { + return launchedAt; + } + + /** + * Return the {@link Host} on which the server is running or null if it is not running on a host. + */ + public Host getHost() { + return host; + } + + @Override + public void start() { + switch (state) { + case PROVISIONING: + LOGGER.debug("User tried to start server but request is already pending: doing nothing"); + case RUNNING: + LOGGER.debug("User tried to start server but server is already running"); + break; + case DELETED: + LOGGER.warn("User tried to start deleted server"); + throw new IllegalStateException("Server is deleted"); + default: + LOGGER.info("User requested to start server {}", uid); + setState(ServerState.PROVISIONING); + assert request == null : "Scheduling request already active"; + request = service.schedule(this); + break; + } + } + + @Override + public void stop() { + switch (state) { + case PROVISIONING: + cancelProvisioningRequest(); + setState(ServerState.TERMINATED); + break; + case RUNNING: + case ERROR: + final Host host = this.host; + if (host == null) { + throw new IllegalStateException("Server not running"); + } + host.stop(this); + break; + } + } + + @Override + public void watch(@NotNull ServerWatcher watcher) { + watchers.add(watcher); + } + + @Override + public void unwatch(@NotNull ServerWatcher watcher) { + watchers.remove(watcher); + } + + @Override + public void reload() { + // No-op: this object is the source-of-truth + } + + @Override + public void delete() { + switch (state) { + case PROVISIONING: + case TERMINATED: + cancelProvisioningRequest(); + service.delete(this); + setState(ServerState.DELETED); + break; + case RUNNING: + case ERROR: + final Host host = this.host; + if (host == null) { + throw new IllegalStateException("Server not running"); + } + host.delete(this); + service.delete(this); + setState(ServerState.DELETED); + break; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ServiceServer server = (ServiceServer) o; + return service.equals(server.service) && uid.equals(server.uid); + } + + @Override + public int hashCode() { + return Objects.hash(service, uid); + } + + @Override + public String toString() { + return "Server[uid=" + uid + ",name=" + name + ",state=" + state + "]"; + } + + void setState(ServerState state) { + if (this.state != state) { + for (ServerWatcher watcher : watchers) { + watcher.onStateChanged(this, state); + } + } + + this.state = state; + } + + /** + * Cancel the provisioning request if active. + */ + private void cancelProvisioningRequest() { + final ComputeService.SchedulingRequest request = this.request; + if (request != null) { + this.request = null; + request.isCancelled = true; + } + } +} diff --git a/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/Host.java b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/Host.java new file mode 100644 index 00000000..760d7f1a --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/Host.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service.driver; + +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import org.opendc.compute.api.Server; +import org.opendc.compute.service.driver.telemetry.GuestCpuStats; +import org.opendc.compute.service.driver.telemetry.GuestSystemStats; +import org.opendc.compute.service.driver.telemetry.HostCpuStats; +import org.opendc.compute.service.driver.telemetry.HostSystemStats; + +/** + * Base interface for representing compute resources that host virtualized {@link Server} instances. + */ +public interface Host { + /** + * Return a unique identifier representing the host. + */ + UUID getUid(); + + /** + * Return the name of this host. + */ + String getName(); + + /** + * Return the machine model of the host. + */ + HostModel getModel(); + + /** + * Return the state of the host. + */ + HostState getState(); + + /** + * Return the meta-data associated with the host. + */ + Map getMeta(); + + /** + * Return the {@link Server} instances known to the host. + */ + Set getInstances(); + + /** + * Determine whether the specified server can still fit on this host. + */ + boolean canFit(Server server); + + /** + * Register the specified server on the host. + */ + void spawn(Server server); + + /** + * Determine whether the specified server exists on the host. + */ + boolean contains(Server server); + + /** + * Start the server if it is currently not running on this host. + * + * @throws IllegalArgumentException if the server is not present on the host. + */ + void start(Server server); + + /** + * Stop the server if it is currently running on this host. + * + * @throws IllegalArgumentException if the server is not present on the host. + */ + void stop(Server server); + + /** + * Delete the specified server on this host and cleanup all resources associated with it. + */ + void delete(Server server); + + /** + * Add a [HostListener] to this host. + */ + void addListener(HostListener listener); + + /** + * Remove a [HostListener] from this host. + */ + void removeListener(HostListener listener); + + /** + * Query the system statistics of the host. + */ + HostSystemStats getSystemStats(); + + /** + * Query the system statistics of a {@link Server} that is located on this host. + * + * @param server The {@link Server} to obtain the system statistics of. + * @throws IllegalArgumentException if the server is not present on the host. + */ + GuestSystemStats getSystemStats(Server server); + + /** + * Query the CPU statistics of the host. + */ + HostCpuStats getCpuStats(); + + /** + * Query the CPU statistics of a {@link Server} that is located on this host. + * + * @param server The {@link Server} to obtain the CPU statistics of. + * @throws IllegalArgumentException if the server is not present on the host. + */ + GuestCpuStats getCpuStats(Server server); +} diff --git a/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/HostListener.java b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/HostListener.java new file mode 100644 index 00000000..feefca40 --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/HostListener.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service.driver; + +import org.opendc.compute.api.Server; +import org.opendc.compute.api.ServerState; + +/** + * Listener interface for events originating from a {@link Host}. + */ +public interface HostListener { + /** + * This method is invoked when the state of server on host changes. + */ + default void onStateChanged(Host host, Server server, ServerState newState) {} + + /** + * This method is invoked when the state of a {@link Host} has changed. + */ + default void onStateChanged(Host host, HostState newState) {} +} diff --git a/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/HostModel.java b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/HostModel.java new file mode 100644 index 00000000..9caa6da7 --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/HostModel.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service.driver; + +/** + * Record describing the static machine properties of the host. + * + * @param cpuCapacity The total CPU capacity of the host in MHz. + * @param cpuCount The number of logical processing cores available for this host. + * @param memoryCapacity The amount of memory available for this host in MB. + */ +public record HostModel(double cpuCapacity, int cpuCount, long memoryCapacity) {} diff --git a/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/HostState.java b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/HostState.java new file mode 100644 index 00000000..ce12a67e --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/HostState.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service.driver; + +/** + * The state of a host. + */ +public enum HostState { + /** + * The host is up and able to host guests. + */ + UP, + + /** + * The host is in a (forced) down state and unable to host any guests. + */ + DOWN, + + /** + * The host is in an error state and unable to host any guests. + */ + ERROR +} diff --git a/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/GuestCpuStats.java b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/GuestCpuStats.java new file mode 100644 index 00000000..0b78c7ea --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/GuestCpuStats.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service.driver.telemetry; + +/** + * Statistics about the CPUs of a guest. + * + * @param activeTime The cumulative time (in seconds) that the CPUs of the guest were actively running. + * @param idleTime The cumulative time (in seconds) the CPUs of the guest were idle. + * @param stealTime The cumulative CPU time (in seconds) that the guest was ready to run, but not granted time by the host. + * @param lostTime The cumulative CPU time (in seconds) that was lost due to interference with other machines. + * @param capacity The available CPU capacity of the guest (in MHz). + * @param usage Amount of CPU resources (in MHz) actually used by the guest. + * @param utilization The utilization of the CPU resources (in %) relative to the total CPU capacity. + */ +public record GuestCpuStats( + long activeTime, + long idleTime, + long stealTime, + long lostTime, + double capacity, + double usage, + double utilization) {} diff --git a/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/GuestSystemStats.java b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/GuestSystemStats.java new file mode 100644 index 00000000..dbf98dd5 --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/GuestSystemStats.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service.driver.telemetry; + +import java.time.Duration; +import java.time.Instant; + +/** + * System-level statistics of a guest. + * + * @param uptime The cumulative uptime of the guest since last boot (in ms). + * @param downtime The cumulative downtime of the guest since last boot (in ms). + * @param bootTime The time at which the guest booted. + */ +public record GuestSystemStats(Duration uptime, Duration downtime, Instant bootTime) {} diff --git a/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/HostCpuStats.java b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/HostCpuStats.java new file mode 100644 index 00000000..d1c2328b --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/HostCpuStats.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service.driver.telemetry; + +/** + * Statistics about the CPUs of a host. + * + * @param activeTime The cumulative time (in seconds) that the CPUs of the host were actively running. + * @param idleTime The cumulative time (in seconds) the CPUs of the host were idle. + * @param stealTime The cumulative CPU time (in seconds) that virtual machines were ready to run, but were not able to. + * @param lostTime The cumulative CPU time (in seconds) that was lost due to interference between virtual machines. + * @param capacity The available CPU capacity of the host (in MHz). + * @param demand Amount of CPU resources (in MHz) the guests would use if there were no CPU contention or CPU + * limits. + * @param usage Amount of CPU resources (in MHz) actually used by the host. + * @param utilization The utilization of the CPU resources (in %) relative to the total CPU capacity. + */ +public record HostCpuStats( + long activeTime, + long idleTime, + long stealTime, + long lostTime, + double capacity, + double demand, + double usage, + double utilization) {} diff --git a/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/HostSystemStats.java b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/HostSystemStats.java new file mode 100644 index 00000000..c0928f1b --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/driver/telemetry/HostSystemStats.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service.driver.telemetry; + +import java.time.Duration; +import java.time.Instant; + +/** + * System-level statistics of a host. + * + * @param uptime The cumulative uptime of the host since last boot (in ms). + * @param downtime The cumulative downtime of the host since last boot (in ms). + * @param bootTime The time at which the server started. + * @param powerUsage Instantaneous power usage of the system (in W). + * @param energyUsage The cumulative energy usage of the system (in J). + * @param guestsTerminated The number of guests that are in a terminated state. + * @param guestsRunning The number of guests that are in a running state. + * @param guestsError The number of guests that are in an error state. + * @param guestsInvalid The number of guests that are in an unknown state. + */ +public record HostSystemStats( + Duration uptime, + Duration downtime, + Instant bootTime, + double powerUsage, + double energyUsage, + int guestsTerminated, + int guestsRunning, + int guestsError, + int guestsInvalid) {} diff --git a/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/telemetry/SchedulerStats.java b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/telemetry/SchedulerStats.java new file mode 100644 index 00000000..2157169b --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/main/java/org/opendc/compute/service/telemetry/SchedulerStats.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service.telemetry; + +/** + * Statistics about the scheduling component of the [ComputeService]. + * + * @param hostsAvailable The number of hosts currently available for scheduling. + * @param hostsUnavailable The number of hosts unavailable for scheduling. + * @param attemptsSuccess Scheduling attempts that resulted into an allocation onto a host. + * @param attemptsFailure The number of failed scheduling attempt due to insufficient capacity at the moment. + * @param attemptsError The number of scheduling attempts that failed due to system error. + * @param serversTotal The number of servers registered with the service. + * @param serversPending The number of servers that are pending to be scheduled. + * @param serversActive The number of servers that are currently managed by the service and running. + */ +public record SchedulerStats( + int hostsAvailable, + int hostsUnavailable, + long attemptsSuccess, + long attemptsFailure, + long attemptsError, + int serversTotal, + int serversPending, + int serversActive) {} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/ComputeService.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/ComputeService.kt deleted file mode 100644 index 9d7dcba6..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/ComputeService.kt +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service - -import org.opendc.common.Dispatcher -import org.opendc.compute.api.ComputeClient -import org.opendc.compute.api.Server -import org.opendc.compute.service.driver.Host -import org.opendc.compute.service.internal.ComputeServiceImpl -import org.opendc.compute.service.scheduler.ComputeScheduler -import org.opendc.compute.service.telemetry.SchedulerStats -import java.time.Duration - -/** - * The [ComputeService] hosts the API implementation of the OpenDC Compute service. - */ -public interface ComputeService : AutoCloseable { - /** - * The servers that are registered with the "compute" service. - */ - public val servers: List - - /** - * The hosts that are registered with the "compute" service. - */ - public val hosts: Set - - /** - * Create a new [ComputeClient] to control the compute service. - */ - public fun newClient(): ComputeClient - - /** - * Add a [host] to the scheduling pool of the compute service. - */ - public fun addHost(host: Host) - - /** - * Remove a [host] from the scheduling pool of the compute service. - */ - public fun removeHost(host: Host) - - /** - * Terminate the lifecycle of the compute service, stopping all running instances. - */ - public override fun close() - - /** - * Lookup the [Host] that currently hosts the specified [server]. - */ - public fun lookupHost(server: Server): Host? - - /** - * Collect the statistics about the scheduler component of this service. - */ - public fun getSchedulerStats(): SchedulerStats - - public companion object { - /** - * Construct a new [ComputeService] implementation. - * - * @param dispatcher The [Dispatcher] for scheduling future events. - * @param scheduler The scheduler implementation to use. - * @param schedulingQuantum The interval between scheduling cycles. - */ - public operator fun invoke( - dispatcher: Dispatcher, - scheduler: ComputeScheduler, - schedulingQuantum: Duration = Duration.ofMinutes(5) - ): ComputeService { - return ComputeServiceImpl(dispatcher, scheduler, schedulingQuantum) - } - } -} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/Host.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/Host.kt deleted file mode 100644 index efcc0f2c..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/Host.kt +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.driver - -import org.opendc.compute.api.Server -import org.opendc.compute.service.driver.telemetry.GuestCpuStats -import org.opendc.compute.service.driver.telemetry.GuestSystemStats -import org.opendc.compute.service.driver.telemetry.HostCpuStats -import org.opendc.compute.service.driver.telemetry.HostSystemStats -import java.util.UUID - -/** - * Base interface for representing compute resources that host virtualized [Server] instances. - */ -public interface Host { - /** - * A unique identifier representing the host. - */ - public val uid: UUID - - /** - * The name of this host. - */ - public val name: String - - /** - * The machine model of the host. - */ - public val model: HostModel - - /** - * The state of the host. - */ - public val state: HostState - - /** - * Meta-data associated with the host. - */ - public val meta: Map - - /** - * The [Server] instances known to the host. - */ - public val instances: Set - - /** - * Determine whether the specified [instance][server] can still fit on this host. - */ - public fun canFit(server: Server): Boolean - - /** - * Register the specified [instance][server] on the host. - */ - public fun spawn(server: Server) - - /** - * Determine whether the specified [instance][server] exists on the host. - */ - public operator fun contains(server: Server): Boolean - - /** - * Start the server [instance][server] if it is currently not running on this host. - * - * @throws IllegalArgumentException if the server is not present on the host. - */ - public fun start(server: Server) - - /** - * Stop the server [instance][server] if it is currently running on this host. - * - * @throws IllegalArgumentException if the server is not present on the host. - */ - public fun stop(server: Server) - - /** - * Delete the specified [instance][server] on this host and cleanup all resources associated with it. - */ - public fun delete(server: Server) - - /** - * Add a [HostListener] to this host. - */ - public fun addListener(listener: HostListener) - - /** - * Remove a [HostListener] from this host. - */ - public fun removeListener(listener: HostListener) - - /** - * Query the system statistics of the host. - */ - public fun getSystemStats(): HostSystemStats - - /** - * Query the system statistics of a [Server] that is located on this host. - * - * @param server The [Server] to obtain the system statistics of. - * @throws IllegalArgumentException if the server is not present on the host. - */ - public fun getSystemStats(server: Server): GuestSystemStats - - /** - * Query the CPU statistics of the host. - */ - public fun getCpuStats(): HostCpuStats - - /** - * Query the CPU statistics of a [Server] that is located on this host. - * - * @param server The [Server] to obtain the CPU statistics of. - * @throws IllegalArgumentException if the server is not present on the host. - */ - public fun getCpuStats(server: Server): GuestCpuStats -} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/HostListener.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/HostListener.kt deleted file mode 100644 index f076cae3..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/HostListener.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.driver - -import org.opendc.compute.api.Server -import org.opendc.compute.api.ServerState - -/** - * Listener interface for events originating from a [Host]. - */ -public interface HostListener { - /** - * This method is invoked when the state of an [instance][server] on [host] changes. - */ - public fun onStateChanged(host: Host, server: Server, newState: ServerState) {} - - /** - * This method is invoked when the state of a [Host] has changed. - */ - public fun onStateChanged(host: Host, newState: HostState) {} -} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/HostModel.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/HostModel.kt deleted file mode 100644 index f3b94e3d..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/HostModel.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.driver - -/** - * Describes the static machine properties of the host. - * - * @property cpuCapacity The total CPU capacity of the host in MHz. - * @property cpuCount The number of logical processing cores available for this host. - * @property memoryCapacity The amount of memory available for this host in MB. - */ -public data class HostModel( - public val cpuCapacity: Double, - public val cpuCount: Int, - public val memoryCapacity: Long -) diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/HostState.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/HostState.kt deleted file mode 100644 index 544b6530..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/HostState.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.driver - -/** - * The state of a host. - */ -public enum class HostState { - /** - * The host is up and able to host guests. - */ - UP, - - /** - * The host is in a (forced) down state and unable to host any guests. - */ - DOWN, - - /** - * The host is in an error state and unable to host any guests. - */ - ERROR -} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/telemetry/GuestCpuStats.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/telemetry/GuestCpuStats.kt deleted file mode 100644 index b5d63471..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/telemetry/GuestCpuStats.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.driver.telemetry - -/** - * Statistics about the CPUs of a guest. - * - * @property activeTime The cumulative time (in seconds) that the CPUs of the guest were actively running. - * @property idleTime The cumulative time (in seconds) the CPUs of the guest were idle. - * @property stealTime The cumulative CPU time (in seconds) that the guest was ready to run, but not granted time by the host. - * @property lostTime The cumulative CPU time (in seconds) that was lost due to interference with other machines. - * @property capacity The available CPU capacity of the guest (in MHz). - * @property usage Amount of CPU resources (in MHz) actually used by the guest. - * @property utilization Utilization of the CPU resources (in %) relative to the total CPU capacity. - */ -public data class GuestCpuStats( - val activeTime: Long, - val idleTime: Long, - val stealTime: Long, - val lostTime: Long, - val capacity: Double, - val usage: Double, - val utilization: Double -) diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/telemetry/GuestSystemStats.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/telemetry/GuestSystemStats.kt deleted file mode 100644 index 6fec5175..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/telemetry/GuestSystemStats.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.driver.telemetry - -import java.time.Duration -import java.time.Instant - -/** - * System-level statistics of a guest. - * - * @property uptime The cumulative uptime of the guest since last boot (in ms). - * @property downtime The cumulative downtime of the guest since last boot (in ms). - * @property bootTime The time at which the guest booted. - */ -public data class GuestSystemStats( - val uptime: Duration, - val downtime: Duration, - val bootTime: Instant? -) diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/telemetry/HostCpuStats.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/telemetry/HostCpuStats.kt deleted file mode 100644 index 55e23c0e..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/telemetry/HostCpuStats.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.driver.telemetry - -/** - * Statistics about the CPUs of a host. - * - * @property activeTime The cumulative time (in seconds) that the CPUs of the host were actively running. - * @property idleTime The cumulative time (in seconds) the CPUs of the host were idle. - * @property stealTime The cumulative CPU time (in seconds) that virtual machines were ready to run, but were not able to. - * @property lostTime The cumulative CPU time (in seconds) that was lost due to interference between virtual machines. - * @property capacity The available CPU capacity of the host (in MHz). - * @property demand Amount of CPU resources (in MHz) the guests would use if there were no CPU contention or CPU - * limits. - * @property usage Amount of CPU resources (in MHz) actually used by the host. - * @property utilization Utilization of the CPU resources (in %) relative to the total CPU capacity. - */ -public data class HostCpuStats( - val activeTime: Long, - val idleTime: Long, - val stealTime: Long, - val lostTime: Long, - val capacity: Double, - val demand: Double, - val usage: Double, - val utilization: Double -) diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/telemetry/HostSystemStats.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/telemetry/HostSystemStats.kt deleted file mode 100644 index 56bd017d..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/telemetry/HostSystemStats.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.driver.telemetry - -import java.time.Duration -import java.time.Instant - -/** - * System-level statistics of a host. - - * @property uptime The cumulative uptime of the host since last boot (in ms). - * @property downtime The cumulative downtime of the host since last boot (in ms). - * @property bootTime The time at which the server started. - * @property powerUsage Instantaneous power usage of the system (in W). - * @property energyUsage The cumulative energy usage of the system (in J). - * @property guestsTerminated The number of guests that are in a terminated state. - * @property guestsRunning The number of guests that are in a running state. - * @property guestsError The number of guests that are in an error state. - * @property guestsInvalid The number of guests that are in an unknown state. - */ -public data class HostSystemStats( - val uptime: Duration, - val downtime: Duration, - val bootTime: Instant?, - val powerUsage: Double, - val energyUsage: Double, - val guestsTerminated: Int, - val guestsRunning: Int, - val guestsError: Int, - val guestsInvalid: Int -) diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt deleted file mode 100644 index ac66ff8b..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt +++ /dev/null @@ -1,477 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.internal - -import mu.KotlinLogging -import org.opendc.common.Dispatcher -import org.opendc.common.util.Pacer -import org.opendc.compute.api.ComputeClient -import org.opendc.compute.api.Flavor -import org.opendc.compute.api.Image -import org.opendc.compute.api.Server -import org.opendc.compute.api.ServerState -import org.opendc.compute.service.ComputeService -import org.opendc.compute.service.driver.Host -import org.opendc.compute.service.driver.HostListener -import org.opendc.compute.service.driver.HostState -import org.opendc.compute.service.scheduler.ComputeScheduler -import org.opendc.compute.service.telemetry.SchedulerStats -import java.time.Duration -import java.time.Instant -import java.util.ArrayDeque -import java.util.Deque -import java.util.Random -import java.util.UUID -import kotlin.math.max - -/** - * Internal implementation of the OpenDC Compute service. - * - * @param dispatcher The [Dispatcher] for scheduling future events. - * @param scheduler The scheduler implementation to use. - * @param schedulingQuantum The interval between scheduling cycles. - */ -internal class ComputeServiceImpl( - private val dispatcher: Dispatcher, - private val scheduler: ComputeScheduler, - schedulingQuantum: Duration -) : ComputeService, HostListener { - /** - * The logger instance of this server. - */ - private val logger = KotlinLogging.logger {} - - /** - * The [Random] instance used to generate unique identifiers for the objects. - */ - private val random = Random(0) - - /** - * A mapping from host to host view. - */ - private val hostToView = mutableMapOf() - - /** - * The available hypervisors. - */ - private val availableHosts: MutableSet = mutableSetOf() - - /** - * The servers that should be launched by the service. - */ - private val queue: Deque = ArrayDeque() - - /** - * The active servers in the system. - */ - private val activeServers: MutableMap = mutableMapOf() - - /** - * The registered flavors for this compute service. - */ - internal val flavorById = mutableMapOf() - private val flavors: MutableList = mutableListOf() - - /** - * The registered images for this compute service. - */ - internal val imageById = mutableMapOf() - private val images: MutableList = mutableListOf() - - /** - * The registered servers for this compute service. - */ - private val serverById = mutableMapOf() - override val servers: MutableList = mutableListOf() - - override val hosts: Set - get() = hostToView.keys - - private val clock = dispatcher.timeSource - private var maxCores = 0 - private var maxMemory = 0L - private var _attemptsSuccess = 0L - private var _attemptsFailure = 0L - private var _attemptsError = 0L - private var _serversPending = 0 - private var _serversActive = 0 - private var isClosed = false - - /** - * The [Pacer] to use for scheduling the scheduler cycles. - */ - private val pacer = Pacer(dispatcher, schedulingQuantum.toMillis()) { doSchedule() } - - override fun newClient(): ComputeClient { - check(!isClosed) { "Service is already closed" } - return Client(this) - } - - override fun addHost(host: Host) { - // Check if host is already known - if (host in hostToView) { - return - } - - val hv = HostView(host) - maxCores = max(maxCores, host.model.cpuCount) - maxMemory = max(maxMemory, host.model.memoryCapacity) - hostToView[host] = hv - - if (host.state == HostState.UP) { - availableHosts += hv - } - - scheduler.addHost(hv) - host.addListener(this) - } - - override fun removeHost(host: Host) { - val view = hostToView.remove(host) - if (view != null) { - availableHosts.remove(view) - scheduler.removeHost(view) - host.removeListener(this) - } - } - - override fun lookupHost(server: Server): Host? { - if (server is InternalServer) { - return server.host - } - - val internal = requireNotNull(serverById[server.uid]) { "Invalid server passed to lookupHost" } - return internal.host - } - - override fun close() { - if (isClosed) { - return - } - - isClosed = true - pacer.cancel() - } - - override fun getSchedulerStats(): SchedulerStats { - return SchedulerStats( - availableHosts.size, - hostToView.size - availableHosts.size, - _attemptsSuccess, - _attemptsFailure, - _attemptsError, - servers.size, - _serversPending, - _serversActive - ) - } - - internal fun schedule(server: InternalServer): SchedulingRequest { - logger.debug { "Enqueueing server ${server.uid} to be assigned to host." } - val now = clock.millis() - val request = SchedulingRequest(server, now) - - server.launchedAt = Instant.ofEpochMilli(now) - queue.add(request) - _serversPending++ - requestSchedulingCycle() - return request - } - - internal fun delete(flavor: InternalFlavor) { - flavorById.remove(flavor.uid) - flavors.remove(flavor) - } - - internal fun delete(image: InternalImage) { - imageById.remove(image.uid) - images.remove(image) - } - - internal fun delete(server: InternalServer) { - serverById.remove(server.uid) - servers.remove(server) - } - - /** - * Indicate that a new scheduling cycle is needed due to a change to the service's state. - */ - private fun requestSchedulingCycle() { - // Bail out in case the queue is empty. - if (queue.isEmpty()) { - return - } - - pacer.enqueue() - } - - /** - * Run a single scheduling iteration. - */ - private fun doSchedule() { - while (queue.isNotEmpty()) { - val request = queue.peek() - - if (request.isCancelled) { - queue.poll() - _serversPending-- - continue - } - - val server = request.server - val hv = scheduler.select(request.server) - if (hv == null || !hv.host.canFit(server)) { - logger.trace { "Server $server selected for scheduling but no capacity available for it at the moment" } - - if (server.flavor.memorySize > maxMemory || server.flavor.cpuCount > maxCores) { - // Remove the incoming image - queue.poll() - _serversPending-- - _attemptsFailure++ - - logger.warn { "Failed to spawn $server: does not fit [${clock.instant()}]" } - - server.state = ServerState.TERMINATED - continue - } else { - break - } - } - - val host = hv.host - - // Remove request from queue - queue.poll() - _serversPending-- - - logger.info { "Assigned server $server to host $host." } - - try { - server.host = host - - host.spawn(server) - host.start(server) - - _serversActive++ - _attemptsSuccess++ - - hv.instanceCount++ - hv.provisionedCores += server.flavor.cpuCount - hv.availableMemory -= server.flavor.memorySize - - activeServers[server] = host - } catch (e: Throwable) { - logger.error(e) { "Failed to deploy VM" } - _attemptsError++ - } - } - } - - /** - * A request to schedule an [InternalServer] onto one of the [Host]s. - */ - internal data class SchedulingRequest(val server: InternalServer, val submitTime: Long) { - /** - * A flag to indicate that the request is cancelled. - */ - var isCancelled: Boolean = false - } - - override fun onStateChanged(host: Host, newState: HostState) { - when (newState) { - HostState.UP -> { - logger.debug { "[${clock.instant()}] Host ${host.uid} state changed: $newState" } - - val hv = hostToView[host] - if (hv != null) { - // Corner case for when the hypervisor already exists - availableHosts += hv - } - - // Re-schedule on the new machine - requestSchedulingCycle() - } - else -> { - logger.debug { "[${clock.instant()}] Host ${host.uid} state changed: $newState" } - - val hv = hostToView[host] ?: return - availableHosts -= hv - - requestSchedulingCycle() - } - } - } - - override fun onStateChanged(host: Host, server: Server, newState: ServerState) { - require(server is InternalServer) { "Invalid server type passed to service" } - - if (server.host != host) { - // This can happen when a server is rescheduled and started on another machine, while being deleted from - // the old machine. - return - } - - server.state = newState - - if (newState == ServerState.TERMINATED || newState == ServerState.DELETED) { - logger.info { "[${clock.instant()}] Server ${server.uid} ${server.name} ${server.flavor} finished." } - - if (activeServers.remove(server) != null) { - _serversActive-- - } - - val hv = hostToView[host] - if (hv != null) { - hv.provisionedCores -= server.flavor.cpuCount - hv.instanceCount-- - hv.availableMemory += server.flavor.memorySize - } else { - logger.error { "Unknown host $host" } - } - - // Try to reschedule if needed - requestSchedulingCycle() - } - } - - /** - * Implementation of [ComputeClient] using a [ComputeService]. - */ - private class Client(private val service: ComputeServiceImpl) : ComputeClient { - private var isClosed: Boolean = false - - override fun queryFlavors(): List { - check(!isClosed) { "Client is already closed" } - - return service.flavors.toList() - } - - override fun findFlavor(id: UUID): Flavor? { - check(!isClosed) { "Client is already closed" } - - return service.flavorById[id] - } - - override fun newFlavor( - name: String, - cpuCount: Int, - memorySize: Long, - labels: Map, - meta: Map - ): Flavor { - check(!isClosed) { "Client is already closed" } - - val service = service - val uid = UUID(service.clock.millis(), service.random.nextLong()) - val flavor = InternalFlavor( - service, - uid, - name, - cpuCount, - memorySize, - labels, - meta - ) - - service.flavorById[uid] = flavor - service.flavors.add(flavor) - - return flavor - } - - override fun queryImages(): List { - check(!isClosed) { "Client is already closed" } - - return service.images.toList() - } - - override fun findImage(id: UUID): Image? { - check(!isClosed) { "Client is already closed" } - - return service.imageById[id] - } - - override fun newImage(name: String, labels: Map, meta: Map): Image { - check(!isClosed) { "Client is already closed" } - - val service = service - val uid = UUID(service.clock.millis(), service.random.nextLong()) - val image = InternalImage(service, uid, name, labels, meta) - - service.imageById[uid] = image - service.images.add(image) - - return image - } - - override fun newServer( - name: String, - image: Image, - flavor: Flavor, - labels: Map, - meta: Map, - start: Boolean - ): Server { - check(!isClosed) { "Client is closed" } - - val service = service - val uid = UUID(service.clock.millis(), service.random.nextLong()) - val server = InternalServer( - service, - uid, - name, - requireNotNull(service.flavorById[flavor.uid]) { "Unknown flavor" }, - requireNotNull(service.imageById[image.uid]) { "Unknown image" }, - labels.toMutableMap(), - meta.toMutableMap() - ) - - service.serverById[uid] = server - service.servers.add(server) - - if (start) { - server.start() - } - - return server - } - - override fun findServer(id: UUID): Server? { - check(!isClosed) { "Client is already closed" } - - return service.serverById[id] - } - - override fun queryServers(): List { - check(!isClosed) { "Client is already closed" } - - return service.servers.toList() - } - - override fun close() { - isClosed = true - } - - override fun toString(): String = "ComputeService.Client" - } -} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/HostView.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/HostView.kt deleted file mode 100644 index 0876209a..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/HostView.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.internal - -import org.opendc.compute.service.ComputeService -import org.opendc.compute.service.driver.Host -import java.util.UUID - -/** - * A view of a [Host] as seen from the [ComputeService] - */ -public class HostView(public val host: Host) { - /** - * The unique identifier of the host. - */ - public val uid: UUID - get() = host.uid - - public var instanceCount: Int = 0 - public var availableMemory: Long = host.model.memoryCapacity - public var provisionedCores: Int = 0 - - override fun toString(): String = "HostView[host=$host]" -} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalFlavor.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalFlavor.kt deleted file mode 100644 index 077ec865..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalFlavor.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.internal - -import org.opendc.compute.api.Flavor -import java.util.UUID - -/** - * Internal stateful representation of a [Flavor]. - */ -internal class InternalFlavor( - private val service: ComputeServiceImpl, - override val uid: UUID, - name: String, - cpuCount: Int, - memorySize: Long, - labels: Map, - meta: Map -) : Flavor { - override var name: String = name - private set - - override var cpuCount: Int = cpuCount - private set - - override var memorySize: Long = memorySize - private set - - override val labels: MutableMap = labels.toMutableMap() - - override val meta: MutableMap = meta.toMutableMap() - - override fun reload() { - // No-op: this object is the source-of-truth - } - - override fun delete() { - service.delete(this) - } - - override fun equals(other: Any?): Boolean = other is Flavor && uid == other.uid - - override fun hashCode(): Int = uid.hashCode() - - override fun toString(): String = "Flavor[uid=$uid,name=$name]" -} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalImage.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalImage.kt deleted file mode 100644 index b27ef33a..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalImage.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.internal - -import org.opendc.compute.api.Image -import java.util.UUID - -/** - * Internal stateful representation of an [Image]. - */ -internal class InternalImage( - private val service: ComputeServiceImpl, - override val uid: UUID, - override val name: String, - labels: Map, - meta: Map -) : Image { - - override val labels: MutableMap = labels.toMutableMap() - - override val meta: MutableMap = meta.toMutableMap() - - override fun reload() { - // No-op: this object is the source-of-truth - } - - override fun delete() { - service.delete(this) - } - - override fun equals(other: Any?): Boolean = other is Image && uid == other.uid - - override fun hashCode(): Int = uid.hashCode() - - override fun toString(): String = "Image[uid=$uid,name=$name]" -} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt deleted file mode 100644 index c1353ba1..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.internal - -import mu.KotlinLogging -import org.opendc.compute.api.Server -import org.opendc.compute.api.ServerState -import org.opendc.compute.api.ServerWatcher -import org.opendc.compute.service.driver.Host -import java.time.Instant -import java.util.UUID - -/** - * Internal implementation of the [Server] interface. - */ -internal class InternalServer( - private val service: ComputeServiceImpl, - override val uid: UUID, - override val name: String, - override val flavor: InternalFlavor, - override val image: InternalImage, - override val labels: MutableMap, - override val meta: MutableMap -) : Server { - /** - * The logger instance of this server. - */ - private val logger = KotlinLogging.logger {} - - /** - * The watchers of this server object. - */ - private val watchers = mutableListOf() - - /** - * The [Host] that has been assigned to host the server. - */ - @JvmField internal var host: Host? = null - - /** - * The most recent timestamp when the server entered a provisioning state. - */ - override var launchedAt: Instant? = null - - /** - * The current scheduling request. - */ - private var request: ComputeServiceImpl.SchedulingRequest? = null - - override fun start() { - when (state) { - ServerState.RUNNING -> { - logger.debug { "User tried to start server but server is already running" } - return - } - ServerState.PROVISIONING -> { - logger.debug { "User tried to start server but request is already pending: doing nothing" } - return - } - ServerState.DELETED -> { - logger.warn { "User tried to start terminated server" } - throw IllegalStateException("Server is terminated") - } - else -> { - logger.info { "User requested to start server $uid" } - state = ServerState.PROVISIONING - assert(request == null) { "Scheduling request already active" } - request = service.schedule(this) - } - } - } - - override fun stop() { - when (state) { - ServerState.PROVISIONING -> { - cancelProvisioningRequest() - state = ServerState.TERMINATED - } - ServerState.RUNNING, ServerState.ERROR -> { - val host = checkNotNull(host) { "Server not running" } - host.stop(this) - } - ServerState.TERMINATED, ServerState.DELETED -> {} // No work needed - } - } - - override fun delete() { - when (state) { - ServerState.PROVISIONING, ServerState.TERMINATED -> { - cancelProvisioningRequest() - service.delete(this) - state = ServerState.DELETED - } - ServerState.RUNNING, ServerState.ERROR -> { - val host = checkNotNull(host) { "Server not running" } - host.delete(this) - service.delete(this) - state = ServerState.DELETED - } - else -> {} // No work needed - } - } - - override fun watch(watcher: ServerWatcher) { - watchers += watcher - } - - override fun unwatch(watcher: ServerWatcher) { - watchers -= watcher - } - - override fun reload() { - // No-op: this object is the source-of-truth - } - - override var state: ServerState = ServerState.TERMINATED - set(value) { - if (value != field) { - watchers.forEach { it.onStateChanged(this, value) } - } - - field = value - } - - /** - * Cancel the provisioning request if active. - */ - private fun cancelProvisioningRequest() { - val request = request - if (request != null) { - this.request = null - request.isCancelled = true - } - } - - override fun equals(other: Any?): Boolean = other is Server && uid == other.uid - - override fun hashCode(): Int = uid.hashCode() - - override fun toString(): String = "Server[uid=$uid,state=$state]" -} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/ComputeScheduler.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/ComputeScheduler.kt index a2ab3a2e..0ccaf991 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/ComputeScheduler.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/ComputeScheduler.kt @@ -24,7 +24,7 @@ package org.opendc.compute.service.scheduler import org.opendc.compute.api.Server import org.opendc.compute.service.ComputeService -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView /** * A generic scheduler interface used by the [ComputeService] to select hosts to place [Server]s on. diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/FilterScheduler.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/FilterScheduler.kt index 0840ba7e..18a319e9 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/FilterScheduler.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/FilterScheduler.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView import org.opendc.compute.service.scheduler.filters.HostFilter import org.opendc.compute.service.scheduler.weights.HostWeigher import java.util.SplittableRandom diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/ReplayScheduler.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/ReplayScheduler.kt index 284c1f91..4339b3de 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/ReplayScheduler.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/ReplayScheduler.kt @@ -24,7 +24,7 @@ package org.opendc.compute.service.scheduler import mu.KotlinLogging import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView /** * Policy replaying VM-cluster assignment. diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/ComputeFilter.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/ComputeFilter.kt index fb842415..b562f838 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/ComputeFilter.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/ComputeFilter.kt @@ -23,8 +23,8 @@ package org.opendc.compute.service.scheduler.filters import org.opendc.compute.api.Server +import org.opendc.compute.service.HostView import org.opendc.compute.service.driver.HostState -import org.opendc.compute.service.internal.HostView /** * A [HostFilter] that filters on active hosts. diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/DifferentHostFilter.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/DifferentHostFilter.kt index f6736ac0..4a9f41c5 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/DifferentHostFilter.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/DifferentHostFilter.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler.filters import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView import java.util.UUID /** diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/HostFilter.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/HostFilter.kt index 9e909ca6..78010fee 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/HostFilter.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/HostFilter.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler.filters import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView import org.opendc.compute.service.scheduler.FilterScheduler /** diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/InstanceCountFilter.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/InstanceCountFilter.kt index ed6674b1..5aa38a88 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/InstanceCountFilter.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/InstanceCountFilter.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler.filters import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView /** * A [HostFilter] that filters hosts based on the number of instances on the host. diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/RamFilter.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/RamFilter.kt index 8a7a646c..275e8f1c 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/RamFilter.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/RamFilter.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler.filters import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView /** * A [HostFilter] that filters hosts based on the memory requirements of a [Server] and the RAM available on the host. diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/SameHostFilter.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/SameHostFilter.kt index 090e1437..c3753866 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/SameHostFilter.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/SameHostFilter.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler.filters import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView import java.util.UUID /** diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/VCpuCapacityFilter.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/VCpuCapacityFilter.kt index 791710c8..d4dff76b 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/VCpuCapacityFilter.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/VCpuCapacityFilter.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler.filters import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView /** * A [HostFilter] that filters hosts based on the vCPU speed requirements of a [Server] and the available diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/VCpuFilter.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/VCpuFilter.kt index abdd79f1..448a6189 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/VCpuFilter.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/VCpuFilter.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler.filters import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView /** * A [HostFilter] that filters hosts based on the vCPU requirements of a [Server] and the available vCPUs on the host. diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/CoreRamWeigher.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/CoreRamWeigher.kt index d668fdaf..f79d6d88 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/CoreRamWeigher.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/CoreRamWeigher.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler.weights import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView /** * A [HostWeigher] that weighs the hosts based on the available memory per core on the host. diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/HostWeigher.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/HostWeigher.kt index aa8a9d53..01799122 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/HostWeigher.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/HostWeigher.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler.weights import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView import org.opendc.compute.service.scheduler.FilterScheduler /** diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/InstanceCountWeigher.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/InstanceCountWeigher.kt index 732cbe03..bfb583a2 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/InstanceCountWeigher.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/InstanceCountWeigher.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler.weights import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView /** * A [HostWeigher] that weighs the hosts based on the number of instances on the host. diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/RamWeigher.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/RamWeigher.kt index d18d31f4..bb837fbe 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/RamWeigher.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/RamWeigher.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler.weights import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView /** * A [HostWeigher] that weighs the hosts based on the available RAM (memory) on the host. diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/VCpuCapacityWeigher.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/VCpuCapacityWeigher.kt index a86226e2..f15f60c9 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/VCpuCapacityWeigher.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/VCpuCapacityWeigher.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler.weights import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView /** * A [HostWeigher] that weighs the hosts based on the difference required vCPU capacity and the available CPU capacity. diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/VCpuWeigher.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/VCpuWeigher.kt index 4a22269b..169ad8cb 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/VCpuWeigher.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/VCpuWeigher.kt @@ -23,7 +23,7 @@ package org.opendc.compute.service.scheduler.weights import org.opendc.compute.api.Server -import org.opendc.compute.service.internal.HostView +import org.opendc.compute.service.HostView /** * A [HostWeigher] that weighs the hosts based on the remaining number of vCPUs available. diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/telemetry/SchedulerStats.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/telemetry/SchedulerStats.kt deleted file mode 100644 index 6e9f458a..00000000 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/telemetry/SchedulerStats.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service.telemetry - -import org.opendc.compute.service.ComputeService - -/** - * Statistics about the scheduling component of the [ComputeService]. - * - * @property hostsAvailable The number of hosts currently available for scheduling. - * @property hostsUnavailable The number of hosts unavailable for scheduling. - * @property attemptsSuccess Scheduling attempts that resulted into an allocation onto a host. - * @property attemptsFailure The number of failed scheduling attempt due to insufficient capacity at the moment. - * @property attemptsError The number of scheduling attempts that failed due to system error. - * @property serversTotal The number of servers registered with the service. - * @property serversPending The number of servers that are pending to be scheduled. - * @property serversActive The number of servers that are currently managed by the service and running. - */ -public data class SchedulerStats( - val hostsAvailable: Int, - val hostsUnavailable: Int, - val attemptsSuccess: Long, - val attemptsFailure: Long, - val attemptsError: Long, - val serversTotal: Int, - val serversPending: Int, - val serversActive: Int -) diff --git a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt index a64c0885..4dc1cfa8 100644 --- a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt +++ b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt @@ -50,6 +50,7 @@ import org.opendc.compute.service.scheduler.filters.VCpuFilter import org.opendc.compute.service.scheduler.weights.RamWeigher import org.opendc.simulator.kotlin.SimulationCoroutineScope import org.opendc.simulator.kotlin.runSimulation +import java.time.Duration import java.util.UUID /** @@ -66,7 +67,7 @@ internal class ComputeServiceTest { filters = listOf(ComputeFilter(), VCpuFilter(allocationRatio = 1.0), RamFilter(allocationRatio = 1.0)), weighers = listOf(RamWeigher()) ) - service = ComputeService(scope.dispatcher, computeScheduler) + service = ComputeService(scope.dispatcher, computeScheduler, Duration.ofMinutes(5)) } @Test @@ -299,25 +300,6 @@ internal class ComputeServiceTest { verify(exactly = 0) { host.canFit(server) } } - @Test - fun testServerInvalidType() = scope.runSimulation { - val host = mockk(relaxUnitFun = true) - val server = mockk(relaxUnitFun = true) - val listeners = mutableListOf() - - every { host.uid } returns UUID.randomUUID() - every { host.model } returns HostModel(4 * 2600.0, 4, 2048) - every { host.state } returns HostState.UP - every { host.canFit(any()) } returns true - every { host.addListener(any()) } answers { listeners.add(it.invocation.args[0] as HostListener) } - - service.addHost(host) - - assertThrows { - listeners.forEach { it.onStateChanged(host, server, ServerState.RUNNING) } - } - } - @Test fun testServerDeploy() = scope.runSimulation { val host = mockk(relaxUnitFun = true) diff --git a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalFlavorTest.kt b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalFlavorTest.kt deleted file mode 100644 index fe92f7f2..00000000 --- a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalFlavorTest.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service - -import io.mockk.every -import io.mockk.mockk -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNotEquals -import org.junit.jupiter.api.Test -import org.opendc.compute.api.Flavor -import org.opendc.compute.service.internal.ComputeServiceImpl -import org.opendc.compute.service.internal.InternalFlavor -import java.util.UUID - -/** - * Test suite for the [InternalFlavor] implementation. - */ -class InternalFlavorTest { - @Test - fun testEquality() { - val service = mockk() - val uid = UUID.randomUUID() - val a = InternalFlavor(service, uid, "test", 1, 1024, mutableMapOf(), mutableMapOf()) - val b = InternalFlavor(service, uid, "test", 1, 1024, mutableMapOf(), mutableMapOf()) - - assertEquals(a, b) - } - - @Test - fun testEqualityWithDifferentType() { - val service = mockk() - val uid = UUID.randomUUID() - val a = InternalFlavor(service, uid, "test", 1, 1024, mutableMapOf(), mutableMapOf()) - - val b = mockk(relaxUnitFun = true) - every { b.uid } returns uid - - assertEquals(a, b) - } - - @Test - fun testInequalityWithDifferentType() { - val service = mockk() - val uid = UUID.randomUUID() - val a = InternalFlavor(service, uid, "test", 1, 1024, mutableMapOf(), mutableMapOf()) - - val b = mockk(relaxUnitFun = true) - every { b.uid } returns UUID.randomUUID() - - assertNotEquals(a, b) - } - - @Test - fun testInequalityWithIncorrectType() { - val service = mockk() - val uid = UUID.randomUUID() - val a = InternalFlavor(service, uid, "test", 1, 1024, mutableMapOf(), mutableMapOf()) - - assertNotEquals(a, Unit) - } -} diff --git a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalImageTest.kt b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalImageTest.kt deleted file mode 100644 index d60aa628..00000000 --- a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalImageTest.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service - -import io.mockk.every -import io.mockk.mockk -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNotEquals -import org.junit.jupiter.api.Test -import org.opendc.compute.api.Image -import org.opendc.compute.service.internal.ComputeServiceImpl -import org.opendc.compute.service.internal.InternalFlavor -import org.opendc.compute.service.internal.InternalImage -import java.util.UUID - -/** - * Test suite for the [InternalFlavor] implementation. - */ -class InternalImageTest { - @Test - fun testEquality() { - val service = mockk() - val uid = UUID.randomUUID() - val a = InternalImage(service, uid, "test", mutableMapOf(), mutableMapOf()) - val b = InternalImage(service, uid, "test", mutableMapOf(), mutableMapOf()) - - assertEquals(a, b) - } - - @Test - fun testEqualityWithDifferentType() { - val service = mockk() - val uid = UUID.randomUUID() - val a = InternalImage(service, uid, "test", mutableMapOf(), mutableMapOf()) - - val b = mockk(relaxUnitFun = true) - every { b.uid } returns uid - - assertEquals(a, b) - } - - @Test - fun testInequalityWithDifferentType() { - val service = mockk() - val uid = UUID.randomUUID() - val a = InternalImage(service, uid, "test", mutableMapOf(), mutableMapOf()) - - val b = mockk(relaxUnitFun = true) - every { b.uid } returns UUID.randomUUID() - - assertNotEquals(a, b) - } - - @Test - fun testInequalityWithIncorrectType() { - val service = mockk() - val uid = UUID.randomUUID() - val a = InternalImage(service, uid, "test", mutableMapOf(), mutableMapOf()) - - assertNotEquals(a, Unit) - } -} diff --git a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalServerTest.kt b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalServerTest.kt deleted file mode 100644 index 05a8160e..00000000 --- a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalServerTest.kt +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.compute.service - -import io.mockk.coVerify -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify -import kotlinx.coroutines.yield -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNotEquals -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.opendc.compute.api.Server -import org.opendc.compute.api.ServerState -import org.opendc.compute.service.driver.Host -import org.opendc.compute.service.internal.ComputeServiceImpl -import org.opendc.compute.service.internal.InternalFlavor -import org.opendc.compute.service.internal.InternalImage -import org.opendc.compute.service.internal.InternalServer -import org.opendc.simulator.kotlin.runSimulation -import java.util.UUID - -/** - * Test suite for the [InternalServer] implementation. - */ -class InternalServerTest { - @Test - fun testEquality() { - val service = mockk() - val uid = UUID.randomUUID() - val flavor = mockFlavor() - val image = mockImage() - - val a = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) - val b = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) - - assertEquals(a, b) - } - - @Test - fun testEqualityWithDifferentType() { - val service = mockk() - val uid = UUID.randomUUID() - val flavor = mockFlavor() - val image = mockImage() - val a = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) - - val b = mockk(relaxUnitFun = true) - every { b.uid } returns uid - - assertEquals(a, b) - } - - @Test - fun testInequalityWithDifferentType() { - val service = mockk() - val uid = UUID.randomUUID() - val flavor = mockFlavor() - val image = mockImage() - val a = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) - - val b = mockk(relaxUnitFun = true) - every { b.uid } returns UUID.randomUUID() - - assertNotEquals(a, b) - } - - @Test - fun testInequalityWithIncorrectType() { - val service = mockk() - val uid = UUID.randomUUID() - val flavor = mockFlavor() - val image = mockImage() - val a = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) - - assertNotEquals(a, Unit) - } - - @Test - fun testStartTerminatedServer() = runSimulation { - val service = mockk() - val uid = UUID.randomUUID() - val flavor = mockFlavor() - val image = mockImage() - val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) - - every { service.schedule(any()) } answers { ComputeServiceImpl.SchedulingRequest(it.invocation.args[0] as InternalServer, 0) } - - server.start() - - verify(exactly = 1) { service.schedule(server) } - assertEquals(ServerState.PROVISIONING, server.state) - } - - @Test - fun testStartDeletedServer() = runSimulation { - val service = mockk() - val uid = UUID.randomUUID() - val flavor = mockFlavor() - val image = mockImage() - val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) - - server.state = ServerState.DELETED - - assertThrows { server.start() } - } - - @Test - fun testStartProvisioningServer() = runSimulation { - val service = mockk() - val uid = UUID.randomUUID() - val flavor = mockFlavor() - val image = mockImage() - val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) - - server.state = ServerState.PROVISIONING - - server.start() - - assertEquals(ServerState.PROVISIONING, server.state) - } - - @Test - fun testStartRunningServer() = runSimulation { - val service = mockk() - val uid = UUID.randomUUID() - val flavor = mockFlavor() - val image = mockImage() - val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) - - server.state = ServerState.RUNNING - - server.start() - - assertEquals(ServerState.RUNNING, server.state) - } - - @Test - fun testStopProvisioningServer() = runSimulation { - val service = mockk() - val uid = UUID.randomUUID() - val flavor = mockFlavor() - val image = mockImage() - val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) - val request = ComputeServiceImpl.SchedulingRequest(server, 0) - - every { service.schedule(any()) } returns request - - server.start() - server.stop() - - assertTrue(request.isCancelled) - assertEquals(ServerState.TERMINATED, server.state) - } - - @Test - fun testStopTerminatedServer() = runSimulation { - val service = mockk() - val uid = UUID.randomUUID() - val flavor = mockFlavor() - val image = mockImage() - val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) - - server.state = ServerState.TERMINATED - server.stop() - - assertEquals(ServerState.TERMINATED, server.state) - } - - @Test - fun testStopDeletedServer() = runSimulation { - val service = mockk() - val uid = UUID.randomUUID() - val flavor = mockFlavor() - val image = mockImage() - val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) - - server.state = ServerState.DELETED - server.stop() - - assertEquals(ServerState.DELETED, server.state) - } - - @Test - fun testStopRunningServer() = runSimulation { - val service = mockk() - val uid = UUID.randomUUID() - val flavor = mockFlavor() - val image = mockImage() - val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) - val host = mockk(relaxUnitFun = true) - - server.state = ServerState.RUNNING - server.host = host - server.stop() - yield() - - coVerify { host.stop(server) } - } - - @Test - fun testDeleteProvisioningServer() = runSimulation { - val service = mockk(relaxUnitFun = true) - val uid = UUID.randomUUID() - val flavor = mockFlavor() - val image = mockImage() - val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) - val request = ComputeServiceImpl.SchedulingRequest(server, 0) - - every { service.schedule(any()) } returns request - - server.start() - server.delete() - - assertTrue(request.isCancelled) - assertEquals(ServerState.DELETED, server.state) - verify { service.delete(server) } - } - - @Test - fun testDeleteTerminatedServer() = runSimulation { - val service = mockk(relaxUnitFun = true) - val uid = UUID.randomUUID() - val flavor = mockFlavor() - val image = mockImage() - val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) - - server.state = ServerState.TERMINATED - server.delete() - - assertEquals(ServerState.DELETED, server.state) - - verify { service.delete(server) } - } - - @Test - fun testDeleteDeletedServer() = runSimulation { - val service = mockk(relaxUnitFun = true) - val uid = UUID.randomUUID() - val flavor = mockFlavor() - val image = mockImage() - val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) - - server.state = ServerState.DELETED - server.delete() - - assertEquals(ServerState.DELETED, server.state) - } - - @Test - fun testDeleteRunningServer() = runSimulation { - val service = mockk(relaxUnitFun = true) - val uid = UUID.randomUUID() - val flavor = mockFlavor() - val image = mockImage() - val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) - val host = mockk(relaxUnitFun = true) - - server.state = ServerState.RUNNING - server.host = host - server.delete() - yield() - - coVerify { host.delete(server) } - verify { service.delete(server) } - } - - private fun mockFlavor(): InternalFlavor { - val flavor = mockk() - every { flavor.name } returns "c5.large" - every { flavor.uid } returns UUID.randomUUID() - every { flavor.cpuCount } returns 2 - every { flavor.memorySize } returns 4096 - return flavor - } - - private fun mockImage(): InternalImage { - val image = mockk() - every { image.name } returns "ubuntu-20.04" - every { image.uid } returns UUID.randomUUID() - return image - } -} diff --git a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ServiceFlavorTest.kt b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ServiceFlavorTest.kt new file mode 100644 index 00000000..7938f789 --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ServiceFlavorTest.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service + +import io.mockk.every +import io.mockk.mockk +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Test +import org.opendc.compute.api.Flavor +import java.util.UUID + +/** + * Test suite for the [ServiceFlavor] implementation. + */ +class ServiceFlavorTest { + @Test + fun testEquality() { + val service = mockk() + val uid = UUID.randomUUID() + val a = ServiceFlavor(service, uid, "test", 1, 1024, mutableMapOf(), mutableMapOf()) + val b = ServiceFlavor(service, uid, "test", 1, 1024, mutableMapOf(), mutableMapOf()) + + assertEquals(a, b) + } + + @Test + fun testInequalityWithDifferentType() { + val service = mockk() + val uid = UUID.randomUUID() + val a = ServiceFlavor(service, uid, "test", 1, 1024, mutableMapOf(), mutableMapOf()) + + val b = mockk(relaxUnitFun = true) + every { b.uid } returns UUID.randomUUID() + + assertNotEquals(a, b) + } + + @Test + fun testInequalityWithIncorrectType() { + val service = mockk() + val uid = UUID.randomUUID() + val a = ServiceFlavor(service, uid, "test", 1, 1024, mutableMapOf(), mutableMapOf()) + + assertNotEquals(a, Unit) + } +} diff --git a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ServiceImageTest.kt b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ServiceImageTest.kt new file mode 100644 index 00000000..c36d75f4 --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ServiceImageTest.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service + +import io.mockk.every +import io.mockk.mockk +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Test +import org.opendc.compute.api.Image +import java.util.UUID + +/** + * Test suite for the [ServiceFlavor] implementation. + */ +class ServiceImageTest { + @Test + fun testEquality() { + val service = mockk() + val uid = UUID.randomUUID() + val a = ServiceImage(service, uid, "test", mutableMapOf(), mutableMapOf()) + val b = ServiceImage(service, uid, "test", mutableMapOf(), mutableMapOf()) + + assertEquals(a, b) + } + + @Test + fun testInequalityWithDifferentType() { + val service = mockk() + val uid = UUID.randomUUID() + val a = ServiceImage(service, uid, "test", mutableMapOf(), mutableMapOf()) + + val b = mockk(relaxUnitFun = true) + every { b.uid } returns UUID.randomUUID() + + assertNotEquals(a, b) + } + + @Test + fun testInequalityWithIncorrectType() { + val service = mockk() + val uid = UUID.randomUUID() + val a = ServiceImage(service, uid, "test", mutableMapOf(), mutableMapOf()) + + assertNotEquals(a, Unit) + } +} diff --git a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ServiceServerTest.kt b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ServiceServerTest.kt new file mode 100644 index 00000000..f9fcd27b --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ServiceServerTest.kt @@ -0,0 +1,286 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.yield +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.opendc.compute.api.Server +import org.opendc.compute.api.ServerState +import org.opendc.compute.service.driver.Host +import org.opendc.simulator.kotlin.runSimulation +import java.util.UUID + +/** + * Test suite for the [ServiceServer] implementation. + */ +class ServiceServerTest { + @Test + fun testEquality() { + val service = mockk() + val uid = UUID.randomUUID() + val flavor = mockFlavor() + val image = mockImage() + + val a = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + val b = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + + assertEquals(a, b) + } + + @Test + fun testInequalityWithDifferentType() { + val service = mockk() + val uid = UUID.randomUUID() + val flavor = mockFlavor() + val image = mockImage() + val a = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + + val b = mockk(relaxUnitFun = true) + every { b.uid } returns UUID.randomUUID() + + assertNotEquals(a, b) + } + + @Test + fun testInequalityWithIncorrectType() { + val service = mockk() + val uid = UUID.randomUUID() + val flavor = mockFlavor() + val image = mockImage() + val a = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + + assertNotEquals(a, Unit) + } + + @Test + fun testStartTerminatedServer() = runSimulation { + val service = mockk() + val uid = UUID.randomUUID() + val flavor = mockFlavor() + val image = mockImage() + val server = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + + every { service.schedule(any()) } answers { ComputeService.SchedulingRequest(it.invocation.args[0] as ServiceServer, 0) } + + server.start() + + verify(exactly = 1) { service.schedule(server) } + assertEquals(ServerState.PROVISIONING, server.state) + } + + @Test + fun testStartDeletedServer() = runSimulation { + val service = mockk() + val uid = UUID.randomUUID() + val flavor = mockFlavor() + val image = mockImage() + val server = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + + server.setState(ServerState.DELETED) + + assertThrows { server.start() } + } + + @Test + fun testStartProvisioningServer() = runSimulation { + val service = mockk() + val uid = UUID.randomUUID() + val flavor = mockFlavor() + val image = mockImage() + val server = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + + server.setState(ServerState.PROVISIONING) + + server.start() + + assertEquals(ServerState.PROVISIONING, server.state) + } + + @Test + fun testStartRunningServer() = runSimulation { + val service = mockk() + val uid = UUID.randomUUID() + val flavor = mockFlavor() + val image = mockImage() + val server = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + + server.setState(ServerState.RUNNING) + + server.start() + + assertEquals(ServerState.RUNNING, server.state) + } + + @Test + fun testStopProvisioningServer() = runSimulation { + val service = mockk() + val uid = UUID.randomUUID() + val flavor = mockFlavor() + val image = mockImage() + val server = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + val request = ComputeService.SchedulingRequest(server, 0) + + every { service.schedule(any()) } returns request + + server.start() + server.stop() + + assertTrue(request.isCancelled) + assertEquals(ServerState.TERMINATED, server.state) + } + + @Test + fun testStopTerminatedServer() = runSimulation { + val service = mockk() + val uid = UUID.randomUUID() + val flavor = mockFlavor() + val image = mockImage() + val server = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + + server.setState(ServerState.TERMINATED) + server.stop() + + assertEquals(ServerState.TERMINATED, server.state) + } + + @Test + fun testStopDeletedServer() = runSimulation { + val service = mockk() + val uid = UUID.randomUUID() + val flavor = mockFlavor() + val image = mockImage() + val server = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + + server.setState(ServerState.DELETED) + server.stop() + + assertEquals(ServerState.DELETED, server.state) + } + + @Test + fun testStopRunningServer() = runSimulation { + val service = mockk() + val uid = UUID.randomUUID() + val flavor = mockFlavor() + val image = mockImage() + val server = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + val host = mockk(relaxUnitFun = true) + + server.setState(ServerState.RUNNING) + server.host = host + server.stop() + yield() + + verify { host.stop(server) } + } + + @Test + fun testDeleteProvisioningServer() = runSimulation { + val service = mockk(relaxUnitFun = true) + val uid = UUID.randomUUID() + val flavor = mockFlavor() + val image = mockImage() + val server = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + val request = ComputeService.SchedulingRequest(server, 0) + + every { service.schedule(any()) } returns request + + server.start() + server.delete() + + assertTrue(request.isCancelled) + assertEquals(ServerState.DELETED, server.state) + verify { service.delete(server) } + } + + @Test + fun testDeleteTerminatedServer() = runSimulation { + val service = mockk(relaxUnitFun = true) + val uid = UUID.randomUUID() + val flavor = mockFlavor() + val image = mockImage() + val server = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + + server.setState(ServerState.TERMINATED) + server.delete() + + assertEquals(ServerState.DELETED, server.state) + + verify { service.delete(server) } + } + + @Test + fun testDeleteDeletedServer() = runSimulation { + val service = mockk(relaxUnitFun = true) + val uid = UUID.randomUUID() + val flavor = mockFlavor() + val image = mockImage() + val server = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + + server.setState(ServerState.DELETED) + server.delete() + + assertEquals(ServerState.DELETED, server.state) + } + + @Test + fun testDeleteRunningServer() = runSimulation { + val service = mockk(relaxUnitFun = true) + val uid = UUID.randomUUID() + val flavor = mockFlavor() + val image = mockImage() + val server = ServiceServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + val host = mockk(relaxUnitFun = true) + + server.setState(ServerState.RUNNING) + server.host = host + server.delete() + yield() + + verify { host.delete(server) } + verify { service.delete(server) } + } + + private fun mockFlavor(): ServiceFlavor { + val flavor = mockk() + every { flavor.name } returns "c5.large" + every { flavor.uid } returns UUID.randomUUID() + every { flavor.cpuCount } returns 2 + every { flavor.memorySize } returns 4096 + return flavor + } + + private fun mockImage(): ServiceImage { + val image = mockk() + every { image.name } returns "ubuntu-20.04" + every { image.uid } returns UUID.randomUUID() + return image + } +} diff --git a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/scheduler/FilterSchedulerTest.kt b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/scheduler/FilterSchedulerTest.kt index 4608bf37..4af6f7ec 100644 --- a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/scheduler/FilterSchedulerTest.kt +++ b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/scheduler/FilterSchedulerTest.kt @@ -30,9 +30,9 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertAll import org.junit.jupiter.api.assertThrows import org.opendc.compute.api.Server +import org.opendc.compute.service.HostView import org.opendc.compute.service.driver.HostModel import org.opendc.compute.service.driver.HostState -import org.opendc.compute.service.internal.HostView import org.opendc.compute.service.scheduler.filters.ComputeFilter import org.opendc.compute.service.scheduler.filters.DifferentHostFilter import org.opendc.compute.service.scheduler.filters.InstanceCountFilter diff --git a/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt index a44ccc27..ec71f095 100644 --- a/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt +++ b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt @@ -65,9 +65,9 @@ import java.util.function.Supplier * @param optimize A flag to indicate to optimize the machine models of the virtual machines. */ public class SimHost( - override val uid: UUID, - override val name: String, - override val meta: Map, + private val uid: UUID, + private val name: String, + private val meta: Map, private val clock: InstantSource, private val machine: SimBareMetalMachine, private val hypervisor: SimHypervisor, @@ -87,11 +87,6 @@ public class SimHost( private val guests = HashMap() private val _guests = mutableListOf() - override val instances: Set - get() = guests.keys - - override val state: HostState - get() = _state private var _state: HostState = HostState.DOWN set(value) { if (value != field) { @@ -100,7 +95,7 @@ public class SimHost( field = value } - override val model: HostModel = HostModel( + private val model: HostModel = HostModel( machine.model.cpus.sumOf { it.frequency }, machine.model.cpus.size, machine.model.memory.sumOf { it.size } @@ -123,6 +118,30 @@ public class SimHost( launch() } + override fun getUid(): UUID { + return uid + } + + override fun getName(): String { + return name + } + + override fun getModel(): HostModel { + return model + } + + override fun getMeta(): Map { + return meta + } + + override fun getState(): HostState { + return _state + } + + override fun getInstances(): Set { + return guests.keys + } + override fun canFit(server: Server): Boolean { val sufficientMemory = model.memoryCapacity >= server.flavor.memorySize val enoughCpus = model.cpuCount >= server.flavor.cpuCount diff --git a/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/ComputeServiceProvisioningStep.kt b/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/ComputeServiceProvisioningStep.kt index d7347327..4470c418 100644 --- a/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/ComputeServiceProvisioningStep.kt +++ b/opendc-experiments/opendc-experiments-compute/src/main/kotlin/org/opendc/experiments/compute/ComputeServiceProvisioningStep.kt @@ -41,7 +41,9 @@ public class ComputeServiceProvisioningStep internal constructor( private val schedulingQuantum: Duration ) : ProvisioningStep { override fun apply(ctx: ProvisioningContext): AutoCloseable { - val service = ComputeService(ctx.dispatcher, scheduler(ctx), schedulingQuantum) + val service = ComputeService.builder(ctx.dispatcher, scheduler(ctx)) + .withQuantum(schedulingQuantum) + .build() ctx.registry.register(serviceDomain, ComputeService::class.java, service) return AutoCloseable { service.close() } -- cgit v1.2.3