From e97774dbf274fcb57b9d173f9d674a2ef1b982af Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Mon, 8 Mar 2021 22:19:37 +0100 Subject: compute: Remove use of bare-metal provisioning from compute module This change removes the usage of bare-metal provisioning from the OpenDC Compute module. This significantly simplifies the experiment setup. --- .../src/main/kotlin/org/opendc/compute/service/driver/Host.kt | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'simulator/opendc-compute/opendc-compute-service/src') diff --git a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/Host.kt b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/Host.kt index 2cd91144..060c35fd 100644 --- a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/Host.kt +++ b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/Host.kt @@ -50,6 +50,11 @@ public interface Host { */ public val state: HostState + /** + * Meta-data associated with the host. + */ + public val meta: Map + /** * The events emitted by the driver. */ -- cgit v1.2.3 From 970f5c6f653c8442ecd9b73b208a53a2dbb9a150 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Tue, 9 Mar 2021 12:57:12 +0100 Subject: compute: Add lifecycle methods for Server instances This change adds more methods for controlling the lifecycle of Server instances. --- .../org/opendc/compute/service/driver/Host.kt | 6 +- .../compute/service/internal/ClientServer.kt | 15 ++++ .../compute/service/internal/ComputeServiceImpl.kt | 99 ++++++++++++++++++---- 3 files changed, 99 insertions(+), 21 deletions(-) (limited to 'simulator/opendc-compute/opendc-compute-service/src') diff --git a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/Host.kt b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/Host.kt index 060c35fd..c3c39572 100644 --- a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/Host.kt +++ b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/Host.kt @@ -79,7 +79,7 @@ public interface Host { public operator fun contains(server: Server): Boolean /** - * Stat the server [instance][server] if it is currently not running on this host. + * 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. */ @@ -93,9 +93,9 @@ public interface Host { public suspend fun stop(server: Server) /** - * Terminate the specified [instance][server] on this host and cleanup all resources associated with it. + * Delete the specified [instance][server] on this host and cleanup all resources associated with it. */ - public suspend fun terminate(server: Server) + public suspend fun delete(server: Server) /** * Add a [HostListener] to this host. diff --git a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientServer.kt b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientServer.kt index f84b7435..e65c5f1c 100644 --- a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientServer.kt +++ b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientServer.kt @@ -52,6 +52,21 @@ internal class ClientServer(private val delegate: Server) : Server, ServerWatche override var state: ServerState = delegate.state private set + override suspend fun start() { + delegate.start() + refresh() + } + + override suspend fun stop() { + delegate.stop() + refresh() + } + + override suspend fun delete() { + delegate.delete() + refresh() + } + override fun watch(watcher: ServerWatcher) { if (watchers.isEmpty()) { delegate.watch(this) diff --git a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt index 69d6bb59..453f5d65 100644 --- a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt +++ b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt @@ -92,7 +92,7 @@ public class ComputeServiceImpl( /** * The active servers in the system. */ - private val activeServers: MutableSet = mutableSetOf() + private val activeServers: MutableMap = mutableMapOf() public var submittedVms: Int = 0 public var queuedVms: Int = 0 @@ -126,7 +126,12 @@ public class ComputeServiceImpl( override fun newClient(): ComputeClient = object : ComputeClient { private var isClosed: Boolean = false - override suspend fun newServer(name: String, image: Image, flavor: Flavor): Server { + override suspend fun newServer( + name: String, + image: Image, + flavor: Flavor, + start: Boolean + ): Server { check(!isClosed) { "Client is closed" } tracer.commit(VmSubmissionEvent(name, image, flavor)) @@ -143,11 +148,12 @@ public class ComputeServiceImpl( ) ) - return suspendCancellableCoroutine { cont -> - val request = LaunchRequest(createServer(name, image, flavor), cont) - queue += request - requestCycle() + val server = createServer(name, image, flavor) + if (start) { + server.start() } + + return ClientServer(server) } override fun close() { @@ -186,13 +192,13 @@ public class ComputeServiceImpl( private fun createServer( name: String, image: Image, - flavor: Flavor - ): Server { + flavor: Flavor, + ): ServerImpl { return ServerImpl( uid = UUID(random.nextLong(), random.nextLong()), - name = name, - flavor = flavor, - image = image + name, + flavor, + image ) } @@ -258,9 +264,9 @@ public class ComputeServiceImpl( scope.launch { try { - cont.resume(ClientServer(server)) selectedHv.host.spawn(server) - activeServers += server + cont.resume(Unit) + activeServers[server] = selectedHv.host tracer.commit(VmScheduledEvent(server.name)) _events.emit( @@ -348,9 +354,8 @@ public class ComputeServiceImpl( override fun onStateChanged(host: Host, server: Server, newState: ServerState) { val serverImpl = server as ServerImpl serverImpl.state = newState - serverImpl.watchers.forEach { it.onStateChanged(server, newState) } - if (newState == ServerState.SHUTOFF) { + if (newState == ServerState.TERMINATED || newState == ServerState.DELETED) { logger.info { "[${clock.millis()}] Server ${server.uid} ${server.name} ${server.flavor} finished." } tracer.commit(VmStoppedEvent(server.name)) @@ -385,9 +390,9 @@ public class ComputeServiceImpl( } } - public data class LaunchRequest(val server: Server, val cont: Continuation) + private data class LaunchRequest(val server: ServerImpl, val cont: Continuation) - private class ServerImpl( + private inner class ServerImpl( override val uid: UUID, override val name: String, override val flavor: Flavor, @@ -395,6 +400,57 @@ public class ComputeServiceImpl( ) : Server { val watchers = mutableListOf() + override suspend 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 IllegalArgumentException("Server is terminated") + } + else -> { + logger.info { "User requested to start server $uid" } + state = ServerState.PROVISIONING + suspendCancellableCoroutine { cont -> + val request = LaunchRequest(this, cont) + queue += request + requestCycle() + } + } + } + } + + override suspend fun stop() { + when (state) { + ServerState.PROVISIONING -> {} // TODO Find way to interrupt these + ServerState.RUNNING, ServerState.ERROR -> { + // Warn: possible race condition on activeServers + val host = checkNotNull(activeServers[this]) { "Server not running" } + host.stop(this) + } + ServerState.TERMINATED -> {} // No work needed + ServerState.DELETED -> throw IllegalStateException("Server is terminated") + } + } + + override suspend fun delete() { + when (state) { + ServerState.PROVISIONING -> {} // TODO Find way to interrupt these + ServerState.RUNNING -> { + // Warn: possible race condition on activeServers + val host = checkNotNull(activeServers[this]) { "Server not running" } + host.delete(this) + } + else -> {} // No work needed + } + } + override fun watch(watcher: ServerWatcher) { watchers += watcher } @@ -409,6 +465,13 @@ public class ComputeServiceImpl( override val tags: Map = emptyMap() - override var state: ServerState = ServerState.BUILD + override var state: ServerState = ServerState.TERMINATED + set(value) { + if (value != field) { + watchers.forEach { it.onStateChanged(this, value) } + } + + field = value + } } } -- cgit v1.2.3 From f5efde88ec95fc139e957303615c302d4aa2035d Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Tue, 9 Mar 2021 15:05:47 +0100 Subject: compute: Introduce labels and meta-data for resources This change adds the ability to define labels and meta-data for resources. This can be used in the future to identify servers and pass data between client and server. --- .../compute/service/internal/ClientServer.kt | 8 +++- .../compute/service/internal/ComputeServiceImpl.kt | 46 +++++++++------------- 2 files changed, 24 insertions(+), 30 deletions(-) (limited to 'simulator/opendc-compute/opendc-compute-service/src') diff --git a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientServer.kt b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientServer.kt index e65c5f1c..bca1ad44 100644 --- a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientServer.kt +++ b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientServer.kt @@ -46,7 +46,10 @@ internal class ClientServer(private val delegate: Server) : Server, ServerWatche override var image: Image = delegate.image private set - override var tags: Map = delegate.tags.toMap() + override var labels: Map = delegate.labels.toMap() + private set + + override var meta: Map = delegate.meta.toMap() private set override var state: ServerState = delegate.state @@ -87,7 +90,8 @@ internal class ClientServer(private val delegate: Server) : Server, ServerWatche name = delegate.name flavor = delegate.flavor image = delegate.image - tags = delegate.tags + labels = delegate.labels + meta = delegate.meta state = delegate.state } diff --git a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt index 453f5d65..0d4f379d 100644 --- a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt +++ b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt @@ -26,7 +26,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch -import kotlinx.coroutines.suspendCancellableCoroutine import mu.KotlinLogging import org.opendc.compute.api.* import org.opendc.compute.service.ComputeService @@ -41,9 +40,7 @@ import org.opendc.utils.TimerScheduler import org.opendc.utils.flow.EventFlow import java.time.Clock import java.util.* -import kotlin.coroutines.Continuation import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.resume import kotlin.math.max /** @@ -130,6 +127,8 @@ public class ComputeServiceImpl( name: String, image: Image, flavor: Flavor, + labels: Map, + meta: Map, start: Boolean ): Server { check(!isClosed) { "Client is closed" } @@ -148,7 +147,14 @@ public class ComputeServiceImpl( ) ) - val server = createServer(name, image, flavor) + val server = ServerImpl( + uid = UUID(random.nextLong(), random.nextLong()), + name, + flavor, + image, + labels.toMutableMap(), + meta.toMutableMap() + ) if (start) { server.start() } @@ -189,19 +195,6 @@ public class ComputeServiceImpl( scope.cancel() } - private fun createServer( - name: String, - image: Image, - flavor: Flavor, - ): ServerImpl { - return ServerImpl( - uid = UUID(random.nextLong(), random.nextLong()), - name, - flavor, - image - ) - } - private fun requestCycle() { // Bail out in case we have already requested a new cycle. if (scheduler.isTimerActive(Unit)) { @@ -220,7 +213,7 @@ public class ComputeServiceImpl( private fun schedule() { while (queue.isNotEmpty()) { - val (server, cont) = queue.peekFirst() + val (server) = queue.peekFirst() val requiredMemory = server.flavor.memorySize val selectedHv = allocationLogic.select(availableHosts, server) @@ -265,7 +258,6 @@ public class ComputeServiceImpl( scope.launch { try { selectedHv.host.spawn(server) - cont.resume(Unit) activeServers[server] = selectedHv.host tracer.commit(VmScheduledEvent(server.name)) @@ -390,13 +382,15 @@ public class ComputeServiceImpl( } } - private data class LaunchRequest(val server: ServerImpl, val cont: Continuation) + private data class LaunchRequest(val server: ServerImpl) private inner class ServerImpl( override val uid: UUID, override val name: String, override val flavor: Flavor, - override val image: Image + override val image: Image, + override val labels: MutableMap, + override val meta: MutableMap ) : Server { val watchers = mutableListOf() @@ -417,11 +411,9 @@ public class ComputeServiceImpl( else -> { logger.info { "User requested to start server $uid" } state = ServerState.PROVISIONING - suspendCancellableCoroutine { cont -> - val request = LaunchRequest(this, cont) - queue += request - requestCycle() - } + val request = LaunchRequest(this) + queue += request + requestCycle() } } } @@ -463,8 +455,6 @@ public class ComputeServiceImpl( // No-op: this object is the source-of-truth } - override val tags: Map = emptyMap() - override var state: ServerState = ServerState.TERMINATED set(value) { if (value != field) { -- cgit v1.2.3 From 6a555542c4a1ba94b96c0cf17b51ceb975c83e21 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Tue, 9 Mar 2021 17:18:02 +0100 Subject: core: Remove OpenDC core module This change removes the opendc-core module. This module was an artifact of the old codebase and remained mostly unused. This change removes all usages of the module and if necessary introduces replacement classes. --- .../org/opendc/compute/service/scheduler/RandomAllocationPolicy.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'simulator/opendc-compute/opendc-compute-service/src') diff --git a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/RandomAllocationPolicy.kt b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/RandomAllocationPolicy.kt index 3facb182..ac7b351d 100644 --- a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/RandomAllocationPolicy.kt +++ b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/RandomAllocationPolicy.kt @@ -38,7 +38,7 @@ public class RandomAllocationPolicy(private val random: Random = Random(0)) : Al ): HostView? { return hypervisors.asIterable() .filter { hv -> - val fitsMemory = hv.availableMemory >= (server.image.tags["required-memory"] as Long) + val fitsMemory = hv.availableMemory >= (server.image.meta["required-memory"] as Long) val fitsCpu = hv.host.model.cpuCount >= server.flavor.cpuCount fitsMemory && fitsCpu } -- cgit v1.2.3 From b3a271794d64bd97ef93abf650137c5a0a1785df Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Tue, 9 Mar 2021 17:57:57 +0100 Subject: compute: Extend capabilities of compute API --- .../compute/service/internal/ComputeServiceImpl.kt | 246 +++++++++------------ .../compute/service/internal/InternalServer.kt | 126 +++++++++++ 2 files changed, 234 insertions(+), 138 deletions(-) create mode 100644 simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt (limited to 'simulator/opendc-compute/opendc-compute-service/src') diff --git a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt index 0d4f379d..3feb80ad 100644 --- a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt +++ b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt @@ -22,10 +22,8 @@ package org.opendc.compute.service.internal -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.cancel +import kotlinx.coroutines.* import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.launch import mu.KotlinLogging import org.opendc.compute.api.* import org.opendc.compute.service.ComputeService @@ -84,13 +82,18 @@ public class ComputeServiceImpl( /** * The servers that should be launched by the service. */ - private val queue: Deque = ArrayDeque() + private val queue: Deque = ArrayDeque() /** * The active servers in the system. */ private val activeServers: MutableMap = mutableMapOf() + /** + * The registered servers for this compute service. + */ + private val servers = mutableMapOf() + public var submittedVms: Int = 0 public var queuedVms: Int = 0 public var runningVms: Int = 0 @@ -147,7 +150,8 @@ public class ComputeServiceImpl( ) ) - val server = ServerImpl( + val server = InternalServer( + this@ComputeServiceImpl, uid = UUID(random.nextLong(), random.nextLong()), name, flavor, @@ -162,6 +166,18 @@ public class ComputeServiceImpl( return ClientServer(server) } + override suspend fun findServer(id: UUID): Server? { + check(!isClosed) { "Client is already closed" } + + return servers[id]?.let { ClientServer(it) } + } + + override suspend fun queryServers(): List { + check(!isClosed) { "Client is already closed" } + + return servers.values.map { ClientServer(it) } + } + override fun close() { isClosed = true } @@ -195,9 +211,19 @@ public class ComputeServiceImpl( scope.cancel() } - private fun requestCycle() { - // Bail out in case we have already requested a new cycle. - if (scheduler.isTimerActive(Unit)) { + internal fun schedule(server: InternalServer) { + logger.debug { "Enqueueing server ${server.uid} to be assigned to host." } + + queue.add(SchedulingRequest(server)) + requestSchedulingCycle() + } + + /** + * Indicate that a new scheduling cycle is needed due to a change to the service's state. + */ + private fun requestSchedulingCycle() { + // Bail out in case we have already requested a new cycle or the queue is empty. + if (scheduler.isTimerActive(Unit) || queue.isEmpty()) { return } @@ -207,20 +233,28 @@ public class ComputeServiceImpl( val delay = schedulingQuantum - (clock.millis() % schedulingQuantum) scheduler.startSingleTimer(Unit, delay) { - schedule() + doSchedule() } } - private fun schedule() { + /** + * Run a single scheduling iteration. + */ + private fun doSchedule() { while (queue.isNotEmpty()) { - val (server) = queue.peekFirst() - val requiredMemory = server.flavor.memorySize - val selectedHv = allocationLogic.select(availableHosts, server) + val request = queue.peek() + + if (request.isCancelled) { + queue.poll() + continue + } - if (selectedHv == null || !selectedHv.host.canFit(server)) { + val server = request.server + val hv = allocationLogic.select(availableHosts, request.server) + if (hv == null || !hv.host.canFit(server)) { logger.trace { "Server $server selected for scheduling but no capacity available for it." } - if (requiredMemory > maxMemory || server.flavor.cpuCount > maxCores) { + if (server.flavor.memorySize > maxMemory || server.flavor.cpuCount > maxCores) { tracer.commit(VmSubmissionInvalidEvent(server.name)) _events.emit( @@ -246,44 +280,62 @@ public class ComputeServiceImpl( } } - logger.info { "[${clock.millis()}] Spawning $server on ${selectedHv.host.uid} ${selectedHv.host.name} ${selectedHv.host.model}" } - queue.poll() - - // Speculatively update the hypervisor view information to prevent other images in the queue from - // deciding on stale values. - selectedHv.numberOfActiveServers++ - selectedHv.provisionedCores += server.flavor.cpuCount - selectedHv.availableMemory -= requiredMemory // XXX Temporary hack + val host = hv.host - scope.launch { - try { - selectedHv.host.spawn(server) - activeServers[server] = selectedHv.host + // Remove request from queue + queue.poll() - tracer.commit(VmScheduledEvent(server.name)) - _events.emit( - ComputeServiceEvent.MetricsAvailable( - this@ComputeServiceImpl, - hostCount, - availableHosts.size, - submittedVms, - ++runningVms, - finishedVms, - --queuedVms, - unscheduledVms + logger.info { "Assigned server $server to host $host." } + try { + // Speculatively update the hypervisor view information to prevent other images in the queue from + // deciding on stale values. + hv.numberOfActiveServers++ + hv.provisionedCores += server.flavor.cpuCount + hv.availableMemory -= server.flavor.memorySize // XXX Temporary hack + + scope.launch { + try { + server.assignHost(host) + host.spawn(server) + activeServers[server] = host + + tracer.commit(VmScheduledEvent(server.name)) + _events.emit( + ComputeServiceEvent.MetricsAvailable( + this@ComputeServiceImpl, + hostCount, + availableHosts.size, + submittedVms, + ++runningVms, + finishedVms, + --queuedVms, + unscheduledVms + ) ) - ) - } catch (e: Throwable) { - logger.error("Failed to deploy VM", e) + } catch (e: Throwable) { + logger.error("Failed to deploy VM", e) - selectedHv.numberOfActiveServers-- - selectedHv.provisionedCores -= server.flavor.cpuCount - selectedHv.availableMemory += requiredMemory + hv.numberOfActiveServers-- + hv.provisionedCores -= server.flavor.cpuCount + hv.availableMemory += server.flavor.memorySize + } } + } catch (e: Exception) { + logger.warn(e) { "Failed to assign server $server to $host. " } } } } + /** + * A request to schedule an [InternalServer] onto one of the [Host]s. + */ + private data class SchedulingRequest(val server: InternalServer) { + /** + * A flag to indicate that the request is cancelled. + */ + var isCancelled: Boolean = false + } + override fun onStateChanged(host: Host, newState: HostState) { when (newState) { HostState.UP -> { @@ -311,9 +363,7 @@ public class ComputeServiceImpl( ) // Re-schedule on the new machine - if (queue.isNotEmpty()) { - requestCycle() - } + requestSchedulingCycle() } HostState.DOWN -> { logger.debug { "[${clock.millis()}] Host ${host.uid} state changed: $newState" } @@ -336,16 +386,21 @@ public class ComputeServiceImpl( ) ) - if (queue.isNotEmpty()) { - requestCycle() - } + requestSchedulingCycle() } } } override fun onStateChanged(host: Host, server: Server, newState: ServerState) { - val serverImpl = server as ServerImpl - serverImpl.state = newState + 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.millis()}] Server ${server.uid} ${server.name} ${server.flavor} finished." } @@ -376,92 +431,7 @@ public class ComputeServiceImpl( } // Try to reschedule if needed - if (queue.isNotEmpty()) { - requestCycle() - } + requestSchedulingCycle() } } - - private data class LaunchRequest(val server: ServerImpl) - - private inner class ServerImpl( - override val uid: UUID, - override val name: String, - override val flavor: Flavor, - override val image: Image, - override val labels: MutableMap, - override val meta: MutableMap - ) : Server { - val watchers = mutableListOf() - - override suspend 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 IllegalArgumentException("Server is terminated") - } - else -> { - logger.info { "User requested to start server $uid" } - state = ServerState.PROVISIONING - val request = LaunchRequest(this) - queue += request - requestCycle() - } - } - } - - override suspend fun stop() { - when (state) { - ServerState.PROVISIONING -> {} // TODO Find way to interrupt these - ServerState.RUNNING, ServerState.ERROR -> { - // Warn: possible race condition on activeServers - val host = checkNotNull(activeServers[this]) { "Server not running" } - host.stop(this) - } - ServerState.TERMINATED -> {} // No work needed - ServerState.DELETED -> throw IllegalStateException("Server is terminated") - } - } - - override suspend fun delete() { - when (state) { - ServerState.PROVISIONING -> {} // TODO Find way to interrupt these - ServerState.RUNNING -> { - // Warn: possible race condition on activeServers - val host = checkNotNull(activeServers[this]) { "Server not running" } - host.delete(this) - } - else -> {} // No work needed - } - } - - override fun watch(watcher: ServerWatcher) { - watchers += watcher - } - - override fun unwatch(watcher: ServerWatcher) { - watchers -= watcher - } - - override suspend fun refresh() { - // 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 - } - } } diff --git a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt new file mode 100644 index 00000000..2656a488 --- /dev/null +++ b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt @@ -0,0 +1,126 @@ +/* + * 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.* +import org.opendc.compute.service.driver.Host +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: Flavor, + override val image: Image, + 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. + */ + internal var host: Host? = null + + override suspend 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 IllegalArgumentException("Server is terminated") + } + else -> { + logger.info { "User requested to start server $uid" } + state = ServerState.PROVISIONING + service.schedule(this) + } + } + } + + override suspend fun stop() { + when (state) { + ServerState.PROVISIONING -> {} // TODO Find way to interrupt these + ServerState.RUNNING, ServerState.ERROR -> { + val host = checkNotNull(host) { "Server not running" } + host.stop(this) + } + ServerState.TERMINATED -> {} // No work needed + ServerState.DELETED -> throw IllegalStateException("Server is terminated") + } + } + + override suspend fun delete() { + when (state) { + ServerState.PROVISIONING -> {} // TODO Find way to interrupt these + ServerState.RUNNING -> { + val host = checkNotNull(host) { "Server not running" } + host.delete(this) + } + else -> {} // No work needed + } + } + + override fun watch(watcher: ServerWatcher) { + watchers += watcher + } + + override fun unwatch(watcher: ServerWatcher) { + watchers -= watcher + } + + override suspend fun refresh() { + // 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 + } + + internal fun assignHost(host: Host) { + this.host = host + } +} -- cgit v1.2.3 From b3f390be783cad21cd4925bcbe8077b91f869b5d Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Tue, 9 Mar 2021 19:38:29 +0100 Subject: compute: Model storage of VM images --- .../opendc/compute/service/internal/ClientImage.kt | 55 ++++++++++++++++++++++ .../compute/service/internal/ClientServer.kt | 2 + .../compute/service/internal/ComputeServiceImpl.kt | 42 ++++++++++++++++- .../compute/service/internal/InternalImage.kt | 54 +++++++++++++++++++++ .../compute/service/internal/InternalServer.kt | 5 ++ 5 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientImage.kt create mode 100644 simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalImage.kt (limited to 'simulator/opendc-compute/opendc-compute-service/src') diff --git a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientImage.kt b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientImage.kt new file mode 100644 index 00000000..6c5b2ab0 --- /dev/null +++ b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientImage.kt @@ -0,0 +1,55 @@ +/* + * 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.* + +/** + * 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 suspend fun delete() { + delegate.delete() + refresh() + } + + override suspend fun refresh() { + delegate.refresh() + + name = delegate.name + labels = delegate.labels + meta = delegate.meta + } +} diff --git a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientServer.kt b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientServer.kt index bca1ad44..ae4cee3b 100644 --- a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientServer.kt +++ b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientServer.kt @@ -87,6 +87,8 @@ internal class ClientServer(private val delegate: Server) : Server, ServerWatche } override suspend fun refresh() { + delegate.refresh() + name = delegate.name flavor = delegate.flavor image = delegate.image diff --git a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt index 3feb80ad..3b694537 100644 --- a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt +++ b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt @@ -89,6 +89,11 @@ public class ComputeServiceImpl( */ private val activeServers: MutableMap = mutableMapOf() + /** + * The registered images for this compute service. + */ + internal val images = mutableMapOf() + /** * The registered servers for this compute service. */ @@ -126,6 +131,29 @@ public class ComputeServiceImpl( override fun newClient(): ComputeClient = object : ComputeClient { private var isClosed: Boolean = false + override suspend fun queryImages(): List { + check(!isClosed) { "Client is already closed" } + + return images.values.map { ClientImage(it) } + } + + override suspend fun findImage(id: UUID): Image? { + check(!isClosed) { "Client is already closed" } + + return images[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) + + images[uid] = image + + return ClientImage(image) + } + override suspend fun newServer( name: String, image: Image, @@ -150,15 +178,19 @@ public class ComputeServiceImpl( ) ) + val uid = UUID(clock.millis(), random.nextLong()) val server = InternalServer( this@ComputeServiceImpl, - uid = UUID(random.nextLong(), random.nextLong()), + uid, name, flavor, image, labels.toMutableMap(), meta.toMutableMap() ) + + servers[uid] = server + if (start) { server.start() } @@ -218,6 +250,14 @@ public class ComputeServiceImpl( requestSchedulingCycle() } + internal fun delete(server: InternalServer) { + checkNotNull(servers.remove(server.uid)) { "Server was not know" } + } + + internal fun delete(image: InternalImage) { + checkNotNull(images.remove(image.uid)) { "Server was not know" } + } + /** * Indicate that a new scheduling cycle is needed due to a change to the service's state. */ diff --git a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalImage.kt b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalImage.kt new file mode 100644 index 00000000..86f2f6b9 --- /dev/null +++ b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalImage.kt @@ -0,0 +1,54 @@ +/* + * 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.* + +/** + * 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 suspend fun refresh() { + // No-op: this object is the source-of-truth + } + + override suspend fun delete() { + service.delete(this) + } + + override fun equals(other: Any?): Boolean = other is InternalImage && uid == other.uid + + override fun hashCode(): Int = uid.hashCode() +} diff --git a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt index 2656a488..ff7c1d15 100644 --- a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt +++ b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt @@ -94,6 +94,7 @@ internal class InternalServer( ServerState.RUNNING -> { val host = checkNotNull(host) { "Server not running" } host.delete(this) + service.delete(this) } else -> {} // No work needed } @@ -123,4 +124,8 @@ internal class InternalServer( internal fun assignHost(host: Host) { this.host = host } + + override fun equals(other: Any?): Boolean = other is InternalServer && uid == other.uid + + override fun hashCode(): Int = uid.hashCode() } -- cgit v1.2.3 From 44ed0023ed783437c3c838780f73e28efe1cc4ca Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Tue, 9 Mar 2021 19:56:50 +0100 Subject: compute: Move implementation of Flavor into service module --- .../compute/service/internal/ClientFlavor.kt | 62 +++++++++++++++++++++ .../compute/service/internal/ComputeServiceImpl.kt | 52 +++++++++++++++++- .../compute/service/internal/InternalFlavor.kt | 64 ++++++++++++++++++++++ 3 files changed, 175 insertions(+), 3 deletions(-) create mode 100644 simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientFlavor.kt create mode 100644 simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalFlavor.kt (limited to 'simulator/opendc-compute/opendc-compute-service/src') diff --git a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientFlavor.kt b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientFlavor.kt new file mode 100644 index 00000000..29f10e27 --- /dev/null +++ b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientFlavor.kt @@ -0,0 +1,62 @@ +/* + * 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 suspend fun delete() { + delegate.delete() + } + + override suspend fun refresh() { + delegate.refresh() + + name = delegate.name + cpuCount = delegate.cpuCount + memorySize = delegate.memorySize + labels = delegate.labels + meta = delegate.meta + } +} diff --git a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt index 3b694537..2c38f7cb 100644 --- a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt +++ b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt @@ -89,6 +89,11 @@ public class ComputeServiceImpl( */ private val activeServers: MutableMap = mutableMapOf() + /** + * The registered flavors for this compute service. + */ + internal val flavors = mutableMapOf() + /** * The registered images for this compute service. */ @@ -131,6 +136,43 @@ public class ComputeServiceImpl( override fun newClient(): ComputeClient = object : ComputeClient { private var isClosed: Boolean = false + override suspend fun queryFlavors(): List { + check(!isClosed) { "Client is already closed" } + + return flavors.values.map { ClientFlavor(it) } + } + + override suspend fun findFlavor(id: UUID): Flavor? { + check(!isClosed) { "Client is already closed" } + + return flavors[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 + ) + + flavors[uid] = flavor + + return ClientFlavor(flavor) + } + override suspend fun queryImages(): List { check(!isClosed) { "Client is already closed" } @@ -250,12 +292,16 @@ public class ComputeServiceImpl( requestSchedulingCycle() } - internal fun delete(server: InternalServer) { - checkNotNull(servers.remove(server.uid)) { "Server was not know" } + internal fun delete(flavor: InternalFlavor) { + checkNotNull(flavors.remove(flavor.uid)) { "Flavor was not known" } } internal fun delete(image: InternalImage) { - checkNotNull(images.remove(image.uid)) { "Server was not know" } + checkNotNull(images.remove(image.uid)) { "Image was not known" } + } + + internal fun delete(server: InternalServer) { + checkNotNull(servers.remove(server.uid)) { "Server was not known" } } /** diff --git a/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalFlavor.kt b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalFlavor.kt new file mode 100644 index 00000000..95e280df --- /dev/null +++ b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalFlavor.kt @@ -0,0 +1,64 @@ +/* + * 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.* + +/** + * 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 suspend fun refresh() { + // No-op: this object is the source-of-truth + } + + override suspend fun delete() { + service.delete(this) + } + + override fun equals(other: Any?): Boolean = other is InternalFlavor && uid == other.uid + + override fun hashCode(): Int = uid.hashCode() +} -- cgit v1.2.3