diff options
| author | Fabian Mastenbroek <mail.fabianm@gmail.com> | 2021-10-25 20:57:51 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-10-25 20:57:51 +0200 |
| commit | a8e2d460a3b6803845687585ae0b34e67a9445a3 (patch) | |
| tree | 6249023f8f0d56392400c7ebb72238ee848f740a /opendc-compute/opendc-compute-service/src | |
| parent | b4bf7268cbb6d22d3966f469a6b7721b04d91907 (diff) | |
| parent | 86c65e875b7dde8872dc81a37aa9dca72eee7782 (diff) | |
merge: Improve the OpenDC compute model (#37)
This pull request contains various improvements to the OpenDC compute simulation model.
- Support filtering hosts based on CPU capacity
- Do not allocate lambda in fast-path
- Redesign VM interference algorithm
- Report provisioning time of virtual machines
- Prevent allocations during collection cycle
- Use correct flow input capacity for counters
- Support running workloads without coroutines
**Breaking API Changes**
- `VirtualMachine` now requires `cpuCapacity` parameter.
- `VmInterferenceModel` needs to be constructed using `VmInterferenceModel.Builder` and can't be passed a list of groups anymore.
- Scheduling latency is not collected anymore. Instead, use the boot time and provisioning time to derive the scheduling latency.
- Telemetry data is recorded using `*TableReader` interfaces as opposed to the `*Data` classes. These classes are re-used per row and should not be shared with other threads, since the underlying data may change.
- `SimMachine` does not implement `AutoCloseable` anymore. Machines can be removed from a `SimHypervisor` using the `removeMachine` method.
- `SimMachine.run` is moved to an extension method called `runWorkload`. Users can now also choose to run a workload using the asynchronous `SimMachine.startWorkload`.
Diffstat (limited to 'opendc-compute/opendc-compute-service/src')
9 files changed, 169 insertions, 36 deletions
diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/HostModel.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/HostModel.kt index fc092a3f..f3b94e3d 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/HostModel.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/driver/HostModel.kt @@ -25,7 +25,12 @@ package org.opendc.compute.service.driver /** * Describes the static machine properties of the host. * + * @property cpuCapacity The total CPU capacity of the host in MHz. * @property cpuCount The number of logical processing cores available for this host. - * @property memorySize The amount of memory available for this host in MB. + * @property memoryCapacity The amount of memory available for this host in MB. */ -public data class HostModel(public val cpuCount: Int, public val memorySize: Long) +public data class HostModel( + public val cpuCapacity: Double, + public val cpuCount: Int, + public val memoryCapacity: Long +) diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt index 57e70fcd..292feabe 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/ComputeServiceImpl.kt @@ -26,6 +26,7 @@ import io.opentelemetry.api.common.AttributeKey import io.opentelemetry.api.common.Attributes import io.opentelemetry.api.metrics.Meter import io.opentelemetry.api.metrics.MeterProvider +import io.opentelemetry.api.metrics.ObservableLongMeasurement import kotlinx.coroutines.* import mu.KotlinLogging import org.opendc.compute.api.* @@ -173,6 +174,12 @@ internal class ComputeServiceImpl( result.observe(available, upState) result.observe(total - available, downState) } + + meter.gaugeBuilder("system.time.provision") + .setDescription("The most recent timestamp where the server entered a provisioned state") + .setUnit("1") + .ofLongs() + .buildWithCallback(::collectProvisionTime) } override fun newClient(): ComputeClient { @@ -298,7 +305,7 @@ internal class ComputeServiceImpl( val hv = HostView(host) maxCores = max(maxCores, host.model.cpuCount) - maxMemory = max(maxMemory, host.model.memorySize) + maxMemory = max(maxMemory, host.model.memoryCapacity) hostToView[host] = hv if (host.state == HostState.UP) { @@ -324,8 +331,10 @@ internal class ComputeServiceImpl( internal fun schedule(server: InternalServer): SchedulingRequest { logger.debug { "Enqueueing server ${server.uid} to be assigned to host." } + val now = clock.millis() + val request = SchedulingRequest(server, now) - val request = SchedulingRequest(server, clock.millis()) + server.lastProvisioningTimestamp = now queue.add(request) _serversPending.add(1) requestSchedulingCycle() @@ -501,4 +510,13 @@ internal class ComputeServiceImpl( requestSchedulingCycle() } } + + /** + * Collect the timestamp when each server entered its provisioning state most recently. + */ + private fun collectProvisionTime(result: ObservableLongMeasurement) { + for ((_, server) in servers) { + result.observe(server.lastProvisioningTimestamp, server.attributes) + } + } } diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/HostView.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/HostView.kt index e2f33f11..0876209a 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/HostView.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/HostView.kt @@ -37,7 +37,7 @@ public class HostView(public val host: Host) { get() = host.uid public var instanceCount: Int = 0 - public var availableMemory: Long = host.model.memorySize + public var availableMemory: Long = host.model.memoryCapacity public var provisionedCores: Int = 0 override fun toString(): String = "HostView[host=$host]" diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt index 05a7e1bf..f1b92c66 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/internal/InternalServer.kt @@ -55,7 +55,7 @@ internal class InternalServer( /** * The attributes of a server. */ - internal val attributes: Attributes = Attributes.builder() + @JvmField internal val attributes: Attributes = Attributes.builder() .put(ResourceAttributes.HOST_NAME, name) .put(ResourceAttributes.HOST_ID, uid.toString()) .put(ResourceAttributes.HOST_TYPE, flavor.name) @@ -70,7 +70,12 @@ internal class InternalServer( /** * The [Host] that has been assigned to host the server. */ - internal var host: Host? = null + @JvmField internal var host: Host? = null + + /** + * The most recent timestamp when the server entered a provisioning state. + */ + @JvmField internal var lastProvisioningTimestamp: Long = Long.MIN_VALUE /** * The current scheduling request. diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/RamFilter.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/RamFilter.kt index a470a453..8a7a646c 100644 --- a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/RamFilter.kt +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/RamFilter.kt @@ -34,7 +34,7 @@ public class RamFilter(private val allocationRatio: Double) : HostFilter { override fun test(host: HostView, server: Server): Boolean { val requested = server.flavor.memorySize val available = host.availableMemory - val total = host.host.model.memorySize + val total = host.host.model.memoryCapacity // Do not allow an instance to overcommit against itself, only against // other instances. diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/VCpuCapacityFilter.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/VCpuCapacityFilter.kt new file mode 100644 index 00000000..791710c8 --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/filters/VCpuCapacityFilter.kt @@ -0,0 +1,40 @@ +/* + * 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.scheduler.filters + +import org.opendc.compute.api.Server +import org.opendc.compute.service.internal.HostView + +/** + * A [HostFilter] that filters hosts based on the vCPU speed requirements of a [Server] and the available + * capacity on the host. + */ +public class VCpuCapacityFilter : HostFilter { + override fun test(host: HostView, server: Server): Boolean { + val requiredCapacity = server.flavor.meta["cpu-capacity"] as? Double + val hostModel = host.host.model + val availableCapacity = hostModel.cpuCapacity / hostModel.cpuCount + + return requiredCapacity == null || availableCapacity >= (requiredCapacity / server.flavor.cpuCount) + } +} diff --git a/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/VCpuCapacityWeigher.kt b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/VCpuCapacityWeigher.kt new file mode 100644 index 00000000..a86226e2 --- /dev/null +++ b/opendc-compute/opendc-compute-service/src/main/kotlin/org/opendc/compute/service/scheduler/weights/VCpuCapacityWeigher.kt @@ -0,0 +1,40 @@ +/* + * 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.scheduler.weights + +import org.opendc.compute.api.Server +import org.opendc.compute.service.internal.HostView + +/** + * A [HostWeigher] that weighs the hosts based on the difference required vCPU capacity and the available CPU capacity. + */ +public class VCpuCapacityWeigher(override val multiplier: Double = 1.0) : HostWeigher { + + override fun getWeight(host: HostView, server: Server): Double { + val model = host.host.model + val requiredCapacity = server.flavor.meta["cpu-capacity"] as? Double ?: 0.0 + return model.cpuCapacity / model.cpuCount - requiredCapacity / server.flavor.cpuCount + } + + override fun toString(): String = "VCpuWeigher" +} diff --git a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt index 564f9493..7b8d0fe2 100644 --- a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt +++ b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt @@ -125,7 +125,7 @@ internal class ComputeServiceTest { fun testAddHost() = scope.runBlockingSimulation { val host = mockk<Host>(relaxUnitFun = true) - every { host.model } returns HostModel(4, 2048) + every { host.model } returns HostModel(4 * 2600.0, 4, 2048) every { host.state } returns HostState.UP assertEquals(0, service.hostCount) @@ -147,7 +147,7 @@ internal class ComputeServiceTest { fun testAddHostDouble() = scope.runBlockingSimulation { val host = mockk<Host>(relaxUnitFun = true) - every { host.model } returns HostModel(4, 2048) + every { host.model } returns HostModel(4 * 2600.0, 4, 2048) every { host.state } returns HostState.DOWN assertEquals(0, service.hostCount) @@ -216,7 +216,7 @@ internal class ComputeServiceTest { fun testServerCannotFitOnHost() = scope.runBlockingSimulation { val host = mockk<Host>(relaxUnitFun = true) - every { host.model } returns HostModel(4, 2048) + every { host.model } returns HostModel(4 * 2600.0, 4, 2048) every { host.state } returns HostState.UP every { host.canFit(any()) } returns false @@ -241,7 +241,7 @@ internal class ComputeServiceTest { val listeners = mutableListOf<HostListener>() every { host.uid } returns UUID.randomUUID() - every { host.model } returns HostModel(4, 2048) + every { host.model } returns HostModel(4 * 2600.0, 4, 2048) every { host.state } returns HostState.DOWN every { host.addListener(any()) } answers { listeners.add(it.invocation.args[0] as HostListener) } every { host.canFit(any()) } returns false @@ -272,7 +272,7 @@ internal class ComputeServiceTest { val listeners = mutableListOf<HostListener>() every { host.uid } returns UUID.randomUUID() - every { host.model } returns HostModel(4, 2048) + every { host.model } returns HostModel(4 * 2600.0, 4, 2048) every { host.state } returns HostState.UP every { host.addListener(any()) } answers { listeners.add(it.invocation.args[0] as HostListener) } every { host.canFit(any()) } returns false @@ -303,7 +303,7 @@ internal class ComputeServiceTest { val listeners = mutableListOf<HostListener>() every { host.uid } returns UUID.randomUUID() - every { host.model } returns HostModel(4, 2048) + every { host.model } returns HostModel(4 * 2600.0, 4, 2048) every { host.state } returns HostState.UP every { host.canFit(any()) } returns true every { host.addListener(any()) } answers { listeners.add(it.invocation.args[0] as HostListener) } @@ -326,7 +326,7 @@ internal class ComputeServiceTest { val listeners = mutableListOf<HostListener>() every { host.uid } returns UUID.randomUUID() - every { host.model } returns HostModel(4, 2048) + every { host.model } returns HostModel(4 * 2600.0, 4, 2048) every { host.state } returns HostState.UP every { host.canFit(any()) } returns true every { host.addListener(any()) } answers { listeners.add(it.invocation.args[0] as HostListener) } @@ -369,7 +369,7 @@ internal class ComputeServiceTest { val listeners = mutableListOf<HostListener>() every { host.uid } returns UUID.randomUUID() - every { host.model } returns HostModel(4, 2048) + every { host.model } returns HostModel(4 * 2600.0, 4, 2048) every { host.state } returns HostState.UP every { host.canFit(any()) } returns true every { host.addListener(any()) } answers { listeners.add(it.invocation.args[0] as HostListener) } diff --git a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/scheduler/FilterSchedulerTest.kt b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/scheduler/FilterSchedulerTest.kt index cafd4498..3f2ce43b 100644 --- a/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/scheduler/FilterSchedulerTest.kt +++ b/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/scheduler/FilterSchedulerTest.kt @@ -33,10 +33,7 @@ import org.opendc.compute.api.Server import org.opendc.compute.service.driver.HostModel import org.opendc.compute.service.driver.HostState import org.opendc.compute.service.internal.HostView -import org.opendc.compute.service.scheduler.filters.ComputeFilter -import org.opendc.compute.service.scheduler.filters.InstanceCountFilter -import org.opendc.compute.service.scheduler.filters.RamFilter -import org.opendc.compute.service.scheduler.filters.VCpuFilter +import org.opendc.compute.service.scheduler.filters.* import org.opendc.compute.service.scheduler.weights.CoreRamWeigher import org.opendc.compute.service.scheduler.weights.InstanceCountWeigher import org.opendc.compute.service.scheduler.weights.RamWeigher @@ -183,12 +180,12 @@ internal class FilterSchedulerTest { val hostA = mockk<HostView>() every { hostA.host.state } returns HostState.UP - every { hostA.host.model } returns HostModel(4, 2048) + every { hostA.host.model } returns HostModel(4 * 2600.0, 4, 2048) every { hostA.availableMemory } returns 512 val hostB = mockk<HostView>() every { hostB.host.state } returns HostState.UP - every { hostB.host.model } returns HostModel(4, 2048) + every { hostB.host.model } returns HostModel(4 * 2600.0, 4, 2048) every { hostB.availableMemory } returns 2048 scheduler.addHost(hostA) @@ -210,7 +207,7 @@ internal class FilterSchedulerTest { val host = mockk<HostView>() every { host.host.state } returns HostState.UP - every { host.host.model } returns HostModel(4, 2048) + every { host.host.model } returns HostModel(4 * 2600.0, 4, 2048) every { host.availableMemory } returns 2048 scheduler.addHost(host) @@ -231,12 +228,12 @@ internal class FilterSchedulerTest { val hostA = mockk<HostView>() every { hostA.host.state } returns HostState.UP - every { hostA.host.model } returns HostModel(4, 2048) + every { hostA.host.model } returns HostModel(4 * 2600.0, 4, 2048) every { hostA.provisionedCores } returns 3 val hostB = mockk<HostView>() every { hostB.host.state } returns HostState.UP - every { hostB.host.model } returns HostModel(4, 2048) + every { hostB.host.model } returns HostModel(4 * 2600.0, 4, 2048) every { hostB.provisionedCores } returns 0 scheduler.addHost(hostA) @@ -258,7 +255,7 @@ internal class FilterSchedulerTest { val host = mockk<HostView>() every { host.host.state } returns HostState.UP - every { host.host.model } returns HostModel(4, 2048) + every { host.host.model } returns HostModel(4 * 2600.0, 4, 2048) every { host.provisionedCores } returns 0 scheduler.addHost(host) @@ -271,6 +268,34 @@ internal class FilterSchedulerTest { } @Test + fun testVCpuCapacityFilter() { + val scheduler = FilterScheduler( + filters = listOf(VCpuCapacityFilter()), + weighers = emptyList(), + ) + + val hostA = mockk<HostView>() + every { hostA.host.state } returns HostState.UP + every { hostA.host.model } returns HostModel(8 * 2600.0, 8, 2048) + every { hostA.availableMemory } returns 512 + scheduler.addHost(hostA) + + val hostB = mockk<HostView>() + every { hostB.host.state } returns HostState.UP + every { hostB.host.model } returns HostModel(4 * 3200.0, 4, 2048) + every { hostB.availableMemory } returns 512 + + scheduler.addHost(hostB) + + val server = mockk<Server>() + every { server.flavor.cpuCount } returns 2 + every { server.flavor.memorySize } returns 1024 + every { server.flavor.meta } returns mapOf("cpu-capacity" to 2 * 3200.0) + + assertEquals(hostB, scheduler.select(server)) + } + + @Test fun testInstanceCountFilter() { val scheduler = FilterScheduler( filters = listOf(InstanceCountFilter(limit = 2)), @@ -279,12 +304,12 @@ internal class FilterSchedulerTest { val hostA = mockk<HostView>() every { hostA.host.state } returns HostState.UP - every { hostA.host.model } returns HostModel(4, 2048) + every { hostA.host.model } returns HostModel(4 * 2600.0, 4, 2048) every { hostA.instanceCount } returns 2 val hostB = mockk<HostView>() every { hostB.host.state } returns HostState.UP - every { hostB.host.model } returns HostModel(4, 2048) + every { hostB.host.model } returns HostModel(4 * 2600.0, 4, 2048) every { hostB.instanceCount } returns 0 scheduler.addHost(hostA) @@ -306,12 +331,12 @@ internal class FilterSchedulerTest { val hostA = mockk<HostView>() every { hostA.host.state } returns HostState.UP - every { hostA.host.model } returns HostModel(4, 2048) + every { hostA.host.model } returns HostModel(4 * 2600.0, 4, 2048) every { hostA.availableMemory } returns 1024 val hostB = mockk<HostView>() every { hostB.host.state } returns HostState.UP - every { hostB.host.model } returns HostModel(4, 2048) + every { hostB.host.model } returns HostModel(4 * 2600.0, 4, 2048) every { hostB.availableMemory } returns 512 scheduler.addHost(hostA) @@ -333,12 +358,12 @@ internal class FilterSchedulerTest { val hostA = mockk<HostView>() every { hostA.host.state } returns HostState.UP - every { hostA.host.model } returns HostModel(12, 2048) + every { hostA.host.model } returns HostModel(12 * 2600.0, 12, 2048) every { hostA.availableMemory } returns 1024 val hostB = mockk<HostView>() every { hostB.host.state } returns HostState.UP - every { hostB.host.model } returns HostModel(4, 2048) + every { hostB.host.model } returns HostModel(4 * 2600.0, 4, 2048) every { hostB.availableMemory } returns 512 scheduler.addHost(hostA) @@ -360,12 +385,12 @@ internal class FilterSchedulerTest { val hostA = mockk<HostView>() every { hostA.host.state } returns HostState.UP - every { hostA.host.model } returns HostModel(4, 2048) + every { hostA.host.model } returns HostModel(4 * 2600.0, 4, 2048) every { hostA.provisionedCores } returns 2 val hostB = mockk<HostView>() every { hostB.host.state } returns HostState.UP - every { hostB.host.model } returns HostModel(4, 2048) + every { hostB.host.model } returns HostModel(4 * 2600.0, 4, 2048) every { hostB.provisionedCores } returns 0 scheduler.addHost(hostA) @@ -387,12 +412,12 @@ internal class FilterSchedulerTest { val hostA = mockk<HostView>() every { hostA.host.state } returns HostState.UP - every { hostA.host.model } returns HostModel(4, 2048) + every { hostA.host.model } returns HostModel(4 * 2600.0, 4, 2048) every { hostA.instanceCount } returns 2 val hostB = mockk<HostView>() every { hostB.host.state } returns HostState.UP - every { hostB.host.model } returns HostModel(4, 2048) + every { hostB.host.model } returns HostModel(4 * 2600.0, 4, 2048) every { hostB.instanceCount } returns 0 scheduler.addHost(hostA) |
