From 0bbb0adb97ba4783bbd0073f845781725e6212e8 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Thu, 25 Mar 2021 10:23:47 +0100 Subject: compute: Add test suite for ComputeService This change adds a test suite for the OpenDC compute service. --- .../opendc/compute/service/ComputeServiceTest.kt | 390 +++++++++++++++++++++ .../opendc/compute/service/InternalFlavorTest.kt | 80 +++++ .../opendc/compute/service/InternalImageTest.kt | 81 +++++ .../opendc/compute/service/InternalServerTest.kt | 285 +++++++++++++++ .../service/scheduler/AllocationPolicyTest.kt | 219 ++++++++++++ .../src/test/resources/log4j2.xml | 38 ++ 6 files changed, 1093 insertions(+) create mode 100644 simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt create mode 100644 simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalFlavorTest.kt create mode 100644 simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalImageTest.kt create mode 100644 simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalServerTest.kt create mode 100644 simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/scheduler/AllocationPolicyTest.kt create mode 100644 simulator/opendc-compute/opendc-compute-service/src/test/resources/log4j2.xml (limited to 'simulator/opendc-compute/opendc-compute-service/src/test') diff --git a/simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt b/simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt new file mode 100644 index 00000000..ffec92ea --- /dev/null +++ b/simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt @@ -0,0 +1,390 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service + +import io.mockk.* +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay +import kotlinx.coroutines.test.TestCoroutineScope +import kotlinx.coroutines.test.runBlockingTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.opendc.compute.api.* +import org.opendc.compute.service.driver.Host +import org.opendc.compute.service.driver.HostListener +import org.opendc.compute.service.driver.HostModel +import org.opendc.compute.service.driver.HostState +import org.opendc.compute.service.scheduler.AvailableMemoryAllocationPolicy +import org.opendc.simulator.utils.DelayControllerClockAdapter +import org.opendc.trace.core.EventTracer +import java.util.* + +/** + * Test suite for the [ComputeService] interface. + */ +@OptIn(ExperimentalCoroutinesApi::class) +internal class ComputeServiceTest { + lateinit var scope: TestCoroutineScope + lateinit var service: ComputeService + + @BeforeEach + fun setUp() { + scope = TestCoroutineScope() + val clock = DelayControllerClockAdapter(scope) + val tracer = EventTracer(clock) + val policy = AvailableMemoryAllocationPolicy() + service = ComputeService(scope.coroutineContext, clock, tracer, policy) + } + + @AfterEach + fun tearDown() { + scope.cleanupTestCoroutines() + } + + @Test + fun testClientClose() = scope.runBlockingTest { + val client = service.newClient() + + assertEquals(emptyList(), client.queryFlavors()) + assertEquals(emptyList(), client.queryImages()) + assertEquals(emptyList(), client.queryServers()) + + client.close() + + assertThrows { client.queryFlavors() } + assertThrows { client.queryImages() } + assertThrows { client.queryServers() } + + assertThrows { client.findFlavor(UUID.randomUUID()) } + assertThrows { client.findImage(UUID.randomUUID()) } + assertThrows { client.findServer(UUID.randomUUID()) } + + assertThrows { client.newFlavor("test", 1, 2) } + assertThrows { client.newImage("test") } + assertThrows { client.newServer("test", mockk(), mockk()) } + } + + @Test + fun testClientCreate() = scope.runBlockingTest { + val client = service.newClient() + + val flavor = client.newFlavor("test", 1, 1024) + assertEquals(listOf(flavor), client.queryFlavors()) + assertEquals(flavor, client.findFlavor(flavor.uid)) + val image = client.newImage("test") + assertEquals(listOf(image), client.queryImages()) + assertEquals(image, client.findImage(image.uid)) + val server = client.newServer("test", image, flavor, start = false) + assertEquals(listOf(server), client.queryServers()) + assertEquals(server, client.findServer(server.uid)) + + server.delete() + assertNull(client.findServer(server.uid)) + + image.delete() + assertNull(client.findImage(image.uid)) + + flavor.delete() + assertNull(client.findFlavor(flavor.uid)) + + assertThrows { server.start() } + } + + @Test + fun testClientOnClose() = scope.runBlockingTest { + service.close() + assertThrows { + service.newClient() + } + } + + @Test + fun testAddHost() = scope.runBlockingTest { + val host = mockk(relaxUnitFun = true) + + every { host.model } returns HostModel(4, 2048) + every { host.state } returns HostState.UP + + assertEquals(0, service.hostCount) + assertEquals(emptySet(), service.hosts) + + service.addHost(host) + + verify(exactly = 1) { host.addListener(any()) } + + assertEquals(1, service.hostCount) + assertEquals(1, service.hosts.size) + + service.removeHost(host) + + verify(exactly = 1) { host.removeListener(any()) } + } + + @Test + fun testAddHostDouble() = scope.runBlockingTest { + val host = mockk(relaxUnitFun = true) + + every { host.model } returns HostModel(4, 2048) + every { host.state } returns HostState.DOWN + + assertEquals(0, service.hostCount) + assertEquals(emptySet(), service.hosts) + + service.addHost(host) + service.addHost(host) + + verify(exactly = 1) { host.addListener(any()) } + } + + @Test + fun testServerStartWithoutEnoughCpus() = scope.runBlockingTest { + val client = service.newClient() + val flavor = client.newFlavor("test", 1, 0) + val image = client.newImage("test") + val server = client.newServer("test", image, flavor, start = false) + + server.start() + delay(5 * 60 * 1000) + server.refresh() + assertEquals(ServerState.ERROR, server.state) + } + + @Test + fun testServerStartWithoutEnoughMemory() = scope.runBlockingTest { + val client = service.newClient() + val flavor = client.newFlavor("test", 0, 1024) + val image = client.newImage("test") + val server = client.newServer("test", image, flavor, start = false) + + server.start() + delay(5 * 60 * 1000) + server.refresh() + assertEquals(ServerState.ERROR, server.state) + } + + @Test + fun testServerStartWithoutEnoughResources() = scope.runBlockingTest { + val client = service.newClient() + val flavor = client.newFlavor("test", 1, 1024) + val image = client.newImage("test") + val server = client.newServer("test", image, flavor, start = false) + + server.start() + delay(5 * 60 * 1000) + server.refresh() + assertEquals(ServerState.ERROR, server.state) + } + + @Test + fun testServerCancelRequest() = scope.runBlockingTest { + val client = service.newClient() + val flavor = client.newFlavor("test", 1, 1024) + val image = client.newImage("test") + val server = client.newServer("test", image, flavor, start = false) + + server.start() + server.stop() + delay(5 * 60 * 1000) + server.refresh() + assertEquals(ServerState.TERMINATED, server.state) + } + + @Test + fun testServerCannotFitOnHost() = scope.runBlockingTest { + val host = mockk(relaxUnitFun = true) + + every { host.model } returns HostModel(4, 2048) + every { host.state } returns HostState.UP + every { host.canFit(any()) } returns false + + service.addHost(host) + + val client = service.newClient() + val flavor = client.newFlavor("test", 1, 1024) + val image = client.newImage("test") + val server = client.newServer("test", image, flavor, start = false) + + server.start() + delay(10 * 60 * 1000) + server.refresh() + assertEquals(ServerState.PROVISIONING, server.state) + + verify { host.canFit(server) } + } + + @Test + fun testHostAvailableAfterSomeTime() = scope.runBlockingTest { + val host = mockk(relaxUnitFun = true) + val listeners = mutableListOf() + + every { host.uid } returns UUID.randomUUID() + every { host.model } returns HostModel(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 + + service.addHost(host) + + val client = service.newClient() + val flavor = client.newFlavor("test", 1, 1024) + val image = client.newImage("test") + val server = client.newServer("test", image, flavor, start = false) + + server.start() + delay(5 * 60 * 1000) + + listeners.forEach { it.onStateChanged(host, HostState.UP) } + + delay(5 * 60 * 1000) + server.refresh() + assertEquals(ServerState.PROVISIONING, server.state) + + verify { host.canFit(server) } + } + + @Test + fun testHostUnavailableAfterSomeTime() = scope.runBlockingTest { + val host = mockk(relaxUnitFun = true) + val listeners = mutableListOf() + + every { host.uid } returns UUID.randomUUID() + every { host.model } returns HostModel(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 + + service.addHost(host) + + val client = service.newClient() + val flavor = client.newFlavor("test", 1, 1024) + val image = client.newImage("test") + val server = client.newServer("test", image, flavor, start = false) + + delay(5 * 60 * 1000) + + listeners.forEach { it.onStateChanged(host, HostState.DOWN) } + + server.start() + delay(5 * 60 * 1000) + server.refresh() + assertEquals(ServerState.PROVISIONING, server.state) + + verify(exactly = 0) { host.canFit(server) } + } + + @Test + fun testServerInvalidType() = scope.runBlockingTest { + val host = mockk(relaxUnitFun = true) + val listeners = mutableListOf() + + every { host.uid } returns UUID.randomUUID() + every { host.model } returns HostModel(4, 2048) + every { host.state } returns HostState.UP + every { host.canFit(any()) } returns true + every { host.addListener(any()) } answers { listeners.add(it.invocation.args[0] as HostListener) } + + service.addHost(host) + + val client = service.newClient() + val flavor = client.newFlavor("test", 1, 1024) + val image = client.newImage("test") + val server = client.newServer("test", image, flavor, start = false) + + assertThrows { + listeners.forEach { it.onStateChanged(host, server, ServerState.RUNNING) } + } + } + + @Test + fun testServerDeploy() = scope.runBlockingTest { + val host = mockk(relaxUnitFun = true) + val listeners = mutableListOf() + + every { host.uid } returns UUID.randomUUID() + every { host.model } returns HostModel(4, 2048) + every { host.state } returns HostState.UP + every { host.canFit(any()) } returns true + every { host.addListener(any()) } answers { listeners.add(it.invocation.args[0] as HostListener) } + + service.addHost(host) + + val client = service.newClient() + val flavor = client.newFlavor("test", 1, 1024) + val image = client.newImage("test") + val server = client.newServer("test", image, flavor, start = false) + val slot = slot() + + val watcher = mockk(relaxUnitFun = true) + server.watch(watcher) + + // Start server + server.start() + delay(5 * 60 * 1000) + coVerify { host.spawn(capture(slot), true) } + + listeners.forEach { it.onStateChanged(host, slot.captured, ServerState.RUNNING) } + + server.refresh() + assertEquals(ServerState.RUNNING, server.state) + + verify { watcher.onStateChanged(server, ServerState.RUNNING) } + + // Stop server + listeners.forEach { it.onStateChanged(host, slot.captured, ServerState.TERMINATED) } + + server.refresh() + assertEquals(ServerState.TERMINATED, server.state) + + verify { watcher.onStateChanged(server, ServerState.TERMINATED) } + } + + @Test + fun testServerDeployFailure() = scope.runBlockingTest { + val host = mockk(relaxUnitFun = true) + val listeners = mutableListOf() + + every { host.uid } returns UUID.randomUUID() + every { host.model } returns HostModel(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) } + coEvery { host.spawn(any(), true) } throws IllegalStateException() + + service.addHost(host) + + val client = service.newClient() + val flavor = client.newFlavor("test", 1, 1024) + val image = client.newImage("test") + val server = client.newServer("test", image, flavor, start = false) + + server.start() + delay(5 * 60 * 1000) + + server.refresh() + assertEquals(ServerState.PROVISIONING, server.state) + } +} diff --git a/simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalFlavorTest.kt b/simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalFlavorTest.kt new file mode 100644 index 00000000..18d698c6 --- /dev/null +++ b/simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalFlavorTest.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service + +import io.mockk.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Test +import org.opendc.compute.api.Flavor +import org.opendc.compute.service.internal.ComputeServiceImpl +import org.opendc.compute.service.internal.InternalFlavor +import java.util.* + +/** + * Test suite for the [InternalFlavor] implementation. + */ +class InternalFlavorTest { + @Test + fun testEquality() { + val service = mockk() + val uid = UUID.randomUUID() + val a = InternalFlavor(service, uid, "test", 1, 1024, mutableMapOf(), mutableMapOf()) + val b = InternalFlavor(service, uid, "test", 1, 1024, mutableMapOf(), mutableMapOf()) + + assertEquals(a, b) + } + + @Test + fun testEqualityWithDifferentType() { + val service = mockk() + val uid = UUID.randomUUID() + val a = InternalFlavor(service, uid, "test", 1, 1024, mutableMapOf(), mutableMapOf()) + + val b = mockk(relaxUnitFun = true) + every { b.uid } returns uid + + assertEquals(a, b) + } + + @Test + fun testInequalityWithDifferentType() { + val service = mockk() + val uid = UUID.randomUUID() + val a = InternalFlavor(service, uid, "test", 1, 1024, mutableMapOf(), mutableMapOf()) + + val b = mockk(relaxUnitFun = true) + every { b.uid } returns UUID.randomUUID() + + assertNotEquals(a, b) + } + + @Test + fun testInequalityWithIncorrectType() { + val service = mockk() + val uid = UUID.randomUUID() + val a = InternalFlavor(service, uid, "test", 1, 1024, mutableMapOf(), mutableMapOf()) + + assertNotEquals(a, Unit) + } +} diff --git a/simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalImageTest.kt b/simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalImageTest.kt new file mode 100644 index 00000000..e1cb0128 --- /dev/null +++ b/simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalImageTest.kt @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service + +import io.mockk.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Test +import org.opendc.compute.api.Image +import org.opendc.compute.service.internal.ComputeServiceImpl +import org.opendc.compute.service.internal.InternalFlavor +import org.opendc.compute.service.internal.InternalImage +import java.util.* + +/** + * Test suite for the [InternalFlavor] implementation. + */ +class InternalImageTest { + @Test + fun testEquality() { + val service = mockk() + val uid = UUID.randomUUID() + val a = InternalImage(service, uid, "test", mutableMapOf(), mutableMapOf()) + val b = InternalImage(service, uid, "test", mutableMapOf(), mutableMapOf()) + + assertEquals(a, b) + } + + @Test + fun testEqualityWithDifferentType() { + val service = mockk() + val uid = UUID.randomUUID() + val a = InternalImage(service, uid, "test", mutableMapOf(), mutableMapOf()) + + val b = mockk(relaxUnitFun = true) + every { b.uid } returns uid + + assertEquals(a, b) + } + + @Test + fun testInequalityWithDifferentType() { + val service = mockk() + val uid = UUID.randomUUID() + val a = InternalImage(service, uid, "test", mutableMapOf(), mutableMapOf()) + + val b = mockk(relaxUnitFun = true) + every { b.uid } returns UUID.randomUUID() + + assertNotEquals(a, b) + } + + @Test + fun testInequalityWithIncorrectType() { + val service = mockk() + val uid = UUID.randomUUID() + val a = InternalImage(service, uid, "test", mutableMapOf(), mutableMapOf()) + + assertNotEquals(a, Unit) + } +} diff --git a/simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalServerTest.kt b/simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalServerTest.kt new file mode 100644 index 00000000..81cb45df --- /dev/null +++ b/simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/InternalServerTest.kt @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.compute.service + +import io.mockk.* +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.yield +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.opendc.compute.api.Server +import org.opendc.compute.api.ServerState +import org.opendc.compute.service.driver.Host +import org.opendc.compute.service.internal.ComputeServiceImpl +import org.opendc.compute.service.internal.InternalFlavor +import org.opendc.compute.service.internal.InternalImage +import org.opendc.compute.service.internal.InternalServer +import java.util.* + +/** + * Test suite for the [InternalServer] implementation. + */ +@OptIn(ExperimentalCoroutinesApi::class) +class InternalServerTest { + @Test + fun testEquality() { + val service = mockk() + val uid = UUID.randomUUID() + val flavor = mockk() + val image = mockk() + val a = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + val b = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + + assertEquals(a, b) + } + + @Test + fun testEqualityWithDifferentType() { + val service = mockk() + val uid = UUID.randomUUID() + val flavor = mockk() + val image = mockk() + val a = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + + val b = mockk(relaxUnitFun = true) + every { b.uid } returns uid + + assertEquals(a, b) + } + + @Test + fun testInequalityWithDifferentType() { + val service = mockk() + val uid = UUID.randomUUID() + val flavor = mockk() + val image = mockk() + val a = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + + val b = mockk(relaxUnitFun = true) + every { b.uid } returns UUID.randomUUID() + + assertNotEquals(a, b) + } + + @Test + fun testInequalityWithIncorrectType() { + val service = mockk() + val uid = UUID.randomUUID() + val flavor = mockk() + val image = mockk() + val a = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + + assertNotEquals(a, Unit) + } + + @Test + fun testStartTerminatedServer() = runBlockingTest { + val service = mockk() + val uid = UUID.randomUUID() + val flavor = mockk() + val image = mockk() + val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + + every { service.schedule(any()) } answers { ComputeServiceImpl.SchedulingRequest(it.invocation.args[0] as InternalServer) } + + server.start() + + verify(exactly = 1) { service.schedule(server) } + assertEquals(ServerState.PROVISIONING, server.state) + } + + @Test + fun testStartDeletedServer() = runBlockingTest { + val service = mockk() + val uid = UUID.randomUUID() + val flavor = mockk() + val image = mockk() + val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + + server.state = ServerState.DELETED + + assertThrows { server.start() } + } + + @Test + fun testStartProvisioningServer() = runBlockingTest { + val service = mockk() + val uid = UUID.randomUUID() + val flavor = mockk() + val image = mockk() + val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + + server.state = ServerState.PROVISIONING + + server.start() + + assertEquals(ServerState.PROVISIONING, server.state) + } + + @Test + fun testStartRunningServer() = runBlockingTest { + val service = mockk() + val uid = UUID.randomUUID() + val flavor = mockk() + val image = mockk() + val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + + server.state = ServerState.RUNNING + + server.start() + + assertEquals(ServerState.RUNNING, server.state) + } + + @Test + fun testStopProvisioningServer() = runBlockingTest { + val service = mockk() + val uid = UUID.randomUUID() + val flavor = mockk() + val image = mockk() + val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + val request = ComputeServiceImpl.SchedulingRequest(server) + + every { service.schedule(any()) } returns request + + server.start() + server.stop() + + assertTrue(request.isCancelled) + assertEquals(ServerState.TERMINATED, server.state) + } + + @Test + fun testStopTerminatedServer() = runBlockingTest { + val service = mockk() + val uid = UUID.randomUUID() + val flavor = mockk() + val image = mockk() + val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + + server.state = ServerState.TERMINATED + server.stop() + + assertEquals(ServerState.TERMINATED, server.state) + } + + @Test + fun testStopDeletedServer() = runBlockingTest { + val service = mockk() + val uid = UUID.randomUUID() + val flavor = mockk() + val image = mockk() + val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + + server.state = ServerState.DELETED + server.stop() + + assertEquals(ServerState.DELETED, server.state) + } + + @Test + fun testStopRunningServer() = runBlockingTest { + val service = mockk() + val uid = UUID.randomUUID() + val flavor = mockk() + val image = mockk() + val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + val host = mockk(relaxUnitFun = true) + + server.state = ServerState.RUNNING + server.host = host + server.stop() + yield() + + coVerify { host.stop(server) } + } + + @Test + fun testDeleteProvisioningServer() = runBlockingTest { + val service = mockk(relaxUnitFun = true) + val uid = UUID.randomUUID() + val flavor = mockk() + val image = mockk() + val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + val request = ComputeServiceImpl.SchedulingRequest(server) + + every { service.schedule(any()) } returns request + + server.start() + server.delete() + + assertTrue(request.isCancelled) + assertEquals(ServerState.DELETED, server.state) + verify { service.delete(server) } + } + + @Test + fun testDeleteTerminatedServer() = runBlockingTest { + val service = mockk(relaxUnitFun = true) + val uid = UUID.randomUUID() + val flavor = mockk() + val image = mockk() + val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + + server.state = ServerState.TERMINATED + server.delete() + + assertEquals(ServerState.DELETED, server.state) + + verify { service.delete(server) } + } + + @Test + fun testDeleteDeletedServer() = runBlockingTest { + val service = mockk(relaxUnitFun = true) + val uid = UUID.randomUUID() + val flavor = mockk() + val image = mockk() + val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + + server.state = ServerState.DELETED + server.delete() + + assertEquals(ServerState.DELETED, server.state) + } + + @Test + fun testDeleteRunningServer() = runBlockingTest { + val service = mockk(relaxUnitFun = true) + val uid = UUID.randomUUID() + val flavor = mockk() + val image = mockk() + val server = InternalServer(service, uid, "test", flavor, image, mutableMapOf(), mutableMapOf()) + val host = mockk(relaxUnitFun = true) + + server.state = ServerState.RUNNING + server.host = host + server.delete() + yield() + + coVerify { host.delete(server) } + verify { service.delete(server) } + } +} diff --git a/simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/scheduler/AllocationPolicyTest.kt b/simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/scheduler/AllocationPolicyTest.kt new file mode 100644 index 00000000..db377914 --- /dev/null +++ b/simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/scheduler/AllocationPolicyTest.kt @@ -0,0 +1,219 @@ +/* + * 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 + +import io.mockk.every +import io.mockk.mockk +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.api.Server +import org.opendc.compute.service.internal.HostView +import java.util.* +import java.util.stream.Stream +import kotlin.random.Random + +/** + * Test suite for the [AllocationPolicy] interface. + */ +internal class AllocationPolicyTest { + @ParameterizedTest + @MethodSource("activeServersArgs") + fun testActiveServersPolicy( + reversed: Boolean, + hosts: Set, + server: Server, + expectedHost: HostView? + ) { + val policy = NumberOfActiveServersAllocationPolicy(reversed) + assertEquals(expectedHost, policy.invoke().select(hosts, server)) + } + + @ParameterizedTest + @MethodSource("availableMemoryArgs") + fun testAvailableMemoryPolicy( + reversed: Boolean, + hosts: Set, + server: Server, + expectedHost: HostView? + ) { + val policy = AvailableMemoryAllocationPolicy(reversed) + assertEquals(expectedHost, policy.invoke().select(hosts, server)) + } + + @ParameterizedTest + @MethodSource("availableCoreMemoryArgs") + fun testAvailableCoreMemoryPolicy( + reversed: Boolean, + hosts: Set, + server: Server, + expectedHost: HostView? + ) { + val policy = AvailableMemoryAllocationPolicy(reversed) + assertEquals(expectedHost, policy.invoke().select(hosts, server)) + } + + @ParameterizedTest + @MethodSource("provisionedCoresArgs") + fun testProvisionedPolicy( + reversed: Boolean, + hosts: Set, + server: Server, + expectedHost: HostView? + ) { + val policy = ProvisionedCoresAllocationPolicy(reversed) + assertEquals(expectedHost, policy.invoke().select(hosts, server)) + } + + @Suppress("unused") + private companion object { + /** + * Test arguments for the [NumberOfActiveServersAllocationPolicy]. + */ + @JvmStatic + fun activeServersArgs(): Stream { + val random = Random(1) + val hosts = List(4) { i -> + val view = mockk() + every { view.host.uid } returns UUID(0, i.toLong()) + every { view.host.model.cpuCount } returns random.nextInt(1, 16) + every { view.host.model.memorySize } returns random.nextLong(1024, 1024 * 1024) + every { view.availableMemory } returns random.nextLong(0, view.host.model.memorySize) + every { view.numberOfActiveServers } returns random.nextInt(0, 6) + every { view.provisionedCores } returns random.nextInt(0, view.host.model.cpuCount) + every { view.toString() } returns "HostView[$i,numberOfActiveServers=${view.numberOfActiveServers}]" + view + } + + val servers = List(2) { + val server = mockk() + every { server.flavor.cpuCount } returns random.nextInt(1, 8) + every { server.flavor.memorySize } returns random.nextLong(1024, 1024 * 512) + server + } + + return Stream.of( + Arguments.of(false, hosts.toSet(), servers[0], hosts[2]), + Arguments.of(false, hosts.toSet(), servers[1], hosts[1]), + Arguments.of(true, hosts.toSet(), servers[1], hosts[0]), + ) + } + + /** + * Test arguments for the [AvailableCoreMemoryAllocationPolicy]. + */ + @JvmStatic + fun availableCoreMemoryArgs(): Stream { + val random = Random(1) + val hosts = List(4) { i -> + val view = mockk() + every { view.host.uid } returns UUID(0, i.toLong()) + every { view.host.model.cpuCount } returns random.nextInt(1, 16) + every { view.host.model.memorySize } returns random.nextLong(1024, 1024 * 1024) + every { view.availableMemory } returns random.nextLong(0, view.host.model.memorySize) + every { view.numberOfActiveServers } returns random.nextInt(0, 6) + every { view.provisionedCores } returns random.nextInt(0, view.host.model.cpuCount) + every { view.toString() } returns "HostView[$i,availableMemory=${view.availableMemory}]" + view + } + + val servers = List(2) { + val server = mockk() + every { server.flavor.cpuCount } returns random.nextInt(1, 8) + every { server.flavor.memorySize } returns random.nextLong(1024, 1024 * 512) + server + } + + return Stream.of( + Arguments.of(false, hosts.toSet(), servers[0], hosts[2]), + Arguments.of(false, hosts.toSet(), servers[1], hosts[2]), + Arguments.of(true, hosts.toSet(), servers[1], hosts[1]), + ) + } + + /** + * Test arguments for the [AvailableMemoryAllocationPolicy]. + */ + @JvmStatic + fun availableMemoryArgs(): Stream { + val random = Random(1) + val hosts = List(4) { i -> + val view = mockk() + every { view.host.uid } returns UUID(0, i.toLong()) + every { view.host.model.cpuCount } returns random.nextInt(1, 16) + every { view.host.model.memorySize } returns random.nextLong(1024, 1024 * 1024) + every { view.availableMemory } returns random.nextLong(0, view.host.model.memorySize) + every { view.numberOfActiveServers } returns random.nextInt(0, 6) + every { view.provisionedCores } returns random.nextInt(0, view.host.model.cpuCount) + every { view.toString() } returns "HostView[$i,availableMemory=${view.availableMemory}]" + view + } + + val servers = List(2) { + val server = mockk() + every { server.flavor.cpuCount } returns random.nextInt(1, 8) + every { server.flavor.memorySize } returns random.nextLong(1024, 1024 * 512) + server + } + + return Stream.of( + Arguments.of(false, hosts.toSet(), servers[0], hosts[2]), + Arguments.of(false, hosts.toSet(), servers[1], hosts[2]), + Arguments.of(true, hosts.toSet(), servers[1], hosts[1]), + ) + } + + /** + * Test arguments for the [ProvisionedCoresAllocationPolicy]. + */ + @JvmStatic + fun provisionedCoresArgs(): Stream { + val random = Random(1) + val hosts = List(4) { i -> + val view = mockk() + every { view.host.uid } returns UUID(0, i.toLong()) + every { view.host.model.cpuCount } returns random.nextInt(1, 16) + every { view.host.model.memorySize } returns random.nextLong(1024, 1024 * 1024) + every { view.availableMemory } returns random.nextLong(0, view.host.model.memorySize) + every { view.numberOfActiveServers } returns random.nextInt(0, 6) + every { view.provisionedCores } returns random.nextInt(0, view.host.model.cpuCount) + every { view.toString() } returns "HostView[$i,provisionedCores=${view.provisionedCores}]" + view + } + + val servers = List(2) { + val server = mockk() + every { server.flavor.cpuCount } returns random.nextInt(1, 8) + every { server.flavor.memorySize } returns random.nextLong(1024, 1024 * 512) + server + } + + return Stream.of( + Arguments.of(false, hosts.toSet(), servers[0], hosts[2]), + Arguments.of(false, hosts.toSet(), servers[1], hosts[0]), + Arguments.of(true, hosts.toSet(), servers[1], hosts[0]), + ) + } + } +} diff --git a/simulator/opendc-compute/opendc-compute-service/src/test/resources/log4j2.xml b/simulator/opendc-compute/opendc-compute-service/src/test/resources/log4j2.xml new file mode 100644 index 00000000..0dfb75f2 --- /dev/null +++ b/simulator/opendc-compute/opendc-compute-service/src/test/resources/log4j2.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + -- cgit v1.2.3 From 0d66ef47d6e1ec0861b4939800c5070f96600ca0 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Thu, 25 Mar 2021 14:49:07 +0100 Subject: compute: Remove dependency on event tracer This change removes the dependency on the event tracer in `opendc-trace`, since we are in the process of migrating OpenDC to use OpenTelemetry for distributed tracing and metrics. --- .../src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'simulator/opendc-compute/opendc-compute-service/src/test') diff --git a/simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt b/simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt index ffec92ea..e1482152 100644 --- a/simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt +++ b/simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt @@ -40,7 +40,6 @@ import org.opendc.compute.service.driver.HostModel import org.opendc.compute.service.driver.HostState import org.opendc.compute.service.scheduler.AvailableMemoryAllocationPolicy import org.opendc.simulator.utils.DelayControllerClockAdapter -import org.opendc.trace.core.EventTracer import java.util.* /** @@ -55,9 +54,8 @@ internal class ComputeServiceTest { fun setUp() { scope = TestCoroutineScope() val clock = DelayControllerClockAdapter(scope) - val tracer = EventTracer(clock) val policy = AvailableMemoryAllocationPolicy() - service = ComputeService(scope.coroutineContext, clock, tracer, policy) + service = ComputeService(scope.coroutineContext, clock, policy) } @AfterEach -- cgit v1.2.3 From 608ff59b2d7e8ce696fe6f7271d80b5efc9c4b87 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Thu, 25 Mar 2021 21:50:45 +0100 Subject: compute: Integrate OpenTelemetry Metrics in OpenDC Compute This change integrates the OpenTelemetry Metrics API in the OpenDC Compute Service implementation. This replaces the old infrastructure for gathering metrics. --- .../src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'simulator/opendc-compute/opendc-compute-service/src/test') diff --git a/simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt b/simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt index e1482152..45a306aa 100644 --- a/simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt +++ b/simulator/opendc-compute/opendc-compute-service/src/test/kotlin/org/opendc/compute/service/ComputeServiceTest.kt @@ -23,6 +23,7 @@ package org.opendc.compute.service import io.mockk.* +import io.opentelemetry.api.metrics.MeterProvider import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.test.TestCoroutineScope @@ -55,7 +56,8 @@ internal class ComputeServiceTest { scope = TestCoroutineScope() val clock = DelayControllerClockAdapter(scope) val policy = AvailableMemoryAllocationPolicy() - service = ComputeService(scope.coroutineContext, clock, policy) + val meter = MeterProvider.noop().get("opendc-compute") + service = ComputeService(scope.coroutineContext, clock, meter, policy) } @AfterEach -- cgit v1.2.3