From b6acc74b38643615df02ef2131380c5e8eba00dd Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Wed, 9 Jun 2021 21:59:20 +0200 Subject: simulator: Add module for datacenter power components This change adds a new module for simulating power components in datacenters such as PDUs and UPSes. This module will serve as the basis for the power monitoring framework in OpenDC and will future integrate with the other simulation components (such as compute). --- .../org/opendc/simulator/power/SimPduTest.kt | 108 ++++++++++++++++ .../opendc/simulator/power/SimPowerSourceTest.kt | 137 +++++++++++++++++++++ 2 files changed, 245 insertions(+) create mode 100644 opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt create mode 100644 opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPowerSourceTest.kt (limited to 'opendc-simulator/opendc-simulator-power/src/test') diff --git a/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt new file mode 100644 index 00000000..cef9a97a --- /dev/null +++ b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt @@ -0,0 +1,108 @@ +/* + * 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.simulator.power + +import io.mockk.spyk +import io.mockk.verify +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.simulator.resources.SimResourceConsumer +import org.opendc.simulator.resources.SimResourceEvent +import org.opendc.simulator.resources.SimResourceInterpreter +import org.opendc.simulator.resources.consumer.SimWorkConsumer + +/** + * Test suite for the [SimPdu] class. + */ +internal class SimPduTest { + @Test + fun testZeroOutlets() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + val pdu = SimPdu(interpreter) + source.connect(pdu) + + assertEquals(0.0, source.powerDraw) + } + + @Test + fun testSingleOutlet() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + val pdu = SimPdu(interpreter) + source.connect(pdu) + pdu.newOutlet().connect(SimpleInlet()) + assertEquals(50.0, source.powerDraw) + } + + @Test + fun testDoubleOutlet() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + val pdu = SimPdu(interpreter) + source.connect(pdu) + + pdu.newOutlet().connect(SimpleInlet()) + pdu.newOutlet().connect(SimpleInlet()) + + assertEquals(100.0, source.powerDraw) + } + + @Test + fun testDisconnect() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + val pdu = SimPdu(interpreter) + source.connect(pdu) + val consumer = spyk(SimWorkConsumer(100.0, utilization = 1.0)) + val inlet = object : SimPowerInlet() { + override fun createConsumer(): SimResourceConsumer = consumer + } + + val outlet = pdu.newOutlet() + outlet.connect(inlet) + outlet.disconnect() + + verify { consumer.onEvent(any(), SimResourceEvent.Exit) } + } + + @Test + fun testOutletClose() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + val pdu = SimPdu(interpreter) + source.connect(pdu) + val outlet = pdu.newOutlet() + outlet.close() + + assertThrows { + outlet.connect(SimpleInlet()) + } + } + + class SimpleInlet : SimPowerInlet() { + override fun createConsumer(): SimResourceConsumer = SimWorkConsumer(100.0, utilization = 0.5) + } +} diff --git a/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPowerSourceTest.kt b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPowerSourceTest.kt new file mode 100644 index 00000000..f3829ba1 --- /dev/null +++ b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPowerSourceTest.kt @@ -0,0 +1,137 @@ +/* + * 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.simulator.power + +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import io.mockk.verify +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows +import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.simulator.resources.SimResourceConsumer +import org.opendc.simulator.resources.SimResourceEvent +import org.opendc.simulator.resources.SimResourceInterpreter +import org.opendc.simulator.resources.consumer.SimWorkConsumer + +/** + * Test suite for the [SimPowerSource] + */ +internal class SimPowerSourceTest { + @Test + fun testInitialState() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + + assertFalse(source.isConnected) + assertNull(source.inlet) + assertEquals(100.0, source.capacity) + } + + @Test + fun testDisconnectIdempotent() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + + assertDoesNotThrow { source.disconnect() } + assertFalse(source.isConnected) + } + + @Test + fun testConnect() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + val inlet = SimpleInlet() + + source.connect(inlet) + + assertTrue(source.isConnected) + assertEquals(inlet, source.inlet) + assertTrue(inlet.isConnected) + assertEquals(source, inlet.outlet) + assertEquals(100.0, source.powerDraw) + } + + @Test + fun testDisconnect() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + val consumer = spyk(SimWorkConsumer(100.0, utilization = 1.0)) + val inlet = object : SimPowerInlet() { + override fun createConsumer(): SimResourceConsumer = consumer + } + + source.connect(inlet) + source.disconnect() + + verify { consumer.onEvent(any(), SimResourceEvent.Exit) } + } + + @Test + fun testDisconnectAssertion() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + val inlet = mockk(relaxUnitFun = true) + every { inlet.isConnected } returns false + every { inlet._outlet } returns null + every { inlet.createConsumer() } returns SimWorkConsumer(100.0, utilization = 1.0) + + source.connect(inlet) + + assertThrows { + source.disconnect() + } + } + + @Test + fun testOutletAlreadyConnected() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + val inlet = SimpleInlet() + + source.connect(inlet) + assertThrows { + source.connect(SimpleInlet()) + } + + assertEquals(inlet, source.inlet) + } + + @Test + fun testInletAlreadyConnected() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + val inlet = mockk(relaxUnitFun = true) + every { inlet.isConnected } returns true + + assertThrows { + source.connect(inlet) + } + } + + class SimpleInlet : SimPowerInlet() { + override fun createConsumer(): SimResourceConsumer = SimWorkConsumer(100.0, utilization = 1.0) + } +} -- cgit v1.2.3 From f0c0c6d45165ad0ce398ec7300b11bf77c7ae5a6 Mon Sep 17 00:00:00 2001 From: Hongyu He Date: Thu, 10 Jun 2021 13:27:56 +0200 Subject: simulator: Add power loss to SimPdu This change adds a model for power loss to the Power Distribution Unit (PDU) model in OpenDC. --- .../src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'opendc-simulator/opendc-simulator-power/src/test') diff --git a/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt index cef9a97a..b7f51ad3 100644 --- a/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt +++ b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt @@ -54,6 +54,7 @@ internal class SimPduTest { val pdu = SimPdu(interpreter) source.connect(pdu) pdu.newOutlet().connect(SimpleInlet()) + assertEquals(50.0, source.powerDraw) } @@ -88,6 +89,17 @@ internal class SimPduTest { verify { consumer.onEvent(any(), SimResourceEvent.Exit) } } + @Test + fun testLoss() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + // https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN + val pdu = SimPdu(interpreter, idlePower = 0.015, lossCoefficient = 0.015) + source.connect(pdu) + pdu.newOutlet().connect(SimpleInlet()) + assertEquals(87.515, source.powerDraw, 0.01) + } + @Test fun testOutletClose() = runBlockingSimulation { val interpreter = SimResourceInterpreter(coroutineContext, clock) -- cgit v1.2.3 From 885137dd79f76a63aee1bcaecbc0c9e9dec80d3a Mon Sep 17 00:00:00 2001 From: Hongyu He Date: Fri, 11 Jun 2021 17:42:47 +0200 Subject: simulator: Add model for UPS This change adds a new model for the UPS to the OpenDC simulator power subsystem. --- .../org/opendc/simulator/power/SimPduTest.kt | 4 +- .../org/opendc/simulator/power/SimUpsTest.kt | 102 +++++++++++++++++++++ 2 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimUpsTest.kt (limited to 'opendc-simulator/opendc-simulator-power/src/test') diff --git a/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt index b7f51ad3..17a174b7 100644 --- a/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt +++ b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimPduTest.kt @@ -94,10 +94,10 @@ internal class SimPduTest { val interpreter = SimResourceInterpreter(coroutineContext, clock) val source = SimPowerSource(interpreter, capacity = 100.0) // https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN - val pdu = SimPdu(interpreter, idlePower = 0.015, lossCoefficient = 0.015) + val pdu = SimPdu(interpreter, idlePower = 1.5, lossCoefficient = 0.015) source.connect(pdu) pdu.newOutlet().connect(SimpleInlet()) - assertEquals(87.515, source.powerDraw, 0.01) + assertEquals(89.0, source.powerDraw, 0.01) } @Test diff --git a/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimUpsTest.kt b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimUpsTest.kt new file mode 100644 index 00000000..8d5fa857 --- /dev/null +++ b/opendc-simulator/opendc-simulator-power/src/test/kotlin/org/opendc/simulator/power/SimUpsTest.kt @@ -0,0 +1,102 @@ +/* + * 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.simulator.power + +import io.mockk.spyk +import io.mockk.verify +import org.junit.jupiter.api.Assertions.assertAll +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.simulator.resources.SimResourceConsumer +import org.opendc.simulator.resources.SimResourceEvent +import org.opendc.simulator.resources.SimResourceInterpreter +import org.opendc.simulator.resources.consumer.SimWorkConsumer + +/** + * Test suite for the [SimUps] class. + */ +internal class SimUpsTest { + @Test + fun testSingleInlet() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + val ups = SimUps(interpreter) + source.connect(ups.newInlet()) + ups.connect(SimpleInlet()) + + assertEquals(50.0, source.powerDraw) + } + + @Test + fun testDoubleInlet() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source1 = SimPowerSource(interpreter, capacity = 100.0) + val source2 = SimPowerSource(interpreter, capacity = 100.0) + val ups = SimUps(interpreter) + source1.connect(ups.newInlet()) + source2.connect(ups.newInlet()) + + ups.connect(SimpleInlet()) + + assertAll( + { assertEquals(50.0, source1.powerDraw) }, + { assertEquals(50.0, source2.powerDraw) } + ) + } + + @Test + fun testLoss() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source = SimPowerSource(interpreter, capacity = 100.0) + // https://download.schneider-electric.com/files?p_Doc_Ref=SPD_NRAN-66CK3D_EN + val ups = SimUps(interpreter, idlePower = 4.0, lossCoefficient = 0.05) + source.connect(ups.newInlet()) + ups.connect(SimpleInlet()) + + assertEquals(56.5, source.powerDraw) + } + + @Test + fun testDisconnect() = runBlockingSimulation { + val interpreter = SimResourceInterpreter(coroutineContext, clock) + val source1 = SimPowerSource(interpreter, capacity = 100.0) + val source2 = SimPowerSource(interpreter, capacity = 100.0) + val ups = SimUps(interpreter) + source1.connect(ups.newInlet()) + source2.connect(ups.newInlet()) + val consumer = spyk(SimWorkConsumer(100.0, utilization = 1.0)) + val inlet = object : SimPowerInlet() { + override fun createConsumer(): SimResourceConsumer = consumer + } + + ups.connect(inlet) + ups.disconnect() + + verify { consumer.onEvent(any(), SimResourceEvent.Exit) } + } + + class SimpleInlet : SimPowerInlet() { + override fun createConsumer(): SimResourceConsumer = SimWorkConsumer(100.0, utilization = 0.5) + } +} -- cgit v1.2.3