summaryrefslogtreecommitdiff
path: root/simulator/opendc-compute
diff options
context:
space:
mode:
authorFabian Mastenbroek <mail.fabianm@gmail.com>2021-03-09 20:47:06 +0100
committerGitHub <noreply@github.com>2021-03-09 20:47:06 +0100
commit3b6fbe0b535bf3398f120373f59f87adbba34005 (patch)
treebc880252a935cc0b1558c50fe83f71d21b735d29 /simulator/opendc-compute
parent66c2501d95b167f9e7474a45e542f82d2d8e83ff (diff)
parent40e5871e01858a55372bfcb51cf90069c080e751 (diff)
compute: Improvements to cloud compute model (v2)
This is the second in the series of pull requests to improve the existing cloud compute model (see #86). This pull request removes the dependency on the bare-metal provisioning code which simplifies experiment setup tremendously: - Remove bare-metal provisioning code (opendc-metal) - Remove opendc-core which was a relic of the previous codebase and was only used sparingly. - Move ownership of Server, Image and Flavor to the compute service. Users are expected to create instances via the compute service.
Diffstat (limited to 'simulator/opendc-compute')
-rw-r--r--simulator/opendc-compute/opendc-compute-api/build.gradle.kts1
-rw-r--r--simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ComputeClient.kt76
-rw-r--r--simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Flavor.kt11
-rw-r--r--simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Image.kt25
-rw-r--r--simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/InsufficientServerCapacityException.kt29
-rw-r--r--simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Resource.kt (renamed from simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ComputeWorkload.kt)41
-rw-r--r--simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Server.kt39
-rw-r--r--simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ServerState.kt18
-rw-r--r--simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/Host.kt11
-rw-r--r--simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientFlavor.kt62
-rw-r--r--simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientImage.kt55
-rw-r--r--simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ClientServer.kt25
-rw-r--r--simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt317
-rw-r--r--simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalFlavor.kt64
-rw-r--r--simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalImage.kt54
-rw-r--r--simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt131
-rw-r--r--simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/RandomAllocationPolicy.kt2
-rw-r--r--simulator/opendc-compute/opendc-compute-simulator/build.gradle.kts1
-rw-r--r--simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimBareMetalDriver.kt188
-rw-r--r--simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt142
-rw-r--r--simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHostProvisioner.kt69
-rw-r--r--simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimMetaWorkloadMapper.kt35
-rw-r--r--simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimWorkloadMapper.kt36
-rw-r--r--simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/api/CpuPowerModel.kt8
-rw-r--r--simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimBareMetalDriverTest.kt92
-rw-r--r--simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimHostTest.kt72
-rw-r--r--simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimProvisioningServiceTest.kt81
-rw-r--r--simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/power/CpuPowerModelTest.kt22
28 files changed, 1011 insertions, 696 deletions
diff --git a/simulator/opendc-compute/opendc-compute-api/build.gradle.kts b/simulator/opendc-compute/opendc-compute-api/build.gradle.kts
index 10046322..835dbbb8 100644
--- a/simulator/opendc-compute/opendc-compute-api/build.gradle.kts
+++ b/simulator/opendc-compute/opendc-compute-api/build.gradle.kts
@@ -29,5 +29,4 @@ plugins {
dependencies {
api(platform(project(":opendc-platform")))
- api(project(":opendc-core"))
}
diff --git a/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ComputeClient.kt b/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ComputeClient.kt
index 025513e6..baa1ba2f 100644
--- a/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ComputeClient.kt
+++ b/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ComputeClient.kt
@@ -22,21 +22,95 @@
package org.opendc.compute.api
+import java.util.UUID
+
/**
* A client interface for the OpenDC Compute service.
*/
public interface ComputeClient : AutoCloseable {
/**
+ * Obtain the list of [Flavor]s accessible by the requesting user.
+ */
+ public suspend fun queryFlavors(): List<Flavor>
+
+ /**
+ * Obtain a [Flavor] by its unique identifier.
+ *
+ * @param id The identifier of the flavor.
+ */
+ public suspend fun findFlavor(id: UUID): Flavor?
+
+ /**
+ * Create a new [Flavor] instance at this compute service.
+ *
+ * @param name The name of the flavor.
+ * @param cpuCount The amount of CPU cores for this flavor.
+ * @param memorySize The size of the memory.
+ * @param labels The identifying labels of the image.
+ * @param meta The non-identifying meta-data of the image.
+ */
+ public suspend fun newFlavor(
+ name: String,
+ cpuCount: Int,
+ memorySize: Long,
+ labels: Map<String, String> = emptyMap(),
+ meta: Map<String, Any> = emptyMap()
+ ): Flavor
+
+ /**
+ * Obtain the list of [Image]s accessible by the requesting user.
+ */
+ public suspend fun queryImages(): List<Image>
+
+ /**
+ * Obtain a [Image] by its unique identifier.
+ *
+ * @param id The identifier of the image.
+ */
+ public suspend fun findImage(id: UUID): Image?
+
+ /**
+ * Create a new [Image] instance at this compute service.
+ *
+ * @param name The name of the image.
+ * @param labels The identifying labels of the image.
+ * @param meta The non-identifying meta-data of the image.
+ */
+ public suspend fun newImage(
+ name: String,
+ labels: Map<String, String> = emptyMap(),
+ meta: Map<String, Any> = emptyMap()
+ ): Image
+
+ /**
+ * Obtain the list of [Server]s accessible by the requesting user.
+ */
+ public suspend fun queryServers(): List<Server>
+
+ /**
+ * Obtain a [Server] by its unique identifier.
+ *
+ * @param id The identifier of the server.
+ */
+ public suspend fun findServer(id: UUID): Server?
+
+ /**
* Create a new [Server] instance at this compute service.
*
* @param name The name of the server to deploy.
* @param image The image to be deployed.
* @param flavor The flavor of the machine instance to run this [image] on.
+ * @param labels The identifying labels of the server.
+ * @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(
name: String,
image: Image,
- flavor: Flavor
+ flavor: Flavor,
+ labels: Map<String, String> = emptyMap(),
+ meta: Map<String, Any> = emptyMap(),
+ start: Boolean = true
): Server
/**
diff --git a/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Flavor.kt b/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Flavor.kt
index bf5f0ce4..5f511f91 100644
--- a/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Flavor.kt
+++ b/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Flavor.kt
@@ -26,14 +26,19 @@ package org.opendc.compute.api
* Flavors define the compute and memory capacity of [Server] instance. To put it simply, a flavor is an available
* hardware configuration for a server. It defines the size of a virtual server that can be launched.
*/
-public data class Flavor(
+public interface Flavor : Resource {
/**
* The number of (virtual) processing cores to use.
*/
- public val cpuCount: Int,
+ public val cpuCount: Int
/**
* 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/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Image.kt b/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Image.kt
index 280c4d89..83e63b81 100644
--- a/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Image.kt
+++ b/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Image.kt
@@ -22,27 +22,12 @@
package org.opendc.compute.api
-import org.opendc.core.resource.Resource
-import org.opendc.core.resource.TagContainer
-import java.util.*
-
/**
* An image containing a bootable operating system that can directly be executed by physical or virtual server.
- *
- * OpenStack: A collection of files used to create or rebuild a server. Operators provide a number of pre-built OS
- * images by default. You may also create custom images from cloud servers you have launched. These custom images are
- * useful for backup purposes or for producing “gold” server images if you plan to deploy a particular server
- * configuration frequently.
*/
-public data class Image(
- public override val uid: UUID,
- public override val name: String,
- public override val tags: TagContainer
-) : Resource {
- public companion object {
- /**
- * An empty boot disk [Image] that exits immediately on start.
- */
- public val EMPTY: Image = Image(UUID.randomUUID(), "empty", emptyMap())
- }
+public interface Image : Resource {
+ /**
+ * Delete the image instance.
+ */
+ public suspend fun delete()
}
diff --git a/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/InsufficientServerCapacityException.kt b/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/InsufficientServerCapacityException.kt
new file mode 100644
index 00000000..8fbb7308
--- /dev/null
+++ b/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/InsufficientServerCapacityException.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.api
+
+/**
+ * This exception is thrown to indicate that the compute service does not have enough capacity at the moment to
+ * fulfill a launch request.
+ */
+public class InsufficientServerCapacityException(override val cause: Throwable? = null) : Exception("There was insufficient capacity available to satisfy the launch request")
diff --git a/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ComputeWorkload.kt b/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Resource.kt
index 64a47277..08120848 100644
--- a/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ComputeWorkload.kt
+++ b/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Resource.kt
@@ -22,25 +22,34 @@
package org.opendc.compute.api
-import org.opendc.core.User
-import org.opendc.core.workload.Workload
import java.util.UUID
/**
- * A workload that represents a VM.
- *
- * @property uid A unique identified of this VM.
- * @property name The name of this VM.
- * @property owner The owner of the VM.
- * @property image The image of the VM.
+ * A generic resource provided by the OpenDC Compute service.
*/
-public data class ComputeWorkload(
- override val uid: UUID,
- override val name: String,
- override val owner: User,
- val image: Image
-) : Workload {
- override fun equals(other: Any?): Boolean = other is ComputeWorkload && uid == other.uid
+public interface Resource {
+ /**
+ * The unique identifier of the resource.
+ */
+ public val uid: UUID
+
+ /**
+ * The name of the resource.
+ */
+ public val name: String
+
+ /**
+ * The identifying labels attached to the resource.
+ */
+ public val labels: Map<String, String>
+
+ /**
+ * The non-identifying metadata attached to the resource.
+ */
+ public val meta: Map<String, Any>
- override fun hashCode(): Int = uid.hashCode()
+ /**
+ * Refresh the local state of the resource.
+ */
+ public suspend fun refresh()
}
diff --git a/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Server.kt b/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Server.kt
index ab1eb860..b508a9f8 100644
--- a/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Server.kt
+++ b/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/Server.kt
@@ -22,18 +22,11 @@
package org.opendc.compute.api
-import org.opendc.core.resource.Resource
-
/**
* A stateful object representing a server instance that is running on some physical or virtual machine.
*/
public interface Server : Resource {
/**
- * The name of the server.
- */
- public override val name: String
-
- /**
* The flavor of the server.
*/
public val flavor: Flavor
@@ -44,14 +37,33 @@ public interface Server : Resource {
public val image: Image
/**
- * The tags assigned to the server.
+ * The last known state of the server.
*/
- public override val tags: Map<String, String>
+ public val state: ServerState
/**
- * The last known state of the server.
+ * 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 val state: ServerState
+ public suspend 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()
/**
* Register the specified [ServerWatcher] to watch the state of the server.
@@ -66,9 +78,4 @@ public interface Server : Resource {
* @param watcher The watcher to de-register from the server.
*/
public fun unwatch(watcher: ServerWatcher)
-
- /**
- * Refresh the local state of the resource.
- */
- public suspend fun refresh()
}
diff --git a/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ServerState.kt b/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ServerState.kt
index 25d2e519..a4d7d7d7 100644
--- a/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ServerState.kt
+++ b/simulator/opendc-compute/opendc-compute-api/src/main/kotlin/org/opendc/compute/api/ServerState.kt
@@ -27,27 +27,27 @@ package org.opendc.compute.api
*/
public enum class ServerState {
/**
- * The server has not yet finished the original build process.
+ * Resources are being allocated for the instance. The instance is not running yet.
*/
- BUILD,
+ PROVISIONING,
/**
- * The server was powered down by the user.
+ * A user shut down the instance.
*/
- SHUTOFF,
+ TERMINATED,
/**
- * The server is active and running.
+ * The server instance is booting up or running.
*/
- ACTIVE,
+ RUNNING,
/**
- * The server is in error.
+ * The server is in an error state.
*/
ERROR,
/**
- * The state of the server is unknown.
+ * The server has been deleted and cannot be started later on.
*/
- UNKNOWN,
+ DELETED,
}
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..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
@@ -51,6 +51,11 @@ public interface Host {
public val state: HostState
/**
+ * Meta-data associated with the host.
+ */
+ public val meta: Map<String, Any>
+
+ /**
* The events emitted by the driver.
*/
public val events: Flow<HostEvent>
@@ -74,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.
*/
@@ -88,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/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<String, String> = delegate.labels.toMap()
+ private set
+
+ override var meta: Map<String, Any> = 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/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<String, String> = delegate.labels.toMap()
+ private set
+
+ override var meta: Map<String, Any> = 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 f84b7435..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
@@ -46,12 +46,30 @@ internal class ClientServer(private val delegate: Server) : Server, ServerWatche
override var image: Image = delegate.image
private set
- override var tags: Map<String, String> = delegate.tags.toMap()
+ override var labels: Map<String, String> = delegate.labels.toMap()
+ private set
+
+ override var meta: Map<String, Any> = delegate.meta.toMap()
private set
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)
@@ -69,10 +87,13 @@ internal class ClientServer(private val delegate: Server) : Server, ServerWatche
}
override suspend fun refresh() {
+ delegate.refresh()
+
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 69d6bb59..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
@@ -22,11 +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 kotlinx.coroutines.suspendCancellableCoroutine
import mu.KotlinLogging
import org.opendc.compute.api.*
import org.opendc.compute.service.ComputeService
@@ -41,9 +38,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
/**
@@ -87,12 +82,27 @@ public class ComputeServiceImpl(
/**
* The servers that should be launched by the service.
*/
- private val queue: Deque<LaunchRequest> = ArrayDeque()
+ private val queue: Deque<SchedulingRequest> = ArrayDeque()
/**
* The active servers in the system.
*/
- private val activeServers: MutableSet<Server> = mutableSetOf()
+ private val activeServers: MutableMap<Server, Host> = mutableMapOf()
+
+ /**
+ * The registered flavors for this compute service.
+ */
+ internal val flavors = mutableMapOf<UUID, InternalFlavor>()
+
+ /**
+ * The registered images for this compute service.
+ */
+ internal val images = mutableMapOf<UUID, InternalImage>()
+
+ /**
+ * The registered servers for this compute service.
+ */
+ private val servers = mutableMapOf<UUID, InternalServer>()
public var submittedVms: Int = 0
public var queuedVms: Int = 0
@@ -126,7 +136,74 @@ 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 queryFlavors(): List<Flavor> {
+ 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<String, String>,
+ meta: Map<String, Any>
+ ): 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<Image> {
+ 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<String, String>, meta: Map<String, Any>): 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,
+ flavor: Flavor,
+ labels: Map<String, String>,
+ meta: Map<String, Any>,
+ start: Boolean
+ ): Server {
check(!isClosed) { "Client is closed" }
tracer.commit(VmSubmissionEvent(name, image, flavor))
@@ -143,11 +220,36 @@ public class ComputeServiceImpl(
)
)
- return suspendCancellableCoroutine { cont ->
- val request = LaunchRequest(createServer(name, image, flavor), cont)
- queue += request
- requestCycle()
+ val uid = UUID(clock.millis(), random.nextLong())
+ val server = InternalServer(
+ this@ComputeServiceImpl,
+ uid,
+ name,
+ flavor,
+ image,
+ labels.toMutableMap(),
+ meta.toMutableMap()
+ )
+
+ servers[uid] = server
+
+ if (start) {
+ server.start()
}
+
+ 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<Server> {
+ check(!isClosed) { "Client is already closed" }
+
+ return servers.values.map { ClientServer(it) }
}
override fun close() {
@@ -183,22 +285,31 @@ public class ComputeServiceImpl(
scope.cancel()
}
- private fun createServer(
- name: String,
- image: Image,
- flavor: Flavor
- ): Server {
- return ServerImpl(
- uid = UUID(random.nextLong(), random.nextLong()),
- name = name,
- flavor = flavor,
- image = image
- )
+ internal fun schedule(server: InternalServer) {
+ logger.debug { "Enqueueing server ${server.uid} to be assigned to host." }
+
+ queue.add(SchedulingRequest(server))
+ requestSchedulingCycle()
+ }
+
+ internal fun delete(flavor: InternalFlavor) {
+ checkNotNull(flavors.remove(flavor.uid)) { "Flavor was not known" }
}
- private fun requestCycle() {
- // Bail out in case we have already requested a new cycle.
- if (scheduler.isTimerActive(Unit)) {
+ internal fun delete(image: InternalImage) {
+ checkNotNull(images.remove(image.uid)) { "Image was not known" }
+ }
+
+ internal fun delete(server: InternalServer) {
+ checkNotNull(servers.remove(server.uid)) { "Server was not known" }
+ }
+
+ /**
+ * 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
}
@@ -208,20 +319,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, cont) = queue.peekFirst()
- val requiredMemory = server.flavor.memorySize
- val selectedHv = allocationLogic.select(availableHosts, server)
+ val request = queue.peek()
- if (selectedHv == null || !selectedHv.host.canFit(server)) {
+ if (request.isCancelled) {
+ queue.poll()
+ continue
+ }
+
+ 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(
@@ -247,45 +366,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 {
- cont.resume(ClientServer(server))
- selectedHv.host.spawn(server)
- activeServers += server
+ // 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 -> {
@@ -313,9 +449,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" }
@@ -338,19 +472,23 @@ 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
- serverImpl.watchers.forEach { it.onStateChanged(server, newState) }
+ require(server is InternalServer) { "Invalid server type passed to service" }
- if (newState == ServerState.SHUTOFF) {
+ 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." }
tracer.commit(VmStoppedEvent(server.name))
@@ -379,36 +517,7 @@ public class ComputeServiceImpl(
}
// Try to reschedule if needed
- if (queue.isNotEmpty()) {
- requestCycle()
- }
+ requestSchedulingCycle()
}
}
-
- public data class LaunchRequest(val server: Server, val cont: Continuation<Server>)
-
- private class ServerImpl(
- override val uid: UUID,
- override val name: String,
- override val flavor: Flavor,
- override val image: Image
- ) : Server {
- val watchers = mutableListOf<ServerWatcher>()
-
- 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 val tags: Map<String, String> = emptyMap()
-
- override var state: ServerState = ServerState.BUILD
- }
}
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<String, String>,
+ meta: Map<String, Any>
+) : 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<String, String> = labels.toMutableMap()
+
+ override val meta: MutableMap<String, Any> = 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()
+}
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<String, String>,
+ meta: Map<String, Any>
+) : Image {
+
+ override val labels: MutableMap<String, String> = labels.toMutableMap()
+
+ override val meta: MutableMap<String, Any> = 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
new file mode 100644
index 00000000..ff7c1d15
--- /dev/null
+++ b/simulator/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt
@@ -0,0 +1,131 @@
+/*
+ * 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<String, String>,
+ override val meta: MutableMap<String, Any>
+) : Server {
+ /**
+ * The logger instance of this server.
+ */
+ private val logger = KotlinLogging.logger {}
+
+ /**
+ * The watchers of this server object.
+ */
+ private val watchers = mutableListOf<ServerWatcher>()
+
+ /**
+ * 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)
+ service.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
+ }
+
+ override fun equals(other: Any?): Boolean = other is InternalServer && 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/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
}
diff --git a/simulator/opendc-compute/opendc-compute-simulator/build.gradle.kts b/simulator/opendc-compute/opendc-compute-simulator/build.gradle.kts
index d7d5f002..31fcda2f 100644
--- a/simulator/opendc-compute/opendc-compute-simulator/build.gradle.kts
+++ b/simulator/opendc-compute/opendc-compute-simulator/build.gradle.kts
@@ -31,7 +31,6 @@ plugins {
dependencies {
api(platform(project(":opendc-platform")))
api(project(":opendc-compute:opendc-compute-service"))
- api(project(":opendc-metal"))
api(project(":opendc-simulator:opendc-simulator-compute"))
api(project(":opendc-simulator:opendc-simulator-failures"))
implementation(project(":opendc-utils"))
diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimBareMetalDriver.kt b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimBareMetalDriver.kt
deleted file mode 100644
index 2405a8f9..00000000
--- a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimBareMetalDriver.kt
+++ /dev/null
@@ -1,188 +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.simulator
-
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.Flow
-import org.opendc.compute.api.Flavor
-import org.opendc.compute.api.Image
-import org.opendc.compute.simulator.power.api.CpuPowerModel
-import org.opendc.compute.simulator.power.api.Powerable
-import org.opendc.compute.simulator.power.models.ConstantPowerModel
-import org.opendc.metal.Node
-import org.opendc.metal.NodeEvent
-import org.opendc.metal.NodeState
-import org.opendc.metal.driver.BareMetalDriver
-import org.opendc.simulator.compute.SimBareMetalMachine
-import org.opendc.simulator.compute.SimMachineModel
-import org.opendc.simulator.compute.workload.SimWorkload
-import org.opendc.simulator.failures.FailureDomain
-import org.opendc.utils.flow.EventFlow
-import org.opendc.utils.flow.StateFlow
-import java.time.Clock
-import java.util.UUID
-
-/**
- * A basic implementation of the [BareMetalDriver] that simulates an [Image] running on a bare-metal machine.
- *
- * @param coroutineScope The [CoroutineScope] the driver runs in.
- * @param clock The virtual clock to keep track of time.
- * @param uid The unique identifier of the machine.
- * @param name An optional name of the machine.
- * @param metadata The initial metadata of the node.
- * @param machine The machine model to simulate.
- * @param cpuPowerModel The CPU power model of this machine.
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-public class SimBareMetalDriver(
- private val coroutineScope: CoroutineScope,
- private val clock: Clock,
- uid: UUID,
- name: String,
- metadata: Map<String, Any>,
- machine: SimMachineModel,
- cpuPowerModel: CpuPowerModel = ConstantPowerModel(0.0),
-) : BareMetalDriver, FailureDomain, Powerable {
- /**
- * The flavor that corresponds to this machine.
- */
- private val flavor = Flavor(
- machine.cpus.size,
- machine.memory.map { it.size }.sum()
- )
-
- /**
- * The events of the machine.
- */
- private val events = EventFlow<NodeEvent>()
-
- /**
- * The machine state.
- */
- private val nodeState =
- StateFlow(Node(uid, name, metadata + ("driver" to this), NodeState.SHUTOFF, flavor, Image.EMPTY, events))
-
- /**
- * The [SimBareMetalMachine] we use to run the workload.
- */
- private val machine = SimBareMetalMachine(coroutineScope, clock, machine)
-
- override val node: Flow<Node> = nodeState
-
- override val usage: Flow<Double>
- get() = this.machine.usage
-
- override val powerDraw: Flow<Double> = cpuPowerModel.getPowerDraw(this)
-
- /**
- * The [Job] that runs the simulated workload.
- */
- private var job: Job? = null
-
- override suspend fun init(): Node {
- return nodeState.value
- }
-
- override suspend fun start(): Node {
- val node = nodeState.value
- if (node.state != NodeState.SHUTOFF) {
- return node
- }
-
- val workload = node.image.tags["workload"] as SimWorkload
-
- job = coroutineScope.launch {
- delay(1) // TODO Introduce boot time
- initMachine()
- try {
- machine.run(workload, mapOf("driver" to this@SimBareMetalDriver, "node" to node))
- exitMachine(null)
- } catch (_: CancellationException) {
- // Ignored
- } catch (cause: Throwable) {
- exitMachine(cause)
- }
- }
-
- setNode(node.copy(state = NodeState.BOOT))
- return nodeState.value
- }
-
- private fun initMachine() {
- setNode(nodeState.value.copy(state = NodeState.ACTIVE))
- }
-
- private fun exitMachine(cause: Throwable?) {
- val newNodeState =
- if (cause == null)
- NodeState.SHUTOFF
- else
- NodeState.ERROR
- setNode(nodeState.value.copy(state = newNodeState))
- }
-
- override suspend fun stop(): Node {
- val node = nodeState.value
- if (node.state == NodeState.SHUTOFF) {
- return node
- }
-
- job?.cancelAndJoin()
- setNode(node.copy(state = NodeState.SHUTOFF))
- return node
- }
-
- override suspend fun reboot(): Node {
- stop()
- return start()
- }
-
- override suspend fun setImage(image: Image): Node {
- setNode(nodeState.value.copy(image = image))
- return nodeState.value
- }
-
- override suspend fun refresh(): Node = nodeState.value
-
- private fun setNode(value: Node) {
- val field = nodeState.value
- if (field.state != value.state) {
- events.emit(NodeEvent.StateChanged(value, field.state))
- }
-
- nodeState.value = value
- }
-
- override val scope: CoroutineScope
- get() = coroutineScope
-
- override suspend fun fail() {
- setNode(nodeState.value.copy(state = NodeState.ERROR))
- }
-
- override suspend fun recover() {
- setNode(nodeState.value.copy(state = NodeState.ACTIVE))
- }
-
- override fun toString(): String = "SimBareMetalDriver(node = ${nodeState.value.uid})"
-}
diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt
index fd547d3d..19fa3e97 100644
--- a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt
+++ b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHost.kt
@@ -29,34 +29,43 @@ import org.opendc.compute.api.Flavor
import org.opendc.compute.api.Server
import org.opendc.compute.api.ServerState
import org.opendc.compute.service.driver.*
-import org.opendc.metal.Node
+import org.opendc.compute.simulator.power.api.CpuPowerModel
+import org.opendc.compute.simulator.power.api.Powerable
+import org.opendc.compute.simulator.power.models.ConstantPowerModel
import org.opendc.simulator.compute.*
import org.opendc.simulator.compute.interference.IMAGE_PERF_INTERFERENCE_MODEL
import org.opendc.simulator.compute.interference.PerformanceInterferenceModel
import org.opendc.simulator.compute.model.MemoryUnit
-import org.opendc.simulator.compute.workload.SimResourceCommand
-import org.opendc.simulator.compute.workload.SimWorkload
+import org.opendc.simulator.failures.FailureDomain
import org.opendc.utils.flow.EventFlow
+import java.time.Clock
import java.util.*
+import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume
/**
* A [Host] that is simulates virtual machines on a physical machine using [SimHypervisor].
*/
public class SimHost(
- public val node: Node,
- private val coroutineScope: CoroutineScope,
- hypervisor: SimHypervisorProvider
-) : Host, SimWorkload {
+ override val uid: UUID,
+ override val name: String,
+ model: SimMachineModel,
+ override val meta: Map<String, Any>,
+ context: CoroutineContext,
+ clock: Clock,
+ hypervisor: SimHypervisorProvider,
+ cpuPowerModel: CpuPowerModel = ConstantPowerModel(0.0),
+ private val mapper: SimWorkloadMapper = SimMetaWorkloadMapper()
+) : Host, FailureDomain, Powerable, AutoCloseable {
/**
- * The logger instance of this server.
+ * The [CoroutineScope] of the host bounded by the lifecycle of the host.
*/
- private val logger = KotlinLogging.logger {}
+ override val scope: CoroutineScope = CoroutineScope(context)
/**
- * The execution context in which the [Host] runs.
+ * The logger instance of this server.
*/
- private lateinit var ctx: SimExecutionContext
+ private val logger = KotlinLogging.logger {}
override val events: Flow<HostEvent>
get() = _events
@@ -70,12 +79,17 @@ public class SimHost(
/**
* Current total memory use of the images on this hypervisor.
*/
- private var availableMemory: Long = 0
+ private var availableMemory: Long = model.memory.map { it.size }.sum()
+
+ /**
+ * The machine to run on.
+ */
+ public val machine: SimBareMetalMachine = SimBareMetalMachine(scope, clock, model)
/**
* The hypervisor to run multiple workloads.
*/
- private val hypervisor = hypervisor.create(
+ public val hypervisor: SimHypervisor = hypervisor.create(
object : SimHypervisor.Listener {
override fun onSliceFinish(
hypervisor: SimHypervisor,
@@ -107,26 +121,40 @@ public class SimHost(
*/
private val guests = HashMap<Server, Guest>()
- override val uid: UUID
- get() = node.uid
-
- override val name: String
- get() = node.name
-
- override val model: HostModel
- get() = HostModel(node.flavor.cpuCount, node.flavor.memorySize)
-
override val state: HostState
get() = _state
- private var _state: HostState = HostState.UP
+ private var _state: HostState = HostState.DOWN
set(value) {
- listeners.forEach { it.onStateChanged(this, value) }
+ if (value != field) {
+ listeners.forEach { it.onStateChanged(this, value) }
+ }
field = value
}
+ override val model: HostModel = HostModel(model.cpus.size, model.memory.map { it.size }.sum())
+
+ override val powerDraw: Flow<Double> = cpuPowerModel.getPowerDraw(this)
+
+ init {
+ // Launch hypervisor onto machine
+ scope.launch {
+ try {
+ _state = HostState.UP
+ machine.run(this@SimHost.hypervisor, emptyMap())
+ } catch (_: CancellationException) {
+ // Ignored
+ } catch (cause: Throwable) {
+ logger.error(cause) { "Host failed" }
+ throw cause
+ } finally {
+ _state = HostState.DOWN
+ }
+ }
+ }
+
override fun canFit(server: Server): Boolean {
val sufficientMemory = availableMemory > server.flavor.memorySize
- val enoughCpus = ctx.machine.cpus.size >= server.flavor.cpuCount
+ val enoughCpus = machine.model.cpus.size >= server.flavor.cpuCount
val canFit = hypervisor.canFit(server.flavor.toMachineModel())
return sufficientMemory && enoughCpus && canFit
@@ -146,7 +174,7 @@ public class SimHost(
guest.start()
}
- _events.emit(HostEvent.VmsUpdated(this, guests.count { it.value.state == ServerState.ACTIVE }, availableMemory))
+ _events.emit(HostEvent.VmsUpdated(this, guests.count { it.value.state == ServerState.RUNNING }, availableMemory))
}
override fun contains(server: Server): Boolean {
@@ -163,7 +191,7 @@ public class SimHost(
guest.stop()
}
- override suspend fun terminate(server: Server) {
+ override suspend fun delete(server: Server) {
val guest = guests.remove(server) ?: return
guest.terminate()
}
@@ -176,11 +204,16 @@ public class SimHost(
listeners.remove(listener)
}
+ override fun close() {
+ scope.cancel()
+ _state = HostState.DOWN
+ }
+
/**
* Convert flavor to machine model.
*/
private fun Flavor.toMachineModel(): SimMachineModel {
- val originalCpu = ctx.machine.cpus[0]
+ val originalCpu = machine.model.cpus[0]
val processingNode = originalCpu.node.copy(coreCount = cpuCount)
val processingUnits = (0 until cpuCount).map { originalCpu.copy(id = it, node = processingNode) }
val memoryUnits = listOf(MemoryUnit("Generic", "Generic", 3200.0, memorySize))
@@ -190,7 +223,7 @@ public class SimHost(
private fun onGuestStart(vm: Guest) {
guests.forEach { _, guest ->
- if (guest.state == ServerState.ACTIVE) {
+ if (guest.state == ServerState.RUNNING) {
vm.performanceInterferenceModel?.onStart(vm.server.image.name)
}
}
@@ -200,58 +233,71 @@ public class SimHost(
private fun onGuestStop(vm: Guest) {
guests.forEach { _, guest ->
- if (guest.state == ServerState.ACTIVE) {
+ if (guest.state == ServerState.RUNNING) {
vm.performanceInterferenceModel?.onStop(vm.server.image.name)
}
}
listeners.forEach { it.onStateChanged(this, vm.server, vm.state) }
- _events.emit(HostEvent.VmsUpdated(this@SimHost, guests.count { it.value.state == ServerState.ACTIVE }, availableMemory))
+ _events.emit(HostEvent.VmsUpdated(this@SimHost, guests.count { it.value.state == ServerState.RUNNING }, availableMemory))
+ }
+
+ override suspend fun fail() {
+ _state = HostState.DOWN
+ }
+
+ override suspend fun recover() {
+ _state = HostState.UP
}
/**
* A virtual machine instance that the driver manages.
*/
private inner class Guest(val server: Server, val machine: SimMachine) {
- val performanceInterferenceModel: PerformanceInterferenceModel? = server.image.tags[IMAGE_PERF_INTERFERENCE_MODEL] as? PerformanceInterferenceModel?
+ val performanceInterferenceModel: PerformanceInterferenceModel? = server.meta[IMAGE_PERF_INTERFERENCE_MODEL] as? PerformanceInterferenceModel?
- var state: ServerState = ServerState.SHUTOFF
+ var state: ServerState = ServerState.TERMINATED
suspend fun start() {
when (state) {
- ServerState.SHUTOFF -> {
+ ServerState.TERMINATED -> {
logger.info { "User requested to start server ${server.uid}" }
launch()
}
- ServerState.ACTIVE -> return
+ ServerState.RUNNING -> return
+ ServerState.DELETED -> {
+ logger.warn { "User tried to start terminated server" }
+ throw IllegalArgumentException("Server is terminated")
+ }
else -> assert(false) { "Invalid state transition" }
}
}
suspend fun stop() {
when (state) {
- ServerState.ACTIVE, ServerState.ERROR -> {
+ ServerState.RUNNING, ServerState.ERROR -> {
val job = job ?: throw IllegalStateException("Server should be active")
job.cancel()
job.join()
}
- ServerState.SHUTOFF -> return
+ ServerState.TERMINATED, ServerState.DELETED -> return
else -> assert(false) { "Invalid state transition" }
}
}
suspend fun terminate() {
stop()
+ state = ServerState.DELETED
}
private var job: Job? = null
private suspend fun launch() = suspendCancellableCoroutine<Unit> { cont ->
assert(job == null) { "Concurrent job running" }
- val workload = server.image.tags["workload"] as SimWorkload
+ val workload = mapper.createWorkload(server)
- val job = coroutineScope.launch {
+ val job = scope.launch {
delay(1) // TODO Introduce boot time
init()
cont.resume(Unit)
@@ -271,14 +317,14 @@ public class SimHost(
}
private fun init() {
- state = ServerState.ACTIVE
+ state = ServerState.RUNNING
onGuestStart(this)
}
private fun exit(cause: Throwable?) {
state =
if (cause == null)
- ServerState.SHUTOFF
+ ServerState.TERMINATED
else
ServerState.ERROR
@@ -286,18 +332,4 @@ public class SimHost(
onGuestStop(this)
}
}
-
- override fun onStart(ctx: SimExecutionContext) {
- this.ctx = ctx
- this.availableMemory = ctx.machine.memory.map { it.size }.sum()
- this.hypervisor.onStart(ctx)
- }
-
- override fun onStart(ctx: SimExecutionContext, cpu: Int): SimResourceCommand {
- return hypervisor.onStart(ctx, cpu)
- }
-
- override fun onNext(ctx: SimExecutionContext, cpu: Int, remainingWork: Double): SimResourceCommand {
- return hypervisor.onNext(ctx, cpu, remainingWork)
- }
}
diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHostProvisioner.kt b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHostProvisioner.kt
deleted file mode 100644
index bb03777b..00000000
--- a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimHostProvisioner.kt
+++ /dev/null
@@ -1,69 +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.simulator
-
-import kotlinx.coroutines.*
-import org.opendc.compute.api.Image
-import org.opendc.compute.service.driver.Host
-import org.opendc.metal.Node
-import org.opendc.metal.service.ProvisioningService
-import org.opendc.simulator.compute.SimHypervisorProvider
-import kotlin.coroutines.CoroutineContext
-
-/**
- * A helper class to provision [SimHost]s on top of bare-metal machines using the [ProvisioningService].
- *
- * @param context The [CoroutineContext] to use.
- * @param metal The [ProvisioningService] to use.
- * @param hypervisor The type of hypervisor to use.
- */
-public class SimHostProvisioner(
- private val context: CoroutineContext,
- private val metal: ProvisioningService,
- private val hypervisor: SimHypervisorProvider
-) : AutoCloseable {
- /**
- * The [CoroutineScope] of the service bounded by the lifecycle of the service.
- */
- private val scope = CoroutineScope(context)
-
- /**
- * Provision all machines with a host.
- */
- public suspend fun provisionAll(): List<Host> = coroutineScope {
- metal.nodes().map { node -> async { provision(node) } }.awaitAll()
- }
-
- /**
- * Provision the specified [Node].
- */
- public suspend fun provision(node: Node): Host = coroutineScope {
- val host = SimHost(node, scope, hypervisor)
- metal.deploy(node, Image(node.uid, node.name, mapOf("workload" to host)))
- host
- }
-
- override fun close() {
- scope.cancel()
- }
-}
diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimMetaWorkloadMapper.kt b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimMetaWorkloadMapper.kt
new file mode 100644
index 00000000..c05f1a2c
--- /dev/null
+++ b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimMetaWorkloadMapper.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.simulator
+
+import org.opendc.compute.api.Server
+import org.opendc.simulator.compute.workload.SimWorkload
+
+/**
+ * A [SimWorkloadMapper] that maps a [Server] to a workload via the meta-data.
+ */
+public class SimMetaWorkloadMapper(private val key: String = "workload") : SimWorkloadMapper {
+ override fun createWorkload(server: Server): SimWorkload {
+ return requireNotNull(server.meta[key] ?: server.image.meta[key]) as SimWorkload
+ }
+}
diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimWorkloadMapper.kt b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimWorkloadMapper.kt
new file mode 100644
index 00000000..7082c5cf
--- /dev/null
+++ b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/SimWorkloadMapper.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.simulator
+
+import org.opendc.compute.api.Server
+import org.opendc.simulator.compute.workload.SimWorkload
+
+/**
+ * A [SimWorkloadMapper] is responsible for mapping a [Server] and [Image] to a [SimWorkload] that can be simulated.
+ */
+public fun interface SimWorkloadMapper {
+ /**
+ * Map the specified [server] to a [SimWorkload] that can be simulated.
+ */
+ public fun createWorkload(server: Server): SimWorkload
+}
diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/api/CpuPowerModel.kt b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/api/CpuPowerModel.kt
index 0141bc8c..604b69c0 100644
--- a/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/api/CpuPowerModel.kt
+++ b/simulator/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/power/api/CpuPowerModel.kt
@@ -2,7 +2,7 @@ package org.opendc.compute.simulator.power.api
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
-import org.opendc.metal.driver.BareMetalDriver
+import org.opendc.compute.simulator.SimHost
public interface CpuPowerModel {
/**
@@ -18,14 +18,14 @@ public interface CpuPowerModel {
/**
* Emits the values of power consumption for servers.
*
- * @param driver A [BareMetalDriver] that offers host CPU utilization.
+ * @param host A [SimHost] that offers host CPU utilization.
* @param withoutIdle A [Boolean] flag indicates whether (false) add a constant
* power consumption value when the server is idle or (true) not
* with a default value being false.
* @return A [Flow] of values representing the server power draw.
*/
- public fun getPowerDraw(driver: BareMetalDriver, withoutIdle: Boolean = false): Flow<Double> =
- driver.usage.map {
+ public fun getPowerDraw(host: SimHost, withoutIdle: Boolean = false): Flow<Double> =
+ host.machine.usage.map {
computeCpuPower(it)
}
}
diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimBareMetalDriverTest.kt b/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimBareMetalDriverTest.kt
deleted file mode 100644
index 0d90376e..00000000
--- a/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimBareMetalDriverTest.kt
+++ /dev/null
@@ -1,92 +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.simulator
-
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.TestCoroutineScope
-import kotlinx.coroutines.withContext
-import org.junit.jupiter.api.Assertions.assertEquals
-import org.junit.jupiter.api.BeforeEach
-import org.junit.jupiter.api.Test
-import org.junit.jupiter.api.assertAll
-import org.opendc.compute.api.Image
-import org.opendc.metal.NodeEvent
-import org.opendc.metal.NodeState
-import org.opendc.simulator.compute.SimMachineModel
-import org.opendc.simulator.compute.model.MemoryUnit
-import org.opendc.simulator.compute.model.ProcessingNode
-import org.opendc.simulator.compute.model.ProcessingUnit
-import org.opendc.simulator.compute.workload.SimFlopsWorkload
-import org.opendc.simulator.utils.DelayControllerClockAdapter
-import java.util.UUID
-
-@OptIn(ExperimentalCoroutinesApi::class)
-internal class SimBareMetalDriverTest {
- private lateinit var machineModel: SimMachineModel
-
- @BeforeEach
- fun setUp() {
- val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 4)
-
- machineModel = SimMachineModel(
- cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 2000.0) },
- memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }
- )
- }
-
- @Test
- fun testFlopsWorkload() {
- val testScope = TestCoroutineScope()
- val clock = DelayControllerClockAdapter(testScope)
-
- var finalState: NodeState = NodeState.UNKNOWN
- var finalTime = 0L
-
- testScope.launch {
- val driver = SimBareMetalDriver(this, clock, UUID.randomUUID(), "test", emptyMap(), machineModel)
- val image = Image(UUID.randomUUID(), "<unnamed>", mapOf("workload" to SimFlopsWorkload(4_000, utilization = 1.0)))
- // Batch driver commands
- withContext(coroutineContext) {
- driver.init()
- driver.setImage(image)
- val node = driver.start()
- node.events.collect { event ->
- when (event) {
- is NodeEvent.StateChanged -> {
- finalState = event.node.state
- finalTime = clock.millis()
- }
- }
- }
- }
- }
-
- testScope.advanceUntilIdle()
- assertAll(
- { assertEquals(NodeState.SHUTOFF, finalState) },
- { assertEquals(501, finalTime) }
- )
- }
-}
diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimHostTest.kt b/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimHostTest.kt
index 61bff39f..e1a1d87e 100644
--- a/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimHostTest.kt
+++ b/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimHostTest.kt
@@ -24,7 +24,6 @@ package org.opendc.compute.simulator
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@@ -39,8 +38,6 @@ import org.opendc.compute.api.Server
import org.opendc.compute.api.ServerState
import org.opendc.compute.api.ServerWatcher
import org.opendc.compute.service.driver.HostEvent
-import org.opendc.metal.Node
-import org.opendc.metal.NodeState
import org.opendc.simulator.compute.SimFairShareHypervisorProvider
import org.opendc.simulator.compute.SimMachineModel
import org.opendc.simulator.compute.model.MemoryUnit
@@ -82,18 +79,13 @@ internal class SimHostTest {
var grantedWork = 0L
var overcommittedWork = 0L
- val node = Node(
- UUID.randomUUID(), "name", emptyMap(), NodeState.SHUTOFF,
- Flavor(machineModel.cpus.size, machineModel.memory.map { it.size }.sum()), Image.EMPTY, emptyFlow()
- )
-
scope.launch {
- val virtDriver = SimHost(node, this, SimFairShareHypervisorProvider())
- val vmm = Image(UUID.randomUUID(), "vmm", mapOf("workload" to virtDriver))
+ val virtDriver = SimHost(UUID.randomUUID(), "test", machineModel, emptyMap(), coroutineContext, clock, SimFairShareHypervisorProvider())
val duration = 5 * 60L
- val vmImageA = Image(
+ val vmImageA = MockImage(
UUID.randomUUID(),
"<unnamed>",
+ emptyMap(),
mapOf(
"workload" to SimTraceWorkload(
sequenceOf(
@@ -105,9 +97,10 @@ internal class SimHostTest {
)
)
)
- val vmImageB = Image(
+ val vmImageB = MockImage(
UUID.randomUUID(),
"<unnamed>",
+ emptyMap(),
mapOf(
"workload" to SimTraceWorkload(
sequenceOf(
@@ -120,16 +113,9 @@ internal class SimHostTest {
)
)
- val metalDriver =
- SimBareMetalDriver(this, clock, UUID.randomUUID(), "test", emptyMap(), machineModel)
-
- metalDriver.init()
- metalDriver.setImage(vmm)
- metalDriver.start()
-
delay(5)
- val flavor = Flavor(2, 0)
+ val flavor = MockFlavor(2, 0)
virtDriver.events
.onEach { event ->
when (event) {
@@ -157,14 +143,56 @@ internal class SimHostTest {
)
}
+ private class MockFlavor(
+ override val cpuCount: Int,
+ override val memorySize: Long
+ ) : Flavor {
+ override val uid: UUID = UUID.randomUUID()
+ override val name: String = "test"
+ override val labels: Map<String, String> = emptyMap()
+ override val meta: Map<String, Any> = emptyMap()
+
+ override suspend fun delete() {
+ throw NotImplementedError()
+ }
+
+ override suspend fun refresh() {
+ throw NotImplementedError()
+ }
+ }
+
+ private class MockImage(
+ override val uid: UUID,
+ override val name: String,
+ override val labels: Map<String, String>,
+ override val meta: Map<String, Any>
+ ) : Image {
+ override suspend fun delete() {
+ throw NotImplementedError()
+ }
+
+ override suspend fun refresh() {
+ throw NotImplementedError()
+ }
+ }
+
private class MockServer(
override val uid: UUID,
override val name: String,
override val flavor: Flavor,
override val image: Image
) : Server {
- override val tags: Map<String, String> = emptyMap()
- override val state: ServerState = ServerState.BUILD
+ override val labels: Map<String, String> = emptyMap()
+
+ override val meta: Map<String, Any> = emptyMap()
+
+ override val state: ServerState = ServerState.TERMINATED
+
+ override suspend fun start() {}
+
+ override suspend fun stop() {}
+
+ override suspend fun delete() {}
override fun watch(watcher: ServerWatcher) {}
diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimProvisioningServiceTest.kt b/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimProvisioningServiceTest.kt
deleted file mode 100644
index 33b3db94..00000000
--- a/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/SimProvisioningServiceTest.kt
+++ /dev/null
@@ -1,81 +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.simulator
-
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.TestCoroutineScope
-import org.junit.jupiter.api.BeforeEach
-import org.junit.jupiter.api.Test
-import org.opendc.compute.api.Image
-import org.opendc.metal.service.SimpleProvisioningService
-import org.opendc.simulator.compute.SimMachineModel
-import org.opendc.simulator.compute.model.MemoryUnit
-import org.opendc.simulator.compute.model.ProcessingNode
-import org.opendc.simulator.compute.model.ProcessingUnit
-import org.opendc.simulator.compute.workload.SimFlopsWorkload
-import org.opendc.simulator.utils.DelayControllerClockAdapter
-import java.util.UUID
-
-/**
- * Test suite for the [SimpleProvisioningService].
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-internal class SimProvisioningServiceTest {
- private lateinit var machineModel: SimMachineModel
-
- @BeforeEach
- fun setUp() {
- val cpuNode = ProcessingNode("Intel", "Xeon", "amd64", 4)
-
- machineModel = SimMachineModel(
- cpus = List(cpuNode.coreCount) { ProcessingUnit(cpuNode, it, 2000.0) },
- memory = List(4) { MemoryUnit("Crucial", "MTA18ASF4G72AZ-3G2B1", 3200.0, 32_000) }
- )
- }
-
- /**
- * A basic smoke test.
- */
- @Test
- fun testSmoke() {
- val testScope = TestCoroutineScope()
- val clock = DelayControllerClockAdapter(testScope)
-
- testScope.launch {
- val image = Image(UUID.randomUUID(), "<unnamed>", mapOf("machine" to SimFlopsWorkload(1000)))
- val driver = SimBareMetalDriver(this, clock, UUID.randomUUID(), "test", emptyMap(), machineModel)
-
- val provisioner = SimpleProvisioningService()
- provisioner.create(driver)
- delay(5)
- val nodes = provisioner.nodes()
- val node = provisioner.deploy(nodes.first(), image)
- node.events.collect { println(it) }
- }
-
- testScope.advanceUntilIdle()
- }
-}
diff --git a/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/power/CpuPowerModelTest.kt b/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/power/CpuPowerModelTest.kt
index d4d88fb1..9d034a5d 100644
--- a/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/power/CpuPowerModelTest.kt
+++ b/simulator/opendc-compute/opendc-compute-simulator/src/test/kotlin/org/opendc/compute/simulator/power/CpuPowerModelTest.kt
@@ -1,18 +1,21 @@
package org.opendc.compute.simulator.power
import io.mockk.*
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runBlockingTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
+import org.opendc.compute.simulator.SimHost
import org.opendc.compute.simulator.power.api.CpuPowerModel
import org.opendc.compute.simulator.power.models.*
-import org.opendc.metal.driver.BareMetalDriver
+import org.opendc.simulator.compute.SimBareMetalMachine
import java.util.stream.Stream
import kotlin.math.pow
+@OptIn(ExperimentalCoroutinesApi::class)
internal class CpuPowerModelTest {
private val epsilon = 10.0.pow(-3)
private val cpuUtil = .9
@@ -44,21 +47,24 @@ internal class CpuPowerModelTest {
powerModel: CpuPowerModel,
expectedPowerConsumption: Double
) {
- val cpuLoads = flowOf(cpuUtil, cpuUtil, cpuUtil)
- val bareMetalDriver = mockkClass(BareMetalDriver::class)
- every { bareMetalDriver.usage } returns cpuLoads
+ runBlockingTest {
+ val cpuLoads = flowOf(cpuUtil, cpuUtil, cpuUtil).stateIn(this)
+ val bareMetalDriver = mockkClass(SimHost::class)
+ val machine = mockkClass(SimBareMetalMachine::class)
+ every { bareMetalDriver.machine } returns machine
+ every { machine.usage } returns cpuLoads
- runBlocking {
val serverPowerDraw = powerModel.getPowerDraw(bareMetalDriver)
- assertEquals(serverPowerDraw.count(), cpuLoads.count())
assertEquals(
serverPowerDraw.first().toDouble(),
flowOf(expectedPowerConsumption).first().toDouble(),
epsilon
)
+
+ verify(exactly = 1) { bareMetalDriver.machine }
+ verify(exactly = 1) { machine.usage }
}
- verify(exactly = 1) { bareMetalDriver.usage }
}
@Suppress("unused")