From 6d5734447048552c46a5c46dbf630f0e5be50765 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Thu, 7 Jul 2022 19:33:41 +0200 Subject: build(trace/parquet): Ignore reload4j dependency This change updates the build configuration to ignore the reload4j dependency that was recently added to the hadoop-common module. Reload4j replaces the old unmaintained log4j1 module. However, since we expose this module as a library, we do not want to include a logging implementation in the dependencies. Currently, there are already instances where this new dependency leads to duplicate logging implementations on the classpath. --- opendc-trace/opendc-trace-parquet/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opendc-trace/opendc-trace-parquet/build.gradle.kts b/opendc-trace/opendc-trace-parquet/build.gradle.kts index 9b1e1273..2217a017 100644 --- a/opendc-trace/opendc-trace-parquet/build.gradle.kts +++ b/opendc-trace/opendc-trace-parquet/build.gradle.kts @@ -33,8 +33,8 @@ dependencies { exclude(group = "org.apache.hadoop") } api(libs.hadoop.common) { - exclude(group = "org.slf4j", module = "slf4j-log4j12") - exclude(group = "log4j") + exclude(group = "org.slf4j", module = "slf4j-reload4j") + exclude(group = "ch.qos.reload4j", module = "reload4j") exclude(group = "org.apache.hadoop") exclude(group = "org.apache.curator") exclude(group = "org.apache.zookeeper") -- cgit v1.2.3 From 0f7773e8546ad2fdc6ea44a1bbc0a5d82399667f Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Fri, 29 Jul 2022 16:59:16 +0200 Subject: fix(trace/api): Do not cache trace formats This change updates the TraceFormat lookup algorithm to prevent caching the available trace format on first access. Since the result of ServiceLoader depends on the Thread's context ClassLoader, they may differ between different threads. Furthermore, ServiceLoader maintains its own thread-local cache, so we can instead utilize that cache and always use the results returned by it. --- .../src/main/kotlin/org/opendc/trace/spi/TraceFormat.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/spi/TraceFormat.kt b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/spi/TraceFormat.kt index eff6fa83..26e81cf8 100644 --- a/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/spi/TraceFormat.kt +++ b/opendc-trace/opendc-trace-api/src/main/kotlin/org/opendc/trace/spi/TraceFormat.kt @@ -90,18 +90,20 @@ public interface TraceFormat { */ public companion object { /** - * A list of [TraceFormat] that are available on this system. + * Obtain a list of [TraceFormat] that are available in the current thread context. */ @JvmStatic - public val installedProviders: List by lazy { - val loader = ServiceLoader.load(TraceFormat::class.java) - loader.toList() + public fun getInstalledProviders(): Iterable { + return ServiceLoader.load(TraceFormat::class.java) } /** * Obtain a [TraceFormat] implementation by [name]. */ @JvmStatic - public fun byName(name: String): TraceFormat? = installedProviders.find { it.name == name } + public fun byName(name: String): TraceFormat? { + val loader = ServiceLoader.load(TraceFormat::class.java) + return loader.find { it.name == name } + } } } -- cgit v1.2.3 From 5c824ecafdc16022456ef05c5eeeeea19a5ac9b3 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Fri, 29 Jul 2022 21:02:29 +0200 Subject: build: Update to Quarkus 2.11.1 This change updates Quarkus to version 2.11.1 which fixes an issue with the Gradle development mode where the working directory was incorrect. This meant that the traces failed to be loaded. --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 899d3cfc..4e81ff35 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ dokka = "1.6.21" gradle-node = "3.3.0" hadoop = "3.3.3" jackson = "2.13.3" -jandex-gradle = "0.12.0" +jandex-gradle = "0.13.2" jline = "3.21.0" jmh-gradle = "0.6.6" jakarta-validation = "2.0.2" @@ -20,7 +20,7 @@ microprofile-openapi = "3.0" mockk = "1.12.4" parquet = "1.12.3" progressbar = "0.9.3" -quarkus = "2.10.0.Final" +quarkus = "2.11.1.Final" quarkus-junit5-mockk = "1.1.1" sentry = "6.1.2" slf4j = "1.7.36" -- cgit v1.2.3 From 6c6281716ac5ee644b6abacf5164dc3e27687503 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Fri, 29 Jul 2022 17:04:06 +0200 Subject: fix(web/runner): Register all available trace formats This change updates the Quarkus extension to register all the available trace formats that are on the class path during processing time. Without this change, the OpenDC web runner is unable to load any of the available trace formats via Quarkus. --- .../build.gradle.kts | 1 + .../web/runner/deployment/OpenDCRunnerProcessor.java | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/opendc-web/opendc-web-runner-quarkus-deployment/build.gradle.kts b/opendc-web/opendc-web-runner-quarkus-deployment/build.gradle.kts index 24f667cf..d31c4839 100644 --- a/opendc-web/opendc-web-runner-quarkus-deployment/build.gradle.kts +++ b/opendc-web/opendc-web-runner-quarkus-deployment/build.gradle.kts @@ -29,6 +29,7 @@ plugins { dependencies { implementation(projects.opendcWeb.opendcWebRunnerQuarkus) + implementation(projects.opendcTrace.opendcTraceApi) implementation(platform(libs.quarkus.bom)) implementation(libs.quarkus.core.deployment) diff --git a/opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerProcessor.java b/opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerProcessor.java index b9a334ac..1d01aabc 100644 --- a/opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerProcessor.java +++ b/opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerProcessor.java @@ -26,11 +26,17 @@ import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.*; +import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; +import io.quarkus.deployment.util.ServiceUtil; import io.quarkus.runtime.RuntimeValue; +import org.opendc.trace.spi.TraceFormat; import org.opendc.web.runner.OpenDCRunner; import org.opendc.web.runner.runtime.OpenDCRunnerRecorder; import org.opendc.web.runner.runtime.OpenDCRunnerRuntimeConfig; +import java.io.IOException; +import java.util.Collections; +import java.util.Set; import java.util.function.BooleanSupplier; import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; @@ -51,12 +57,18 @@ public class OpenDCRunnerProcessor { } /** - * Build step to index the necessary dependencies for the OpenDC runner. + * Build step to register the trace formats used by OpenDC. */ @BuildStep - void addDependencies(BuildProducer indexDependency) { - indexDependency.produce(new IndexDependencyBuildItem("org.opendc.trace", "opendc-trace-opendc")); - indexDependency.produce(new IndexDependencyBuildItem("org.opendc.trace", "opendc-trace-bitbrains")); + void registerTraceFormats(BuildProducer services) throws IOException { + String service = "META-INF/services/" + TraceFormat.class.getName(); + + Set implementations = ServiceUtil.classNamesNamedIn(Thread.currentThread().getContextClassLoader(), + service); + + services.produce( + new ServiceProviderBuildItem(TraceFormat.class.getName(), + implementations.toArray(new String[0]))); } /** -- cgit v1.2.3 From ebc6cdd08a0d8dac045305839421b726ae5c91b3 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Fri, 29 Jul 2022 17:06:24 +0200 Subject: fix(web/runner): Use correct context ClassLoader for ForkJoinPool This change updates the OpenDC web runner implementation to use the correct context ClassLoader for simulation jobs running inside a ForkJoinPool. By default, the ForkJoinPool will use the system class loader which does not have access to the services needed by the web runner. --- .../src/main/kotlin/org/opendc/web/runner/OpenDCRunner.kt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/OpenDCRunner.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/OpenDCRunner.kt index 9c9a866d..ccc7f03a 100644 --- a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/OpenDCRunner.kt +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/OpenDCRunner.kt @@ -45,6 +45,7 @@ import java.io.File import java.time.Duration import java.util.* import java.util.concurrent.* +import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory /** * Class to execute the pending jobs via the OpenDC web API. @@ -81,7 +82,7 @@ public class OpenDCRunner( /** * The [ForkJoinPool] that is used to execute the simulation jobs. */ - private val pool = ForkJoinPool(parallelism) + private val pool = ForkJoinPool(parallelism, RunnerThreadFactory(Thread.currentThread().contextClassLoader), null, false) /** * A [ScheduledExecutorService] to manage the heartbeat of simulation jobs as well as tracking the deadline of @@ -303,4 +304,15 @@ public class OpenDCRunner( override fun toString(): String = "WebRunnerTopologyFactory" } } + + /** + * A custom [ForkJoinWorkerThreadFactory] that uses the [ClassLoader] of specified by the runner. + */ + private class RunnerThreadFactory(private val classLoader: ClassLoader) : ForkJoinWorkerThreadFactory { + override fun newThread(pool: ForkJoinPool): ForkJoinWorkerThread = object : ForkJoinWorkerThread(pool) { + init { + contextClassLoader = classLoader + } + } + } } -- cgit v1.2.3 From fab42945e8e5a1e9a8296f5e4bcbe476a6a5bbd6 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Sat, 30 Jul 2022 13:11:38 +0200 Subject: fix(web/runner): Gracefully exit on interrupt This change updates the web runner implementation to gracefully exit the current thread when interrupted. --- .../src/main/kotlin/org/opendc/web/runner/OpenDCRunner.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/OpenDCRunner.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/OpenDCRunner.kt index ccc7f03a..7e0133d0 100644 --- a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/OpenDCRunner.kt +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/OpenDCRunner.kt @@ -98,11 +98,6 @@ public class OpenDCRunner( override fun run() { try { while (true) { - // Check if anyone has interrupted the thread - if (Thread.interrupted()) { - throw InterruptedException() - } - val job = manager.findNext() if (job == null) { Thread.sleep(pollInterval.toMillis()) @@ -120,6 +115,8 @@ public class OpenDCRunner( pool.submit(JobAction(job)) } + } catch (_: InterruptedException) { + // Gracefully exit when the thread is interrupted } finally { workloadLoader.reset() -- cgit v1.2.3 From 5deb055565606c94fc29bd594832586f3dfdf3de Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Tue, 2 Aug 2022 10:07:17 +0200 Subject: fix(web/runner): Prevent reporting NaN values This change fixes an issue with the OpenDC web runner where it would report NaN values for some of the metrics due to the topology being empty. This in turn causes issues in the frontend. --- .../main/kotlin/org/opendc/web/runner/internal/WebComputeMonitor.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/internal/WebComputeMonitor.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/internal/WebComputeMonitor.kt index 01002c70..4c3d1cfa 100644 --- a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/internal/WebComputeMonitor.kt +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/internal/WebComputeMonitor.kt @@ -105,9 +105,9 @@ internal class WebComputeMonitor : ComputeMonitor { hostAggregateMetrics.totalIdleTime, hostAggregateMetrics.totalStealTime, hostAggregateMetrics.totalLostTime, - hostMetrics.map { it.value.cpuUsage / it.value.count }.average(), - hostMetrics.map { it.value.cpuDemand / it.value.count }.average(), - hostMetrics.map { it.value.instanceCount.toDouble() / it.value.count }.average(), + hostMetrics.map { it.value.cpuUsage / it.value.count }.average().let { if (it.isNaN()) 0.0 else it }, + hostMetrics.map { it.value.cpuDemand / it.value.count }.average().let { if (it.isNaN()) 0.0 else it }, + hostMetrics.map { it.value.instanceCount.toDouble() / it.value.count }.average().let { if (it.isNaN()) 0.0 else it }, hostMetrics.map { it.value.instanceCount.toDouble() / it.value.count }.maxOrNull() ?: 0.0, hostAggregateMetrics.totalPowerDraw, hostAggregateMetrics.totalFailureSlices.roundToLong(), -- cgit v1.2.3 From f6932d264db5b2e185ec7ea7aaec84dfb83f8fe9 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Sun, 31 Jul 2022 21:27:02 +0200 Subject: feat(web/runner): Automatically compute experiment parallelism This change updates the runner configuration to support specifying the parallelism as zero to let the runtime decide the appropriate number of threads based on the available CPU cores on the machine. --- .../org/opendc/web/runner/runtime/OpenDCRunnerRecorder.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRecorder.java b/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRecorder.java index b243dbc3..c1f356bc 100644 --- a/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRecorder.java +++ b/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRecorder.java @@ -44,11 +44,19 @@ public class OpenDCRunnerRecorder { */ public RuntimeValue createRunner(OpenDCRunnerRuntimeConfig config) { URI apiUrl = URI.create(config.apiUrl); + + int parallelism = config.parallelism; + if (parallelism < 0) { + throw new IllegalArgumentException("Parallelism must be non-negative"); + } else if (parallelism == 0) { + parallelism = Math.min(1, Runtime.getRuntime().availableProcessors() - 1); + } + OpenDCRunnerClient client = new OpenDCRunnerClient(apiUrl, null); OpenDCRunner runner = new OpenDCRunner( client, new File(config.tracePath), - config.parallelism, + parallelism, config.jobTimeout, config.pollInterval, config.heartbeatInterval -- cgit v1.2.3 From a424aa5e81c31f8cc6ba8846f0a6af29623588d4 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Wed, 3 Aug 2022 11:11:58 +0200 Subject: refactor(web/runner): Support pluggable job manager This change introduces a new interface `JobManager` that is responsible for communicating with the backend about the available jobs and updating their status when the runner is simulating a job. This manager can be injected into the `OpenDCRunner` class and allows users to provide different sources for the jobs, not only the current REST API. --- .../org/opendc/web/api/rest/runner/JobResource.kt | 9 ++- .../web/runner/runtime/OpenDCRunnerRecorder.java | 5 +- .../src/cli/kotlin/org/opendc/web/runner/Main.kt | 3 +- .../kotlin/org/opendc/web/runner/JobManager.kt | 68 ++++++++++++++++ .../kotlin/org/opendc/web/runner/OpenDCRunner.kt | 35 ++++++--- .../org/opendc/web/runner/internal/JobManager.kt | 90 ---------------------- .../opendc/web/runner/internal/JobManagerImpl.kt | 58 ++++++++++++++ 7 files changed, 163 insertions(+), 105 deletions(-) create mode 100644 opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/JobManager.kt delete mode 100644 opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/internal/JobManager.kt create mode 100644 opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/internal/JobManagerImpl.kt diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/runner/JobResource.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/runner/JobResource.kt index 7e31e2c5..d9923505 100644 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/runner/JobResource.kt +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/runner/JobResource.kt @@ -60,6 +60,13 @@ class JobResource @Inject constructor(private val jobService: JobService) { @Path("{job}") @Transactional fun update(@PathParam("job") id: Long, @Valid update: Job.Update): Job { - return jobService.updateState(id, update.state, update.results) ?: throw WebApplicationException("Job not found", 404) + return try { + jobService.updateState(id, update.state, update.results) + ?: throw WebApplicationException("Job not found", 404) + } catch (e: IllegalArgumentException) { + throw WebApplicationException(e, 400) + } catch (e: IllegalStateException) { + throw WebApplicationException(e, 409) + } } } diff --git a/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRecorder.java b/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRecorder.java index c1f356bc..eccc8dcf 100644 --- a/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRecorder.java +++ b/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRecorder.java @@ -27,6 +27,7 @@ import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Recorder; import org.jboss.logging.Logger; import org.opendc.web.client.runner.OpenDCRunnerClient; +import org.opendc.web.runner.JobManager; import org.opendc.web.runner.OpenDCRunner; import java.io.File; @@ -54,7 +55,7 @@ public class OpenDCRunnerRecorder { OpenDCRunnerClient client = new OpenDCRunnerClient(apiUrl, null); OpenDCRunner runner = new OpenDCRunner( - client, + JobManager.create(client), new File(config.tracePath), parallelism, config.jobTimeout, @@ -77,7 +78,7 @@ public class OpenDCRunnerRecorder { Thread thread = new Thread(() -> { try { // Wait for some time to allow Vert.x to bind to the local port - Thread.sleep(1000); + Thread.sleep(4000); } catch (InterruptedException e) { throw new RuntimeException(e); } diff --git a/opendc-web/opendc-web-runner/src/cli/kotlin/org/opendc/web/runner/Main.kt b/opendc-web/opendc-web-runner/src/cli/kotlin/org/opendc/web/runner/Main.kt index 348a838c..4cfbdd7c 100644 --- a/opendc-web/opendc-web-runner/src/cli/kotlin/org/opendc/web/runner/Main.kt +++ b/opendc-web/opendc-web-runner/src/cli/kotlin/org/opendc/web/runner/Main.kt @@ -114,7 +114,8 @@ class RunnerCli : CliktCommand(name = "opendc-runner") { logger.info { "Starting OpenDC web runner" } val client = OpenDCRunnerClient(baseUrl = apiUrl, OpenIdAuthController(authDomain, authClientId, authClientSecret, authAudience)) - val runner = OpenDCRunner(client, tracePath, parallelism = parallelism) + val manager = JobManager(client) + val runner = OpenDCRunner(manager, tracePath, parallelism = parallelism) logger.info { "Watching for queued scenarios" } runner.run() diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/JobManager.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/JobManager.kt new file mode 100644 index 00000000..50aa03d8 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/JobManager.kt @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2022 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.web.runner + +import org.opendc.web.client.runner.OpenDCRunnerClient +import org.opendc.web.proto.runner.Job +import org.opendc.web.runner.internal.JobManagerImpl + +/** + * Interface used by the [OpenDCRunner] to manage the available jobs to be processed. + */ +public interface JobManager { + /** + * Find the next job that the simulator needs to process. + */ + public fun findNext(): Job? + + /** + * Claim the simulation job with the specified id. + */ + public fun claim(id: Long): Boolean + + /** + * Update the heartbeat of the specified job. + */ + public fun heartbeat(id: Long) + + /** + * Mark the job as failed. + */ + public fun fail(id: Long) + + /** + * Persist the specified results for the specified job. + */ + public fun finish(id: Long, results: Map) + + public companion object { + /** + * Create a [JobManager] given the specified [client]. + */ + @JvmStatic + @JvmName("create") + public operator fun invoke(client: OpenDCRunnerClient): JobManager { + return JobManagerImpl(client) + } + } +} diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/OpenDCRunner.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/OpenDCRunner.kt index 7e0133d0..c958bdb2 100644 --- a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/OpenDCRunner.kt +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/OpenDCRunner.kt @@ -36,10 +36,8 @@ import org.opendc.simulator.compute.model.ProcessingUnit import org.opendc.simulator.compute.power.LinearPowerModel import org.opendc.simulator.compute.power.SimplePowerDriver import org.opendc.simulator.core.runBlockingSimulation -import org.opendc.web.client.runner.OpenDCRunnerClient import org.opendc.web.proto.runner.Job import org.opendc.web.proto.runner.Scenario -import org.opendc.web.runner.internal.JobManager import org.opendc.web.runner.internal.WebComputeMonitor import java.io.File import java.time.Duration @@ -50,14 +48,14 @@ import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory /** * Class to execute the pending jobs via the OpenDC web API. * - * @param client The [OpenDCRunnerClient] to connect to the OpenDC web API. + * @param manager The underlying [JobManager] to manage the available jobs. * @param tracePath The directory where the traces are located. * @param jobTimeout The maximum duration of a simulation job. * @param pollInterval The interval to poll the API with. * @param heartbeatInterval The interval to send a heartbeat to the API server. */ public class OpenDCRunner( - client: OpenDCRunnerClient, + private val manager: JobManager, private val tracePath: File, parallelism: Int = Runtime.getRuntime().availableProcessors(), private val jobTimeout: Duration = Duration.ofMillis(10), @@ -69,11 +67,6 @@ public class OpenDCRunner( */ private val logger = KotlinLogging.logger {} - /** - * Helper class to manage the available jobs. - */ - private val manager = JobManager(client) - /** * Helper class to load the workloads. */ @@ -142,12 +135,32 @@ public class OpenDCRunner( try { val topology = convertTopology(scenario.topology) val jobs = (0 until scenario.portfolio.targets.repeats).map { repeat -> SimulationTask(scenario, repeat, topology) } - val results = invokeAll(jobs) + val results = invokeAll(jobs).map { it.rawResult } logger.info { "Finished simulation for job $id" } heartbeat.cancel(true) - manager.finish(id, results.map { it.rawResult }) + + manager.finish( + id, + mapOf( + "total_requested_burst" to results.map { it.totalActiveTime + it.totalIdleTime }, + "total_granted_burst" to results.map { it.totalActiveTime }, + "total_overcommitted_burst" to results.map { it.totalStealTime }, + "total_interfered_burst" to results.map { it.totalLostTime }, + "mean_cpu_usage" to results.map { it.meanCpuUsage }, + "mean_cpu_demand" to results.map { it.meanCpuDemand }, + "mean_num_deployed_images" to results.map { it.meanNumDeployedImages }, + "max_num_deployed_images" to results.map { it.maxNumDeployedImages }, + "total_power_draw" to results.map { it.totalPowerDraw }, + "total_failure_slices" to results.map { it.totalFailureSlices }, + "total_failure_vm_slices" to results.map { it.totalFailureVmSlices }, + "total_vms_submitted" to results.map { it.totalVmsSubmitted }, + "total_vms_queued" to results.map { it.totalVmsQueued }, + "total_vms_finished" to results.map { it.totalVmsFinished }, + "total_vms_failed" to results.map { it.totalVmsFailed } + ) + ) } catch (e: Exception) { // Check whether the job failed due to exceeding its time budget if (Thread.interrupted()) { diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/internal/JobManager.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/internal/JobManager.kt deleted file mode 100644 index 99b8aaf1..00000000 --- a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/internal/JobManager.kt +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2022 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.web.runner.internal - -import org.opendc.web.client.runner.OpenDCRunnerClient -import org.opendc.web.proto.JobState -import org.opendc.web.proto.runner.Job - -/** - * Helper class to manage the queue of jobs that need to be simulated. - */ -internal class JobManager(private val client: OpenDCRunnerClient) { - /** - * Find the next job that the simulator needs to process. - */ - fun findNext(): Job? { - return client.jobs.queryPending().firstOrNull() - } - - /** - * Claim the simulation job with the specified id. - */ - fun claim(id: Long): Boolean { - client.jobs.update(id, Job.Update(JobState.CLAIMED)) // TODO Handle conflict - return true - } - - /** - * Update the heartbeat of the specified scenario. - */ - fun heartbeat(id: Long) { - client.jobs.update(id, Job.Update(JobState.RUNNING)) - } - - /** - * Mark the scenario as failed. - */ - fun fail(id: Long) { - client.jobs.update(id, Job.Update(JobState.FAILED)) - } - - /** - * Persist the specified results. - */ - fun finish(id: Long, results: List) { - client.jobs.update( - id, - Job.Update( - JobState.FINISHED, - mapOf( - "total_requested_burst" to results.map { it.totalActiveTime + it.totalIdleTime }, - "total_granted_burst" to results.map { it.totalActiveTime }, - "total_overcommitted_burst" to results.map { it.totalStealTime }, - "total_interfered_burst" to results.map { it.totalLostTime }, - "mean_cpu_usage" to results.map { it.meanCpuUsage }, - "mean_cpu_demand" to results.map { it.meanCpuDemand }, - "mean_num_deployed_images" to results.map { it.meanNumDeployedImages }, - "max_num_deployed_images" to results.map { it.maxNumDeployedImages }, - "total_power_draw" to results.map { it.totalPowerDraw }, - "total_failure_slices" to results.map { it.totalFailureSlices }, - "total_failure_vm_slices" to results.map { it.totalFailureVmSlices }, - "total_vms_submitted" to results.map { it.totalVmsSubmitted }, - "total_vms_queued" to results.map { it.totalVmsQueued }, - "total_vms_finished" to results.map { it.totalVmsFinished }, - "total_vms_failed" to results.map { it.totalVmsFailed } - ) - ) - ) - } -} diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/internal/JobManagerImpl.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/internal/JobManagerImpl.kt new file mode 100644 index 00000000..39a6851c --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/internal/JobManagerImpl.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022 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.web.runner.internal + +import org.opendc.web.client.runner.OpenDCRunnerClient +import org.opendc.web.proto.JobState +import org.opendc.web.proto.runner.Job +import org.opendc.web.runner.JobManager + +/** + * Default implementation of [JobManager] that uses the OpenDC client to receive jobs. + */ +internal class JobManagerImpl(private val client: OpenDCRunnerClient) : JobManager { + override fun findNext(): Job? { + return client.jobs.queryPending().firstOrNull() + } + + override fun claim(id: Long): Boolean { + return try { + client.jobs.update(id, Job.Update(JobState.CLAIMED)) + true + } catch (e: IllegalStateException) { + false + } + } + + override fun heartbeat(id: Long) { + client.jobs.update(id, Job.Update(JobState.RUNNING)) + } + + override fun fail(id: Long) { + client.jobs.update(id, Job.Update(JobState.FAILED)) + } + + override fun finish(id: Long, results: Map) { + client.jobs.update(id, Job.Update(JobState.FINISHED, results)) + } +} -- cgit v1.2.3 From 16439ea8db70fe7d531fde30914291a9707f32f0 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Sat, 30 Jul 2022 12:37:13 +0200 Subject: feat(web/runner): Avoid REST layer if possible This change updates the Quarkus extension for the OpenDC runner to avoid the REST layer if possible, by providing an implementation of `JobManager` that directly communicates with the `JobService`. This means the runner does not have to traverse the authentication layer. --- .../src/main/resources/application-dev.properties | 2 +- .../build.gradle.kts | 1 + .../runner/deployment/OpenDCRunnerProcessor.java | 10 ++++ .../web/runner/runtime/OpenDCRunnerRecorder.java | 23 ++------ .../runner/runtime/OpenDCRunnerRuntimeConfig.java | 6 -- .../web/server/util/runner/QuarkusJobManager.kt | 67 ++++++++++++++++++++++ 6 files changed, 85 insertions(+), 24 deletions(-) create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/runner/QuarkusJobManager.kt diff --git a/opendc-web/opendc-web-api/src/main/resources/application-dev.properties b/opendc-web/opendc-web-api/src/main/resources/application-dev.properties index 08d11609..98e53ee7 100644 --- a/opendc-web/opendc-web-api/src/main/resources/application-dev.properties +++ b/opendc-web/opendc-web-api/src/main/resources/application-dev.properties @@ -37,4 +37,4 @@ quarkus.opendc-ui.path=/ quarkus.resteasy.path=/api opendc.security.enabled=false - +quarkus.opendc-runner.auth.enabled=false diff --git a/opendc-web/opendc-web-runner-quarkus-deployment/build.gradle.kts b/opendc-web/opendc-web-runner-quarkus-deployment/build.gradle.kts index d31c4839..b3f1ec3b 100644 --- a/opendc-web/opendc-web-runner-quarkus-deployment/build.gradle.kts +++ b/opendc-web/opendc-web-runner-quarkus-deployment/build.gradle.kts @@ -33,4 +33,5 @@ dependencies { implementation(platform(libs.quarkus.bom)) implementation(libs.quarkus.core.deployment) + implementation(libs.quarkus.arc.deployment) } diff --git a/opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerProcessor.java b/opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerProcessor.java index 1d01aabc..94921454 100644 --- a/opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerProcessor.java +++ b/opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerProcessor.java @@ -22,6 +22,7 @@ package org.opendc.web.runner.deployment; +import io.quarkus.arc.deployment.UnremovableBeanBuildItem; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.Record; @@ -30,6 +31,7 @@ import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; import io.quarkus.deployment.util.ServiceUtil; import io.quarkus.runtime.RuntimeValue; import org.opendc.trace.spi.TraceFormat; +import org.opendc.web.runner.JobManager; import org.opendc.web.runner.OpenDCRunner; import org.opendc.web.runner.runtime.OpenDCRunnerRecorder; import org.opendc.web.runner.runtime.OpenDCRunnerRuntimeConfig; @@ -71,6 +73,14 @@ public class OpenDCRunnerProcessor { implementations.toArray(new String[0]))); } + /** + * Mark {@link JobManager} as unremoveable, since we look up this service dynamically in {@link OpenDCRunnerRecorder}. + */ + @BuildStep + UnremovableBeanBuildItem unremovableBeans() { + return UnremovableBeanBuildItem.beanTypes(JobManager.class); + } + /** * Build step to create the runner service. */ diff --git a/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRecorder.java b/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRecorder.java index eccc8dcf..f5c056ef 100644 --- a/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRecorder.java +++ b/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRecorder.java @@ -26,12 +26,11 @@ import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Recorder; import org.jboss.logging.Logger; -import org.opendc.web.client.runner.OpenDCRunnerClient; import org.opendc.web.runner.JobManager; import org.opendc.web.runner.OpenDCRunner; +import javax.enterprise.inject.spi.CDI; import java.io.File; -import java.net.URI; /** * Helper class for starting the OpenDC web runner. @@ -44,8 +43,6 @@ public class OpenDCRunnerRecorder { * Helper method to create an {@link OpenDCRunner} instance. */ public RuntimeValue createRunner(OpenDCRunnerRuntimeConfig config) { - URI apiUrl = URI.create(config.apiUrl); - int parallelism = config.parallelism; if (parallelism < 0) { throw new IllegalArgumentException("Parallelism must be non-negative"); @@ -53,9 +50,9 @@ public class OpenDCRunnerRecorder { parallelism = Math.min(1, Runtime.getRuntime().availableProcessors() - 1); } - OpenDCRunnerClient client = new OpenDCRunnerClient(apiUrl, null); + JobManager manager = CDI.current().select(JobManager.class).get(); OpenDCRunner runner = new OpenDCRunner( - JobManager.create(client), + manager, new File(config.tracePath), parallelism, config.jobTimeout, @@ -73,18 +70,10 @@ public class OpenDCRunnerRecorder { OpenDCRunnerRuntimeConfig config, ShutdownContext shutdownContext) { if (config.enable) { - LOGGER.info("Starting OpenDC Runner in background (polling " + config.apiUrl + ")"); - - Thread thread = new Thread(() -> { - try { - // Wait for some time to allow Vert.x to bind to the local port - Thread.sleep(4000); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + LOGGER.info("Starting OpenDC Runner in background (polling every " + config.pollInterval + ")"); - runner.getValue().run(); - }); + Thread thread = new Thread(runner.getValue()); + thread.setName("opendc-runner"); thread.start(); shutdownContext.addShutdownTask(thread::interrupt); diff --git a/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRuntimeConfig.java b/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRuntimeConfig.java index e1dbf0a8..e9258f06 100644 --- a/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRuntimeConfig.java +++ b/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRuntimeConfig.java @@ -39,12 +39,6 @@ public class OpenDCRunnerRuntimeConfig { @ConfigItem(defaultValue = "true") public boolean enable; - /** - * The URI to the (local) API to communicate with. - */ - @ConfigItem(defaultValue = "http://${quarkus.http.host}:${quarkus.http.port}${quarkus.resteasy.path:}") - public String apiUrl; - /** * The path where the workload traces are located. */ diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/runner/QuarkusJobManager.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/runner/QuarkusJobManager.kt new file mode 100644 index 00000000..4704c5ae --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/runner/QuarkusJobManager.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2022 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.web.server.util.runner + +import org.opendc.web.proto.JobState +import org.opendc.web.proto.runner.Job +import org.opendc.web.runner.JobManager +import org.opendc.web.server.service.JobService +import javax.enterprise.context.ApplicationScoped +import javax.inject.Inject +import javax.transaction.Transactional + +/** + * Implementation of [JobManager] that interfaces directly with [JobService] without overhead of the REST API. + */ +@ApplicationScoped +class QuarkusJobManager @Inject constructor(private val service: JobService) : JobManager { + @Transactional + override fun findNext(): Job? { + return service.queryPending().firstOrNull() + } + + @Transactional + override fun claim(id: Long): Boolean { + return try { + service.updateState(id, JobState.CLAIMED, null) + true + } catch (e: IllegalStateException) { + false + } + } + + @Transactional + override fun heartbeat(id: Long) { + service.updateState(id, JobState.RUNNING, null) + } + + @Transactional + override fun fail(id: Long) { + service.updateState(id, JobState.FAILED, null) + } + + @Transactional + override fun finish(id: Long, results: Map) { + service.updateState(id, JobState.FINISHED, results) + } +} -- cgit v1.2.3 From 41b0ed59421b301bac652e47d1a2909145aa5936 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Sat, 30 Jul 2022 13:10:58 +0200 Subject: fix(web/server): Reduce logging output from web runner This change updates the web server configuration to reduce the logging output produced by simulation runs. --- opendc-web/opendc-web-api/src/main/resources/application.properties | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/opendc-web/opendc-web-api/src/main/resources/application.properties b/opendc-web/opendc-web-api/src/main/resources/application.properties index fa134e7e..e9285401 100644 --- a/opendc-web/opendc-web-api/src/main/resources/application.properties +++ b/opendc-web/opendc-web-api/src/main/resources/application.properties @@ -26,6 +26,11 @@ quarkus.oidc.client-id=${OPENDC_AUTH0_AUDIENCE} quarkus.oidc.token.audience=${quarkus.oidc.client-id} quarkus.oidc.roles.role-claim-path=scope +# Runner logging +quarkus.log.category."org.opendc".level=ERROR +quarkus.log.category."org.opendc.web".level=INFO +quarkus.log.category."org.apache".level=WARN + # OpenAPI and Swagger quarkus.smallrye-openapi.info-title=OpenDC REST API %dev.quarkus.smallrye-openapi.info-title=OpenDC REST API (development) -- cgit v1.2.3 From a01f964b531f12fd89cbdb0f2132aecbfaebf546 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Sat, 30 Jul 2022 12:15:10 +0200 Subject: refactor(web/server): Create standalone OpenDC distribution This change updates the Quarkus configuration of the OpenDC web server to serve as a fully standalone distribution that is capable of serving the web UI, web API, and experiment runner. Such an approach vastly simplifies local deployments. For Docker deployments, we create a custom Quarkus profile that uses PostgreSQL and disables the web UI. --- .github/workflows/build.yml | 4 +- docker-compose.override.yml | 15 +- docker-compose.prod.yml | 10 +- docker-compose.yml | 38 +-- opendc-web/opendc-web-api/Dockerfile | 17 -- opendc-web/opendc-web-api/build.gradle.kts | 88 ------ .../opendc-web-api/config/application.properties | 1 - .../kotlin/org/opendc/web/api/OpenDCApplication.kt | 30 -- .../main/kotlin/org/opendc/web/api/model/Job.kt | 95 ------- .../kotlin/org/opendc/web/api/model/Portfolio.kt | 89 ------ .../kotlin/org/opendc/web/api/model/Project.kt | 134 --------- .../opendc/web/api/model/ProjectAuthorization.kt | 58 ---- .../web/api/model/ProjectAuthorizationKey.kt | 38 --- .../kotlin/org/opendc/web/api/model/Scenario.kt | 107 -------- .../kotlin/org/opendc/web/api/model/Topology.kt | 92 ------- .../main/kotlin/org/opendc/web/api/model/Trace.kt | 58 ---- .../kotlin/org/opendc/web/api/model/Workload.kt | 39 --- .../org/opendc/web/api/repository/JobRepository.kt | 93 ------- .../web/api/repository/PortfolioRepository.kt | 76 ------ .../opendc/web/api/repository/ProjectRepository.kt | 157 ----------- .../web/api/repository/ScenarioRepository.kt | 90 ------ .../web/api/repository/TopologyRepository.kt | 86 ------ .../opendc/web/api/repository/TraceRepository.kt | 53 ---- .../org/opendc/web/api/rest/SchedulerResource.kt | 48 ---- .../org/opendc/web/api/rest/TraceResource.kt | 51 ---- .../web/api/rest/error/GenericExceptionMapper.kt | 45 --- .../error/MissingKotlinParameterExceptionMapper.kt | 43 --- .../org/opendc/web/api/rest/runner/JobResource.kt | 72 ----- .../opendc/web/api/rest/user/PortfolioResource.kt | 77 ------ .../web/api/rest/user/PortfolioScenarioResource.kt | 59 ---- .../opendc/web/api/rest/user/ProjectResource.kt | 82 ------ .../opendc/web/api/rest/user/ScenarioResource.kt | 60 ---- .../opendc/web/api/rest/user/TopologyResource.kt | 88 ------ .../org/opendc/web/api/service/JobService.kt | 81 ------ .../org/opendc/web/api/service/PortfolioService.kt | 104 ------- .../org/opendc/web/api/service/ProjectService.kt | 86 ------ .../opendc/web/api/service/RunnerConversions.kt | 69 ----- .../org/opendc/web/api/service/ScenarioService.kt | 128 --------- .../org/opendc/web/api/service/TopologyService.kt | 127 --------- .../org/opendc/web/api/service/TraceService.kt | 48 ---- .../org/opendc/web/api/service/UserConversions.kt | 120 -------- .../kotlin/org/opendc/web/api/service/Utils.kt | 40 --- .../web/api/util/DevSecurityOverrideFilter.kt | 51 ---- .../opendc/web/api/util/KotlinModuleCustomizer.kt | 38 --- .../json/AbstractJsonSqlTypeDescriptor.kt | 74 ----- .../hibernate/json/JsonBinarySqlTypeDescriptor.kt | 26 -- .../hibernate/json/JsonBytesSqlTypeDescriptor.kt | 83 ------ .../util/hibernate/json/JsonSqlTypeDescriptor.kt | 107 -------- .../hibernate/json/JsonStringSqlTypeDescriptor.kt | 38 --- .../opendc/web/api/util/hibernate/json/JsonType.kt | 48 ---- .../api/util/hibernate/json/JsonTypeDescriptor.kt | 149 ---------- .../src/main/resources/META-INF/branding/logo.png | Bin 2825 -> 0 bytes .../src/main/resources/application-dev.properties | 40 --- .../src/main/resources/application-prod.properties | 33 --- .../src/main/resources/application-test.properties | 40 --- .../src/main/resources/application.properties | 53 ---- .../opendc-web-api/src/main/resources/init-dev.sql | 3 - .../opendc/web/api/rest/SchedulerResourceTest.kt | 48 ---- .../org/opendc/web/api/rest/TraceResourceTest.kt | 100 ------- .../opendc/web/api/rest/runner/JobResourceTest.kt | 200 -------------- .../web/api/rest/user/PortfolioResourceTest.kt | 265 ------------------ .../api/rest/user/PortfolioScenarioResourceTest.kt | 213 --------------- .../web/api/rest/user/ProjectResourceTest.kt | 240 ---------------- .../web/api/rest/user/ScenarioResourceTest.kt | 178 ------------ .../web/api/rest/user/TopologyResourceTest.kt | 304 --------------------- opendc-web/opendc-web-server/Dockerfile | 17 ++ opendc-web/opendc-web-server/build.gradle.kts | 88 ++++++ .../config/application.properties | 1 + .../org/opendc/web/server/OpenDCApplication.kt | 30 ++ .../main/kotlin/org/opendc/web/server/model/Job.kt | 95 +++++++ .../org/opendc/web/server/model/Portfolio.kt | 89 ++++++ .../kotlin/org/opendc/web/server/model/Project.kt | 134 +++++++++ .../web/server/model/ProjectAuthorization.kt | 58 ++++ .../web/server/model/ProjectAuthorizationKey.kt | 38 +++ .../kotlin/org/opendc/web/server/model/Scenario.kt | 107 ++++++++ .../kotlin/org/opendc/web/server/model/Topology.kt | 92 +++++++ .../kotlin/org/opendc/web/server/model/Trace.kt | 58 ++++ .../kotlin/org/opendc/web/server/model/Workload.kt | 39 +++ .../opendc/web/server/repository/JobRepository.kt | 93 +++++++ .../web/server/repository/PortfolioRepository.kt | 76 ++++++ .../web/server/repository/ProjectRepository.kt | 157 +++++++++++ .../web/server/repository/ScenarioRepository.kt | 90 ++++++ .../web/server/repository/TopologyRepository.kt | 86 ++++++ .../web/server/repository/TraceRepository.kt | 53 ++++ .../opendc/web/server/rest/SchedulerResource.kt | 48 ++++ .../org/opendc/web/server/rest/TraceResource.kt | 51 ++++ .../server/rest/error/GenericExceptionMapper.kt | 45 +++ .../error/MissingKotlinParameterExceptionMapper.kt | 43 +++ .../opendc/web/server/rest/runner/JobResource.kt | 72 +++++ .../web/server/rest/user/PortfolioResource.kt | 77 ++++++ .../server/rest/user/PortfolioScenarioResource.kt | 59 ++++ .../opendc/web/server/rest/user/ProjectResource.kt | 82 ++++++ .../web/server/rest/user/ScenarioResource.kt | 60 ++++ .../web/server/rest/user/TopologyResource.kt | 88 ++++++ .../org/opendc/web/server/service/JobService.kt | 81 ++++++ .../opendc/web/server/service/PortfolioService.kt | 104 +++++++ .../opendc/web/server/service/ProjectService.kt | 86 ++++++ .../opendc/web/server/service/RunnerConversions.kt | 69 +++++ .../opendc/web/server/service/ScenarioService.kt | 128 +++++++++ .../opendc/web/server/service/TopologyService.kt | 127 +++++++++ .../org/opendc/web/server/service/TraceService.kt | 48 ++++ .../opendc/web/server/service/UserConversions.kt | 120 ++++++++ .../kotlin/org/opendc/web/server/service/Utils.kt | 40 +++ .../web/server/util/DevSecurityOverrideFilter.kt | 51 ++++ .../web/server/util/KotlinModuleCustomizer.kt | 38 +++ .../json/AbstractJsonSqlTypeDescriptor.kt | 74 +++++ .../hibernate/json/JsonBinarySqlTypeDescriptor.kt | 26 ++ .../hibernate/json/JsonBytesSqlTypeDescriptor.kt | 83 ++++++ .../util/hibernate/json/JsonSqlTypeDescriptor.kt | 107 ++++++++ .../hibernate/json/JsonStringSqlTypeDescriptor.kt | 38 +++ .../web/server/util/hibernate/json/JsonType.kt | 48 ++++ .../util/hibernate/json/JsonTypeDescriptor.kt | 149 ++++++++++ .../src/main/resources/META-INF/branding/logo.png | Bin 0 -> 2825 bytes .../src/main/resources/application-dev.properties | 37 +++ .../main/resources/application-docker.properties | 50 ++++ .../src/main/resources/application-prod.properties | 38 +++ .../src/main/resources/application-test.properties | 37 +++ .../src/main/resources/application.properties | 44 +++ .../src/main/resources/import.sql | 3 + .../web/server/rest/SchedulerResourceTest.kt | 48 ++++ .../opendc/web/server/rest/TraceResourceTest.kt | 100 +++++++ .../web/server/rest/runner/JobResourceTest.kt | 200 ++++++++++++++ .../web/server/rest/user/PortfolioResourceTest.kt | 265 ++++++++++++++++++ .../rest/user/PortfolioScenarioResourceTest.kt | 213 +++++++++++++++ .../web/server/rest/user/ProjectResourceTest.kt | 240 ++++++++++++++++ .../web/server/rest/user/ScenarioResourceTest.kt | 178 ++++++++++++ .../web/server/rest/user/TopologyResourceTest.kt | 304 +++++++++++++++++++++ settings.gradle.kts | 2 +- 128 files changed, 5223 insertions(+), 5206 deletions(-) delete mode 100644 opendc-web/opendc-web-api/Dockerfile delete mode 100644 opendc-web/opendc-web-api/build.gradle.kts delete mode 100644 opendc-web/opendc-web-api/config/application.properties delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/OpenDCApplication.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Job.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Portfolio.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Project.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/ProjectAuthorization.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/ProjectAuthorizationKey.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Scenario.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Topology.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Trace.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Workload.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/repository/JobRepository.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/repository/PortfolioRepository.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/repository/ProjectRepository.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/repository/ScenarioRepository.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/repository/TopologyRepository.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/repository/TraceRepository.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/SchedulerResource.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/TraceResource.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/error/GenericExceptionMapper.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/error/MissingKotlinParameterExceptionMapper.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/runner/JobResource.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/user/PortfolioResource.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/user/PortfolioScenarioResource.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/user/ProjectResource.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/user/ScenarioResource.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/user/TopologyResource.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/JobService.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/PortfolioService.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/ProjectService.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/RunnerConversions.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/ScenarioService.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/TopologyService.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/TraceService.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/UserConversions.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/Utils.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/DevSecurityOverrideFilter.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/KotlinModuleCustomizer.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/AbstractJsonSqlTypeDescriptor.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/JsonBinarySqlTypeDescriptor.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/JsonBytesSqlTypeDescriptor.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/JsonSqlTypeDescriptor.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/JsonStringSqlTypeDescriptor.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/JsonType.kt delete mode 100644 opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/JsonTypeDescriptor.kt delete mode 100644 opendc-web/opendc-web-api/src/main/resources/META-INF/branding/logo.png delete mode 100644 opendc-web/opendc-web-api/src/main/resources/application-dev.properties delete mode 100644 opendc-web/opendc-web-api/src/main/resources/application-prod.properties delete mode 100644 opendc-web/opendc-web-api/src/main/resources/application-test.properties delete mode 100644 opendc-web/opendc-web-api/src/main/resources/application.properties delete mode 100644 opendc-web/opendc-web-api/src/main/resources/init-dev.sql delete mode 100644 opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/SchedulerResourceTest.kt delete mode 100644 opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/TraceResourceTest.kt delete mode 100644 opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/runner/JobResourceTest.kt delete mode 100644 opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/PortfolioResourceTest.kt delete mode 100644 opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/PortfolioScenarioResourceTest.kt delete mode 100644 opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/ProjectResourceTest.kt delete mode 100644 opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/ScenarioResourceTest.kt delete mode 100644 opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/TopologyResourceTest.kt create mode 100644 opendc-web/opendc-web-server/Dockerfile create mode 100644 opendc-web/opendc-web-server/build.gradle.kts create mode 100644 opendc-web/opendc-web-server/config/application.properties create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/OpenDCApplication.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Job.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Portfolio.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Project.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/ProjectAuthorization.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/ProjectAuthorizationKey.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Scenario.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Topology.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Trace.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Workload.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/repository/JobRepository.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/repository/PortfolioRepository.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/repository/ProjectRepository.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/repository/ScenarioRepository.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/repository/TopologyRepository.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/repository/TraceRepository.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/SchedulerResource.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/TraceResource.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/error/GenericExceptionMapper.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/error/MissingKotlinParameterExceptionMapper.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/runner/JobResource.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/PortfolioResource.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/PortfolioScenarioResource.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/ProjectResource.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/ScenarioResource.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/TopologyResource.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/JobService.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/PortfolioService.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/ProjectService.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/RunnerConversions.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/ScenarioService.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/TopologyService.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/TraceService.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/UserConversions.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/Utils.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/DevSecurityOverrideFilter.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/KotlinModuleCustomizer.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/AbstractJsonSqlTypeDescriptor.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/JsonBinarySqlTypeDescriptor.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/JsonBytesSqlTypeDescriptor.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/JsonSqlTypeDescriptor.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/JsonStringSqlTypeDescriptor.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/JsonType.kt create mode 100644 opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/JsonTypeDescriptor.kt create mode 100644 opendc-web/opendc-web-server/src/main/resources/META-INF/branding/logo.png create mode 100644 opendc-web/opendc-web-server/src/main/resources/application-dev.properties create mode 100644 opendc-web/opendc-web-server/src/main/resources/application-docker.properties create mode 100644 opendc-web/opendc-web-server/src/main/resources/application-prod.properties create mode 100644 opendc-web/opendc-web-server/src/main/resources/application-test.properties create mode 100644 opendc-web/opendc-web-server/src/main/resources/application.properties create mode 100644 opendc-web/opendc-web-server/src/main/resources/import.sql create mode 100644 opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/SchedulerResourceTest.kt create mode 100644 opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/TraceResourceTest.kt create mode 100644 opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/runner/JobResourceTest.kt create mode 100644 opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/PortfolioResourceTest.kt create mode 100644 opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/PortfolioScenarioResourceTest.kt create mode 100644 opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/ProjectResourceTest.kt create mode 100644 opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/ScenarioResourceTest.kt create mode 100644 opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/TopologyResourceTest.kt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3b6b11d2..3f6ca4dd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -66,7 +66,7 @@ jobs: uses: docker/build-push-action@v3 with: file: opendc-web/opendc-web-runner/Dockerfile - - name: Build API + - name: Build Web Server uses: docker/build-push-action@v3 with: - file: opendc-web/opendc-web-api/Dockerfile + file: opendc-web/opendc-web-server/Dockerfile diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 314adcb1..86aff681 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -2,26 +2,19 @@ version: "3.8" # Docker Compose overrides for development environments services: - frontend: + ui: build: opendc-web/opendc-web-ui ports: - "8080:3000" environment: NEXT_PUBLIC_API_BASE_URL: http://localhost:8081 - api: + server: build: context: . - dockerfile: opendc-web/opendc-web-api/Dockerfile + dockerfile: opendc-web/opendc-web-server/Dockerfile ports: - - "8081:80" - environment: - SENTRY_ENVIRONMENT: "development" - - runner: - build: - context: . - dockerfile: opendc-web/opendc-web-runner/Dockerfile + - "8081:8080" environment: SENTRY_ENVIRONMENT: "development" diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 1206ff9c..58d5ce55 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -2,18 +2,14 @@ version: "3.8" # Docker Compose overrides for production environments services: - frontend: + ui: ports: - "8080:3000" environment: NEXT_PUBLIC_API_BASE_URL: ${OPENDC_API_BASE_URL} - api: + server: ports: - - "8081:80" - environment: - SENTRY_ENVIRONMENT: "production" - - runner: + - "8081:8080" environment: SENTRY_ENVIRONMENT: "production" diff --git a/docker-compose.yml b/docker-compose.yml index faaecc03..a6d6ce1d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,51 +1,39 @@ version: "3.8" services: - frontend: - image: atlargeresearch/opendc-web-ui:v2.1 + ui: + image: atlargeresearch/opendc-ui:v2.1 restart: on-failure networks: - backend depends_on: - - api + - server environment: NEXT_PUBLIC_AUTH0_DOMAIN: ${OPENDC_AUTH0_DOMAIN} NEXT_PUBLIC_AUTH0_CLIENT_ID: ${OPENDC_AUTH0_CLIENT_ID} NEXT_PUBLIC_AUTH0_AUDIENCE: ${OPENDC_AUTH0_AUDIENCE} - NEXT_PUBLIC_SENTRY_DSN: ${OPENDC_FRONTEND_SENTRY_DSN-} + NEXT_PUBLIC_SENTRY_DSN: ${OPENDC_UI_SENTRY_DSN-} - api: - image: atlargeresearch/opendc-web-api:v2.1 + server: + image: atlargeresearch/opendc:v2.1 restart: on-failure networks: - backend depends_on: - postgres + volumes: + - type: bind + source: ./traces + target: /opt/opendc/traces environment: OPENDC_DB_USERNAME: ${OPENDC_DB_USERNAME:?No database username specified} OPENDC_DB_PASSWORD: ${OPENDC_DB_PASSWORD:?No database password specified} OPENDC_DB_URL: jdbc:postgresql://postgres:5432/opendc OPENDC_AUTH0_DOMAIN: ${OPENDC_AUTH0_DOMAIN:?No Auth0 domain specified} OPENDC_AUTH0_AUDIENCE: ${OPENDC_AUTH0_AUDIENCE:?No Auth0 audience specified} - SENTRY_DSN: ${OPENDC_API_SENTRY_DSN-} - - runner: - image: atlargeresearch/opendc:v2.1 - restart: on-failure - networks: - - backend - depends_on: - - api - volumes: - - type: bind - source: ./traces - target: /opt/opendc/traces - environment: OPENDC_API_URL: ${OPENDC_API_BASE_URL:-http://web:8080} - AUTH0_DOMAIN: ${OPENDC_AUTH0_DOMAIN:?No Auth0 domain specified} - AUTH0_AUDIENCE: ${OPENDC_AUTH0_AUDIENCE:?No Auth0 audience specified} - AUTH0_CLIENT_ID: ${OPENDC_AUTH0_CLIENT_ID_RUNNER:?No client id for runner} - AUTH0_CLIENT_SECRET: ${OPENDC_AUTH0_CLIENT_SECRET_RUNNER:?No client secret for runner} - SENTRY_DSN: ${OPENDC_SIMULATOR_SENTRY_DSN-} + OPENDC_AUTH0_CLIENT_ID_RUNNER: ${OPENDC_AUTH0_CLIENT_ID_RUNNER:?No client id for runner} + OPENDC_AUTH0_CLIENT_SECRET_RUNNER: ${OPENDC_AUTH0_CLIENT_SECRET_RUNNER:?No client secret for runner} + SENTRY_DSN: ${OPENDC_SERVER_SENTRY_DSN-} postgres: image: postgres diff --git a/opendc-web/opendc-web-api/Dockerfile b/opendc-web/opendc-web-api/Dockerfile deleted file mode 100644 index ff300170..00000000 --- a/opendc-web/opendc-web-api/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM openjdk:17-slim -MAINTAINER OpenDC Maintainers - -# Obtain (cache) Gradle wrapper -COPY gradlew /app/ -COPY gradle /app/gradle -WORKDIR /app -RUN ./gradlew --version - -# Build project -COPY ./ /app/ -RUN ./gradlew --no-daemon :opendc-web:opendc-web-api:build - -FROM openjdk:17-slim -COPY --from=0 /app/opendc-web/opendc-web-api/build/quarkus-app /opt/opendc -WORKDIR /opt/opendc -CMD java -jar quarkus-run.jar diff --git a/opendc-web/opendc-web-api/build.gradle.kts b/opendc-web/opendc-web-api/build.gradle.kts deleted file mode 100644 index 89b8273c..00000000 --- a/opendc-web/opendc-web-api/build.gradle.kts +++ /dev/null @@ -1,88 +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. - */ - -description = "REST API for the OpenDC website" - -/* Build configuration */ -plugins { - `quarkus-conventions` - distribution -} - -dependencies { - implementation(enforcedPlatform(libs.quarkus.bom)) - - implementation(projects.opendcWeb.opendcWebProto) - compileOnly(projects.opendcWeb.opendcWebUiQuarkusDeployment) /* Temporary fix for Quarkus/Gradle issues */ - compileOnly(projects.opendcWeb.opendcWebRunnerQuarkusDeployment) - implementation(projects.opendcWeb.opendcWebUiQuarkus) - implementation(projects.opendcWeb.opendcWebRunnerQuarkus) - - implementation(libs.quarkus.kotlin) - implementation(libs.quarkus.resteasy.core) - implementation(libs.quarkus.resteasy.jackson) - implementation(libs.jackson.module.kotlin) - implementation(libs.quarkus.smallrye.openapi) - - implementation(libs.quarkus.security) - implementation(libs.quarkus.oidc) - - implementation(libs.quarkus.hibernate.orm) - implementation(libs.quarkus.hibernate.validator) - implementation(libs.quarkus.jdbc.postgresql) - quarkusDev(libs.quarkus.jdbc.h2) - - testImplementation(libs.quarkus.junit5.core) - testImplementation(libs.quarkus.junit5.mockk) - testImplementation(libs.quarkus.jacoco) - testImplementation(libs.restassured.core) - testImplementation(libs.restassured.kotlin) - testImplementation(libs.quarkus.test.security) - testImplementation(libs.quarkus.jdbc.h2) -} - -val createStartScripts by tasks.creating(CreateStartScripts::class) { - applicationName = "opendc-server" - mainClass.set("io.quarkus.bootstrap.runner.QuarkusEntryPoint") - classpath = files("lib/quarkus-run.jar") - outputDir = project.buildDir.resolve("scripts") -} - -distributions { - create("server") { - distributionBaseName.set("opendc-server") - - contents { - from("../../LICENSE.txt") - from("config") { - into("config") - } - - from(createStartScripts) { - into("bin") - } - from(tasks.quarkusBuild) { - into("lib") - } - } - } -} diff --git a/opendc-web/opendc-web-api/config/application.properties b/opendc-web/opendc-web-api/config/application.properties deleted file mode 100644 index 30eaaef9..00000000 --- a/opendc-web/opendc-web-api/config/application.properties +++ /dev/null @@ -1 +0,0 @@ -# Custom server properties diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/OpenDCApplication.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/OpenDCApplication.kt deleted file mode 100644 index ddbd5390..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/OpenDCApplication.kt +++ /dev/null @@ -1,30 +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.web.api - -import javax.ws.rs.core.Application - -/** - * [Application] definition for the OpenDC web API. - */ -class OpenDCApplication : Application() diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Job.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Job.kt deleted file mode 100644 index b09b46a1..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Job.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.model - -import org.hibernate.annotations.Type -import org.hibernate.annotations.TypeDef -import org.opendc.web.api.util.hibernate.json.JsonType -import org.opendc.web.proto.JobState -import java.time.Instant -import javax.persistence.* - -/** - * A simulation job to be run by the simulator. - */ -@TypeDef(name = "json", typeClass = JsonType::class) -@Entity -@Table(name = "jobs") -@NamedQueries( - value = [ - NamedQuery( - name = "Job.findAll", - query = "SELECT j FROM Job j WHERE j.state = :state" - ), - NamedQuery( - name = "Job.updateOne", - query = """ - UPDATE Job j - SET j.state = :newState, j.updatedAt = :updatedAt, j.results = :results - WHERE j.id = :id AND j.state = :oldState - """ - ) - ] -) -class Job( - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - val id: Long, - - @OneToOne(optional = false, mappedBy = "job", fetch = FetchType.EAGER) - @JoinColumn(name = "scenario_id", nullable = false) - val scenario: Scenario, - - @Column(name = "created_at", nullable = false, updatable = false) - val createdAt: Instant, - - /** - * The number of simulation runs to perform. - */ - @Column(nullable = false, updatable = false) - val repeats: Int -) { - /** - * The instant at which the job was updated. - */ - @Column(name = "updated_at", nullable = false) - var updatedAt: Instant = createdAt - - /** - * The state of the job. - */ - @Column(nullable = false) - var state: JobState = JobState.PENDING - - /** - * Experiment results in JSON - */ - @Type(type = "json") - @Column(columnDefinition = "jsonb") - var results: Map? = null - - /** - * Return a string representation of this job. - */ - override fun toString(): String = "Job[id=$id,scenario=${scenario.id},state=$state]" -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Portfolio.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Portfolio.kt deleted file mode 100644 index c8b94daf..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Portfolio.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.model - -import org.hibernate.annotations.Type -import org.hibernate.annotations.TypeDef -import org.opendc.web.api.util.hibernate.json.JsonType -import org.opendc.web.proto.Targets -import javax.persistence.* - -/** - * A portfolio is the composition of multiple scenarios. - */ -@TypeDef(name = "json", typeClass = JsonType::class) -@Entity -@Table( - name = "portfolios", - uniqueConstraints = [UniqueConstraint(columnNames = ["project_id", "number"])], - indexes = [Index(name = "fn_portfolios_number", columnList = "project_id, number")] -) -@NamedQueries( - value = [ - NamedQuery( - name = "Portfolio.findAll", - query = "SELECT p FROM Portfolio p WHERE p.project.id = :projectId" - ), - NamedQuery( - name = "Portfolio.findOne", - query = "SELECT p FROM Portfolio p WHERE p.project.id = :projectId AND p.number = :number" - ) - ] -) -class Portfolio( - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - val id: Long, - - /** - * Unique number of the portfolio for the project. - */ - @Column(nullable = false) - val number: Int, - - @Column(nullable = false) - val name: String, - - @ManyToOne(optional = false) - @JoinColumn(name = "project_id", nullable = false) - val project: Project, - - /** - * The portfolio targets (metrics, repetitions). - */ - @Type(type = "json") - @Column(columnDefinition = "jsonb", nullable = false, updatable = false) - val targets: Targets, -) { - /** - * The scenarios in this portfolio. - */ - @OneToMany(cascade = [CascadeType.ALL], mappedBy = "portfolio", orphanRemoval = true) - @OrderBy("id ASC") - val scenarios: MutableSet = mutableSetOf() - - /** - * Return a string representation of this portfolio. - */ - override fun toString(): String = "Job[id=$id,name=$name,project=${project.id},targets=$targets]" -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Project.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Project.kt deleted file mode 100644 index e0440bf4..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Project.kt +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.model - -import java.time.Instant -import javax.persistence.* - -/** - * A project in OpenDC encapsulates all the datacenter designs and simulation runs for a set of users. - */ -@Entity -@Table(name = "projects") -@NamedQueries( - value = [ - NamedQuery( - name = "Project.findAll", - query = """ - SELECT a - FROM ProjectAuthorization a - WHERE a.key.userId = :userId - """ - ), - NamedQuery( - name = "Project.allocatePortfolio", - query = """ - UPDATE Project p - SET p.portfoliosCreated = :oldState + 1, p.updatedAt = :now - WHERE p.id = :id AND p.portfoliosCreated = :oldState - """ - ), - NamedQuery( - name = "Project.allocateTopology", - query = """ - UPDATE Project p - SET p.topologiesCreated = :oldState + 1, p.updatedAt = :now - WHERE p.id = :id AND p.topologiesCreated = :oldState - """ - ), - NamedQuery( - name = "Project.allocateScenario", - query = """ - UPDATE Project p - SET p.scenariosCreated = :oldState + 1, p.updatedAt = :now - WHERE p.id = :id AND p.scenariosCreated = :oldState - """ - ) - ] -) -class Project( - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - val id: Long, - - @Column(nullable = false) - var name: String, - - @Column(name = "created_at", nullable = false, updatable = false) - val createdAt: Instant, -) { - /** - * The instant at which the project was updated. - */ - @Column(name = "updated_at", nullable = false) - var updatedAt: Instant = createdAt - - /** - * The portfolios belonging to this project. - */ - @OneToMany(cascade = [CascadeType.ALL], mappedBy = "project", orphanRemoval = true) - @OrderBy("id ASC") - val portfolios: MutableSet = mutableSetOf() - - /** - * The number of portfolios created for this project (including deleted portfolios). - */ - @Column(name = "portfolios_created", nullable = false) - var portfoliosCreated: Int = 0 - - /** - * The topologies belonging to this project. - */ - @OneToMany(cascade = [CascadeType.ALL], mappedBy = "project", orphanRemoval = true) - @OrderBy("id ASC") - val topologies: MutableSet = mutableSetOf() - - /** - * The number of topologies created for this project (including deleted topologies). - */ - @Column(name = "topologies_created", nullable = false) - var topologiesCreated: Int = 0 - - /** - * The scenarios belonging to this project. - */ - @OneToMany(mappedBy = "project", orphanRemoval = true) - val scenarios: MutableSet = mutableSetOf() - - /** - * The number of scenarios created for this project (including deleted scenarios). - */ - @Column(name = "scenarios_created", nullable = false) - var scenariosCreated: Int = 0 - - /** - * The users authorized to access the project. - */ - @OneToMany(cascade = [CascadeType.ALL], mappedBy = "project", orphanRemoval = true) - val authorizations: MutableSet = mutableSetOf() - - /** - * Return a string representation of this project. - */ - override fun toString(): String = "Project[id=$id,name=$name]" -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/ProjectAuthorization.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/ProjectAuthorization.kt deleted file mode 100644 index a72ff06a..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/ProjectAuthorization.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.model - -import org.opendc.web.proto.user.ProjectRole -import javax.persistence.* - -/** - * An authorization for some user to participate in a project. - */ -@Entity -@Table(name = "project_authorizations") -class ProjectAuthorization( - /** - * The user identifier of the authorization. - */ - @EmbeddedId - val key: ProjectAuthorizationKey, - - /** - * The project that the user is authorized to participate in. - */ - @ManyToOne(optional = false) - @MapsId("projectId") - @JoinColumn(name = "project_id", updatable = false, insertable = false, nullable = false) - val project: Project, - - /** - * The role of the user in the project. - */ - @Column(nullable = false) - val role: ProjectRole -) { - /** - * Return a string representation of this project authorization. - */ - override fun toString(): String = "ProjectAuthorization[project=${key.projectId},user=${key.userId},role=$role]" -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/ProjectAuthorizationKey.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/ProjectAuthorizationKey.kt deleted file mode 100644 index b5f66e70..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/ProjectAuthorizationKey.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.model - -import javax.persistence.Column -import javax.persistence.Embeddable - -/** - * Key for representing a [ProjectAuthorization] object. - */ -@Embeddable -data class ProjectAuthorizationKey( - @Column(name = "user_id", nullable = false) - val userId: String, - - @Column(name = "project_id", nullable = false) - val projectId: Long -) : java.io.Serializable diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Scenario.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Scenario.kt deleted file mode 100644 index 5c9cb259..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Scenario.kt +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.model - -import org.hibernate.annotations.Type -import org.hibernate.annotations.TypeDef -import org.opendc.web.api.util.hibernate.json.JsonType -import org.opendc.web.proto.OperationalPhenomena -import javax.persistence.* - -/** - * A single scenario to be explored by the simulator. - */ -@TypeDef(name = "json", typeClass = JsonType::class) -@Entity -@Table( - name = "scenarios", - uniqueConstraints = [UniqueConstraint(columnNames = ["project_id", "number"])], - indexes = [Index(name = "fn_scenarios_number", columnList = "project_id, number")] -) -@NamedQueries( - value = [ - NamedQuery( - name = "Scenario.findAll", - query = "SELECT s FROM Scenario s WHERE s.project.id = :projectId" - ), - NamedQuery( - name = "Scenario.findAllForPortfolio", - query = """ - SELECT s - FROM Scenario s - JOIN Portfolio p ON p.id = s.portfolio.id AND p.number = :number - WHERE s.project.id = :projectId - """ - ), - NamedQuery( - name = "Scenario.findOne", - query = "SELECT s FROM Scenario s WHERE s.project.id = :projectId AND s.number = :number" - ) - ] -) -class Scenario( - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - val id: Long, - - /** - * Unique number of the scenario for the project. - */ - @Column(nullable = false) - val number: Int, - - @Column(nullable = false, updatable = false) - val name: String, - - @ManyToOne(optional = false) - @JoinColumn(name = "project_id", nullable = false) - val project: Project, - - @ManyToOne(optional = false) - @JoinColumn(name = "portfolio_id", nullable = false) - val portfolio: Portfolio, - - @Embedded - val workload: Workload, - - @ManyToOne(optional = false) - val topology: Topology, - - @Type(type = "json") - @Column(columnDefinition = "jsonb", nullable = false, updatable = false) - val phenomena: OperationalPhenomena, - - @Column(name = "scheduler_name", nullable = false, updatable = false) - val schedulerName: String, -) { - /** - * The [Job] associated with the scenario. - */ - @OneToOne(cascade = [CascadeType.ALL]) - lateinit var job: Job - - /** - * Return a string representation of this scenario. - */ - override fun toString(): String = "Scenario[id=$id,name=$name,project=${project.id},portfolio=${portfolio.id}]" -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Topology.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Topology.kt deleted file mode 100644 index 9b64e382..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Topology.kt +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.model - -import org.hibernate.annotations.Type -import org.hibernate.annotations.TypeDef -import org.opendc.web.api.util.hibernate.json.JsonType -import org.opendc.web.proto.Room -import java.time.Instant -import javax.persistence.* - -/** - * A datacenter design in OpenDC. - */ -@TypeDef(name = "json", typeClass = JsonType::class) -@Entity -@Table( - name = "topologies", - uniqueConstraints = [UniqueConstraint(columnNames = ["project_id", "number"])], - indexes = [Index(name = "fn_topologies_number", columnList = "project_id, number")] -) -@NamedQueries( - value = [ - NamedQuery( - name = "Topology.findAll", - query = "SELECT t FROM Topology t WHERE t.project.id = :projectId" - ), - NamedQuery( - name = "Topology.findOne", - query = "SELECT t FROM Topology t WHERE t.project.id = :projectId AND t.number = :number" - ) - ] -) -class Topology( - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - val id: Long, - - /** - * Unique number of the topology for the project. - */ - @Column(nullable = false) - val number: Int, - - @Column(nullable = false) - val name: String, - - @ManyToOne(optional = false) - @JoinColumn(name = "project_id", nullable = false) - val project: Project, - - @Column(name = "created_at", nullable = false, updatable = false) - val createdAt: Instant, - - /** - * Datacenter design in JSON - */ - @Type(type = "json") - @Column(columnDefinition = "jsonb", nullable = false) - var rooms: List = emptyList() -) { - /** - * The instant at which the topology was updated. - */ - @Column(name = "updated_at", nullable = false) - var updatedAt: Instant = createdAt - - /** - * Return a string representation of this topology. - */ - override fun toString(): String = "Topology[id=$id,name=$name,project=${project.id}]" -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Trace.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Trace.kt deleted file mode 100644 index 2e2d71f8..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Trace.kt +++ /dev/null @@ -1,58 +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.web.api.model - -import javax.persistence.* - -/** - * A workload trace available for simulation. - * - * @param id The unique identifier of the trace. - * @param name The name of the trace. - * @param type The type of trace. - */ -@Entity -@Table(name = "traces") -@NamedQueries( - value = [ - NamedQuery( - name = "Trace.findAll", - query = "SELECT t FROM Trace t" - ), - ] -) -class Trace( - @Id - val id: String, - - @Column(nullable = false, updatable = false) - val name: String, - - @Column(nullable = false, updatable = false) - val type: String, -) { - /** - * Return a string representation of this trace. - */ - override fun toString(): String = "Trace[id=$id,name=$name,type=$type]" -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Workload.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Workload.kt deleted file mode 100644 index 07fc096b..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Workload.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.model - -import javax.persistence.Column -import javax.persistence.Embeddable -import javax.persistence.ManyToOne - -/** - * Specification of the workload for a [Scenario]. - */ -@Embeddable -class Workload( - @ManyToOne(optional = false) - val trace: Trace, - - @Column(name = "sampling_fraction", nullable = false, updatable = false) - val samplingFraction: Double -) diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/repository/JobRepository.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/repository/JobRepository.kt deleted file mode 100644 index 558d7c38..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/repository/JobRepository.kt +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.repository - -import org.opendc.web.api.model.Job -import org.opendc.web.proto.JobState -import java.time.Instant -import javax.enterprise.context.ApplicationScoped -import javax.inject.Inject -import javax.persistence.EntityManager - -/** - * A repository to manage [Job] entities. - */ -@ApplicationScoped -class JobRepository @Inject constructor(private val em: EntityManager) { - /** - * Find all jobs currently residing in [state]. - * - * @param state The state in which the jobs should be. - * @return The list of jobs in state [state]. - */ - fun findAll(state: JobState): List { - return em.createNamedQuery("Job.findAll", Job::class.java) - .setParameter("state", state) - .resultList - } - - /** - * Find the [Job] with the specified [id]. - * - * @param id The unique identifier of the job. - * @return The trace or `null` if it does not exist. - */ - fun findOne(id: Long): Job? { - return em.find(Job::class.java, id) - } - - /** - * Delete the specified [job]. - */ - fun delete(job: Job) { - em.remove(job) - } - - /** - * Save the specified [job] to the database. - */ - fun save(job: Job) { - em.persist(job) - } - - /** - * Atomically update the specified [job]. - * - * @param job The job to update atomically. - * @param newState The new state to enter into. - * @param time The time at which the update occurs. - * @param results The results to possible set. - * @return `true` when the update succeeded`, `false` when there was a conflict. - */ - fun updateOne(job: Job, newState: JobState, time: Instant, results: Map?): Boolean { - val count = em.createNamedQuery("Job.updateOne") - .setParameter("id", job.id) - .setParameter("oldState", job.state) - .setParameter("newState", newState) - .setParameter("updatedAt", Instant.now()) - .setParameter("results", results) - .executeUpdate() - em.refresh(job) - return count > 0 - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/repository/PortfolioRepository.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/repository/PortfolioRepository.kt deleted file mode 100644 index 34b3598c..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/repository/PortfolioRepository.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.repository - -import org.opendc.web.api.model.Portfolio -import javax.enterprise.context.ApplicationScoped -import javax.inject.Inject -import javax.persistence.EntityManager - -/** - * A repository to manage [Portfolio] entities. - */ -@ApplicationScoped -class PortfolioRepository @Inject constructor(private val em: EntityManager) { - /** - * Find all [Portfolio]s that belong to [project][projectId]. - * - * @param projectId The unique identifier of the project. - * @return The list of portfolios that belong to the specified project. - */ - fun findAll(projectId: Long): List { - return em.createNamedQuery("Portfolio.findAll", Portfolio::class.java) - .setParameter("projectId", projectId) - .resultList - } - - /** - * Find the [Portfolio] with the specified [number] belonging to [project][projectId]. - * - * @param projectId The unique identifier of the project. - * @param number The number of the portfolio. - * @return The portfolio or `null` if it does not exist. - */ - fun findOne(projectId: Long, number: Int): Portfolio? { - return em.createNamedQuery("Portfolio.findOne", Portfolio::class.java) - .setParameter("projectId", projectId) - .setParameter("number", number) - .setMaxResults(1) - .resultList - .firstOrNull() - } - - /** - * Delete the specified [portfolio]. - */ - fun delete(portfolio: Portfolio) { - em.remove(portfolio) - } - - /** - * Save the specified [portfolio] to the database. - */ - fun save(portfolio: Portfolio) { - em.persist(portfolio) - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/repository/ProjectRepository.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/repository/ProjectRepository.kt deleted file mode 100644 index 6529f778..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/repository/ProjectRepository.kt +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.repository - -import org.opendc.web.api.model.Project -import org.opendc.web.api.model.ProjectAuthorization -import org.opendc.web.api.model.ProjectAuthorizationKey -import java.time.Instant -import javax.enterprise.context.ApplicationScoped -import javax.inject.Inject -import javax.persistence.EntityManager - -/** - * A repository to manage [Project] entities. - */ -@ApplicationScoped -class ProjectRepository @Inject constructor(private val em: EntityManager) { - /** - * List all projects for the user with the specified [userId]. - * - * @param userId The identifier of the user that is requesting the list of projects. - * @return A list of projects that the user has received authorization for. - */ - fun findAll(userId: String): List { - return em.createNamedQuery("Project.findAll", ProjectAuthorization::class.java) - .setParameter("userId", userId) - .resultList - } - - /** - * Find the project with [id] for the user with the specified [userId]. - * - * @param userId The identifier of the user that is requesting the list of projects. - * @param id The unique identifier of the project. - * @return The project with the specified identifier or `null` if it does not exist or is not accessible to the - * user with the specified identifier. - */ - fun findOne(userId: String, id: Long): ProjectAuthorization? { - return em.find(ProjectAuthorization::class.java, ProjectAuthorizationKey(userId, id)) - } - - /** - * Delete the specified [project]. - */ - fun delete(project: Project) { - em.remove(project) - } - - /** - * Save the specified [project] to the database. - */ - fun save(project: Project) { - em.persist(project) - } - - /** - * Save the specified [auth] to the database. - */ - fun save(auth: ProjectAuthorization) { - em.persist(auth) - } - - /** - * Allocate the next portfolio number for the specified [project]. - * - * @param project The project to allocate the portfolio number for. - * @param time The time at which the new portfolio is created. - * @param tries The number of times to try to allocate the number before failing. - */ - fun allocatePortfolio(project: Project, time: Instant, tries: Int = 4): Int { - repeat(tries) { - val count = em.createNamedQuery("Project.allocatePortfolio") - .setParameter("id", project.id) - .setParameter("oldState", project.portfoliosCreated) - .setParameter("now", time) - .executeUpdate() - - if (count > 0) { - return project.portfoliosCreated + 1 - } else { - em.refresh(project) - } - } - - throw IllegalStateException("Failed to allocate next portfolio") - } - - /** - * Allocate the next topology number for the specified [project]. - * - * @param project The project to allocate the topology number for. - * @param time The time at which the new topology is created. - * @param tries The number of times to try to allocate the number before failing. - */ - fun allocateTopology(project: Project, time: Instant, tries: Int = 4): Int { - repeat(tries) { - val count = em.createNamedQuery("Project.allocateTopology") - .setParameter("id", project.id) - .setParameter("oldState", project.topologiesCreated) - .setParameter("now", time) - .executeUpdate() - - if (count > 0) { - return project.topologiesCreated + 1 - } else { - em.refresh(project) - } - } - - throw IllegalStateException("Failed to allocate next topology") - } - - /** - * Allocate the next scenario number for the specified [project]. - * - * @param project The project to allocate the scenario number for. - * @param time The time at which the new scenario is created. - * @param tries The number of times to try to allocate the number before failing. - */ - fun allocateScenario(project: Project, time: Instant, tries: Int = 4): Int { - repeat(tries) { - val count = em.createNamedQuery("Project.allocateScenario") - .setParameter("id", project.id) - .setParameter("oldState", project.scenariosCreated) - .setParameter("now", time) - .executeUpdate() - - if (count > 0) { - return project.scenariosCreated + 1 - } else { - em.refresh(project) - } - } - - throw IllegalStateException("Failed to allocate next scenario") - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/repository/ScenarioRepository.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/repository/ScenarioRepository.kt deleted file mode 100644 index de116ad6..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/repository/ScenarioRepository.kt +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.repository - -import org.opendc.web.api.model.Scenario -import javax.enterprise.context.ApplicationScoped -import javax.inject.Inject -import javax.persistence.EntityManager - -/** - * A repository to manage [Scenario] entities. - */ -@ApplicationScoped -class ScenarioRepository @Inject constructor(private val em: EntityManager) { - /** - * Find all [Scenario]s that belong to [project][projectId]. - * - * @param projectId The unique identifier of the project. - * @return The list of scenarios that belong to the specified project. - */ - fun findAll(projectId: Long): List { - return em.createNamedQuery("Scenario.findAll", Scenario::class.java) - .setParameter("projectId", projectId) - .resultList - } - - /** - * Find all [Scenario]s that belong to [portfolio][number] of [project][projectId]. - * - * @param projectId The unique identifier of the project. - * @param number The number of the portfolio to which the scenarios should belong. - * @return The list of scenarios that belong to the specified portfolio. - */ - fun findAll(projectId: Long, number: Int): List { - return em.createNamedQuery("Scenario.findAllForPortfolio", Scenario::class.java) - .setParameter("projectId", projectId) - .setParameter("number", number) - .resultList - } - - /** - * Find the [Scenario] with the specified [number] belonging to [project][projectId]. - * - * @param projectId The unique identifier of the project. - * @param number The number of the scenario. - * @return The scenario or `null` if it does not exist. - */ - fun findOne(projectId: Long, number: Int): Scenario? { - return em.createNamedQuery("Scenario.findOne", Scenario::class.java) - .setParameter("projectId", projectId) - .setParameter("number", number) - .setMaxResults(1) - .resultList - .firstOrNull() - } - - /** - * Delete the specified [scenario]. - */ - fun delete(scenario: Scenario) { - em.remove(scenario) - } - - /** - * Save the specified [scenario] to the database. - */ - fun save(scenario: Scenario) { - em.persist(scenario) - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/repository/TopologyRepository.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/repository/TopologyRepository.kt deleted file mode 100644 index cd8f666e..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/repository/TopologyRepository.kt +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.repository - -import org.opendc.web.api.model.Topology -import javax.enterprise.context.ApplicationScoped -import javax.inject.Inject -import javax.persistence.EntityManager - -/** - * A repository to manage [Topology] entities. - */ -@ApplicationScoped -class TopologyRepository @Inject constructor(private val em: EntityManager) { - /** - * Find all [Topology]s that belong to [project][projectId]. - * - * @param projectId The unique identifier of the project. - * @return The list of topologies that belong to the specified project. - */ - fun findAll(projectId: Long): List { - return em.createNamedQuery("Topology.findAll", Topology::class.java) - .setParameter("projectId", projectId) - .resultList - } - - /** - * Find the [Topology] with the specified [number] belonging to [project][projectId]. - * - * @param projectId The unique identifier of the project. - * @param number The number of the topology. - * @return The topology or `null` if it does not exist. - */ - fun findOne(projectId: Long, number: Int): Topology? { - return em.createNamedQuery("Topology.findOne", Topology::class.java) - .setParameter("projectId", projectId) - .setParameter("number", number) - .setMaxResults(1) - .resultList - .firstOrNull() - } - - /** - * Find the [Topology] with the specified [id]. - * - * @param id Unique identifier of the topology. - * @return The topology or `null` if it does not exist. - */ - fun findOne(id: Long): Topology? { - return em.find(Topology::class.java, id) - } - - /** - * Delete the specified [topology]. - */ - fun delete(topology: Topology) { - em.remove(topology) - } - - /** - * Save the specified [topology] to the database. - */ - fun save(topology: Topology) { - em.persist(topology) - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/repository/TraceRepository.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/repository/TraceRepository.kt deleted file mode 100644 index 6652fc80..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/repository/TraceRepository.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.repository - -import org.opendc.web.api.model.Trace -import javax.enterprise.context.ApplicationScoped -import javax.inject.Inject -import javax.persistence.EntityManager - -/** - * A repository to manage [Trace] entities. - */ -@ApplicationScoped -class TraceRepository @Inject constructor(private val em: EntityManager) { - /** - * Find all workload traces in the database. - * - * @return The list of available workload traces. - */ - fun findAll(): List { - return em.createNamedQuery("Trace.findAll", Trace::class.java).resultList - } - - /** - * Find the [Trace] with the specified [id]. - * - * @param id The unique identifier of the trace. - * @return The trace or `null` if it does not exist. - */ - fun findOne(id: String): Trace? { - return em.find(Trace::class.java, id) - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/SchedulerResource.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/SchedulerResource.kt deleted file mode 100644 index 735fdd9b..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/SchedulerResource.kt +++ /dev/null @@ -1,48 +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.web.api.rest - -import javax.ws.rs.GET -import javax.ws.rs.Path - -/** - * A resource representing the available schedulers that can be used during experiments. - */ -@Path("/schedulers") -class SchedulerResource { - /** - * Obtain all available schedulers. - */ - @GET - fun getAll() = listOf( - "mem", - "mem-inv", - "core-mem", - "core-mem-inv", - "active-servers", - "active-servers-inv", - "provisioned-cores", - "provisioned-cores-inv", - "random" - ) -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/TraceResource.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/TraceResource.kt deleted file mode 100644 index e87fe602..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/TraceResource.kt +++ /dev/null @@ -1,51 +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.web.api.rest - -import org.opendc.web.api.service.TraceService -import org.opendc.web.proto.Trace -import javax.inject.Inject -import javax.ws.rs.* - -/** - * A resource representing the workload traces available in the OpenDC instance. - */ -@Path("/traces") -class TraceResource @Inject constructor(private val traceService: TraceService) { - /** - * Obtain all available traces. - */ - @GET - fun getAll(): List { - return traceService.findAll() - } - - /** - * Obtain trace information by identifier. - */ - @GET - @Path("{id}") - fun get(@PathParam("id") id: String): Trace { - return traceService.findById(id) ?: throw WebApplicationException("Trace not found", 404) - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/error/GenericExceptionMapper.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/error/GenericExceptionMapper.kt deleted file mode 100644 index fb253758..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/error/GenericExceptionMapper.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.rest.error - -import org.opendc.web.proto.ProtocolError -import javax.ws.rs.WebApplicationException -import javax.ws.rs.core.MediaType -import javax.ws.rs.core.Response -import javax.ws.rs.ext.ExceptionMapper -import javax.ws.rs.ext.Provider - -/** - * Helper class to transform an exception into an JSON error response. - */ -@Provider -class GenericExceptionMapper : ExceptionMapper { - override fun toResponse(exception: Exception): Response { - val code = if (exception is WebApplicationException) exception.response.status else 500 - - return Response.status(code) - .entity(ProtocolError(code, exception.message ?: "Unknown error")) - .type(MediaType.APPLICATION_JSON) - .build() - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/error/MissingKotlinParameterExceptionMapper.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/error/MissingKotlinParameterExceptionMapper.kt deleted file mode 100644 index 57cd35d1..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/error/MissingKotlinParameterExceptionMapper.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.rest.error - -import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException -import org.opendc.web.proto.ProtocolError -import javax.ws.rs.core.MediaType -import javax.ws.rs.core.Response -import javax.ws.rs.ext.ExceptionMapper -import javax.ws.rs.ext.Provider - -/** - * An [ExceptionMapper] for [MissingKotlinParameterException] thrown by Jackson. - */ -@Provider -class MissingKotlinParameterExceptionMapper : ExceptionMapper { - override fun toResponse(exception: MissingKotlinParameterException): Response { - return Response.status(Response.Status.BAD_REQUEST) - .entity(ProtocolError(Response.Status.BAD_REQUEST.statusCode, "Field '${exception.parameter.name}' is missing from body.")) - .type(MediaType.APPLICATION_JSON) - .build() - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/runner/JobResource.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/runner/JobResource.kt deleted file mode 100644 index d9923505..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/runner/JobResource.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.rest.runner - -import org.opendc.web.api.service.JobService -import org.opendc.web.proto.runner.Job -import javax.annotation.security.RolesAllowed -import javax.inject.Inject -import javax.transaction.Transactional -import javax.validation.Valid -import javax.ws.rs.* - -/** - * A resource representing the available simulation jobs. - */ -@Path("/jobs") -@RolesAllowed("runner") -class JobResource @Inject constructor(private val jobService: JobService) { - /** - * Obtain all pending simulation jobs. - */ - @GET - fun queryPending(): List { - return jobService.queryPending() - } - - /** - * Get a job by identifier. - */ - @GET - @Path("{job}") - fun get(@PathParam("job") id: Long): Job { - return jobService.findById(id) ?: throw WebApplicationException("Job not found", 404) - } - - /** - * Atomically update the state of a job. - */ - @POST - @Path("{job}") - @Transactional - fun update(@PathParam("job") id: Long, @Valid update: Job.Update): Job { - return try { - jobService.updateState(id, update.state, update.results) - ?: throw WebApplicationException("Job not found", 404) - } catch (e: IllegalArgumentException) { - throw WebApplicationException(e, 400) - } catch (e: IllegalStateException) { - throw WebApplicationException(e, 409) - } - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/user/PortfolioResource.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/user/PortfolioResource.kt deleted file mode 100644 index e720de75..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/user/PortfolioResource.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.rest.user - -import io.quarkus.security.identity.SecurityIdentity -import org.opendc.web.api.service.PortfolioService -import org.opendc.web.proto.user.Portfolio -import javax.annotation.security.RolesAllowed -import javax.inject.Inject -import javax.transaction.Transactional -import javax.validation.Valid -import javax.ws.rs.* - -/** - * A resource representing the portfolios of a project. - */ -@Path("/projects/{project}/portfolios") -@RolesAllowed("openid") -class PortfolioResource @Inject constructor( - private val portfolioService: PortfolioService, - private val identity: SecurityIdentity, -) { - /** - * Get all portfolios that belong to the specified project. - */ - @GET - fun getAll(@PathParam("project") projectId: Long): List { - return portfolioService.findAll(identity.principal.name, projectId) - } - - /** - * Create a portfolio for this project. - */ - @POST - @Transactional - fun create(@PathParam("project") projectId: Long, @Valid request: Portfolio.Create): Portfolio { - return portfolioService.create(identity.principal.name, projectId, request) ?: throw WebApplicationException("Project not found", 404) - } - - /** - * Obtain a portfolio by its identifier. - */ - @GET - @Path("{portfolio}") - fun get(@PathParam("project") projectId: Long, @PathParam("portfolio") number: Int): Portfolio { - return portfolioService.findOne(identity.principal.name, projectId, number) ?: throw WebApplicationException("Portfolio not found", 404) - } - - /** - * Delete a portfolio. - */ - @DELETE - @Path("{portfolio}") - fun delete(@PathParam("project") projectId: Long, @PathParam("portfolio") number: Int): Portfolio { - return portfolioService.delete(identity.principal.name, projectId, number) ?: throw WebApplicationException("Portfolio not found", 404) - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/user/PortfolioScenarioResource.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/user/PortfolioScenarioResource.kt deleted file mode 100644 index 8d24b2eb..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/user/PortfolioScenarioResource.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.rest.user - -import io.quarkus.security.identity.SecurityIdentity -import org.opendc.web.api.service.ScenarioService -import org.opendc.web.proto.user.Scenario -import javax.annotation.security.RolesAllowed -import javax.inject.Inject -import javax.transaction.Transactional -import javax.validation.Valid -import javax.ws.rs.* - -/** - * A resource representing the scenarios of a portfolio. - */ -@Path("/projects/{project}/portfolios/{portfolio}/scenarios") -@RolesAllowed("openid") -class PortfolioScenarioResource @Inject constructor( - private val scenarioService: ScenarioService, - private val identity: SecurityIdentity, -) { - /** - * Get all scenarios that belong to the specified portfolio. - */ - @GET - fun get(@PathParam("project") projectId: Long, @PathParam("portfolio") portfolioNumber: Int): List { - return scenarioService.findAll(identity.principal.name, projectId, portfolioNumber) - } - - /** - * Create a scenario for this portfolio. - */ - @POST - @Transactional - fun create(@PathParam("project") projectId: Long, @PathParam("portfolio") portfolioNumber: Int, @Valid request: Scenario.Create): Scenario { - return scenarioService.create(identity.principal.name, projectId, portfolioNumber, request) ?: throw WebApplicationException("Portfolio not found", 404) - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/user/ProjectResource.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/user/ProjectResource.kt deleted file mode 100644 index a27d50e7..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/user/ProjectResource.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.rest.user - -import io.quarkus.security.identity.SecurityIdentity -import org.opendc.web.api.service.ProjectService -import org.opendc.web.proto.user.Project -import javax.annotation.security.RolesAllowed -import javax.inject.Inject -import javax.transaction.Transactional -import javax.validation.Valid -import javax.ws.rs.* - -/** - * A resource representing the created projects. - */ -@Path("/projects") -@RolesAllowed("openid") -class ProjectResource @Inject constructor( - private val projectService: ProjectService, - private val identity: SecurityIdentity -) { - /** - * Obtain all the projects of the current user. - */ - @GET - fun getAll(): List { - return projectService.findWithUser(identity.principal.name) - } - - /** - * Create a new project for the current user. - */ - @POST - @Transactional - fun create(@Valid request: Project.Create): Project { - return projectService.createForUser(identity.principal.name, request.name) - } - - /** - * Obtain a single project by its identifier. - */ - @GET - @Path("{project}") - fun get(@PathParam("project") id: Long): Project { - return projectService.findWithUser(identity.principal.name, id) ?: throw WebApplicationException("Project not found", 404) - } - - /** - * Delete a project. - */ - @DELETE - @Path("{project}") - @Transactional - fun delete(@PathParam("project") id: Long): Project { - try { - return projectService.deleteWithUser(identity.principal.name, id) ?: throw WebApplicationException("Project not found", 404) - } catch (e: IllegalArgumentException) { - throw WebApplicationException(e.message, 403) - } - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/user/ScenarioResource.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/user/ScenarioResource.kt deleted file mode 100644 index 3690f987..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/user/ScenarioResource.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.rest.user - -import io.quarkus.security.identity.SecurityIdentity -import org.opendc.web.api.service.ScenarioService -import org.opendc.web.proto.user.Scenario -import javax.annotation.security.RolesAllowed -import javax.inject.Inject -import javax.transaction.Transactional -import javax.ws.rs.* - -/** - * A resource representing the scenarios of a portfolio. - */ -@Path("/projects/{project}/scenarios") -@RolesAllowed("openid") -class ScenarioResource @Inject constructor( - private val scenarioService: ScenarioService, - private val identity: SecurityIdentity -) { - /** - * Obtain a scenario by its identifier. - */ - @GET - @Path("{scenario}") - fun get(@PathParam("project") projectId: Long, @PathParam("scenario") number: Int): Scenario { - return scenarioService.findOne(identity.principal.name, projectId, number) ?: throw WebApplicationException("Scenario not found", 404) - } - - /** - * Delete a scenario. - */ - @DELETE - @Path("{scenario}") - @Transactional - fun delete(@PathParam("project") projectId: Long, @PathParam("scenario") number: Int): Scenario { - return scenarioService.delete(identity.principal.name, projectId, number) ?: throw WebApplicationException("Scenario not found", 404) - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/user/TopologyResource.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/user/TopologyResource.kt deleted file mode 100644 index 52c5eaaa..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/user/TopologyResource.kt +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.rest.user - -import io.quarkus.security.identity.SecurityIdentity -import org.opendc.web.api.service.TopologyService -import org.opendc.web.proto.user.Topology -import javax.annotation.security.RolesAllowed -import javax.inject.Inject -import javax.transaction.Transactional -import javax.validation.Valid -import javax.ws.rs.* - -/** - * A resource representing the constructed datacenter topologies. - */ -@Path("/projects/{project}/topologies") -@RolesAllowed("openid") -class TopologyResource @Inject constructor( - private val topologyService: TopologyService, - private val identity: SecurityIdentity -) { - /** - * Get all topologies that belong to the specified project. - */ - @GET - fun getAll(@PathParam("project") projectId: Long): List { - return topologyService.findAll(identity.principal.name, projectId) - } - - /** - * Create a topology for this project. - */ - @POST - @Transactional - fun create(@PathParam("project") projectId: Long, @Valid request: Topology.Create): Topology { - return topologyService.create(identity.principal.name, projectId, request) ?: throw WebApplicationException("Topology not found", 404) - } - - /** - * Obtain a topology by its number. - */ - @GET - @Path("{topology}") - fun get(@PathParam("project") projectId: Long, @PathParam("topology") number: Int): Topology { - return topologyService.findOne(identity.principal.name, projectId, number) ?: throw WebApplicationException("Topology not found", 404) - } - - /** - * Update the specified topology by its number. - */ - @PUT - @Path("{topology}") - @Transactional - fun update(@PathParam("project") projectId: Long, @PathParam("topology") number: Int, @Valid request: Topology.Update): Topology { - return topologyService.update(identity.principal.name, projectId, number, request) ?: throw WebApplicationException("Topology not found", 404) - } - - /** - * Delete the specified topology. - */ - @Path("{topology}") - @DELETE - @Transactional - fun delete(@PathParam("project") projectId: Long, @PathParam("topology") number: Int): Topology { - return topologyService.delete(identity.principal.name, projectId, number) ?: throw WebApplicationException("Topology not found", 404) - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/JobService.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/JobService.kt deleted file mode 100644 index 1b33248d..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/JobService.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.service - -import org.opendc.web.api.repository.JobRepository -import org.opendc.web.proto.JobState -import org.opendc.web.proto.runner.Job -import java.time.Instant -import javax.enterprise.context.ApplicationScoped -import javax.inject.Inject - -/** - * Service for managing [Job]s. - */ -@ApplicationScoped -class JobService @Inject constructor(private val repository: JobRepository) { - /** - * Query the pending simulation jobs. - */ - fun queryPending(): List { - return repository.findAll(JobState.PENDING).map { it.toRunnerDto() } - } - - /** - * Find a job by its identifier. - */ - fun findById(id: Long): Job? { - return repository.findOne(id)?.toRunnerDto() - } - - /** - * Atomically update the state of a [Job]. - */ - fun updateState(id: Long, newState: JobState, results: Map?): Job? { - val entity = repository.findOne(id) ?: return null - val state = entity.state - if (!state.isTransitionLegal(newState)) { - throw IllegalArgumentException("Invalid transition from $state to $newState") - } - - val now = Instant.now() - if (!repository.updateOne(entity, newState, now, results)) { - throw IllegalStateException("Conflicting update") - } - - return entity.toRunnerDto() - } - - /** - * Determine whether the transition from [this] to [newState] is legal. - */ - private fun JobState.isTransitionLegal(newState: JobState): Boolean { - // Note that we always allow transitions from the state - return newState == this || when (this) { - JobState.PENDING -> newState == JobState.CLAIMED - JobState.CLAIMED -> newState == JobState.RUNNING || newState == JobState.FAILED - JobState.RUNNING -> newState == JobState.FINISHED || newState == JobState.FAILED - JobState.FINISHED, JobState.FAILED -> false - } - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/PortfolioService.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/PortfolioService.kt deleted file mode 100644 index 1f41c2d7..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/PortfolioService.kt +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.service - -import org.opendc.web.api.model.* -import org.opendc.web.api.repository.PortfolioRepository -import org.opendc.web.api.repository.ProjectRepository -import org.opendc.web.proto.user.Portfolio -import java.time.Instant -import javax.enterprise.context.ApplicationScoped -import javax.inject.Inject -import org.opendc.web.api.model.Portfolio as PortfolioEntity - -/** - * Service for managing [Portfolio]s. - */ -@ApplicationScoped -class PortfolioService @Inject constructor( - private val projectRepository: ProjectRepository, - private val portfolioRepository: PortfolioRepository -) { - /** - * List all [Portfolio]s that belong a certain project. - */ - fun findAll(userId: String, projectId: Long): List { - // User must have access to project - val auth = projectRepository.findOne(userId, projectId) ?: return emptyList() - val project = auth.toUserDto() - return portfolioRepository.findAll(projectId).map { it.toUserDto(project) } - } - - /** - * Find a [Portfolio] with the specified [number] belonging to [project][projectId]. - */ - fun findOne(userId: String, projectId: Long, number: Int): Portfolio? { - // User must have access to project - val auth = projectRepository.findOne(userId, projectId) ?: return null - return portfolioRepository.findOne(projectId, number)?.toUserDto(auth.toUserDto()) - } - - /** - * Delete the portfolio with the specified [number] belonging to [project][projectId]. - */ - fun delete(userId: String, projectId: Long, number: Int): Portfolio? { - // User must have access to project - val auth = projectRepository.findOne(userId, projectId) - - if (auth == null) { - return null - } else if (!auth.role.canEdit) { - throw IllegalStateException("Not permitted to edit project") - } - - val entity = portfolioRepository.findOne(projectId, number) ?: return null - val portfolio = entity.toUserDto(auth.toUserDto()) - portfolioRepository.delete(entity) - return portfolio - } - - /** - * Construct a new [Portfolio] with the specified name. - */ - fun create(userId: String, projectId: Long, request: Portfolio.Create): Portfolio? { - // User must have access to project - val auth = projectRepository.findOne(userId, projectId) - - if (auth == null) { - return null - } else if (!auth.role.canEdit) { - throw IllegalStateException("Not permitted to edit project") - } - - val now = Instant.now() - val project = auth.project - val number = projectRepository.allocatePortfolio(auth.project, now) - - val portfolio = PortfolioEntity(0, number, request.name, project, request.targets) - - project.portfolios.add(portfolio) - portfolioRepository.save(portfolio) - - return portfolio.toUserDto(auth.toUserDto()) - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/ProjectService.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/ProjectService.kt deleted file mode 100644 index c3e43395..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/ProjectService.kt +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.service - -import org.opendc.web.api.model.* -import org.opendc.web.api.repository.ProjectRepository -import org.opendc.web.proto.user.Project -import org.opendc.web.proto.user.ProjectRole -import java.time.Instant -import javax.enterprise.context.ApplicationScoped -import javax.inject.Inject - -/** - * Service for managing [Project]s. - */ -@ApplicationScoped -class ProjectService @Inject constructor(private val repository: ProjectRepository) { - /** - * List all projects for the user with the specified [userId]. - */ - fun findWithUser(userId: String): List { - return repository.findAll(userId).map { it.toUserDto() } - } - - /** - * Obtain the project with the specified [id] for the user with the specified [userId]. - */ - fun findWithUser(userId: String, id: Long): Project? { - return repository.findOne(userId, id)?.toUserDto() - } - - /** - * Create a new [Project] for the user with the specified [userId]. - */ - fun createForUser(userId: String, name: String): Project { - val now = Instant.now() - val entity = Project(0, name, now) - repository.save(entity) - - val authorization = ProjectAuthorization(ProjectAuthorizationKey(userId, entity.id), entity, ProjectRole.OWNER) - - entity.authorizations.add(authorization) - repository.save(authorization) - - return authorization.toUserDto() - } - - /** - * Delete a project by its identifier. - * - * @param userId The user that invokes the action. - * @param id The identifier of the project. - */ - fun deleteWithUser(userId: String, id: Long): Project? { - val auth = repository.findOne(userId, id) ?: return null - - if (!auth.role.canDelete) { - throw IllegalArgumentException("Not allowed to delete project") - } - - val now = Instant.now() - val project = auth.toUserDto().copy(updatedAt = now) - repository.delete(auth.project) - return project - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/RunnerConversions.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/RunnerConversions.kt deleted file mode 100644 index 3722a641..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/RunnerConversions.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.service - -import org.opendc.web.api.model.Job -import org.opendc.web.api.model.Portfolio -import org.opendc.web.api.model.Scenario -import org.opendc.web.api.model.Topology - -/** - * Conversions into DTOs provided to OpenDC runners. - */ - -/** - * Convert a [Topology] into a runner-facing DTO. - */ -internal fun Topology.toRunnerDto(): org.opendc.web.proto.runner.Topology { - return org.opendc.web.proto.runner.Topology(id, number, name, rooms, createdAt, updatedAt) -} - -/** - * Convert a [Portfolio] into a runner-facing DTO. - */ -internal fun Portfolio.toRunnerDto(): org.opendc.web.proto.runner.Portfolio { - return org.opendc.web.proto.runner.Portfolio(id, number, name, targets) -} - -/** - * Convert a [Job] into a runner-facing DTO. - */ -internal fun Job.toRunnerDto(): org.opendc.web.proto.runner.Job { - return org.opendc.web.proto.runner.Job(id, scenario.toRunnerDto(), state, createdAt, updatedAt, results) -} - -/** - * Convert a [Job] into a runner-facing DTO. - */ -internal fun Scenario.toRunnerDto(): org.opendc.web.proto.runner.Scenario { - return org.opendc.web.proto.runner.Scenario( - id, - number, - portfolio.toRunnerDto(), - name, - workload.toDto(), - topology.toRunnerDto(), - phenomena, - schedulerName - ) -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/ScenarioService.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/ScenarioService.kt deleted file mode 100644 index dd51a929..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/ScenarioService.kt +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.service - -import org.opendc.web.api.model.* -import org.opendc.web.api.repository.* -import org.opendc.web.proto.user.Scenario -import java.time.Instant -import javax.enterprise.context.ApplicationScoped -import javax.inject.Inject - -/** - * Service for managing [Scenario]s. - */ -@ApplicationScoped -class ScenarioService @Inject constructor( - private val projectRepository: ProjectRepository, - private val portfolioRepository: PortfolioRepository, - private val topologyRepository: TopologyRepository, - private val traceRepository: TraceRepository, - private val scenarioRepository: ScenarioRepository, -) { - /** - * List all [Scenario]s that belong a certain portfolio. - */ - fun findAll(userId: String, projectId: Long, number: Int): List { - // User must have access to project - val auth = projectRepository.findOne(userId, projectId) ?: return emptyList() - val project = auth.toUserDto() - return scenarioRepository.findAll(projectId).map { it.toUserDto(project) } - } - - /** - * Obtain a [Scenario] by identifier. - */ - fun findOne(userId: String, projectId: Long, number: Int): Scenario? { - // User must have access to project - val auth = projectRepository.findOne(userId, projectId) ?: return null - val project = auth.toUserDto() - return scenarioRepository.findOne(projectId, number)?.toUserDto(project) - } - - /** - * Delete the specified scenario. - */ - fun delete(userId: String, projectId: Long, number: Int): Scenario? { - // User must have access to project - val auth = projectRepository.findOne(userId, projectId) - - if (auth == null) { - return null - } else if (!auth.role.canEdit) { - throw IllegalStateException("Not permitted to edit project") - } - - val entity = scenarioRepository.findOne(projectId, number) ?: return null - val scenario = entity.toUserDto(auth.toUserDto()) - scenarioRepository.delete(entity) - return scenario - } - - /** - * Construct a new [Scenario] with the specified data. - */ - fun create(userId: String, projectId: Long, portfolioNumber: Int, request: Scenario.Create): Scenario? { - // User must have access to project - val auth = projectRepository.findOne(userId, projectId) - - if (auth == null) { - return null - } else if (!auth.role.canEdit) { - throw IllegalStateException("Not permitted to edit project") - } - - val portfolio = portfolioRepository.findOne(projectId, portfolioNumber) ?: return null - val topology = requireNotNull( - topologyRepository.findOne( - projectId, - request.topology.toInt() - ) - ) { "Referred topology does not exist" } - val trace = - requireNotNull(traceRepository.findOne(request.workload.trace)) { "Referred trace does not exist" } - - val now = Instant.now() - val project = auth.project - val number = projectRepository.allocateScenario(auth.project, now) - - val scenario = Scenario( - 0, - number, - request.name, - project, - portfolio, - Workload(trace, request.workload.samplingFraction), - topology, - request.phenomena, - request.schedulerName - ) - val job = Job(0, scenario, now, portfolio.targets.repeats) - - scenario.job = job - portfolio.scenarios.add(scenario) - scenarioRepository.save(scenario) - - return scenario.toUserDto(auth.toUserDto()) - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/TopologyService.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/TopologyService.kt deleted file mode 100644 index f3460496..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/TopologyService.kt +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.service - -import org.opendc.web.api.repository.ProjectRepository -import org.opendc.web.api.repository.TopologyRepository -import org.opendc.web.proto.user.Topology -import java.time.Instant -import javax.enterprise.context.ApplicationScoped -import javax.inject.Inject -import org.opendc.web.api.model.Topology as TopologyEntity - -/** - * Service for managing [Topology]s. - */ -@ApplicationScoped -class TopologyService @Inject constructor( - private val projectRepository: ProjectRepository, - private val topologyRepository: TopologyRepository -) { - /** - * List all [Topology]s that belong a certain project. - */ - fun findAll(userId: String, projectId: Long): List { - // User must have access to project - val auth = projectRepository.findOne(userId, projectId) ?: return emptyList() - val project = auth.toUserDto() - return topologyRepository.findAll(projectId).map { it.toUserDto(project) } - } - - /** - * Find the [Topology] with the specified [number] belonging to [project][projectId]. - */ - fun findOne(userId: String, projectId: Long, number: Int): Topology? { - // User must have access to project - val auth = projectRepository.findOne(userId, projectId) ?: return null - return topologyRepository.findOne(projectId, number)?.toUserDto(auth.toUserDto()) - } - - /** - * Delete the [Topology] with the specified [number] belonging to [project][projectId]. - */ - fun delete(userId: String, projectId: Long, number: Int): Topology? { - // User must have access to project - val auth = projectRepository.findOne(userId, projectId) - - if (auth == null) { - return null - } else if (!auth.role.canEdit) { - throw IllegalStateException("Not permitted to edit project") - } - - val entity = topologyRepository.findOne(projectId, number) ?: return null - val now = Instant.now() - val topology = entity.toUserDto(auth.toUserDto()).copy(updatedAt = now) - topologyRepository.delete(entity) - - return topology - } - - /** - * Update a [Topology] with the specified [number] belonging to [project][projectId]. - */ - fun update(userId: String, projectId: Long, number: Int, request: Topology.Update): Topology? { - // User must have access to project - val auth = projectRepository.findOne(userId, projectId) - - if (auth == null) { - return null - } else if (!auth.role.canEdit) { - throw IllegalStateException("Not permitted to edit project") - } - - val entity = topologyRepository.findOne(projectId, number) ?: return null - val now = Instant.now() - - entity.updatedAt = now - entity.rooms = request.rooms - - return entity.toUserDto(auth.toUserDto()) - } - - /** - * Construct a new [Topology] with the specified name. - */ - fun create(userId: String, projectId: Long, request: Topology.Create): Topology? { - // User must have access to project - val auth = projectRepository.findOne(userId, projectId) - - if (auth == null) { - return null - } else if (!auth.role.canEdit) { - throw IllegalStateException("Not permitted to edit project") - } - - val now = Instant.now() - val project = auth.project - val number = projectRepository.allocateTopology(auth.project, now) - - val topology = TopologyEntity(0, number, request.name, project, now, request.rooms) - - project.topologies.add(topology) - topologyRepository.save(topology) - - return topology.toUserDto(auth.toUserDto()) - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/TraceService.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/TraceService.kt deleted file mode 100644 index a942696e..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/TraceService.kt +++ /dev/null @@ -1,48 +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.web.api.service - -import org.opendc.web.api.repository.TraceRepository -import org.opendc.web.proto.Trace -import javax.enterprise.context.ApplicationScoped -import javax.inject.Inject - -/** - * Service for managing [Trace]s. - */ -@ApplicationScoped -class TraceService @Inject constructor(private val repository: TraceRepository) { - /** - * Obtain all available workload traces. - */ - fun findAll(): List { - return repository.findAll().map { it.toUserDto() } - } - - /** - * Obtain a workload trace by identifier. - */ - fun findById(id: String): Trace? { - return repository.findOne(id)?.toUserDto() - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/UserConversions.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/UserConversions.kt deleted file mode 100644 index 8612ee8c..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/UserConversions.kt +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.service - -import org.opendc.web.api.model.* -import org.opendc.web.proto.user.Project - -/** - * Conversions into DTOs provided to users. - */ - -/** - * Convert a [Trace] entity into a [org.opendc.web.proto.Trace] DTO. - */ -internal fun Trace.toUserDto(): org.opendc.web.proto.Trace { - return org.opendc.web.proto.Trace(id, name, type) -} - -/** - * Convert a [ProjectAuthorization] entity into a [Project] DTO. - */ -internal fun ProjectAuthorization.toUserDto(): Project { - return Project(project.id, project.name, project.createdAt, project.updatedAt, role) -} - -/** - * Convert a [Topology] entity into a [org.opendc.web.proto.user.Topology] DTO. - */ -internal fun Topology.toUserDto(project: Project): org.opendc.web.proto.user.Topology { - return org.opendc.web.proto.user.Topology(id, number, project, name, rooms, createdAt, updatedAt) -} - -/** - * Convert a [Topology] entity into a [org.opendc.web.proto.user.Topology.Summary] DTO. - */ -private fun Topology.toSummaryDto(): org.opendc.web.proto.user.Topology.Summary { - return org.opendc.web.proto.user.Topology.Summary(id, number, name, createdAt, updatedAt) -} - -/** - * Convert a [Portfolio] entity into a [org.opendc.web.proto.user.Portfolio] DTO. - */ -internal fun Portfolio.toUserDto(project: Project): org.opendc.web.proto.user.Portfolio { - return org.opendc.web.proto.user.Portfolio(id, number, project, name, targets, scenarios.map { it.toSummaryDto() }) -} - -/** - * Convert a [Portfolio] entity into a [org.opendc.web.proto.user.Portfolio.Summary] DTO. - */ -private fun Portfolio.toSummaryDto(): org.opendc.web.proto.user.Portfolio.Summary { - return org.opendc.web.proto.user.Portfolio.Summary(id, number, name, targets) -} - -/** - * Convert a [Scenario] entity into a [org.opendc.web.proto.user.Scenario] DTO. - */ -internal fun Scenario.toUserDto(project: Project): org.opendc.web.proto.user.Scenario { - return org.opendc.web.proto.user.Scenario( - id, - number, - project, - portfolio.toSummaryDto(), - name, - workload.toDto(), - topology.toSummaryDto(), - phenomena, - schedulerName, - job.toUserDto() - ) -} - -/** - * Convert a [Scenario] entity into a [org.opendc.web.proto.user.Scenario.Summary] DTO. - */ -private fun Scenario.toSummaryDto(): org.opendc.web.proto.user.Scenario.Summary { - return org.opendc.web.proto.user.Scenario.Summary( - id, - number, - name, - workload.toDto(), - topology.toSummaryDto(), - phenomena, - schedulerName, - job.toUserDto() - ) -} - -/** - * Convert a [Job] entity into a [org.opendc.web.proto.user.Job] DTO. - */ -internal fun Job.toUserDto(): org.opendc.web.proto.user.Job { - return org.opendc.web.proto.user.Job(id, state, createdAt, updatedAt, results) -} - -/** - * Convert a [Workload] entity into a DTO. - */ -internal fun Workload.toDto(): org.opendc.web.proto.Workload { - return org.opendc.web.proto.Workload(trace.toUserDto(), samplingFraction) -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/Utils.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/Utils.kt deleted file mode 100644 index 254be8b7..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/service/Utils.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.service - -import org.opendc.web.proto.user.ProjectRole - -/** - * Flag to indicate that the user can edit a project. - */ -internal val ProjectRole.canEdit: Boolean - get() = when (this) { - ProjectRole.OWNER, ProjectRole.EDITOR -> true - ProjectRole.VIEWER -> false - } - -/** - * Flag to indicate that the user can delete a project. - */ -internal val ProjectRole.canDelete: Boolean - get() = this == ProjectRole.OWNER diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/DevSecurityOverrideFilter.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/DevSecurityOverrideFilter.kt deleted file mode 100644 index ba2cf2ae..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/DevSecurityOverrideFilter.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.util - -import io.quarkus.arc.properties.IfBuildProperty -import java.security.Principal -import javax.ws.rs.container.ContainerRequestContext -import javax.ws.rs.container.ContainerRequestFilter -import javax.ws.rs.container.PreMatching -import javax.ws.rs.core.SecurityContext -import javax.ws.rs.ext.Provider - -/** - * Helper class to disable security for the OpenDC web API when in development mode. - */ -@Provider -@PreMatching -@IfBuildProperty(name = "opendc.security.enabled", stringValue = "false") -class DevSecurityOverrideFilter : ContainerRequestFilter { - override fun filter(requestContext: ContainerRequestContext) { - requestContext.securityContext = object : SecurityContext { - override fun getUserPrincipal(): Principal = Principal { "anon" } - - override fun isSecure(): Boolean = false - - override fun isUserInRole(role: String): Boolean = true - - override fun getAuthenticationScheme(): String = "basic" - } - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/KotlinModuleCustomizer.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/KotlinModuleCustomizer.kt deleted file mode 100644 index 8d91a00c..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/KotlinModuleCustomizer.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.util - -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.kotlin.KotlinModule -import io.quarkus.jackson.ObjectMapperCustomizer -import javax.inject.Singleton - -/** - * Helper class to register the Kotlin Jackson module. - */ -@Singleton -class KotlinModuleCustomizer : ObjectMapperCustomizer { - override fun customize(objectMapper: ObjectMapper) { - objectMapper.registerModule(KotlinModule.Builder().build()) - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/AbstractJsonSqlTypeDescriptor.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/AbstractJsonSqlTypeDescriptor.kt deleted file mode 100644 index 134739c9..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/AbstractJsonSqlTypeDescriptor.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.util.hibernate.json - -import org.hibernate.type.descriptor.ValueExtractor -import org.hibernate.type.descriptor.WrapperOptions -import org.hibernate.type.descriptor.java.JavaTypeDescriptor -import org.hibernate.type.descriptor.sql.BasicExtractor -import org.hibernate.type.descriptor.sql.SqlTypeDescriptor -import java.sql.CallableStatement -import java.sql.ResultSet -import java.sql.Types - -/** - * Abstract implementation of a [SqlTypeDescriptor] for Hibernate JSON type. - */ -internal abstract class AbstractJsonSqlTypeDescriptor : SqlTypeDescriptor { - - override fun getSqlType(): Int { - return Types.OTHER - } - - override fun canBeRemapped(): Boolean { - return true - } - - override fun getExtractor(typeDescriptor: JavaTypeDescriptor): ValueExtractor { - return object : BasicExtractor(typeDescriptor, this) { - override fun doExtract(rs: ResultSet, name: String, options: WrapperOptions): X { - return typeDescriptor.wrap(extractJson(rs, name), options) - } - - override fun doExtract(statement: CallableStatement, index: Int, options: WrapperOptions): X { - return typeDescriptor.wrap(extractJson(statement, index), options) - } - - override fun doExtract(statement: CallableStatement, name: String, options: WrapperOptions): X { - return typeDescriptor.wrap(extractJson(statement, name), options) - } - } - } - - open fun extractJson(rs: ResultSet, name: String): Any? { - return rs.getObject(name) - } - - open fun extractJson(statement: CallableStatement, index: Int): Any? { - return statement.getObject(index) - } - - open fun extractJson(statement: CallableStatement, name: String): Any? { - return statement.getObject(name) - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/JsonBinarySqlTypeDescriptor.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/JsonBinarySqlTypeDescriptor.kt deleted file mode 100644 index 32f69928..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/JsonBinarySqlTypeDescriptor.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.opendc.web.api.util.hibernate.json - -import com.fasterxml.jackson.databind.JsonNode -import org.hibernate.type.descriptor.ValueBinder -import org.hibernate.type.descriptor.WrapperOptions -import org.hibernate.type.descriptor.java.JavaTypeDescriptor -import org.hibernate.type.descriptor.sql.BasicBinder -import java.sql.CallableStatement -import java.sql.PreparedStatement - -/** - * A [AbstractJsonSqlTypeDescriptor] that stores the JSON as binary (JSONB). - */ -internal object JsonBinarySqlTypeDescriptor : AbstractJsonSqlTypeDescriptor() { - override fun getBinder(typeDescriptor: JavaTypeDescriptor): ValueBinder { - return object : BasicBinder(typeDescriptor, this) { - override fun doBind(st: PreparedStatement, value: X, index: Int, options: WrapperOptions) { - st.setObject(index, typeDescriptor.unwrap(value, JsonNode::class.java, options), sqlType) - } - - override fun doBind(st: CallableStatement, value: X, name: String, options: WrapperOptions) { - st.setObject(name, typeDescriptor.unwrap(value, JsonNode::class.java, options), sqlType) - } - } - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/JsonBytesSqlTypeDescriptor.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/JsonBytesSqlTypeDescriptor.kt deleted file mode 100644 index eaecc5b0..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/JsonBytesSqlTypeDescriptor.kt +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.util.hibernate.json - -import org.hibernate.type.descriptor.ValueBinder -import org.hibernate.type.descriptor.WrapperOptions -import org.hibernate.type.descriptor.java.JavaTypeDescriptor -import org.hibernate.type.descriptor.sql.BasicBinder -import java.io.UnsupportedEncodingException -import java.sql.* - -/** - * A [AbstractJsonSqlTypeDescriptor] that stores the JSON as UTF-8 encoded bytes. - */ -internal object JsonBytesSqlTypeDescriptor : AbstractJsonSqlTypeDescriptor() { - private val CHARSET = Charsets.UTF_8 - - override fun getSqlType(): Int { - return Types.BINARY - } - - override fun getBinder(javaTypeDescriptor: JavaTypeDescriptor): ValueBinder { - return object : BasicBinder(javaTypeDescriptor, this) { - override fun doBind(st: PreparedStatement, value: X, index: Int, options: WrapperOptions) { - st.setBytes(index, toJsonBytes(javaTypeDescriptor.unwrap(value, String::class.java, options))) - } - - override fun doBind(st: CallableStatement, value: X, name: String, options: WrapperOptions) { - st.setBytes(name, toJsonBytes(javaTypeDescriptor.unwrap(value, String::class.java, options))) - } - } - } - - override fun extractJson(rs: ResultSet, name: String): Any? { - return fromJsonBytes(rs.getBytes(name)) - } - - override fun extractJson(statement: CallableStatement, index: Int): Any? { - return fromJsonBytes(statement.getBytes(index)) - } - - override fun extractJson(statement: CallableStatement, name: String): Any? { - return fromJsonBytes(statement.getBytes(name)) - } - - private fun toJsonBytes(jsonValue: String): ByteArray? { - return try { - jsonValue.toByteArray(CHARSET) - } catch (e: UnsupportedEncodingException) { - throw IllegalStateException(e) - } - } - - private fun fromJsonBytes(jsonBytes: ByteArray?): String? { - return if (jsonBytes == null) { - null - } else try { - String(jsonBytes, CHARSET) - } catch (e: UnsupportedEncodingException) { - throw IllegalStateException(e) - } - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/JsonSqlTypeDescriptor.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/JsonSqlTypeDescriptor.kt deleted file mode 100644 index e005f368..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/JsonSqlTypeDescriptor.kt +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.util.hibernate.json - -import org.hibernate.dialect.H2Dialect -import org.hibernate.dialect.PostgreSQL81Dialect -import org.hibernate.internal.SessionImpl -import org.hibernate.type.descriptor.ValueBinder -import org.hibernate.type.descriptor.ValueExtractor -import org.hibernate.type.descriptor.WrapperOptions -import org.hibernate.type.descriptor.java.JavaTypeDescriptor -import org.hibernate.type.descriptor.sql.BasicBinder -import org.hibernate.type.descriptor.sql.BasicExtractor -import org.hibernate.type.descriptor.sql.SqlTypeDescriptor -import java.sql.* - -/** - * A [SqlTypeDescriptor] that automatically selects the correct implementation for the database dialect. - */ -internal object JsonSqlTypeDescriptor : SqlTypeDescriptor { - - override fun getSqlType(): Int = Types.OTHER - - override fun canBeRemapped(): Boolean = true - - override fun getExtractor(javaTypeDescriptor: JavaTypeDescriptor): ValueExtractor { - return object : BasicExtractor(javaTypeDescriptor, this) { - private var delegate: AbstractJsonSqlTypeDescriptor? = null - - override fun doExtract(rs: ResultSet, name: String, options: WrapperOptions): X { - return javaTypeDescriptor.wrap(delegate(options).extractJson(rs, name), options) - } - - override fun doExtract(statement: CallableStatement, index: Int, options: WrapperOptions): X { - return javaTypeDescriptor.wrap(delegate(options).extractJson(statement, index), options) - } - - override fun doExtract(statement: CallableStatement, name: String, options: WrapperOptions): X { - return javaTypeDescriptor.wrap(delegate(options).extractJson(statement, name), options) - } - - private fun delegate(options: WrapperOptions): AbstractJsonSqlTypeDescriptor { - var delegate = delegate - if (delegate == null) { - delegate = resolveSqlTypeDescriptor(options) - this.delegate = delegate - } - return delegate - } - } - } - - override fun getBinder(javaTypeDescriptor: JavaTypeDescriptor): ValueBinder { - return object : BasicBinder(javaTypeDescriptor, this) { - private var delegate: ValueBinder? = null - - override fun doBind(st: PreparedStatement, value: X, index: Int, options: WrapperOptions) { - delegate(options).bind(st, value, index, options) - } - - override fun doBind(st: CallableStatement, value: X, name: String, options: WrapperOptions) { - delegate(options).bind(st, value, name, options) - } - - private fun delegate(options: WrapperOptions): ValueBinder { - var delegate = delegate - if (delegate == null) { - delegate = checkNotNull(resolveSqlTypeDescriptor(options).getBinder(javaTypeDescriptor)) - this.delegate = delegate - } - return delegate - } - } - } - - /** - * Helper method to resolve the appropriate [SqlTypeDescriptor] based on the [WrapperOptions]. - */ - private fun resolveSqlTypeDescriptor(options: WrapperOptions): AbstractJsonSqlTypeDescriptor { - val session = options as? SessionImpl - return when (session?.jdbcServices?.dialect) { - is PostgreSQL81Dialect -> JsonBinarySqlTypeDescriptor - is H2Dialect -> JsonBytesSqlTypeDescriptor - else -> JsonStringSqlTypeDescriptor - } - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/JsonStringSqlTypeDescriptor.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/JsonStringSqlTypeDescriptor.kt deleted file mode 100644 index cf400c95..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/JsonStringSqlTypeDescriptor.kt +++ /dev/null @@ -1,38 +0,0 @@ -package org.opendc.web.api.util.hibernate.json - -import org.hibernate.type.descriptor.ValueBinder -import org.hibernate.type.descriptor.WrapperOptions -import org.hibernate.type.descriptor.java.JavaTypeDescriptor -import org.hibernate.type.descriptor.sql.BasicBinder -import java.sql.* - -/** - * A [AbstractJsonSqlTypeDescriptor] that stores the JSON as string (VARCHAR). - */ -internal object JsonStringSqlTypeDescriptor : AbstractJsonSqlTypeDescriptor() { - override fun getSqlType(): Int = Types.VARCHAR - - override fun getBinder(typeDescriptor: JavaTypeDescriptor): ValueBinder { - return object : BasicBinder(typeDescriptor, this) { - override fun doBind(st: PreparedStatement, value: X, index: Int, options: WrapperOptions) { - st.setString(index, typeDescriptor.unwrap(value, String::class.java, options)) - } - - override fun doBind(st: CallableStatement, value: X, name: String, options: WrapperOptions) { - st.setString(name, typeDescriptor.unwrap(value, String::class.java, options)) - } - } - } - - override fun extractJson(rs: ResultSet, name: String): Any? { - return rs.getString(name) - } - - override fun extractJson(statement: CallableStatement, index: Int): Any? { - return statement.getString(index) - } - - override fun extractJson(statement: CallableStatement, name: String): Any? { - return statement.getString(name) - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/JsonType.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/JsonType.kt deleted file mode 100644 index 2206e82f..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/JsonType.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.util.hibernate.json - -import com.fasterxml.jackson.databind.ObjectMapper -import org.hibernate.type.AbstractSingleColumnStandardBasicType -import org.hibernate.type.BasicType -import org.hibernate.usertype.DynamicParameterizedType -import java.util.* -import javax.enterprise.inject.spi.CDI - -/** - * A [BasicType] that contains JSON. - */ -class JsonType(objectMapper: ObjectMapper) : AbstractSingleColumnStandardBasicType(JsonSqlTypeDescriptor, JsonTypeDescriptor(objectMapper)), DynamicParameterizedType { - /** - * No-arg constructor for Hibernate to instantiate. - */ - constructor() : this(CDI.current().select(ObjectMapper::class.java).get()) - - override fun getName(): String = "json" - - override fun registerUnderJavaType(): Boolean = true - - override fun setParameterValues(parameters: Properties) { - (javaTypeDescriptor as JsonTypeDescriptor).setParameterValues(parameters) - } -} diff --git a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/JsonTypeDescriptor.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/JsonTypeDescriptor.kt deleted file mode 100644 index 3386582e..00000000 --- a/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/hibernate/json/JsonTypeDescriptor.kt +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.util.hibernate.json - -import com.fasterxml.jackson.databind.ObjectMapper -import org.hibernate.HibernateException -import org.hibernate.annotations.common.reflection.XProperty -import org.hibernate.annotations.common.reflection.java.JavaXMember -import org.hibernate.engine.jdbc.BinaryStream -import org.hibernate.engine.jdbc.internal.BinaryStreamImpl -import org.hibernate.type.descriptor.WrapperOptions -import org.hibernate.type.descriptor.java.AbstractTypeDescriptor -import org.hibernate.type.descriptor.java.BlobTypeDescriptor -import org.hibernate.type.descriptor.java.DataHelper -import org.hibernate.type.descriptor.java.MutableMutabilityPlan -import org.hibernate.usertype.DynamicParameterizedType -import java.io.ByteArrayInputStream -import java.io.IOException -import java.io.InputStream -import java.lang.reflect.Type -import java.sql.Blob -import java.sql.SQLException -import java.util.* - -/** - * An [AbstractTypeDescriptor] implementation for Hibernate JSON type. - */ -internal class JsonTypeDescriptor(private val objectMapper: ObjectMapper) : AbstractTypeDescriptor(Any::class.java, JsonMutabilityPlan(objectMapper)), DynamicParameterizedType { - private var type: Type? = null - - override fun setParameterValues(parameters: Properties) { - val xProperty = parameters[DynamicParameterizedType.XPROPERTY] as XProperty - type = if (xProperty is JavaXMember) { - val x = xProperty as JavaXMember - x.javaType - } else { - (parameters[DynamicParameterizedType.PARAMETER_TYPE] as DynamicParameterizedType.ParameterType).returnedClass - } - } - - override fun areEqual(one: Any?, another: Any?): Boolean { - return when { - one === another -> true - one == null || another == null -> false - one is String && another is String -> one == another - one is Collection<*> && another is Collection<*> -> Objects.equals(one, another) - else -> areJsonEqual(one, another) - } - } - - override fun toString(value: Any?): String { - return objectMapper.writeValueAsString(value) - } - - override fun fromString(string: String): Any? { - return objectMapper.readValue(string, objectMapper.typeFactory.constructType(type)) - } - - override fun unwrap(value: Any?, type: Class, options: WrapperOptions): X? { - if (value == null) { - return null - } - - @Suppress("UNCHECKED_CAST") - return when { - String::class.java.isAssignableFrom(type) -> toString(value) - BinaryStream::class.java.isAssignableFrom(type) || ByteArray::class.java.isAssignableFrom(type) -> { - val stringValue = if (value is String) value else toString(value) - BinaryStreamImpl(DataHelper.extractBytes(ByteArrayInputStream(stringValue.toByteArray()))) - } - Blob::class.java.isAssignableFrom(type) -> { - val stringValue = if (value is String) value else toString(value) - BlobTypeDescriptor.INSTANCE.fromString(stringValue) - } - Any::class.java.isAssignableFrom(type) -> toJsonType(value) - else -> throw unknownUnwrap(type) - } as X - } - - override fun wrap(value: X?, options: WrapperOptions): Any? { - if (value == null) { - return null - } - - var blob: Blob? = null - if (Blob::class.java.isAssignableFrom(value.javaClass)) { - blob = options.lobCreator.wrap(value as Blob?) - } else if (ByteArray::class.java.isAssignableFrom(value.javaClass)) { - blob = options.lobCreator.createBlob(value as ByteArray?) - } else if (InputStream::class.java.isAssignableFrom(value.javaClass)) { - val inputStream = value as InputStream - blob = try { - options.lobCreator.createBlob(inputStream, inputStream.available().toLong()) - } catch (e: IOException) { - throw unknownWrap(value.javaClass) - } - } - - val stringValue: String = try { - if (blob != null) String(DataHelper.extractBytes(blob.binaryStream)) else value.toString() - } catch (e: SQLException) { - throw HibernateException("Unable to extract binary stream from Blob", e) - } - - return fromString(stringValue) - } - - private class JsonMutabilityPlan(private val objectMapper: ObjectMapper) : MutableMutabilityPlan() { - override fun deepCopyNotNull(value: Any): Any { - return objectMapper.treeToValue(objectMapper.valueToTree(value), value.javaClass) - } - } - - private fun readObject(value: String): Any { - return objectMapper.readTree(value) - } - - private fun areJsonEqual(one: Any, another: Any): Boolean { - return readObject(objectMapper.writeValueAsString(one)) == readObject(objectMapper.writeValueAsString(another)) - } - - private fun toJsonType(value: Any?): Any { - return try { - readObject(objectMapper.writeValueAsString(value)) - } catch (e: Exception) { - throw IllegalArgumentException(e) - } - } -} diff --git a/opendc-web/opendc-web-api/src/main/resources/META-INF/branding/logo.png b/opendc-web/opendc-web-api/src/main/resources/META-INF/branding/logo.png deleted file mode 100644 index d743038b..00000000 Binary files a/opendc-web/opendc-web-api/src/main/resources/META-INF/branding/logo.png and /dev/null differ diff --git a/opendc-web/opendc-web-api/src/main/resources/application-dev.properties b/opendc-web/opendc-web-api/src/main/resources/application-dev.properties deleted file mode 100644 index 98e53ee7..00000000 --- a/opendc-web/opendc-web-api/src/main/resources/application-dev.properties +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright (c) 2022 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. - -# Datasource (H2) -quarkus.datasource.db-kind = h2 -quarkus.datasource.jdbc.url=jdbc:h2:mem:default;DB_CLOSE_DELAY=-1;INIT=CREATE TYPE IF NOT EXISTS "JSONB" AS blob; - -# Hibernate -quarkus.hibernate-orm.dialect=org.hibernate.dialect.H2Dialect -quarkus.hibernate-orm.database.generation=drop-and-create -quarkus.hibernate-orm.sql-load-script=init-dev.sql - -# OpenID -quarkus.oidc.enabled=false -quarkus.oidc.auth-server-url= -quarkus.oidc.client-id= - -# OpenDC web UI -quarkus.opendc-ui.path=/ -quarkus.resteasy.path=/api - -opendc.security.enabled=false -quarkus.opendc-runner.auth.enabled=false diff --git a/opendc-web/opendc-web-api/src/main/resources/application-prod.properties b/opendc-web/opendc-web-api/src/main/resources/application-prod.properties deleted file mode 100644 index cebcdaab..00000000 --- a/opendc-web/opendc-web-api/src/main/resources/application-prod.properties +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (c) 2022 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. - -# Datasource -quarkus.datasource.db-kind=postgresql -quarkus.datasource.username=${OPENDC_DB_USERNAME} -quarkus.datasource.password=${OPENDC_DB_PASSWORD} -quarkus.datasource.jdbc.url=${OPENDC_DB_URL} - -# Hibernate -quarkus.hibernate-orm.dialect=org.hibernate.dialect.PostgreSQL95Dialect -quarkus.hibernate-orm.database.generation=validate - -# Disable OpenDC web UI and runner -quarkus.opendc-ui.include=false -quarkus.opendc-runner.include=false diff --git a/opendc-web/opendc-web-api/src/main/resources/application-test.properties b/opendc-web/opendc-web-api/src/main/resources/application-test.properties deleted file mode 100644 index 10197119..00000000 --- a/opendc-web/opendc-web-api/src/main/resources/application-test.properties +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright (c) 2022 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. - -# Datasource configuration -quarkus.datasource.db-kind = h2 -quarkus.datasource.jdbc.url=jdbc:h2:mem:default;DB_CLOSE_DELAY=-1;INIT=CREATE TYPE "JSONB" AS blob; - -quarkus.hibernate-orm.dialect=org.hibernate.dialect.H2Dialect -quarkus.hibernate-orm.database.generation=drop-and-create - -# No OIDC for tests -quarkus.oidc.enabled=false -quarkus.oidc.auth-server-url= -quarkus.oidc.client-id= - -# Disable OpenAPI/Swagger -quarkus.smallrye-openapi.enable=false -quarkus.swagger-ui.enable=false -quarkus.smallrye-openapi.oidc-open-id-connect-url= - -# Disable OpenDC web UI and runner -quarkus.opendc-ui.include=false -quarkus.opendc-runner.include=false diff --git a/opendc-web/opendc-web-api/src/main/resources/application.properties b/opendc-web/opendc-web-api/src/main/resources/application.properties deleted file mode 100644 index e9285401..00000000 --- a/opendc-web/opendc-web-api/src/main/resources/application.properties +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright (c) 2022 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. - -quarkus.http.cors=true - -# OpenID -quarkus.oidc.auth-server-url=https://${OPENDC_AUTH0_DOMAIN} -quarkus.oidc.client-id=${OPENDC_AUTH0_AUDIENCE} -quarkus.oidc.token.audience=${quarkus.oidc.client-id} -quarkus.oidc.roles.role-claim-path=scope - -# Runner logging -quarkus.log.category."org.opendc".level=ERROR -quarkus.log.category."org.opendc.web".level=INFO -quarkus.log.category."org.apache".level=WARN - -# OpenAPI and Swagger -quarkus.smallrye-openapi.info-title=OpenDC REST API -%dev.quarkus.smallrye-openapi.info-title=OpenDC REST API (development) -quarkus.smallrye-openapi.info-version=2.1-rc1 -quarkus.smallrye-openapi.info-description=OpenDC is an open-source datacenter simulator for education, featuring real-time online collaboration, diverse simulation models, and detailed performance feedback statistics. -quarkus.smallrye-openapi.info-contact-email=opendc@atlarge-research.com -quarkus.smallrye-openapi.info-contact-name=OpenDC Support -quarkus.smallrye-openapi.info-contact-url=https://opendc.org -quarkus.smallrye-openapi.info-license-name=MIT -quarkus.smallrye-openapi.info-license-url=https://github.com/atlarge-research/opendc/blob/master/LICENSE.txt - -quarkus.swagger-ui.path=docs -quarkus.swagger-ui.always-include=true -quarkus.swagger-ui.oauth-client-id=${OPENDC_AUTH0_DOCS_CLIENT_ID:} -quarkus.swagger-ui.oauth-additional-query-string-params={"audience":"${OPENDC_AUTH0_AUDIENCE:https://api.opendc.org/}"} - -quarkus.smallrye-openapi.security-scheme=oidc -quarkus.smallrye-openapi.security-scheme-name=Auth0 -quarkus.smallrye-openapi.oidc-open-id-connect-url=https://${OPENDC_AUTH0_DOMAIN:opendc.eu.auth0.com}/.well-known/openid-configuration -quarkus.smallrye-openapi.servers=http://localhost:8080 diff --git a/opendc-web/opendc-web-api/src/main/resources/init-dev.sql b/opendc-web/opendc-web-api/src/main/resources/init-dev.sql deleted file mode 100644 index 756eff46..00000000 --- a/opendc-web/opendc-web-api/src/main/resources/init-dev.sql +++ /dev/null @@ -1,3 +0,0 @@ - --- Add example traces -INSERT INTO traces (id, name, type) VALUES ('bitbrains-small', 'Bitbrains Small', 'vm'); diff --git a/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/SchedulerResourceTest.kt b/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/SchedulerResourceTest.kt deleted file mode 100644 index e88e1c1c..00000000 --- a/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/SchedulerResourceTest.kt +++ /dev/null @@ -1,48 +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.web.api.rest - -import io.quarkus.test.junit.QuarkusTest -import io.restassured.http.ContentType -import io.restassured.module.kotlin.extensions.Then -import io.restassured.module.kotlin.extensions.When -import org.junit.jupiter.api.Test - -/** - * Test suite for [SchedulerResource] - */ -@QuarkusTest -class SchedulerResourceTest { - /** - * Test to verify whether we can obtain all schedulers. - */ - @Test - fun testGetSchedulers() { - When { - get("/schedulers") - } Then { - statusCode(200) - contentType(ContentType.JSON) - } - } -} diff --git a/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/TraceResourceTest.kt b/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/TraceResourceTest.kt deleted file mode 100644 index 6fab9953..00000000 --- a/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/TraceResourceTest.kt +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.rest - -import io.mockk.every -import io.quarkiverse.test.junit.mockk.InjectMock -import io.quarkus.test.common.http.TestHTTPEndpoint -import io.quarkus.test.junit.QuarkusMock -import io.quarkus.test.junit.QuarkusTest -import io.restassured.http.ContentType -import io.restassured.module.kotlin.extensions.Then -import io.restassured.module.kotlin.extensions.When -import org.hamcrest.Matchers -import org.hamcrest.Matchers.equalTo -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.opendc.web.api.service.TraceService -import org.opendc.web.proto.Trace - -/** - * Test suite for [TraceResource]. - */ -@QuarkusTest -@TestHTTPEndpoint(TraceResource::class) -class TraceResourceTest { - @InjectMock - private lateinit var traceService: TraceService - - @BeforeEach - fun setUp() { - QuarkusMock.installMockForType(traceService, TraceService::class.java) - } - - /** - * Test that tries to obtain all traces (empty response). - */ - @Test - fun testGetAllEmpy() { - every { traceService.findAll() } returns emptyList() - - When { - get() - } Then { - statusCode(200) - contentType(ContentType.JSON) - body("", Matchers.empty()) - } - } - - /** - * Test that tries to obtain a non-existent trace. - */ - @Test - fun testGetNonExisting() { - every { traceService.findById("bitbrains") } returns null - - When { - get("/bitbrains") - } Then { - statusCode(404) - contentType(ContentType.JSON) - } - } - - /** - * Test that tries to obtain an existing trace. - */ - @Test - fun testGetExisting() { - every { traceService.findById("bitbrains") } returns Trace("bitbrains", "Bitbrains", "VM") - - When { - get("/bitbrains") - } Then { - statusCode(200) - contentType(ContentType.JSON) - body("name", equalTo("Bitbrains")) - } - } -} diff --git a/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/runner/JobResourceTest.kt b/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/runner/JobResourceTest.kt deleted file mode 100644 index b82c60e8..00000000 --- a/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/runner/JobResourceTest.kt +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.rest.runner - -import io.mockk.every -import io.quarkiverse.test.junit.mockk.InjectMock -import io.quarkus.test.common.http.TestHTTPEndpoint -import io.quarkus.test.junit.QuarkusMock -import io.quarkus.test.junit.QuarkusTest -import io.quarkus.test.security.TestSecurity -import io.restassured.http.ContentType -import io.restassured.module.kotlin.extensions.Given -import io.restassured.module.kotlin.extensions.Then -import io.restassured.module.kotlin.extensions.When -import org.hamcrest.Matchers.equalTo -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.opendc.web.api.service.JobService -import org.opendc.web.proto.* -import org.opendc.web.proto.Targets -import org.opendc.web.proto.runner.Job -import org.opendc.web.proto.runner.Portfolio -import org.opendc.web.proto.runner.Scenario -import org.opendc.web.proto.runner.Topology -import java.time.Instant - -/** - * Test suite for [JobResource]. - */ -@QuarkusTest -@TestHTTPEndpoint(JobResource::class) -class JobResourceTest { - @InjectMock - private lateinit var jobService: JobService - - /** - * Dummy values - */ - private val dummyPortfolio = Portfolio(1, 1, "test", Targets(emptySet())) - private val dummyTopology = Topology(1, 1, "test", emptyList(), Instant.now(), Instant.now()) - private val dummyTrace = Trace("bitbrains", "Bitbrains", "vm") - private val dummyScenario = Scenario(1, 1, dummyPortfolio, "test", Workload(dummyTrace, 1.0), dummyTopology, OperationalPhenomena(false, false), "test",) - private val dummyJob = Job(1, dummyScenario, JobState.PENDING, Instant.now(), Instant.now()) - - @BeforeEach - fun setUp() { - QuarkusMock.installMockForType(jobService, JobService::class.java) - } - - /** - * Test that tries to query the pending jobs without token. - */ - @Test - fun testQueryWithoutToken() { - When { - get() - } Then { - statusCode(401) - } - } - - /** - * Test that tries to query the pending jobs for a user. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testQueryInvalidScope() { - When { - get() - } Then { - statusCode(403) - } - } - - /** - * Test that tries to query the pending jobs for a runner. - */ - @Test - @TestSecurity(user = "testUser", roles = ["runner"]) - fun testQuery() { - every { jobService.queryPending() } returns listOf(dummyJob) - - When { - get() - } Then { - statusCode(200) - contentType(ContentType.JSON) - body("get(0).id", equalTo(1)) - } - } - - /** - * Test that tries to obtain a non-existent job. - */ - @Test - @TestSecurity(user = "testUser", roles = ["runner"]) - fun testGetNonExisting() { - every { jobService.findById(1) } returns null - - When { - get("/1") - } Then { - statusCode(404) - contentType(ContentType.JSON) - } - } - - /** - * Test that tries to obtain a job. - */ - @Test - @TestSecurity(user = "testUser", roles = ["runner"]) - fun testGetExisting() { - every { jobService.findById(1) } returns dummyJob - - When { - get("/1") - } Then { - statusCode(200) - contentType(ContentType.JSON) - body("id", equalTo(1)) - } - } - - /** - * Test that tries to update a non-existent job. - */ - @Test - @TestSecurity(user = "testUser", roles = ["runner"]) - fun testUpdateNonExistent() { - every { jobService.updateState(1, any(), any()) } returns null - - Given { - body(Job.Update(JobState.PENDING)) - contentType(ContentType.JSON) - } When { - post("/1") - } Then { - statusCode(404) - contentType(ContentType.JSON) - } - } - - /** - * Test that tries to update a job. - */ - @Test - @TestSecurity(user = "testUser", roles = ["runner"]) - fun testUpdateState() { - every { jobService.updateState(1, any(), any()) } returns dummyJob.copy(state = JobState.CLAIMED) - - Given { - body(Job.Update(JobState.CLAIMED)) - contentType(ContentType.JSON) - } When { - post("/1") - } Then { - statusCode(200) - contentType(ContentType.JSON) - body("state", equalTo(JobState.CLAIMED.toString())) - } - } - - /** - * Test that tries to update a job with invalid input. - */ - @Test - @TestSecurity(user = "testUser", roles = ["runner"]) - fun testUpdateInvalidInput() { - Given { - body("""{ "test": "test" }""") - contentType(ContentType.JSON) - } When { - post("/1") - } Then { - statusCode(400) - contentType(ContentType.JSON) - } - } -} diff --git a/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/PortfolioResourceTest.kt b/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/PortfolioResourceTest.kt deleted file mode 100644 index f74efbca..00000000 --- a/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/PortfolioResourceTest.kt +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.rest.user - -import io.mockk.every -import io.quarkiverse.test.junit.mockk.InjectMock -import io.quarkus.test.common.http.TestHTTPEndpoint -import io.quarkus.test.junit.QuarkusMock -import io.quarkus.test.junit.QuarkusTest -import io.quarkus.test.security.TestSecurity -import io.restassured.http.ContentType -import io.restassured.module.kotlin.extensions.Given -import io.restassured.module.kotlin.extensions.Then -import io.restassured.module.kotlin.extensions.When -import org.hamcrest.Matchers -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.opendc.web.api.service.PortfolioService -import org.opendc.web.proto.Targets -import org.opendc.web.proto.user.Portfolio -import org.opendc.web.proto.user.Project -import org.opendc.web.proto.user.ProjectRole -import java.time.Instant - -/** - * Test suite for [PortfolioResource]. - */ -@QuarkusTest -@TestHTTPEndpoint(PortfolioResource::class) -class PortfolioResourceTest { - @InjectMock - private lateinit var portfolioService: PortfolioService - - /** - * Dummy project and portfolio - */ - private val dummyProject = Project(1, "test", Instant.now(), Instant.now(), ProjectRole.OWNER) - private val dummyPortfolio = Portfolio(1, 1, dummyProject, "test", Targets(emptySet(), 1), emptyList()) - - @BeforeEach - fun setUp() { - QuarkusMock.installMockForType(portfolioService, PortfolioService::class.java) - } - - /** - * Test that tries to obtain the list of portfolios belonging to a project. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testGetForProject() { - every { portfolioService.findAll("testUser", 1) } returns emptyList() - - Given { - pathParam("project", "1") - } When { - get() - } Then { - statusCode(200) - contentType(ContentType.JSON) - } - } - - /** - * Test that tries to create a topology for a project. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testCreateNonExistent() { - every { portfolioService.create("testUser", 1, any()) } returns null - - Given { - pathParam("project", "1") - - body(Portfolio.Create("test", Targets(emptySet(), 1))) - contentType(ContentType.JSON) - } When { - post() - } Then { - statusCode(404) - contentType(ContentType.JSON) - } - } - - /** - * Test that tries to create a portfolio for a scenario. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testCreate() { - every { portfolioService.create("testUser", 1, any()) } returns dummyPortfolio - - Given { - pathParam("project", "1") - - body(Portfolio.Create("test", Targets(emptySet(), 1))) - contentType(ContentType.JSON) - } When { - post() - } Then { - statusCode(200) - contentType(ContentType.JSON) - body("id", Matchers.equalTo(1)) - body("name", Matchers.equalTo("test")) - } - } - - /** - * Test to create a portfolio with an empty body. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testCreateEmpty() { - Given { - pathParam("project", "1") - - body("{}") - contentType(ContentType.JSON) - } When { - post() - } Then { - statusCode(400) - contentType(ContentType.JSON) - } - } - - /** - * Test to create a portfolio with a blank name. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testCreateBlankName() { - Given { - pathParam("project", "1") - - body(Portfolio.Create("", Targets(emptySet(), 1))) - contentType(ContentType.JSON) - } When { - post() - } Then { - statusCode(400) - contentType(ContentType.JSON) - } - } - - /** - * Test that tries to obtain a portfolio without token. - */ - @Test - fun testGetWithoutToken() { - Given { - pathParam("project", "1") - } When { - get("/1") - } Then { - statusCode(401) - } - } - - /** - * Test that tries to obtain a portfolio with an invalid scope. - */ - @Test - @TestSecurity(user = "testUser", roles = ["runner"]) - fun testGetInvalidToken() { - Given { - pathParam("project", "1") - } When { - get("/1") - } Then { - statusCode(403) - } - } - - /** - * Test that tries to obtain a non-existent portfolio. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testGetNonExisting() { - every { portfolioService.findOne("testUser", 1, 1) } returns null - - Given { - pathParam("project", "1") - } When { - get("/1") - } Then { - statusCode(404) - contentType(ContentType.JSON) - } - } - - /** - * Test that tries to obtain a portfolio. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testGetExisting() { - every { portfolioService.findOne("testUser", 1, 1) } returns dummyPortfolio - - Given { - pathParam("project", "1") - } When { - get("/1") - } Then { - statusCode(200) - contentType(ContentType.JSON) - body("id", Matchers.equalTo(1)) - } - } - - /** - * Test to delete a non-existent portfolio. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testDeleteNonExistent() { - every { portfolioService.delete("testUser", 1, 1) } returns null - - Given { - pathParam("project", "1") - } When { - delete("/1") - } Then { - statusCode(404) - } - } - - /** - * Test to delete a portfolio. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testDelete() { - every { portfolioService.delete("testUser", 1, 1) } returns dummyPortfolio - - Given { - pathParam("project", "1") - } When { - delete("/1") - } Then { - statusCode(200) - contentType(ContentType.JSON) - } - } -} diff --git a/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/PortfolioScenarioResourceTest.kt b/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/PortfolioScenarioResourceTest.kt deleted file mode 100644 index dbafa8c0..00000000 --- a/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/PortfolioScenarioResourceTest.kt +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.rest.user - -import io.mockk.every -import io.quarkiverse.test.junit.mockk.InjectMock -import io.quarkus.test.common.http.TestHTTPEndpoint -import io.quarkus.test.junit.QuarkusMock -import io.quarkus.test.junit.QuarkusTest -import io.quarkus.test.security.TestSecurity -import io.restassured.http.ContentType -import io.restassured.module.kotlin.extensions.Given -import io.restassured.module.kotlin.extensions.Then -import io.restassured.module.kotlin.extensions.When -import org.hamcrest.Matchers -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.opendc.web.api.service.ScenarioService -import org.opendc.web.proto.* -import org.opendc.web.proto.user.* -import java.time.Instant - -/** - * Test suite for [PortfolioScenarioResource]. - */ -@QuarkusTest -@TestHTTPEndpoint(PortfolioScenarioResource::class) -class PortfolioScenarioResourceTest { - @InjectMock - private lateinit var scenarioService: ScenarioService - - /** - * Dummy values - */ - private val dummyProject = Project(0, "test", Instant.now(), Instant.now(), ProjectRole.OWNER) - private val dummyPortfolio = Portfolio.Summary(1, 1, "test", Targets(emptySet())) - private val dummyJob = Job(1, JobState.PENDING, Instant.now(), Instant.now(), null) - private val dummyTrace = Trace("bitbrains", "Bitbrains", "vm") - private val dummyTopology = Topology.Summary(1, 1, "test", Instant.now(), Instant.now()) - private val dummyScenario = Scenario( - 1, - 1, - dummyProject, - dummyPortfolio, - "test", - Workload(dummyTrace, 1.0), - dummyTopology, - OperationalPhenomena(false, false), - "test", - dummyJob - ) - - @BeforeEach - fun setUp() { - QuarkusMock.installMockForType(scenarioService, ScenarioService::class.java) - } - - /** - * Test that tries to obtain a portfolio without token. - */ - @Test - fun testGetWithoutToken() { - Given { - pathParam("project", "1") - pathParam("portfolio", "1") - } When { - get() - } Then { - statusCode(401) - } - } - - /** - * Test that tries to obtain a portfolio with an invalid scope. - */ - @Test - @TestSecurity(user = "testUser", roles = ["runner"]) - fun testGetInvalidToken() { - Given { - pathParam("project", "1") - pathParam("portfolio", "1") - } When { - get() - } Then { - statusCode(403) - } - } - - /** - * Test that tries to obtain a non-existent portfolio. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testGet() { - every { scenarioService.findAll("testUser", 1, 1) } returns emptyList() - - Given { - pathParam("project", "1") - pathParam("portfolio", "1") - } When { - get() - } Then { - statusCode(200) - contentType(ContentType.JSON) - } - } - - /** - * Test that tries to create a scenario for a portfolio. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testCreateNonExistent() { - every { scenarioService.create("testUser", 1, any(), any()) } returns null - - Given { - pathParam("project", "1") - pathParam("portfolio", "1") - - body(Scenario.Create("test", Workload.Spec("test", 1.0), 1, OperationalPhenomena(false, false), "test")) - contentType(ContentType.JSON) - } When { - post() - } Then { - statusCode(404) - contentType(ContentType.JSON) - } - } - - /** - * Test that tries to create a scenario for a portfolio. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testCreate() { - every { scenarioService.create("testUser", 1, 1, any()) } returns dummyScenario - - Given { - pathParam("project", "1") - pathParam("portfolio", "1") - - body(Scenario.Create("test", Workload.Spec("test", 1.0), 1, OperationalPhenomena(false, false), "test")) - contentType(ContentType.JSON) - } When { - post() - } Then { - statusCode(200) - contentType(ContentType.JSON) - body("id", Matchers.equalTo(1)) - body("name", Matchers.equalTo("test")) - } - } - - /** - * Test to create a project with an empty body. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testCreateEmpty() { - Given { - pathParam("project", "1") - pathParam("portfolio", "1") - - body("{}") - contentType(ContentType.JSON) - } When { - post() - } Then { - statusCode(400) - contentType(ContentType.JSON) - } - } - - /** - * Test to create a project with a blank name. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testCreateBlankName() { - Given { - pathParam("project", "1") - pathParam("portfolio", "1") - - body(Scenario.Create("", Workload.Spec("test", 1.0), 1, OperationalPhenomena(false, false), "test")) - contentType(ContentType.JSON) - } When { - post() - } Then { - statusCode(400) - contentType(ContentType.JSON) - } - } -} diff --git a/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/ProjectResourceTest.kt b/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/ProjectResourceTest.kt deleted file mode 100644 index bcbcbab1..00000000 --- a/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/ProjectResourceTest.kt +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.rest.user - -import io.mockk.every -import io.quarkiverse.test.junit.mockk.InjectMock -import io.quarkus.test.common.http.TestHTTPEndpoint -import io.quarkus.test.junit.QuarkusMock -import io.quarkus.test.junit.QuarkusTest -import io.quarkus.test.security.TestSecurity -import io.restassured.http.ContentType -import io.restassured.module.kotlin.extensions.Given -import io.restassured.module.kotlin.extensions.Then -import io.restassured.module.kotlin.extensions.When -import org.hamcrest.Matchers.equalTo -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.opendc.web.api.service.ProjectService -import org.opendc.web.proto.user.Project -import org.opendc.web.proto.user.ProjectRole -import java.time.Instant - -/** - * Test suite for [ProjectResource]. - */ -@QuarkusTest -@TestHTTPEndpoint(ProjectResource::class) -class ProjectResourceTest { - @InjectMock - private lateinit var projectService: ProjectService - - /** - * Dummy values. - */ - private val dummyProject = Project(0, "test", Instant.now(), Instant.now(), ProjectRole.OWNER) - - @BeforeEach - fun setUp() { - QuarkusMock.installMockForType(projectService, ProjectService::class.java) - } - - /** - * Test that tries to obtain all projects without token. - */ - @Test - fun testGetAllWithoutToken() { - When { - get() - } Then { - statusCode(401) - } - } - - /** - * Test that tries to obtain all projects with an invalid scope. - */ - @Test - @TestSecurity(user = "testUser", roles = ["runner"]) - fun testGetAllWithInvalidScope() { - When { - get() - } Then { - statusCode(403) - } - } - - /** - * Test that tries to obtain all project for a user. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testGetAll() { - val projects = listOf(dummyProject) - every { projectService.findWithUser("testUser") } returns projects - - When { - get() - } Then { - statusCode(200) - contentType(ContentType.JSON) - body("get(0).name", equalTo("test")) - } - } - - /** - * Test that tries to obtain a non-existent project. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testGetNonExisting() { - every { projectService.findWithUser("testUser", 1) } returns null - - When { - get("/1") - } Then { - statusCode(404) - contentType(ContentType.JSON) - } - } - - /** - * Test that tries to obtain a job. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testGetExisting() { - every { projectService.findWithUser("testUser", 1) } returns dummyProject - - When { - get("/1") - } Then { - statusCode(200) - contentType(ContentType.JSON) - body("id", equalTo(0)) - } - } - - /** - * Test that tries to create a project. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testCreate() { - every { projectService.createForUser("testUser", "test") } returns dummyProject - - Given { - body(Project.Create("test")) - contentType(ContentType.JSON) - } When { - post() - } Then { - statusCode(200) - contentType(ContentType.JSON) - body("id", equalTo(0)) - body("name", equalTo("test")) - } - } - - /** - * Test to create a project with an empty body. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testCreateEmpty() { - Given { - body("{}") - contentType(ContentType.JSON) - } When { - post() - } Then { - statusCode(400) - contentType(ContentType.JSON) - } - } - - /** - * Test to create a project with a blank name. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testCreateBlankName() { - Given { - body(Project.Create("")) - contentType(ContentType.JSON) - } When { - post() - } Then { - statusCode(400) - contentType(ContentType.JSON) - } - } - - /** - * Test to delete a non-existent project. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testDeleteNonExistent() { - every { projectService.deleteWithUser("testUser", 1) } returns null - - When { - delete("/1") - } Then { - statusCode(404) - contentType(ContentType.JSON) - } - } - - /** - * Test to delete a project. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testDelete() { - every { projectService.deleteWithUser("testUser", 1) } returns dummyProject - - When { - delete("/1") - } Then { - statusCode(200) - contentType(ContentType.JSON) - } - } - - /** - * Test to delete a project which the user does not own. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testDeleteNonOwner() { - every { projectService.deleteWithUser("testUser", 1) } throws IllegalArgumentException("User does not own project") - - When { - delete("/1") - } Then { - statusCode(403) - contentType(ContentType.JSON) - } - } -} diff --git a/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/ScenarioResourceTest.kt b/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/ScenarioResourceTest.kt deleted file mode 100644 index 65e6e9a1..00000000 --- a/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/ScenarioResourceTest.kt +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.rest.user - -import io.mockk.every -import io.quarkiverse.test.junit.mockk.InjectMock -import io.quarkus.test.common.http.TestHTTPEndpoint -import io.quarkus.test.junit.QuarkusMock -import io.quarkus.test.junit.QuarkusTest -import io.quarkus.test.security.TestSecurity -import io.restassured.http.ContentType -import io.restassured.module.kotlin.extensions.Given -import io.restassured.module.kotlin.extensions.Then -import io.restassured.module.kotlin.extensions.When -import org.hamcrest.Matchers -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.opendc.web.api.service.ScenarioService -import org.opendc.web.proto.* -import org.opendc.web.proto.user.* -import java.time.Instant - -/** - * Test suite for [ScenarioResource]. - */ -@QuarkusTest -@TestHTTPEndpoint(ScenarioResource::class) -class ScenarioResourceTest { - @InjectMock - private lateinit var scenarioService: ScenarioService - - /** - * Dummy values - */ - private val dummyProject = Project(0, "test", Instant.now(), Instant.now(), ProjectRole.OWNER) - private val dummyPortfolio = Portfolio.Summary(1, 1, "test", Targets(emptySet())) - private val dummyJob = Job(1, JobState.PENDING, Instant.now(), Instant.now(), null) - private val dummyTrace = Trace("bitbrains", "Bitbrains", "vm") - private val dummyTopology = Topology.Summary(1, 1, "test", Instant.now(), Instant.now()) - private val dummyScenario = Scenario( - 1, - 1, - dummyProject, - dummyPortfolio, - "test", - Workload(dummyTrace, 1.0), - dummyTopology, - OperationalPhenomena(false, false), - "test", - dummyJob - ) - - @BeforeEach - fun setUp() { - QuarkusMock.installMockForType(scenarioService, ScenarioService::class.java) - } - - /** - * Test that tries to obtain a scenario without token. - */ - @Test - fun testGetWithoutToken() { - Given { - pathParam("project", "1") - } When { - get("/1") - } Then { - statusCode(401) - } - } - - /** - * Test that tries to obtain a scenario with an invalid scope. - */ - @Test - @TestSecurity(user = "testUser", roles = ["runner"]) - fun testGetInvalidToken() { - Given { - pathParam("project", "1") - } When { - get("/1") - } Then { - statusCode(403) - } - } - - /** - * Test that tries to obtain a non-existent scenario. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testGetNonExisting() { - every { scenarioService.findOne("testUser", 1, 1) } returns null - - Given { - pathParam("project", "1") - } When { - get("/1") - } Then { - statusCode(404) - contentType(ContentType.JSON) - } - } - - /** - * Test that tries to obtain a scenario. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testGetExisting() { - every { scenarioService.findOne("testUser", 1, 1) } returns dummyScenario - - Given { - pathParam("project", "1") - } When { - get("/1") - } Then { - statusCode(200) - contentType(ContentType.JSON) - body("id", Matchers.equalTo(1)) - } - } - - /** - * Test to delete a non-existent scenario. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testDeleteNonExistent() { - every { scenarioService.delete("testUser", 1, 1) } returns null - - Given { - pathParam("project", "1") - } When { - delete("/1") - } Then { - statusCode(404) - } - } - - /** - * Test to delete a scenario. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testDelete() { - every { scenarioService.delete("testUser", 1, 1) } returns dummyScenario - - Given { - pathParam("project", "1") - } When { - delete("/1") - } Then { - statusCode(200) - contentType(ContentType.JSON) - } - } -} diff --git a/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/TopologyResourceTest.kt b/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/TopologyResourceTest.kt deleted file mode 100644 index ececeaca..00000000 --- a/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/TopologyResourceTest.kt +++ /dev/null @@ -1,304 +0,0 @@ -/* - * Copyright (c) 2022 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.web.api.rest.user - -import io.mockk.every -import io.quarkiverse.test.junit.mockk.InjectMock -import io.quarkus.test.common.http.TestHTTPEndpoint -import io.quarkus.test.junit.QuarkusMock -import io.quarkus.test.junit.QuarkusTest -import io.quarkus.test.security.TestSecurity -import io.restassured.http.ContentType -import io.restassured.module.kotlin.extensions.Given -import io.restassured.module.kotlin.extensions.Then -import io.restassured.module.kotlin.extensions.When -import org.hamcrest.Matchers -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.opendc.web.api.service.TopologyService -import org.opendc.web.proto.user.Project -import org.opendc.web.proto.user.ProjectRole -import org.opendc.web.proto.user.Topology -import java.time.Instant - -/** - * Test suite for [TopologyResource]. - */ -@QuarkusTest -@TestHTTPEndpoint(TopologyResource::class) -class TopologyResourceTest { - @InjectMock - private lateinit var topologyService: TopologyService - - /** - * Dummy project and topology. - */ - private val dummyProject = Project(1, "test", Instant.now(), Instant.now(), ProjectRole.OWNER) - private val dummyTopology = Topology(1, 1, dummyProject, "test", emptyList(), Instant.now(), Instant.now()) - - @BeforeEach - fun setUp() { - QuarkusMock.installMockForType(topologyService, TopologyService::class.java) - } - - /** - * Test that tries to obtain the list of topologies belonging to a project. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testGetForProject() { - every { topologyService.findAll("testUser", 1) } returns emptyList() - - Given { - pathParam("project", "1") - } When { - get() - } Then { - statusCode(200) - contentType(ContentType.JSON) - } - } - - /** - * Test that tries to create a topology for a project. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testCreateNonExistent() { - every { topologyService.create("testUser", 1, any()) } returns null - - Given { - pathParam("project", "1") - - body(Topology.Create("test", emptyList())) - contentType(ContentType.JSON) - } When { - post() - } Then { - statusCode(404) - contentType(ContentType.JSON) - } - } - - /** - * Test that tries to create a topology for a project. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testCreate() { - every { topologyService.create("testUser", 1, any()) } returns dummyTopology - - Given { - pathParam("project", "1") - - body(Topology.Create("test", emptyList())) - contentType(ContentType.JSON) - } When { - post() - } Then { - statusCode(200) - contentType(ContentType.JSON) - body("id", Matchers.equalTo(1)) - body("name", Matchers.equalTo("test")) - } - } - - /** - * Test to create a topology with an empty body. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testCreateEmpty() { - Given { - pathParam("project", "1") - - body("{}") - contentType(ContentType.JSON) - } When { - post() - } Then { - statusCode(400) - contentType(ContentType.JSON) - } - } - - /** - * Test to create a topology with a blank name. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testCreateBlankName() { - Given { - pathParam("project", "1") - - body(Topology.Create("", emptyList())) - contentType(ContentType.JSON) - } When { - post() - } Then { - statusCode(400) - contentType(ContentType.JSON) - } - } - - /** - * Test that tries to obtain a topology without token. - */ - @Test - fun testGetWithoutToken() { - Given { - pathParam("project", "1") - } When { - get("/1") - } Then { - statusCode(401) - } - } - - /** - * Test that tries to obtain a topology with an invalid scope. - */ - @Test - @TestSecurity(user = "testUser", roles = ["runner"]) - fun testGetInvalidToken() { - Given { - pathParam("project", "1") - } When { - get("/1") - } Then { - statusCode(403) - } - } - - /** - * Test that tries to obtain a non-existent topology. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testGetNonExisting() { - every { topologyService.findOne("testUser", 1, 1) } returns null - - Given { - pathParam("project", "1") - } When { - get("/1") - } Then { - statusCode(404) - contentType(ContentType.JSON) - } - } - - /** - * Test that tries to obtain a topology. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testGetExisting() { - every { topologyService.findOne("testUser", 1, 1) } returns dummyTopology - - Given { - pathParam("project", "1") - } When { - get("/1") - } Then { - statusCode(200) - contentType(ContentType.JSON) - body("id", Matchers.equalTo(1)) - println(extract().asPrettyString()) - } - } - - /** - * Test to delete a non-existent topology. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testUpdateNonExistent() { - every { topologyService.update("testUser", any(), any(), any()) } returns null - - Given { - pathParam("project", "1") - body(Topology.Update(emptyList())) - contentType(ContentType.JSON) - } When { - put("/1") - } Then { - statusCode(404) - } - } - - /** - * Test to update a topology. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testUpdate() { - every { topologyService.update("testUser", any(), any(), any()) } returns dummyTopology - - Given { - pathParam("project", "1") - body(Topology.Update(emptyList())) - contentType(ContentType.JSON) - } When { - put("/1") - } Then { - statusCode(200) - contentType(ContentType.JSON) - } - } - - /** - * Test to delete a non-existent topology. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testDeleteNonExistent() { - every { topologyService.delete("testUser", 1, 1) } returns null - - Given { - pathParam("project", "1") - } When { - delete("/1") - } Then { - statusCode(404) - } - } - - /** - * Test to delete a topology. - */ - @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testDelete() { - every { topologyService.delete("testUser", 1, 1) } returns dummyTopology - - Given { - pathParam("project", "1") - } When { - delete("/1") - } Then { - statusCode(200) - contentType(ContentType.JSON) - } - } -} diff --git a/opendc-web/opendc-web-server/Dockerfile b/opendc-web/opendc-web-server/Dockerfile new file mode 100644 index 00000000..444d787e --- /dev/null +++ b/opendc-web/opendc-web-server/Dockerfile @@ -0,0 +1,17 @@ +FROM openjdk:17-slim +MAINTAINER OpenDC Maintainers + +# Obtain (cache) Gradle wrapper +COPY gradlew /app/ +COPY gradle /app/gradle +WORKDIR /app +RUN ./gradlew --version + +# Build project +COPY ./ /app/ +RUN ./gradlew --no-daemon :opendc-web:opendc-web-server:quarkusBuild -Dquarkus.profile=docker + +FROM openjdk:17-slim +COPY --from=0 /app/opendc-web/opendc-web-server/build/quarkus-app /opt/opendc +WORKDIR /opt/opendc +CMD java -jar quarkus-run.jar diff --git a/opendc-web/opendc-web-server/build.gradle.kts b/opendc-web/opendc-web-server/build.gradle.kts new file mode 100644 index 00000000..d6b9164c --- /dev/null +++ b/opendc-web/opendc-web-server/build.gradle.kts @@ -0,0 +1,88 @@ +/* + * 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. + */ + +description = "Web server of OpenDC" + +/* Build configuration */ +plugins { + `quarkus-conventions` + distribution +} + +dependencies { + implementation(enforcedPlatform(libs.quarkus.bom)) + + implementation(projects.opendcWeb.opendcWebProto) + compileOnly(projects.opendcWeb.opendcWebUiQuarkusDeployment) /* Temporary fix for Quarkus/Gradle issues */ + compileOnly(projects.opendcWeb.opendcWebRunnerQuarkusDeployment) + implementation(projects.opendcWeb.opendcWebUiQuarkus) + implementation(projects.opendcWeb.opendcWebRunnerQuarkus) + + implementation(libs.quarkus.kotlin) + implementation(libs.quarkus.resteasy.core) + implementation(libs.quarkus.resteasy.jackson) + implementation(libs.jackson.module.kotlin) + implementation(libs.quarkus.smallrye.openapi) + + implementation(libs.quarkus.security) + implementation(libs.quarkus.oidc) + + implementation(libs.quarkus.hibernate.orm) + implementation(libs.quarkus.hibernate.validator) + implementation(libs.quarkus.jdbc.postgresql) + implementation(libs.quarkus.jdbc.h2) + + testImplementation(libs.quarkus.junit5.core) + testImplementation(libs.quarkus.junit5.mockk) + testImplementation(libs.quarkus.jacoco) + testImplementation(libs.restassured.core) + testImplementation(libs.restassured.kotlin) + testImplementation(libs.quarkus.test.security) + testImplementation(libs.quarkus.jdbc.h2) +} + +val createStartScripts by tasks.creating(CreateStartScripts::class) { + applicationName = "opendc-server" + mainClass.set("io.quarkus.bootstrap.runner.QuarkusEntryPoint") + classpath = files("lib/quarkus-run.jar") + outputDir = project.buildDir.resolve("scripts") +} + +distributions { + main { + distributionBaseName.set("opendc") + + contents { + from("../../LICENSE.txt") + from("config") { + into("config") + } + + from(createStartScripts) { + into("bin") + } + from(tasks.quarkusBuild) { + into("lib") + } + } + } +} diff --git a/opendc-web/opendc-web-server/config/application.properties b/opendc-web/opendc-web-server/config/application.properties new file mode 100644 index 00000000..30eaaef9 --- /dev/null +++ b/opendc-web/opendc-web-server/config/application.properties @@ -0,0 +1 @@ +# Custom server properties diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/OpenDCApplication.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/OpenDCApplication.kt new file mode 100644 index 00000000..1a426095 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/OpenDCApplication.kt @@ -0,0 +1,30 @@ +/* + * 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.web.server + +import javax.ws.rs.core.Application + +/** + * [Application] definition for the OpenDC web API. + */ +class OpenDCApplication : Application() diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Job.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Job.kt new file mode 100644 index 00000000..024e7b89 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Job.kt @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2022 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.web.server.model + +import org.hibernate.annotations.Type +import org.hibernate.annotations.TypeDef +import org.opendc.web.proto.JobState +import org.opendc.web.server.util.hibernate.json.JsonType +import java.time.Instant +import javax.persistence.* + +/** + * A simulation job to be run by the simulator. + */ +@TypeDef(name = "json", typeClass = JsonType::class) +@Entity +@Table(name = "jobs") +@NamedQueries( + value = [ + NamedQuery( + name = "Job.findAll", + query = "SELECT j FROM Job j WHERE j.state = :state" + ), + NamedQuery( + name = "Job.updateOne", + query = """ + UPDATE Job j + SET j.state = :newState, j.updatedAt = :updatedAt, j.results = :results + WHERE j.id = :id AND j.state = :oldState + """ + ) + ] +) +class Job( + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + val id: Long, + + @OneToOne(optional = false, mappedBy = "job", fetch = FetchType.EAGER) + @JoinColumn(name = "scenario_id", nullable = false) + val scenario: Scenario, + + @Column(name = "created_at", nullable = false, updatable = false) + val createdAt: Instant, + + /** + * The number of simulation runs to perform. + */ + @Column(nullable = false, updatable = false) + val repeats: Int +) { + /** + * The instant at which the job was updated. + */ + @Column(name = "updated_at", nullable = false) + var updatedAt: Instant = createdAt + + /** + * The state of the job. + */ + @Column(nullable = false) + var state: JobState = JobState.PENDING + + /** + * Experiment results in JSON + */ + @Type(type = "json") + @Column(columnDefinition = "jsonb") + var results: Map? = null + + /** + * Return a string representation of this job. + */ + override fun toString(): String = "Job[id=$id,scenario=${scenario.id},state=$state]" +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Portfolio.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Portfolio.kt new file mode 100644 index 00000000..3e3f76a0 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Portfolio.kt @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2022 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.web.server.model + +import org.hibernate.annotations.Type +import org.hibernate.annotations.TypeDef +import org.opendc.web.proto.Targets +import org.opendc.web.server.util.hibernate.json.JsonType +import javax.persistence.* + +/** + * A portfolio is the composition of multiple scenarios. + */ +@TypeDef(name = "json", typeClass = JsonType::class) +@Entity +@Table( + name = "portfolios", + uniqueConstraints = [UniqueConstraint(columnNames = ["project_id", "number"])], + indexes = [Index(name = "fn_portfolios_number", columnList = "project_id, number")] +) +@NamedQueries( + value = [ + NamedQuery( + name = "Portfolio.findAll", + query = "SELECT p FROM Portfolio p WHERE p.project.id = :projectId" + ), + NamedQuery( + name = "Portfolio.findOne", + query = "SELECT p FROM Portfolio p WHERE p.project.id = :projectId AND p.number = :number" + ) + ] +) +class Portfolio( + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + val id: Long, + + /** + * Unique number of the portfolio for the project. + */ + @Column(nullable = false) + val number: Int, + + @Column(nullable = false) + val name: String, + + @ManyToOne(optional = false) + @JoinColumn(name = "project_id", nullable = false) + val project: Project, + + /** + * The portfolio targets (metrics, repetitions). + */ + @Type(type = "json") + @Column(columnDefinition = "jsonb", nullable = false, updatable = false) + val targets: Targets, +) { + /** + * The scenarios in this portfolio. + */ + @OneToMany(cascade = [CascadeType.ALL], mappedBy = "portfolio", orphanRemoval = true) + @OrderBy("id ASC") + val scenarios: MutableSet = mutableSetOf() + + /** + * Return a string representation of this portfolio. + */ + override fun toString(): String = "Job[id=$id,name=$name,project=${project.id},targets=$targets]" +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Project.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Project.kt new file mode 100644 index 00000000..aa98b677 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Project.kt @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2022 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.web.server.model + +import java.time.Instant +import javax.persistence.* + +/** + * A project in OpenDC encapsulates all the datacenter designs and simulation runs for a set of users. + */ +@Entity +@Table(name = "projects") +@NamedQueries( + value = [ + NamedQuery( + name = "Project.findAll", + query = """ + SELECT a + FROM ProjectAuthorization a + WHERE a.key.userId = :userId + """ + ), + NamedQuery( + name = "Project.allocatePortfolio", + query = """ + UPDATE Project p + SET p.portfoliosCreated = :oldState + 1, p.updatedAt = :now + WHERE p.id = :id AND p.portfoliosCreated = :oldState + """ + ), + NamedQuery( + name = "Project.allocateTopology", + query = """ + UPDATE Project p + SET p.topologiesCreated = :oldState + 1, p.updatedAt = :now + WHERE p.id = :id AND p.topologiesCreated = :oldState + """ + ), + NamedQuery( + name = "Project.allocateScenario", + query = """ + UPDATE Project p + SET p.scenariosCreated = :oldState + 1, p.updatedAt = :now + WHERE p.id = :id AND p.scenariosCreated = :oldState + """ + ) + ] +) +class Project( + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + val id: Long, + + @Column(nullable = false) + var name: String, + + @Column(name = "created_at", nullable = false, updatable = false) + val createdAt: Instant, +) { + /** + * The instant at which the project was updated. + */ + @Column(name = "updated_at", nullable = false) + var updatedAt: Instant = createdAt + + /** + * The portfolios belonging to this project. + */ + @OneToMany(cascade = [CascadeType.ALL], mappedBy = "project", orphanRemoval = true) + @OrderBy("id ASC") + val portfolios: MutableSet = mutableSetOf() + + /** + * The number of portfolios created for this project (including deleted portfolios). + */ + @Column(name = "portfolios_created", nullable = false) + var portfoliosCreated: Int = 0 + + /** + * The topologies belonging to this project. + */ + @OneToMany(cascade = [CascadeType.ALL], mappedBy = "project", orphanRemoval = true) + @OrderBy("id ASC") + val topologies: MutableSet = mutableSetOf() + + /** + * The number of topologies created for this project (including deleted topologies). + */ + @Column(name = "topologies_created", nullable = false) + var topologiesCreated: Int = 0 + + /** + * The scenarios belonging to this project. + */ + @OneToMany(mappedBy = "project", orphanRemoval = true) + val scenarios: MutableSet = mutableSetOf() + + /** + * The number of scenarios created for this project (including deleted scenarios). + */ + @Column(name = "scenarios_created", nullable = false) + var scenariosCreated: Int = 0 + + /** + * The users authorized to access the project. + */ + @OneToMany(cascade = [CascadeType.ALL], mappedBy = "project", orphanRemoval = true) + val authorizations: MutableSet = mutableSetOf() + + /** + * Return a string representation of this project. + */ + override fun toString(): String = "Project[id=$id,name=$name]" +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/ProjectAuthorization.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/ProjectAuthorization.kt new file mode 100644 index 00000000..a353186e --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/ProjectAuthorization.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022 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.web.server.model + +import org.opendc.web.proto.user.ProjectRole +import javax.persistence.* + +/** + * An authorization for some user to participate in a project. + */ +@Entity +@Table(name = "project_authorizations") +class ProjectAuthorization( + /** + * The user identifier of the authorization. + */ + @EmbeddedId + val key: ProjectAuthorizationKey, + + /** + * The project that the user is authorized to participate in. + */ + @ManyToOne(optional = false) + @MapsId("projectId") + @JoinColumn(name = "project_id", updatable = false, insertable = false, nullable = false) + val project: Project, + + /** + * The role of the user in the project. + */ + @Column(nullable = false) + val role: ProjectRole +) { + /** + * Return a string representation of this project authorization. + */ + override fun toString(): String = "ProjectAuthorization[project=${key.projectId},user=${key.userId},role=$role]" +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/ProjectAuthorizationKey.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/ProjectAuthorizationKey.kt new file mode 100644 index 00000000..449b6608 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/ProjectAuthorizationKey.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 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.web.server.model + +import javax.persistence.Column +import javax.persistence.Embeddable + +/** + * Key for representing a [ProjectAuthorization] object. + */ +@Embeddable +data class ProjectAuthorizationKey( + @Column(name = "user_id", nullable = false) + val userId: String, + + @Column(name = "project_id", nullable = false) + val projectId: Long +) : java.io.Serializable diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Scenario.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Scenario.kt new file mode 100644 index 00000000..e40cff47 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Scenario.kt @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2022 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.web.server.model + +import org.hibernate.annotations.Type +import org.hibernate.annotations.TypeDef +import org.opendc.web.proto.OperationalPhenomena +import org.opendc.web.server.util.hibernate.json.JsonType +import javax.persistence.* + +/** + * A single scenario to be explored by the simulator. + */ +@TypeDef(name = "json", typeClass = JsonType::class) +@Entity +@Table( + name = "scenarios", + uniqueConstraints = [UniqueConstraint(columnNames = ["project_id", "number"])], + indexes = [Index(name = "fn_scenarios_number", columnList = "project_id, number")] +) +@NamedQueries( + value = [ + NamedQuery( + name = "Scenario.findAll", + query = "SELECT s FROM Scenario s WHERE s.project.id = :projectId" + ), + NamedQuery( + name = "Scenario.findAllForPortfolio", + query = """ + SELECT s + FROM Scenario s + JOIN Portfolio p ON p.id = s.portfolio.id AND p.number = :number + WHERE s.project.id = :projectId + """ + ), + NamedQuery( + name = "Scenario.findOne", + query = "SELECT s FROM Scenario s WHERE s.project.id = :projectId AND s.number = :number" + ) + ] +) +class Scenario( + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + val id: Long, + + /** + * Unique number of the scenario for the project. + */ + @Column(nullable = false) + val number: Int, + + @Column(nullable = false, updatable = false) + val name: String, + + @ManyToOne(optional = false) + @JoinColumn(name = "project_id", nullable = false) + val project: Project, + + @ManyToOne(optional = false) + @JoinColumn(name = "portfolio_id", nullable = false) + val portfolio: Portfolio, + + @Embedded + val workload: Workload, + + @ManyToOne(optional = false) + val topology: Topology, + + @Type(type = "json") + @Column(columnDefinition = "jsonb", nullable = false, updatable = false) + val phenomena: OperationalPhenomena, + + @Column(name = "scheduler_name", nullable = false, updatable = false) + val schedulerName: String, +) { + /** + * The [Job] associated with the scenario. + */ + @OneToOne(cascade = [CascadeType.ALL]) + lateinit var job: Job + + /** + * Return a string representation of this scenario. + */ + override fun toString(): String = "Scenario[id=$id,name=$name,project=${project.id},portfolio=${portfolio.id}]" +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Topology.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Topology.kt new file mode 100644 index 00000000..a190b1ee --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Topology.kt @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2022 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.web.server.model + +import org.hibernate.annotations.Type +import org.hibernate.annotations.TypeDef +import org.opendc.web.proto.Room +import org.opendc.web.server.util.hibernate.json.JsonType +import java.time.Instant +import javax.persistence.* + +/** + * A datacenter design in OpenDC. + */ +@TypeDef(name = "json", typeClass = JsonType::class) +@Entity +@Table( + name = "topologies", + uniqueConstraints = [UniqueConstraint(columnNames = ["project_id", "number"])], + indexes = [Index(name = "fn_topologies_number", columnList = "project_id, number")] +) +@NamedQueries( + value = [ + NamedQuery( + name = "Topology.findAll", + query = "SELECT t FROM Topology t WHERE t.project.id = :projectId" + ), + NamedQuery( + name = "Topology.findOne", + query = "SELECT t FROM Topology t WHERE t.project.id = :projectId AND t.number = :number" + ) + ] +) +class Topology( + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + val id: Long, + + /** + * Unique number of the topology for the project. + */ + @Column(nullable = false) + val number: Int, + + @Column(nullable = false) + val name: String, + + @ManyToOne(optional = false) + @JoinColumn(name = "project_id", nullable = false) + val project: Project, + + @Column(name = "created_at", nullable = false, updatable = false) + val createdAt: Instant, + + /** + * Datacenter design in JSON + */ + @Type(type = "json") + @Column(columnDefinition = "jsonb", nullable = false) + var rooms: List = emptyList() +) { + /** + * The instant at which the topology was updated. + */ + @Column(name = "updated_at", nullable = false) + var updatedAt: Instant = createdAt + + /** + * Return a string representation of this topology. + */ + override fun toString(): String = "Topology[id=$id,name=$name,project=${project.id}]" +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Trace.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Trace.kt new file mode 100644 index 00000000..8aaac613 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Trace.kt @@ -0,0 +1,58 @@ +/* + * 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.web.server.model + +import javax.persistence.* + +/** + * A workload trace available for simulation. + * + * @param id The unique identifier of the trace. + * @param name The name of the trace. + * @param type The type of trace. + */ +@Entity +@Table(name = "traces") +@NamedQueries( + value = [ + NamedQuery( + name = "Trace.findAll", + query = "SELECT t FROM Trace t" + ), + ] +) +class Trace( + @Id + val id: String, + + @Column(nullable = false, updatable = false) + val name: String, + + @Column(nullable = false, updatable = false) + val type: String, +) { + /** + * Return a string representation of this trace. + */ + override fun toString(): String = "Trace[id=$id,name=$name,type=$type]" +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Workload.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Workload.kt new file mode 100644 index 00000000..9c59dc25 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/model/Workload.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022 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.web.server.model + +import javax.persistence.Column +import javax.persistence.Embeddable +import javax.persistence.ManyToOne + +/** + * Specification of the workload for a [Scenario]. + */ +@Embeddable +class Workload( + @ManyToOne(optional = false) + val trace: Trace, + + @Column(name = "sampling_fraction", nullable = false, updatable = false) + val samplingFraction: Double +) diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/repository/JobRepository.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/repository/JobRepository.kt new file mode 100644 index 00000000..5fee07a3 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/repository/JobRepository.kt @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2022 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.web.server.repository + +import org.opendc.web.proto.JobState +import org.opendc.web.server.model.Job +import java.time.Instant +import javax.enterprise.context.ApplicationScoped +import javax.inject.Inject +import javax.persistence.EntityManager + +/** + * A repository to manage [Job] entities. + */ +@ApplicationScoped +class JobRepository @Inject constructor(private val em: EntityManager) { + /** + * Find all jobs currently residing in [state]. + * + * @param state The state in which the jobs should be. + * @return The list of jobs in state [state]. + */ + fun findAll(state: JobState): List { + return em.createNamedQuery("Job.findAll", Job::class.java) + .setParameter("state", state) + .resultList + } + + /** + * Find the [Job] with the specified [id]. + * + * @param id The unique identifier of the job. + * @return The trace or `null` if it does not exist. + */ + fun findOne(id: Long): Job? { + return em.find(Job::class.java, id) + } + + /** + * Delete the specified [job]. + */ + fun delete(job: Job) { + em.remove(job) + } + + /** + * Save the specified [job] to the database. + */ + fun save(job: Job) { + em.persist(job) + } + + /** + * Atomically update the specified [job]. + * + * @param job The job to update atomically. + * @param newState The new state to enter into. + * @param time The time at which the update occurs. + * @param results The results to possible set. + * @return `true` when the update succeeded`, `false` when there was a conflict. + */ + fun updateOne(job: Job, newState: JobState, time: Instant, results: Map?): Boolean { + val count = em.createNamedQuery("Job.updateOne") + .setParameter("id", job.id) + .setParameter("oldState", job.state) + .setParameter("newState", newState) + .setParameter("updatedAt", Instant.now()) + .setParameter("results", results) + .executeUpdate() + em.refresh(job) + return count > 0 + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/repository/PortfolioRepository.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/repository/PortfolioRepository.kt new file mode 100644 index 00000000..77130c15 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/repository/PortfolioRepository.kt @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2022 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.web.server.repository + +import org.opendc.web.server.model.Portfolio +import javax.enterprise.context.ApplicationScoped +import javax.inject.Inject +import javax.persistence.EntityManager + +/** + * A repository to manage [Portfolio] entities. + */ +@ApplicationScoped +class PortfolioRepository @Inject constructor(private val em: EntityManager) { + /** + * Find all [Portfolio]s that belong to [project][projectId]. + * + * @param projectId The unique identifier of the project. + * @return The list of portfolios that belong to the specified project. + */ + fun findAll(projectId: Long): List { + return em.createNamedQuery("Portfolio.findAll", Portfolio::class.java) + .setParameter("projectId", projectId) + .resultList + } + + /** + * Find the [Portfolio] with the specified [number] belonging to [project][projectId]. + * + * @param projectId The unique identifier of the project. + * @param number The number of the portfolio. + * @return The portfolio or `null` if it does not exist. + */ + fun findOne(projectId: Long, number: Int): Portfolio? { + return em.createNamedQuery("Portfolio.findOne", Portfolio::class.java) + .setParameter("projectId", projectId) + .setParameter("number", number) + .setMaxResults(1) + .resultList + .firstOrNull() + } + + /** + * Delete the specified [portfolio]. + */ + fun delete(portfolio: Portfolio) { + em.remove(portfolio) + } + + /** + * Save the specified [portfolio] to the database. + */ + fun save(portfolio: Portfolio) { + em.persist(portfolio) + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/repository/ProjectRepository.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/repository/ProjectRepository.kt new file mode 100644 index 00000000..519da3de --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/repository/ProjectRepository.kt @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2022 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.web.server.repository + +import org.opendc.web.server.model.Project +import org.opendc.web.server.model.ProjectAuthorization +import org.opendc.web.server.model.ProjectAuthorizationKey +import java.time.Instant +import javax.enterprise.context.ApplicationScoped +import javax.inject.Inject +import javax.persistence.EntityManager + +/** + * A repository to manage [Project] entities. + */ +@ApplicationScoped +class ProjectRepository @Inject constructor(private val em: EntityManager) { + /** + * List all projects for the user with the specified [userId]. + * + * @param userId The identifier of the user that is requesting the list of projects. + * @return A list of projects that the user has received authorization for. + */ + fun findAll(userId: String): List { + return em.createNamedQuery("Project.findAll", ProjectAuthorization::class.java) + .setParameter("userId", userId) + .resultList + } + + /** + * Find the project with [id] for the user with the specified [userId]. + * + * @param userId The identifier of the user that is requesting the list of projects. + * @param id The unique identifier of the project. + * @return The project with the specified identifier or `null` if it does not exist or is not accessible to the + * user with the specified identifier. + */ + fun findOne(userId: String, id: Long): ProjectAuthorization? { + return em.find(ProjectAuthorization::class.java, ProjectAuthorizationKey(userId, id)) + } + + /** + * Delete the specified [project]. + */ + fun delete(project: Project) { + em.remove(project) + } + + /** + * Save the specified [project] to the database. + */ + fun save(project: Project) { + em.persist(project) + } + + /** + * Save the specified [auth] to the database. + */ + fun save(auth: ProjectAuthorization) { + em.persist(auth) + } + + /** + * Allocate the next portfolio number for the specified [project]. + * + * @param project The project to allocate the portfolio number for. + * @param time The time at which the new portfolio is created. + * @param tries The number of times to try to allocate the number before failing. + */ + fun allocatePortfolio(project: Project, time: Instant, tries: Int = 4): Int { + repeat(tries) { + val count = em.createNamedQuery("Project.allocatePortfolio") + .setParameter("id", project.id) + .setParameter("oldState", project.portfoliosCreated) + .setParameter("now", time) + .executeUpdate() + + if (count > 0) { + return project.portfoliosCreated + 1 + } else { + em.refresh(project) + } + } + + throw IllegalStateException("Failed to allocate next portfolio") + } + + /** + * Allocate the next topology number for the specified [project]. + * + * @param project The project to allocate the topology number for. + * @param time The time at which the new topology is created. + * @param tries The number of times to try to allocate the number before failing. + */ + fun allocateTopology(project: Project, time: Instant, tries: Int = 4): Int { + repeat(tries) { + val count = em.createNamedQuery("Project.allocateTopology") + .setParameter("id", project.id) + .setParameter("oldState", project.topologiesCreated) + .setParameter("now", time) + .executeUpdate() + + if (count > 0) { + return project.topologiesCreated + 1 + } else { + em.refresh(project) + } + } + + throw IllegalStateException("Failed to allocate next topology") + } + + /** + * Allocate the next scenario number for the specified [project]. + * + * @param project The project to allocate the scenario number for. + * @param time The time at which the new scenario is created. + * @param tries The number of times to try to allocate the number before failing. + */ + fun allocateScenario(project: Project, time: Instant, tries: Int = 4): Int { + repeat(tries) { + val count = em.createNamedQuery("Project.allocateScenario") + .setParameter("id", project.id) + .setParameter("oldState", project.scenariosCreated) + .setParameter("now", time) + .executeUpdate() + + if (count > 0) { + return project.scenariosCreated + 1 + } else { + em.refresh(project) + } + } + + throw IllegalStateException("Failed to allocate next scenario") + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/repository/ScenarioRepository.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/repository/ScenarioRepository.kt new file mode 100644 index 00000000..145db71d --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/repository/ScenarioRepository.kt @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2022 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.web.server.repository + +import org.opendc.web.server.model.Scenario +import javax.enterprise.context.ApplicationScoped +import javax.inject.Inject +import javax.persistence.EntityManager + +/** + * A repository to manage [Scenario] entities. + */ +@ApplicationScoped +class ScenarioRepository @Inject constructor(private val em: EntityManager) { + /** + * Find all [Scenario]s that belong to [project][projectId]. + * + * @param projectId The unique identifier of the project. + * @return The list of scenarios that belong to the specified project. + */ + fun findAll(projectId: Long): List { + return em.createNamedQuery("Scenario.findAll", Scenario::class.java) + .setParameter("projectId", projectId) + .resultList + } + + /** + * Find all [Scenario]s that belong to [portfolio][number] of [project][projectId]. + * + * @param projectId The unique identifier of the project. + * @param number The number of the portfolio to which the scenarios should belong. + * @return The list of scenarios that belong to the specified portfolio. + */ + fun findAll(projectId: Long, number: Int): List { + return em.createNamedQuery("Scenario.findAllForPortfolio", Scenario::class.java) + .setParameter("projectId", projectId) + .setParameter("number", number) + .resultList + } + + /** + * Find the [Scenario] with the specified [number] belonging to [project][projectId]. + * + * @param projectId The unique identifier of the project. + * @param number The number of the scenario. + * @return The scenario or `null` if it does not exist. + */ + fun findOne(projectId: Long, number: Int): Scenario? { + return em.createNamedQuery("Scenario.findOne", Scenario::class.java) + .setParameter("projectId", projectId) + .setParameter("number", number) + .setMaxResults(1) + .resultList + .firstOrNull() + } + + /** + * Delete the specified [scenario]. + */ + fun delete(scenario: Scenario) { + em.remove(scenario) + } + + /** + * Save the specified [scenario] to the database. + */ + fun save(scenario: Scenario) { + em.persist(scenario) + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/repository/TopologyRepository.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/repository/TopologyRepository.kt new file mode 100644 index 00000000..e8eadd63 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/repository/TopologyRepository.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2022 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.web.server.repository + +import org.opendc.web.server.model.Topology +import javax.enterprise.context.ApplicationScoped +import javax.inject.Inject +import javax.persistence.EntityManager + +/** + * A repository to manage [Topology] entities. + */ +@ApplicationScoped +class TopologyRepository @Inject constructor(private val em: EntityManager) { + /** + * Find all [Topology]s that belong to [project][projectId]. + * + * @param projectId The unique identifier of the project. + * @return The list of topologies that belong to the specified project. + */ + fun findAll(projectId: Long): List { + return em.createNamedQuery("Topology.findAll", Topology::class.java) + .setParameter("projectId", projectId) + .resultList + } + + /** + * Find the [Topology] with the specified [number] belonging to [project][projectId]. + * + * @param projectId The unique identifier of the project. + * @param number The number of the topology. + * @return The topology or `null` if it does not exist. + */ + fun findOne(projectId: Long, number: Int): Topology? { + return em.createNamedQuery("Topology.findOne", Topology::class.java) + .setParameter("projectId", projectId) + .setParameter("number", number) + .setMaxResults(1) + .resultList + .firstOrNull() + } + + /** + * Find the [Topology] with the specified [id]. + * + * @param id Unique identifier of the topology. + * @return The topology or `null` if it does not exist. + */ + fun findOne(id: Long): Topology? { + return em.find(Topology::class.java, id) + } + + /** + * Delete the specified [topology]. + */ + fun delete(topology: Topology) { + em.remove(topology) + } + + /** + * Save the specified [topology] to the database. + */ + fun save(topology: Topology) { + em.persist(topology) + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/repository/TraceRepository.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/repository/TraceRepository.kt new file mode 100644 index 00000000..f328eea6 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/repository/TraceRepository.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022 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.web.server.repository + +import org.opendc.web.server.model.Trace +import javax.enterprise.context.ApplicationScoped +import javax.inject.Inject +import javax.persistence.EntityManager + +/** + * A repository to manage [Trace] entities. + */ +@ApplicationScoped +class TraceRepository @Inject constructor(private val em: EntityManager) { + /** + * Find all workload traces in the database. + * + * @return The list of available workload traces. + */ + fun findAll(): List { + return em.createNamedQuery("Trace.findAll", Trace::class.java).resultList + } + + /** + * Find the [Trace] with the specified [id]. + * + * @param id The unique identifier of the trace. + * @return The trace or `null` if it does not exist. + */ + fun findOne(id: String): Trace? { + return em.find(Trace::class.java, id) + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/SchedulerResource.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/SchedulerResource.kt new file mode 100644 index 00000000..919b25fc --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/SchedulerResource.kt @@ -0,0 +1,48 @@ +/* + * 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.web.server.rest + +import javax.ws.rs.GET +import javax.ws.rs.Path + +/** + * A resource representing the available schedulers that can be used during experiments. + */ +@Path("/schedulers") +class SchedulerResource { + /** + * Obtain all available schedulers. + */ + @GET + fun getAll() = listOf( + "mem", + "mem-inv", + "core-mem", + "core-mem-inv", + "active-servers", + "active-servers-inv", + "provisioned-cores", + "provisioned-cores-inv", + "random" + ) +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/TraceResource.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/TraceResource.kt new file mode 100644 index 00000000..f46f7f91 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/TraceResource.kt @@ -0,0 +1,51 @@ +/* + * 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.web.server.rest + +import org.opendc.web.proto.Trace +import org.opendc.web.server.service.TraceService +import javax.inject.Inject +import javax.ws.rs.* + +/** + * A resource representing the workload traces available in the OpenDC instance. + */ +@Path("/traces") +class TraceResource @Inject constructor(private val traceService: TraceService) { + /** + * Obtain all available traces. + */ + @GET + fun getAll(): List { + return traceService.findAll() + } + + /** + * Obtain trace information by identifier. + */ + @GET + @Path("{id}") + fun get(@PathParam("id") id: String): Trace { + return traceService.findById(id) ?: throw WebApplicationException("Trace not found", 404) + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/error/GenericExceptionMapper.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/error/GenericExceptionMapper.kt new file mode 100644 index 00000000..d8df72e0 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/error/GenericExceptionMapper.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022 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.web.server.rest.error + +import org.opendc.web.proto.ProtocolError +import javax.ws.rs.WebApplicationException +import javax.ws.rs.core.MediaType +import javax.ws.rs.core.Response +import javax.ws.rs.ext.ExceptionMapper +import javax.ws.rs.ext.Provider + +/** + * Helper class to transform an exception into an JSON error response. + */ +@Provider +class GenericExceptionMapper : ExceptionMapper { + override fun toResponse(exception: Exception): Response { + val code = if (exception is WebApplicationException) exception.response.status else 500 + + return Response.status(code) + .entity(ProtocolError(code, exception.message ?: "Unknown error")) + .type(MediaType.APPLICATION_JSON) + .build() + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/error/MissingKotlinParameterExceptionMapper.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/error/MissingKotlinParameterExceptionMapper.kt new file mode 100644 index 00000000..e50917aa --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/error/MissingKotlinParameterExceptionMapper.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022 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.web.server.rest.error + +import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException +import org.opendc.web.proto.ProtocolError +import javax.ws.rs.core.MediaType +import javax.ws.rs.core.Response +import javax.ws.rs.ext.ExceptionMapper +import javax.ws.rs.ext.Provider + +/** + * An [ExceptionMapper] for [MissingKotlinParameterException] thrown by Jackson. + */ +@Provider +class MissingKotlinParameterExceptionMapper : ExceptionMapper { + override fun toResponse(exception: MissingKotlinParameterException): Response { + return Response.status(Response.Status.BAD_REQUEST) + .entity(ProtocolError(Response.Status.BAD_REQUEST.statusCode, "Field '${exception.parameter.name}' is missing from body.")) + .type(MediaType.APPLICATION_JSON) + .build() + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/runner/JobResource.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/runner/JobResource.kt new file mode 100644 index 00000000..351a2237 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/runner/JobResource.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2022 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.web.server.rest.runner + +import org.opendc.web.proto.runner.Job +import org.opendc.web.server.service.JobService +import javax.annotation.security.RolesAllowed +import javax.inject.Inject +import javax.transaction.Transactional +import javax.validation.Valid +import javax.ws.rs.* + +/** + * A resource representing the available simulation jobs. + */ +@Path("/jobs") +@RolesAllowed("runner") +class JobResource @Inject constructor(private val jobService: JobService) { + /** + * Obtain all pending simulation jobs. + */ + @GET + fun queryPending(): List { + return jobService.queryPending() + } + + /** + * Get a job by identifier. + */ + @GET + @Path("{job}") + fun get(@PathParam("job") id: Long): Job { + return jobService.findById(id) ?: throw WebApplicationException("Job not found", 404) + } + + /** + * Atomically update the state of a job. + */ + @POST + @Path("{job}") + @Transactional + fun update(@PathParam("job") id: Long, @Valid update: Job.Update): Job { + return try { + jobService.updateState(id, update.state, update.results) + ?: throw WebApplicationException("Job not found", 404) + } catch (e: IllegalArgumentException) { + throw WebApplicationException(e, 400) + } catch (e: IllegalStateException) { + throw WebApplicationException(e, 409) + } + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/PortfolioResource.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/PortfolioResource.kt new file mode 100644 index 00000000..352dd491 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/PortfolioResource.kt @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2022 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.web.server.rest.user + +import io.quarkus.security.identity.SecurityIdentity +import org.opendc.web.proto.user.Portfolio +import org.opendc.web.server.service.PortfolioService +import javax.annotation.security.RolesAllowed +import javax.inject.Inject +import javax.transaction.Transactional +import javax.validation.Valid +import javax.ws.rs.* + +/** + * A resource representing the portfolios of a project. + */ +@Path("/projects/{project}/portfolios") +@RolesAllowed("openid") +class PortfolioResource @Inject constructor( + private val portfolioService: PortfolioService, + private val identity: SecurityIdentity, +) { + /** + * Get all portfolios that belong to the specified project. + */ + @GET + fun getAll(@PathParam("project") projectId: Long): List { + return portfolioService.findAll(identity.principal.name, projectId) + } + + /** + * Create a portfolio for this project. + */ + @POST + @Transactional + fun create(@PathParam("project") projectId: Long, @Valid request: Portfolio.Create): Portfolio { + return portfolioService.create(identity.principal.name, projectId, request) ?: throw WebApplicationException("Project not found", 404) + } + + /** + * Obtain a portfolio by its identifier. + */ + @GET + @Path("{portfolio}") + fun get(@PathParam("project") projectId: Long, @PathParam("portfolio") number: Int): Portfolio { + return portfolioService.findOne(identity.principal.name, projectId, number) ?: throw WebApplicationException("Portfolio not found", 404) + } + + /** + * Delete a portfolio. + */ + @DELETE + @Path("{portfolio}") + fun delete(@PathParam("project") projectId: Long, @PathParam("portfolio") number: Int): Portfolio { + return portfolioService.delete(identity.principal.name, projectId, number) ?: throw WebApplicationException("Portfolio not found", 404) + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/PortfolioScenarioResource.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/PortfolioScenarioResource.kt new file mode 100644 index 00000000..f2372bde --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/PortfolioScenarioResource.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2022 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.web.server.rest.user + +import io.quarkus.security.identity.SecurityIdentity +import org.opendc.web.proto.user.Scenario +import org.opendc.web.server.service.ScenarioService +import javax.annotation.security.RolesAllowed +import javax.inject.Inject +import javax.transaction.Transactional +import javax.validation.Valid +import javax.ws.rs.* + +/** + * A resource representing the scenarios of a portfolio. + */ +@Path("/projects/{project}/portfolios/{portfolio}/scenarios") +@RolesAllowed("openid") +class PortfolioScenarioResource @Inject constructor( + private val scenarioService: ScenarioService, + private val identity: SecurityIdentity, +) { + /** + * Get all scenarios that belong to the specified portfolio. + */ + @GET + fun get(@PathParam("project") projectId: Long, @PathParam("portfolio") portfolioNumber: Int): List { + return scenarioService.findAll(identity.principal.name, projectId, portfolioNumber) + } + + /** + * Create a scenario for this portfolio. + */ + @POST + @Transactional + fun create(@PathParam("project") projectId: Long, @PathParam("portfolio") portfolioNumber: Int, @Valid request: Scenario.Create): Scenario { + return scenarioService.create(identity.principal.name, projectId, portfolioNumber, request) ?: throw WebApplicationException("Portfolio not found", 404) + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/ProjectResource.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/ProjectResource.kt new file mode 100644 index 00000000..f3d96f55 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/ProjectResource.kt @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2022 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.web.server.rest.user + +import io.quarkus.security.identity.SecurityIdentity +import org.opendc.web.proto.user.Project +import org.opendc.web.server.service.ProjectService +import javax.annotation.security.RolesAllowed +import javax.inject.Inject +import javax.transaction.Transactional +import javax.validation.Valid +import javax.ws.rs.* + +/** + * A resource representing the created projects. + */ +@Path("/projects") +@RolesAllowed("openid") +class ProjectResource @Inject constructor( + private val projectService: ProjectService, + private val identity: SecurityIdentity +) { + /** + * Obtain all the projects of the current user. + */ + @GET + fun getAll(): List { + return projectService.findWithUser(identity.principal.name) + } + + /** + * Create a new project for the current user. + */ + @POST + @Transactional + fun create(@Valid request: Project.Create): Project { + return projectService.createForUser(identity.principal.name, request.name) + } + + /** + * Obtain a single project by its identifier. + */ + @GET + @Path("{project}") + fun get(@PathParam("project") id: Long): Project { + return projectService.findWithUser(identity.principal.name, id) ?: throw WebApplicationException("Project not found", 404) + } + + /** + * Delete a project. + */ + @DELETE + @Path("{project}") + @Transactional + fun delete(@PathParam("project") id: Long): Project { + try { + return projectService.deleteWithUser(identity.principal.name, id) ?: throw WebApplicationException("Project not found", 404) + } catch (e: IllegalArgumentException) { + throw WebApplicationException(e.message, 403) + } + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/ScenarioResource.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/ScenarioResource.kt new file mode 100644 index 00000000..24cdcb6a --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/ScenarioResource.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2022 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.web.server.rest.user + +import io.quarkus.security.identity.SecurityIdentity +import org.opendc.web.proto.user.Scenario +import org.opendc.web.server.service.ScenarioService +import javax.annotation.security.RolesAllowed +import javax.inject.Inject +import javax.transaction.Transactional +import javax.ws.rs.* + +/** + * A resource representing the scenarios of a portfolio. + */ +@Path("/projects/{project}/scenarios") +@RolesAllowed("openid") +class ScenarioResource @Inject constructor( + private val scenarioService: ScenarioService, + private val identity: SecurityIdentity +) { + /** + * Obtain a scenario by its identifier. + */ + @GET + @Path("{scenario}") + fun get(@PathParam("project") projectId: Long, @PathParam("scenario") number: Int): Scenario { + return scenarioService.findOne(identity.principal.name, projectId, number) ?: throw WebApplicationException("Scenario not found", 404) + } + + /** + * Delete a scenario. + */ + @DELETE + @Path("{scenario}") + @Transactional + fun delete(@PathParam("project") projectId: Long, @PathParam("scenario") number: Int): Scenario { + return scenarioService.delete(identity.principal.name, projectId, number) ?: throw WebApplicationException("Scenario not found", 404) + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/TopologyResource.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/TopologyResource.kt new file mode 100644 index 00000000..40b3741c --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/TopologyResource.kt @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2022 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.web.server.rest.user + +import io.quarkus.security.identity.SecurityIdentity +import org.opendc.web.proto.user.Topology +import org.opendc.web.server.service.TopologyService +import javax.annotation.security.RolesAllowed +import javax.inject.Inject +import javax.transaction.Transactional +import javax.validation.Valid +import javax.ws.rs.* + +/** + * A resource representing the constructed datacenter topologies. + */ +@Path("/projects/{project}/topologies") +@RolesAllowed("openid") +class TopologyResource @Inject constructor( + private val topologyService: TopologyService, + private val identity: SecurityIdentity +) { + /** + * Get all topologies that belong to the specified project. + */ + @GET + fun getAll(@PathParam("project") projectId: Long): List { + return topologyService.findAll(identity.principal.name, projectId) + } + + /** + * Create a topology for this project. + */ + @POST + @Transactional + fun create(@PathParam("project") projectId: Long, @Valid request: Topology.Create): Topology { + return topologyService.create(identity.principal.name, projectId, request) ?: throw WebApplicationException("Topology not found", 404) + } + + /** + * Obtain a topology by its number. + */ + @GET + @Path("{topology}") + fun get(@PathParam("project") projectId: Long, @PathParam("topology") number: Int): Topology { + return topologyService.findOne(identity.principal.name, projectId, number) ?: throw WebApplicationException("Topology not found", 404) + } + + /** + * Update the specified topology by its number. + */ + @PUT + @Path("{topology}") + @Transactional + fun update(@PathParam("project") projectId: Long, @PathParam("topology") number: Int, @Valid request: Topology.Update): Topology { + return topologyService.update(identity.principal.name, projectId, number, request) ?: throw WebApplicationException("Topology not found", 404) + } + + /** + * Delete the specified topology. + */ + @Path("{topology}") + @DELETE + @Transactional + fun delete(@PathParam("project") projectId: Long, @PathParam("topology") number: Int): Topology { + return topologyService.delete(identity.principal.name, projectId, number) ?: throw WebApplicationException("Topology not found", 404) + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/JobService.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/JobService.kt new file mode 100644 index 00000000..6b49e8b6 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/JobService.kt @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2022 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.web.server.service + +import org.opendc.web.proto.JobState +import org.opendc.web.proto.runner.Job +import org.opendc.web.server.repository.JobRepository +import java.time.Instant +import javax.enterprise.context.ApplicationScoped +import javax.inject.Inject + +/** + * Service for managing [Job]s. + */ +@ApplicationScoped +class JobService @Inject constructor(private val repository: JobRepository) { + /** + * Query the pending simulation jobs. + */ + fun queryPending(): List { + return repository.findAll(JobState.PENDING).map { it.toRunnerDto() } + } + + /** + * Find a job by its identifier. + */ + fun findById(id: Long): Job? { + return repository.findOne(id)?.toRunnerDto() + } + + /** + * Atomically update the state of a [Job]. + */ + fun updateState(id: Long, newState: JobState, results: Map?): Job? { + val entity = repository.findOne(id) ?: return null + val state = entity.state + if (!state.isTransitionLegal(newState)) { + throw IllegalArgumentException("Invalid transition from $state to $newState") + } + + val now = Instant.now() + if (!repository.updateOne(entity, newState, now, results)) { + throw IllegalStateException("Conflicting update") + } + + return entity.toRunnerDto() + } + + /** + * Determine whether the transition from [this] to [newState] is legal. + */ + private fun JobState.isTransitionLegal(newState: JobState): Boolean { + // Note that we always allow transitions from the state + return newState == this || when (this) { + JobState.PENDING -> newState == JobState.CLAIMED + JobState.CLAIMED -> newState == JobState.RUNNING || newState == JobState.FAILED + JobState.RUNNING -> newState == JobState.FINISHED || newState == JobState.FAILED + JobState.FINISHED, JobState.FAILED -> false + } + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/PortfolioService.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/PortfolioService.kt new file mode 100644 index 00000000..0d380190 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/PortfolioService.kt @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2022 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.web.server.service + +import org.opendc.web.proto.user.Portfolio +import org.opendc.web.server.model.* +import org.opendc.web.server.repository.PortfolioRepository +import org.opendc.web.server.repository.ProjectRepository +import java.time.Instant +import javax.enterprise.context.ApplicationScoped +import javax.inject.Inject +import org.opendc.web.server.model.Portfolio as PortfolioEntity + +/** + * Service for managing [Portfolio]s. + */ +@ApplicationScoped +class PortfolioService @Inject constructor( + private val projectRepository: ProjectRepository, + private val portfolioRepository: PortfolioRepository +) { + /** + * List all [Portfolio]s that belong a certain project. + */ + fun findAll(userId: String, projectId: Long): List { + // User must have access to project + val auth = projectRepository.findOne(userId, projectId) ?: return emptyList() + val project = auth.toUserDto() + return portfolioRepository.findAll(projectId).map { it.toUserDto(project) } + } + + /** + * Find a [Portfolio] with the specified [number] belonging to [project][projectId]. + */ + fun findOne(userId: String, projectId: Long, number: Int): Portfolio? { + // User must have access to project + val auth = projectRepository.findOne(userId, projectId) ?: return null + return portfolioRepository.findOne(projectId, number)?.toUserDto(auth.toUserDto()) + } + + /** + * Delete the portfolio with the specified [number] belonging to [project][projectId]. + */ + fun delete(userId: String, projectId: Long, number: Int): Portfolio? { + // User must have access to project + val auth = projectRepository.findOne(userId, projectId) + + if (auth == null) { + return null + } else if (!auth.role.canEdit) { + throw IllegalStateException("Not permitted to edit project") + } + + val entity = portfolioRepository.findOne(projectId, number) ?: return null + val portfolio = entity.toUserDto(auth.toUserDto()) + portfolioRepository.delete(entity) + return portfolio + } + + /** + * Construct a new [Portfolio] with the specified name. + */ + fun create(userId: String, projectId: Long, request: Portfolio.Create): Portfolio? { + // User must have access to project + val auth = projectRepository.findOne(userId, projectId) + + if (auth == null) { + return null + } else if (!auth.role.canEdit) { + throw IllegalStateException("Not permitted to edit project") + } + + val now = Instant.now() + val project = auth.project + val number = projectRepository.allocatePortfolio(auth.project, now) + + val portfolio = PortfolioEntity(0, number, request.name, project, request.targets) + + project.portfolios.add(portfolio) + portfolioRepository.save(portfolio) + + return portfolio.toUserDto(auth.toUserDto()) + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/ProjectService.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/ProjectService.kt new file mode 100644 index 00000000..44348195 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/ProjectService.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2022 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.web.server.service + +import org.opendc.web.proto.user.Project +import org.opendc.web.proto.user.ProjectRole +import org.opendc.web.server.model.* +import org.opendc.web.server.repository.ProjectRepository +import java.time.Instant +import javax.enterprise.context.ApplicationScoped +import javax.inject.Inject + +/** + * Service for managing [Project]s. + */ +@ApplicationScoped +class ProjectService @Inject constructor(private val repository: ProjectRepository) { + /** + * List all projects for the user with the specified [userId]. + */ + fun findWithUser(userId: String): List { + return repository.findAll(userId).map { it.toUserDto() } + } + + /** + * Obtain the project with the specified [id] for the user with the specified [userId]. + */ + fun findWithUser(userId: String, id: Long): Project? { + return repository.findOne(userId, id)?.toUserDto() + } + + /** + * Create a new [Project] for the user with the specified [userId]. + */ + fun createForUser(userId: String, name: String): Project { + val now = Instant.now() + val entity = Project(0, name, now) + repository.save(entity) + + val authorization = ProjectAuthorization(ProjectAuthorizationKey(userId, entity.id), entity, ProjectRole.OWNER) + + entity.authorizations.add(authorization) + repository.save(authorization) + + return authorization.toUserDto() + } + + /** + * Delete a project by its identifier. + * + * @param userId The user that invokes the action. + * @param id The identifier of the project. + */ + fun deleteWithUser(userId: String, id: Long): Project? { + val auth = repository.findOne(userId, id) ?: return null + + if (!auth.role.canDelete) { + throw IllegalArgumentException("Not allowed to delete project") + } + + val now = Instant.now() + val project = auth.toUserDto().copy(updatedAt = now) + repository.delete(auth.project) + return project + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/RunnerConversions.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/RunnerConversions.kt new file mode 100644 index 00000000..1dcc95ee --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/RunnerConversions.kt @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2022 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.web.server.service + +import org.opendc.web.server.model.Job +import org.opendc.web.server.model.Portfolio +import org.opendc.web.server.model.Scenario +import org.opendc.web.server.model.Topology + +/** + * Conversions into DTOs provided to OpenDC runners. + */ + +/** + * Convert a [Topology] into a runner-facing DTO. + */ +internal fun Topology.toRunnerDto(): org.opendc.web.proto.runner.Topology { + return org.opendc.web.proto.runner.Topology(id, number, name, rooms, createdAt, updatedAt) +} + +/** + * Convert a [Portfolio] into a runner-facing DTO. + */ +internal fun Portfolio.toRunnerDto(): org.opendc.web.proto.runner.Portfolio { + return org.opendc.web.proto.runner.Portfolio(id, number, name, targets) +} + +/** + * Convert a [Job] into a runner-facing DTO. + */ +internal fun Job.toRunnerDto(): org.opendc.web.proto.runner.Job { + return org.opendc.web.proto.runner.Job(id, scenario.toRunnerDto(), state, createdAt, updatedAt, results) +} + +/** + * Convert a [Job] into a runner-facing DTO. + */ +internal fun Scenario.toRunnerDto(): org.opendc.web.proto.runner.Scenario { + return org.opendc.web.proto.runner.Scenario( + id, + number, + portfolio.toRunnerDto(), + name, + workload.toDto(), + topology.toRunnerDto(), + phenomena, + schedulerName + ) +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/ScenarioService.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/ScenarioService.kt new file mode 100644 index 00000000..5b56068d --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/ScenarioService.kt @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2022 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.web.server.service + +import org.opendc.web.proto.user.Scenario +import org.opendc.web.server.model.* +import org.opendc.web.server.repository.* +import java.time.Instant +import javax.enterprise.context.ApplicationScoped +import javax.inject.Inject + +/** + * Service for managing [Scenario]s. + */ +@ApplicationScoped +class ScenarioService @Inject constructor( + private val projectRepository: ProjectRepository, + private val portfolioRepository: PortfolioRepository, + private val topologyRepository: TopologyRepository, + private val traceRepository: TraceRepository, + private val scenarioRepository: ScenarioRepository, +) { + /** + * List all [Scenario]s that belong a certain portfolio. + */ + fun findAll(userId: String, projectId: Long, number: Int): List { + // User must have access to project + val auth = projectRepository.findOne(userId, projectId) ?: return emptyList() + val project = auth.toUserDto() + return scenarioRepository.findAll(projectId).map { it.toUserDto(project) } + } + + /** + * Obtain a [Scenario] by identifier. + */ + fun findOne(userId: String, projectId: Long, number: Int): Scenario? { + // User must have access to project + val auth = projectRepository.findOne(userId, projectId) ?: return null + val project = auth.toUserDto() + return scenarioRepository.findOne(projectId, number)?.toUserDto(project) + } + + /** + * Delete the specified scenario. + */ + fun delete(userId: String, projectId: Long, number: Int): Scenario? { + // User must have access to project + val auth = projectRepository.findOne(userId, projectId) + + if (auth == null) { + return null + } else if (!auth.role.canEdit) { + throw IllegalStateException("Not permitted to edit project") + } + + val entity = scenarioRepository.findOne(projectId, number) ?: return null + val scenario = entity.toUserDto(auth.toUserDto()) + scenarioRepository.delete(entity) + return scenario + } + + /** + * Construct a new [Scenario] with the specified data. + */ + fun create(userId: String, projectId: Long, portfolioNumber: Int, request: Scenario.Create): Scenario? { + // User must have access to project + val auth = projectRepository.findOne(userId, projectId) + + if (auth == null) { + return null + } else if (!auth.role.canEdit) { + throw IllegalStateException("Not permitted to edit project") + } + + val portfolio = portfolioRepository.findOne(projectId, portfolioNumber) ?: return null + val topology = requireNotNull( + topologyRepository.findOne( + projectId, + request.topology.toInt() + ) + ) { "Referred topology does not exist" } + val trace = + requireNotNull(traceRepository.findOne(request.workload.trace)) { "Referred trace does not exist" } + + val now = Instant.now() + val project = auth.project + val number = projectRepository.allocateScenario(auth.project, now) + + val scenario = Scenario( + 0, + number, + request.name, + project, + portfolio, + Workload(trace, request.workload.samplingFraction), + topology, + request.phenomena, + request.schedulerName + ) + val job = Job(0, scenario, now, portfolio.targets.repeats) + + scenario.job = job + portfolio.scenarios.add(scenario) + scenarioRepository.save(scenario) + + return scenario.toUserDto(auth.toUserDto()) + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/TopologyService.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/TopologyService.kt new file mode 100644 index 00000000..5c2a457a --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/TopologyService.kt @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2022 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.web.server.service + +import org.opendc.web.proto.user.Topology +import org.opendc.web.server.repository.ProjectRepository +import org.opendc.web.server.repository.TopologyRepository +import java.time.Instant +import javax.enterprise.context.ApplicationScoped +import javax.inject.Inject +import org.opendc.web.server.model.Topology as TopologyEntity + +/** + * Service for managing [Topology]s. + */ +@ApplicationScoped +class TopologyService @Inject constructor( + private val projectRepository: ProjectRepository, + private val topologyRepository: TopologyRepository +) { + /** + * List all [Topology]s that belong a certain project. + */ + fun findAll(userId: String, projectId: Long): List { + // User must have access to project + val auth = projectRepository.findOne(userId, projectId) ?: return emptyList() + val project = auth.toUserDto() + return topologyRepository.findAll(projectId).map { it.toUserDto(project) } + } + + /** + * Find the [Topology] with the specified [number] belonging to [project][projectId]. + */ + fun findOne(userId: String, projectId: Long, number: Int): Topology? { + // User must have access to project + val auth = projectRepository.findOne(userId, projectId) ?: return null + return topologyRepository.findOne(projectId, number)?.toUserDto(auth.toUserDto()) + } + + /** + * Delete the [Topology] with the specified [number] belonging to [project][projectId]. + */ + fun delete(userId: String, projectId: Long, number: Int): Topology? { + // User must have access to project + val auth = projectRepository.findOne(userId, projectId) + + if (auth == null) { + return null + } else if (!auth.role.canEdit) { + throw IllegalStateException("Not permitted to edit project") + } + + val entity = topologyRepository.findOne(projectId, number) ?: return null + val now = Instant.now() + val topology = entity.toUserDto(auth.toUserDto()).copy(updatedAt = now) + topologyRepository.delete(entity) + + return topology + } + + /** + * Update a [Topology] with the specified [number] belonging to [project][projectId]. + */ + fun update(userId: String, projectId: Long, number: Int, request: Topology.Update): Topology? { + // User must have access to project + val auth = projectRepository.findOne(userId, projectId) + + if (auth == null) { + return null + } else if (!auth.role.canEdit) { + throw IllegalStateException("Not permitted to edit project") + } + + val entity = topologyRepository.findOne(projectId, number) ?: return null + val now = Instant.now() + + entity.updatedAt = now + entity.rooms = request.rooms + + return entity.toUserDto(auth.toUserDto()) + } + + /** + * Construct a new [Topology] with the specified name. + */ + fun create(userId: String, projectId: Long, request: Topology.Create): Topology? { + // User must have access to project + val auth = projectRepository.findOne(userId, projectId) + + if (auth == null) { + return null + } else if (!auth.role.canEdit) { + throw IllegalStateException("Not permitted to edit project") + } + + val now = Instant.now() + val project = auth.project + val number = projectRepository.allocateTopology(auth.project, now) + + val topology = TopologyEntity(0, number, request.name, project, now, request.rooms) + + project.topologies.add(topology) + topologyRepository.save(topology) + + return topology.toUserDto(auth.toUserDto()) + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/TraceService.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/TraceService.kt new file mode 100644 index 00000000..bd14950c --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/TraceService.kt @@ -0,0 +1,48 @@ +/* + * 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.web.server.service + +import org.opendc.web.proto.Trace +import org.opendc.web.server.repository.TraceRepository +import javax.enterprise.context.ApplicationScoped +import javax.inject.Inject + +/** + * Service for managing [Trace]s. + */ +@ApplicationScoped +class TraceService @Inject constructor(private val repository: TraceRepository) { + /** + * Obtain all available workload traces. + */ + fun findAll(): List { + return repository.findAll().map { it.toUserDto() } + } + + /** + * Obtain a workload trace by identifier. + */ + fun findById(id: String): Trace? { + return repository.findOne(id)?.toUserDto() + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/UserConversions.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/UserConversions.kt new file mode 100644 index 00000000..ee78d103 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/UserConversions.kt @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2022 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.web.server.service + +import org.opendc.web.proto.user.Project +import org.opendc.web.server.model.* + +/** + * Conversions into DTOs provided to users. + */ + +/** + * Convert a [Trace] entity into a [org.opendc.web.proto.Trace] DTO. + */ +internal fun Trace.toUserDto(): org.opendc.web.proto.Trace { + return org.opendc.web.proto.Trace(id, name, type) +} + +/** + * Convert a [ProjectAuthorization] entity into a [Project] DTO. + */ +internal fun ProjectAuthorization.toUserDto(): Project { + return Project(project.id, project.name, project.createdAt, project.updatedAt, role) +} + +/** + * Convert a [Topology] entity into a [org.opendc.web.proto.user.Topology] DTO. + */ +internal fun Topology.toUserDto(project: Project): org.opendc.web.proto.user.Topology { + return org.opendc.web.proto.user.Topology(id, number, project, name, rooms, createdAt, updatedAt) +} + +/** + * Convert a [Topology] entity into a [org.opendc.web.proto.user.Topology.Summary] DTO. + */ +private fun Topology.toSummaryDto(): org.opendc.web.proto.user.Topology.Summary { + return org.opendc.web.proto.user.Topology.Summary(id, number, name, createdAt, updatedAt) +} + +/** + * Convert a [Portfolio] entity into a [org.opendc.web.proto.user.Portfolio] DTO. + */ +internal fun Portfolio.toUserDto(project: Project): org.opendc.web.proto.user.Portfolio { + return org.opendc.web.proto.user.Portfolio(id, number, project, name, targets, scenarios.map { it.toSummaryDto() }) +} + +/** + * Convert a [Portfolio] entity into a [org.opendc.web.proto.user.Portfolio.Summary] DTO. + */ +private fun Portfolio.toSummaryDto(): org.opendc.web.proto.user.Portfolio.Summary { + return org.opendc.web.proto.user.Portfolio.Summary(id, number, name, targets) +} + +/** + * Convert a [Scenario] entity into a [org.opendc.web.proto.user.Scenario] DTO. + */ +internal fun Scenario.toUserDto(project: Project): org.opendc.web.proto.user.Scenario { + return org.opendc.web.proto.user.Scenario( + id, + number, + project, + portfolio.toSummaryDto(), + name, + workload.toDto(), + topology.toSummaryDto(), + phenomena, + schedulerName, + job.toUserDto() + ) +} + +/** + * Convert a [Scenario] entity into a [org.opendc.web.proto.user.Scenario.Summary] DTO. + */ +private fun Scenario.toSummaryDto(): org.opendc.web.proto.user.Scenario.Summary { + return org.opendc.web.proto.user.Scenario.Summary( + id, + number, + name, + workload.toDto(), + topology.toSummaryDto(), + phenomena, + schedulerName, + job.toUserDto() + ) +} + +/** + * Convert a [Job] entity into a [org.opendc.web.proto.user.Job] DTO. + */ +internal fun Job.toUserDto(): org.opendc.web.proto.user.Job { + return org.opendc.web.proto.user.Job(id, state, createdAt, updatedAt, results) +} + +/** + * Convert a [Workload] entity into a DTO. + */ +internal fun Workload.toDto(): org.opendc.web.proto.Workload { + return org.opendc.web.proto.Workload(trace.toUserDto(), samplingFraction) +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/Utils.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/Utils.kt new file mode 100644 index 00000000..2d0da3b3 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/service/Utils.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 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.web.server.service + +import org.opendc.web.proto.user.ProjectRole + +/** + * Flag to indicate that the user can edit a project. + */ +internal val ProjectRole.canEdit: Boolean + get() = when (this) { + ProjectRole.OWNER, ProjectRole.EDITOR -> true + ProjectRole.VIEWER -> false + } + +/** + * Flag to indicate that the user can delete a project. + */ +internal val ProjectRole.canDelete: Boolean + get() = this == ProjectRole.OWNER diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/DevSecurityOverrideFilter.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/DevSecurityOverrideFilter.kt new file mode 100644 index 00000000..0bdf959a --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/DevSecurityOverrideFilter.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022 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.web.server.util + +import io.quarkus.arc.properties.IfBuildProperty +import java.security.Principal +import javax.ws.rs.container.ContainerRequestContext +import javax.ws.rs.container.ContainerRequestFilter +import javax.ws.rs.container.PreMatching +import javax.ws.rs.core.SecurityContext +import javax.ws.rs.ext.Provider + +/** + * Helper class to disable security for the OpenDC web API when in development mode. + */ +@Provider +@PreMatching +@IfBuildProperty(name = "opendc.security.enabled", stringValue = "false") +class DevSecurityOverrideFilter : ContainerRequestFilter { + override fun filter(requestContext: ContainerRequestContext) { + requestContext.securityContext = object : SecurityContext { + override fun getUserPrincipal(): Principal = Principal { "anon" } + + override fun isSecure(): Boolean = false + + override fun isUserInRole(role: String): Boolean = true + + override fun getAuthenticationScheme(): String = "basic" + } + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/KotlinModuleCustomizer.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/KotlinModuleCustomizer.kt new file mode 100644 index 00000000..8634c8a4 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/KotlinModuleCustomizer.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 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.web.server.util + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.KotlinModule +import io.quarkus.jackson.ObjectMapperCustomizer +import javax.inject.Singleton + +/** + * Helper class to register the Kotlin Jackson module. + */ +@Singleton +class KotlinModuleCustomizer : ObjectMapperCustomizer { + override fun customize(objectMapper: ObjectMapper) { + objectMapper.registerModule(KotlinModule.Builder().build()) + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/AbstractJsonSqlTypeDescriptor.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/AbstractJsonSqlTypeDescriptor.kt new file mode 100644 index 00000000..9e29b734 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/AbstractJsonSqlTypeDescriptor.kt @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2022 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.web.server.util.hibernate.json + +import org.hibernate.type.descriptor.ValueExtractor +import org.hibernate.type.descriptor.WrapperOptions +import org.hibernate.type.descriptor.java.JavaTypeDescriptor +import org.hibernate.type.descriptor.sql.BasicExtractor +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor +import java.sql.CallableStatement +import java.sql.ResultSet +import java.sql.Types + +/** + * Abstract implementation of a [SqlTypeDescriptor] for Hibernate JSON type. + */ +internal abstract class AbstractJsonSqlTypeDescriptor : SqlTypeDescriptor { + + override fun getSqlType(): Int { + return Types.OTHER + } + + override fun canBeRemapped(): Boolean { + return true + } + + override fun getExtractor(typeDescriptor: JavaTypeDescriptor): ValueExtractor { + return object : BasicExtractor(typeDescriptor, this) { + override fun doExtract(rs: ResultSet, name: String, options: WrapperOptions): X { + return typeDescriptor.wrap(extractJson(rs, name), options) + } + + override fun doExtract(statement: CallableStatement, index: Int, options: WrapperOptions): X { + return typeDescriptor.wrap(extractJson(statement, index), options) + } + + override fun doExtract(statement: CallableStatement, name: String, options: WrapperOptions): X { + return typeDescriptor.wrap(extractJson(statement, name), options) + } + } + } + + open fun extractJson(rs: ResultSet, name: String): Any? { + return rs.getObject(name) + } + + open fun extractJson(statement: CallableStatement, index: Int): Any? { + return statement.getObject(index) + } + + open fun extractJson(statement: CallableStatement, name: String): Any? { + return statement.getObject(name) + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/JsonBinarySqlTypeDescriptor.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/JsonBinarySqlTypeDescriptor.kt new file mode 100644 index 00000000..45752d4e --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/JsonBinarySqlTypeDescriptor.kt @@ -0,0 +1,26 @@ +package org.opendc.web.server.util.hibernate.json + +import com.fasterxml.jackson.databind.JsonNode +import org.hibernate.type.descriptor.ValueBinder +import org.hibernate.type.descriptor.WrapperOptions +import org.hibernate.type.descriptor.java.JavaTypeDescriptor +import org.hibernate.type.descriptor.sql.BasicBinder +import java.sql.CallableStatement +import java.sql.PreparedStatement + +/** + * A [AbstractJsonSqlTypeDescriptor] that stores the JSON as binary (JSONB). + */ +internal object JsonBinarySqlTypeDescriptor : AbstractJsonSqlTypeDescriptor() { + override fun getBinder(typeDescriptor: JavaTypeDescriptor): ValueBinder { + return object : BasicBinder(typeDescriptor, this) { + override fun doBind(st: PreparedStatement, value: X, index: Int, options: WrapperOptions) { + st.setObject(index, typeDescriptor.unwrap(value, JsonNode::class.java, options), sqlType) + } + + override fun doBind(st: CallableStatement, value: X, name: String, options: WrapperOptions) { + st.setObject(name, typeDescriptor.unwrap(value, JsonNode::class.java, options), sqlType) + } + } + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/JsonBytesSqlTypeDescriptor.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/JsonBytesSqlTypeDescriptor.kt new file mode 100644 index 00000000..216c465f --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/JsonBytesSqlTypeDescriptor.kt @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2022 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.web.server.util.hibernate.json + +import org.hibernate.type.descriptor.ValueBinder +import org.hibernate.type.descriptor.WrapperOptions +import org.hibernate.type.descriptor.java.JavaTypeDescriptor +import org.hibernate.type.descriptor.sql.BasicBinder +import java.io.UnsupportedEncodingException +import java.sql.* + +/** + * A [AbstractJsonSqlTypeDescriptor] that stores the JSON as UTF-8 encoded bytes. + */ +internal object JsonBytesSqlTypeDescriptor : AbstractJsonSqlTypeDescriptor() { + private val CHARSET = Charsets.UTF_8 + + override fun getSqlType(): Int { + return Types.BINARY + } + + override fun getBinder(javaTypeDescriptor: JavaTypeDescriptor): ValueBinder { + return object : BasicBinder(javaTypeDescriptor, this) { + override fun doBind(st: PreparedStatement, value: X, index: Int, options: WrapperOptions) { + st.setBytes(index, toJsonBytes(javaTypeDescriptor.unwrap(value, String::class.java, options))) + } + + override fun doBind(st: CallableStatement, value: X, name: String, options: WrapperOptions) { + st.setBytes(name, toJsonBytes(javaTypeDescriptor.unwrap(value, String::class.java, options))) + } + } + } + + override fun extractJson(rs: ResultSet, name: String): Any? { + return fromJsonBytes(rs.getBytes(name)) + } + + override fun extractJson(statement: CallableStatement, index: Int): Any? { + return fromJsonBytes(statement.getBytes(index)) + } + + override fun extractJson(statement: CallableStatement, name: String): Any? { + return fromJsonBytes(statement.getBytes(name)) + } + + private fun toJsonBytes(jsonValue: String): ByteArray? { + return try { + jsonValue.toByteArray(CHARSET) + } catch (e: UnsupportedEncodingException) { + throw IllegalStateException(e) + } + } + + private fun fromJsonBytes(jsonBytes: ByteArray?): String? { + return if (jsonBytes == null) { + null + } else try { + String(jsonBytes, CHARSET) + } catch (e: UnsupportedEncodingException) { + throw IllegalStateException(e) + } + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/JsonSqlTypeDescriptor.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/JsonSqlTypeDescriptor.kt new file mode 100644 index 00000000..f5069c4c --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/JsonSqlTypeDescriptor.kt @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2022 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.web.server.util.hibernate.json + +import org.hibernate.dialect.H2Dialect +import org.hibernate.dialect.PostgreSQL81Dialect +import org.hibernate.internal.SessionImpl +import org.hibernate.type.descriptor.ValueBinder +import org.hibernate.type.descriptor.ValueExtractor +import org.hibernate.type.descriptor.WrapperOptions +import org.hibernate.type.descriptor.java.JavaTypeDescriptor +import org.hibernate.type.descriptor.sql.BasicBinder +import org.hibernate.type.descriptor.sql.BasicExtractor +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor +import java.sql.* + +/** + * A [SqlTypeDescriptor] that automatically selects the correct implementation for the database dialect. + */ +internal object JsonSqlTypeDescriptor : SqlTypeDescriptor { + + override fun getSqlType(): Int = Types.OTHER + + override fun canBeRemapped(): Boolean = true + + override fun getExtractor(javaTypeDescriptor: JavaTypeDescriptor): ValueExtractor { + return object : BasicExtractor(javaTypeDescriptor, this) { + private var delegate: AbstractJsonSqlTypeDescriptor? = null + + override fun doExtract(rs: ResultSet, name: String, options: WrapperOptions): X { + return javaTypeDescriptor.wrap(delegate(options).extractJson(rs, name), options) + } + + override fun doExtract(statement: CallableStatement, index: Int, options: WrapperOptions): X { + return javaTypeDescriptor.wrap(delegate(options).extractJson(statement, index), options) + } + + override fun doExtract(statement: CallableStatement, name: String, options: WrapperOptions): X { + return javaTypeDescriptor.wrap(delegate(options).extractJson(statement, name), options) + } + + private fun delegate(options: WrapperOptions): AbstractJsonSqlTypeDescriptor { + var delegate = delegate + if (delegate == null) { + delegate = resolveSqlTypeDescriptor(options) + this.delegate = delegate + } + return delegate + } + } + } + + override fun getBinder(javaTypeDescriptor: JavaTypeDescriptor): ValueBinder { + return object : BasicBinder(javaTypeDescriptor, this) { + private var delegate: ValueBinder? = null + + override fun doBind(st: PreparedStatement, value: X, index: Int, options: WrapperOptions) { + delegate(options).bind(st, value, index, options) + } + + override fun doBind(st: CallableStatement, value: X, name: String, options: WrapperOptions) { + delegate(options).bind(st, value, name, options) + } + + private fun delegate(options: WrapperOptions): ValueBinder { + var delegate = delegate + if (delegate == null) { + delegate = checkNotNull(resolveSqlTypeDescriptor(options).getBinder(javaTypeDescriptor)) + this.delegate = delegate + } + return delegate + } + } + } + + /** + * Helper method to resolve the appropriate [SqlTypeDescriptor] based on the [WrapperOptions]. + */ + private fun resolveSqlTypeDescriptor(options: WrapperOptions): AbstractJsonSqlTypeDescriptor { + val session = options as? SessionImpl + return when (session?.jdbcServices?.dialect) { + is PostgreSQL81Dialect -> JsonBinarySqlTypeDescriptor + is H2Dialect -> JsonBytesSqlTypeDescriptor + else -> JsonStringSqlTypeDescriptor + } + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/JsonStringSqlTypeDescriptor.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/JsonStringSqlTypeDescriptor.kt new file mode 100644 index 00000000..3d10cb0e --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/JsonStringSqlTypeDescriptor.kt @@ -0,0 +1,38 @@ +package org.opendc.web.server.util.hibernate.json + +import org.hibernate.type.descriptor.ValueBinder +import org.hibernate.type.descriptor.WrapperOptions +import org.hibernate.type.descriptor.java.JavaTypeDescriptor +import org.hibernate.type.descriptor.sql.BasicBinder +import java.sql.* + +/** + * A [AbstractJsonSqlTypeDescriptor] that stores the JSON as string (VARCHAR). + */ +internal object JsonStringSqlTypeDescriptor : AbstractJsonSqlTypeDescriptor() { + override fun getSqlType(): Int = Types.VARCHAR + + override fun getBinder(typeDescriptor: JavaTypeDescriptor): ValueBinder { + return object : BasicBinder(typeDescriptor, this) { + override fun doBind(st: PreparedStatement, value: X, index: Int, options: WrapperOptions) { + st.setString(index, typeDescriptor.unwrap(value, String::class.java, options)) + } + + override fun doBind(st: CallableStatement, value: X, name: String, options: WrapperOptions) { + st.setString(name, typeDescriptor.unwrap(value, String::class.java, options)) + } + } + } + + override fun extractJson(rs: ResultSet, name: String): Any? { + return rs.getString(name) + } + + override fun extractJson(statement: CallableStatement, index: Int): Any? { + return statement.getString(index) + } + + override fun extractJson(statement: CallableStatement, name: String): Any? { + return statement.getString(name) + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/JsonType.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/JsonType.kt new file mode 100644 index 00000000..98663640 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/JsonType.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022 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.web.server.util.hibernate.json + +import com.fasterxml.jackson.databind.ObjectMapper +import org.hibernate.type.AbstractSingleColumnStandardBasicType +import org.hibernate.type.BasicType +import org.hibernate.usertype.DynamicParameterizedType +import java.util.* +import javax.enterprise.inject.spi.CDI + +/** + * A [BasicType] that contains JSON. + */ +class JsonType(objectMapper: ObjectMapper) : AbstractSingleColumnStandardBasicType(JsonSqlTypeDescriptor, JsonTypeDescriptor(objectMapper)), DynamicParameterizedType { + /** + * No-arg constructor for Hibernate to instantiate. + */ + constructor() : this(CDI.current().select(ObjectMapper::class.java).get()) + + override fun getName(): String = "json" + + override fun registerUnderJavaType(): Boolean = true + + override fun setParameterValues(parameters: Properties) { + (javaTypeDescriptor as JsonTypeDescriptor).setParameterValues(parameters) + } +} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/JsonTypeDescriptor.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/JsonTypeDescriptor.kt new file mode 100644 index 00000000..6c6078dd --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/hibernate/json/JsonTypeDescriptor.kt @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2022 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.web.server.util.hibernate.json + +import com.fasterxml.jackson.databind.ObjectMapper +import org.hibernate.HibernateException +import org.hibernate.annotations.common.reflection.XProperty +import org.hibernate.annotations.common.reflection.java.JavaXMember +import org.hibernate.engine.jdbc.BinaryStream +import org.hibernate.engine.jdbc.internal.BinaryStreamImpl +import org.hibernate.type.descriptor.WrapperOptions +import org.hibernate.type.descriptor.java.AbstractTypeDescriptor +import org.hibernate.type.descriptor.java.BlobTypeDescriptor +import org.hibernate.type.descriptor.java.DataHelper +import org.hibernate.type.descriptor.java.MutableMutabilityPlan +import org.hibernate.usertype.DynamicParameterizedType +import java.io.ByteArrayInputStream +import java.io.IOException +import java.io.InputStream +import java.lang.reflect.Type +import java.sql.Blob +import java.sql.SQLException +import java.util.* + +/** + * An [AbstractTypeDescriptor] implementation for Hibernate JSON type. + */ +internal class JsonTypeDescriptor(private val objectMapper: ObjectMapper) : AbstractTypeDescriptor(Any::class.java, JsonMutabilityPlan(objectMapper)), DynamicParameterizedType { + private var type: Type? = null + + override fun setParameterValues(parameters: Properties) { + val xProperty = parameters[DynamicParameterizedType.XPROPERTY] as XProperty + type = if (xProperty is JavaXMember) { + val x = xProperty as JavaXMember + x.javaType + } else { + (parameters[DynamicParameterizedType.PARAMETER_TYPE] as DynamicParameterizedType.ParameterType).returnedClass + } + } + + override fun areEqual(one: Any?, another: Any?): Boolean { + return when { + one === another -> true + one == null || another == null -> false + one is String && another is String -> one == another + one is Collection<*> && another is Collection<*> -> Objects.equals(one, another) + else -> areJsonEqual(one, another) + } + } + + override fun toString(value: Any?): String { + return objectMapper.writeValueAsString(value) + } + + override fun fromString(string: String): Any? { + return objectMapper.readValue(string, objectMapper.typeFactory.constructType(type)) + } + + override fun unwrap(value: Any?, type: Class, options: WrapperOptions): X? { + if (value == null) { + return null + } + + @Suppress("UNCHECKED_CAST") + return when { + String::class.java.isAssignableFrom(type) -> toString(value) + BinaryStream::class.java.isAssignableFrom(type) || ByteArray::class.java.isAssignableFrom(type) -> { + val stringValue = if (value is String) value else toString(value) + BinaryStreamImpl(DataHelper.extractBytes(ByteArrayInputStream(stringValue.toByteArray()))) + } + Blob::class.java.isAssignableFrom(type) -> { + val stringValue = if (value is String) value else toString(value) + BlobTypeDescriptor.INSTANCE.fromString(stringValue) + } + Any::class.java.isAssignableFrom(type) -> toJsonType(value) + else -> throw unknownUnwrap(type) + } as X + } + + override fun wrap(value: X?, options: WrapperOptions): Any? { + if (value == null) { + return null + } + + var blob: Blob? = null + if (Blob::class.java.isAssignableFrom(value.javaClass)) { + blob = options.lobCreator.wrap(value as Blob?) + } else if (ByteArray::class.java.isAssignableFrom(value.javaClass)) { + blob = options.lobCreator.createBlob(value as ByteArray?) + } else if (InputStream::class.java.isAssignableFrom(value.javaClass)) { + val inputStream = value as InputStream + blob = try { + options.lobCreator.createBlob(inputStream, inputStream.available().toLong()) + } catch (e: IOException) { + throw unknownWrap(value.javaClass) + } + } + + val stringValue: String = try { + if (blob != null) String(DataHelper.extractBytes(blob.binaryStream)) else value.toString() + } catch (e: SQLException) { + throw HibernateException("Unable to extract binary stream from Blob", e) + } + + return fromString(stringValue) + } + + private class JsonMutabilityPlan(private val objectMapper: ObjectMapper) : MutableMutabilityPlan() { + override fun deepCopyNotNull(value: Any): Any { + return objectMapper.treeToValue(objectMapper.valueToTree(value), value.javaClass) + } + } + + private fun readObject(value: String): Any { + return objectMapper.readTree(value) + } + + private fun areJsonEqual(one: Any, another: Any): Boolean { + return readObject(objectMapper.writeValueAsString(one)) == readObject(objectMapper.writeValueAsString(another)) + } + + private fun toJsonType(value: Any?): Any { + return try { + readObject(objectMapper.writeValueAsString(value)) + } catch (e: Exception) { + throw IllegalArgumentException(e) + } + } +} diff --git a/opendc-web/opendc-web-server/src/main/resources/META-INF/branding/logo.png b/opendc-web/opendc-web-server/src/main/resources/META-INF/branding/logo.png new file mode 100644 index 00000000..d743038b Binary files /dev/null and b/opendc-web/opendc-web-server/src/main/resources/META-INF/branding/logo.png differ diff --git a/opendc-web/opendc-web-server/src/main/resources/application-dev.properties b/opendc-web/opendc-web-server/src/main/resources/application-dev.properties new file mode 100644 index 00000000..3f30e9c4 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/resources/application-dev.properties @@ -0,0 +1,37 @@ +# Copyright (c) 2022 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. + +# Datasource (H2) +quarkus.datasource.db-kind=h2 +quarkus.datasource.jdbc.url=jdbc:h2:mem:default;DB_CLOSE_DELAY=-1;INIT=CREATE TYPE IF NOT EXISTS "JSONB" AS blob; + +# Hibernate +quarkus.hibernate-orm.dialect=org.hibernate.dialect.H2Dialect +quarkus.hibernate-orm.database.generation=drop-and-create + +# Disable authentication +opendc.security.enabled=false + +# Mount web UI at root and API at "/api" +quarkus.opendc-ui.path=/ +quarkus.resteasy.path=/api + +# Swagger UI +quarkus.smallrye-openapi.servers=http://localhost:8080 diff --git a/opendc-web/opendc-web-server/src/main/resources/application-docker.properties b/opendc-web/opendc-web-server/src/main/resources/application-docker.properties new file mode 100644 index 00000000..cd1f9ff3 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/resources/application-docker.properties @@ -0,0 +1,50 @@ +# Copyright (c) 2022 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. + +# Configuration for standalone Docker server distribution without web UI. + +# Datasource +quarkus.datasource.db-kind=postgresql +quarkus.datasource.username=${OPENDC_DB_USERNAME} +quarkus.datasource.password=${OPENDC_DB_PASSWORD} +quarkus.datasource.jdbc.url=${OPENDC_DB_URL} + +# Hibernate +quarkus.hibernate-orm.dialect=org.hibernate.dialect.PostgreSQL95Dialect +quarkus.hibernate-orm.database.generation=validate + +# Disable OpenDC web UI +quarkus.opendc-ui.include=false + +# Security +opendc.security.enabled=true +quarkus.oidc.auth-server-url=https://${OPENDC_AUTH0_DOMAIN} +quarkus.oidc.client-id=${OPENDC_AUTH0_AUDIENCE} +quarkus.oidc.token.audience=${quarkus.oidc.client-id} +quarkus.oidc.roles.role-claim-path=scope + +# Swagger UI +quarkus.swagger-ui.oauth-client-id=${OPENDC_AUTH0_DOCS_CLIENT_ID:} +quarkus.swagger-ui.oauth-additional-query-string-params={"audience":"${OPENDC_AUTH0_AUDIENCE:https://api.opendc.org/v2/}"} + +quarkus.smallrye-openapi.security-scheme=oidc +quarkus.smallrye-openapi.security-scheme-name=Auth0 +quarkus.smallrye-openapi.oidc-open-id-connect-url=https://${OPENDC_AUTH0_DOMAIN:opendc.eu.auth0.com}/.well-known/openid-configuration +quarkus.smallrye-openapi.servers=https://api.opendc.org diff --git a/opendc-web/opendc-web-server/src/main/resources/application-prod.properties b/opendc-web/opendc-web-server/src/main/resources/application-prod.properties new file mode 100644 index 00000000..09653d59 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/resources/application-prod.properties @@ -0,0 +1,38 @@ +# Copyright (c) 2022 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. + +# Datasource (H2) +quarkus.datasource.db-kind=h2 +quarkus.datasource.jdbc.url=jdbc:h2:file:./data/opendc;DB_CLOSE_DELAY=-1;INIT=CREATE TYPE IF NOT EXISTS "JSONB" AS blob; + +# Hibernate +quarkus.hibernate-orm.dialect=org.hibernate.dialect.H2Dialect +quarkus.hibernate-orm.database.generation=validate + +# Disable authentication +opendc.security.enabled=false +quarkus.oidc.enabled=${opendc.security.enabled} + +# Mount web UI at root and API at "/api" +quarkus.opendc-ui.path=/ +quarkus.resteasy.path=/api + +# Swagger UI +quarkus.smallrye-openapi.servers=http://localhost:8080 diff --git a/opendc-web/opendc-web-server/src/main/resources/application-test.properties b/opendc-web/opendc-web-server/src/main/resources/application-test.properties new file mode 100644 index 00000000..78512f3f --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/resources/application-test.properties @@ -0,0 +1,37 @@ +# Copyright (c) 2022 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. + +# Datasource configuration +quarkus.datasource.db-kind = h2 +quarkus.datasource.jdbc.url=jdbc:h2:mem:default;DB_CLOSE_DELAY=-1;INIT=CREATE TYPE "JSONB" AS blob; + +quarkus.hibernate-orm.dialect=org.hibernate.dialect.H2Dialect +quarkus.hibernate-orm.database.generation=drop-and-create + +# Disable security +quarkus.oidc.enabled=false + +# Disable OpenAPI/Swagger +quarkus.smallrye-openapi.enable=false +quarkus.swagger-ui.enable=false + +# Disable OpenDC web UI and runner +quarkus.opendc-ui.include=false +quarkus.opendc-runner.include=false diff --git a/opendc-web/opendc-web-server/src/main/resources/application.properties b/opendc-web/opendc-web-server/src/main/resources/application.properties new file mode 100644 index 00000000..d0b567e5 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/resources/application.properties @@ -0,0 +1,44 @@ +# Copyright (c) 2022 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. + +# Enable CORS +quarkus.http.cors=true + +# Security +quarkus.oidc.enabled=${opendc.security.enabled} + +# Runner logging +quarkus.log.category."org.opendc".level=ERROR +quarkus.log.category."org.opendc.web".level=INFO +quarkus.log.category."org.apache".level=WARN + +# OpenAPI and Swagger +quarkus.smallrye-openapi.info-title=OpenDC REST API +%dev.quarkus.smallrye-openapi.info-title=OpenDC REST API (development) +quarkus.smallrye-openapi.info-version=2.1-rc1 +quarkus.smallrye-openapi.info-description=OpenDC is an open-source datacenter simulator for education, featuring real-time online collaboration, diverse simulation models, and detailed performance feedback statistics. +quarkus.smallrye-openapi.info-contact-email=opendc@atlarge-research.com +quarkus.smallrye-openapi.info-contact-name=OpenDC Support +quarkus.smallrye-openapi.info-contact-url=https://opendc.org +quarkus.smallrye-openapi.info-license-name=MIT +quarkus.smallrye-openapi.info-license-url=https://github.com/atlarge-research/opendc/blob/master/LICENSE.txt + +quarkus.swagger-ui.path=docs +quarkus.swagger-ui.always-include=true diff --git a/opendc-web/opendc-web-server/src/main/resources/import.sql b/opendc-web/opendc-web-server/src/main/resources/import.sql new file mode 100644 index 00000000..756eff46 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/resources/import.sql @@ -0,0 +1,3 @@ + +-- Add example traces +INSERT INTO traces (id, name, type) VALUES ('bitbrains-small', 'Bitbrains Small', 'vm'); diff --git a/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/SchedulerResourceTest.kt b/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/SchedulerResourceTest.kt new file mode 100644 index 00000000..c1460db9 --- /dev/null +++ b/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/SchedulerResourceTest.kt @@ -0,0 +1,48 @@ +/* + * 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.web.server.rest + +import io.quarkus.test.junit.QuarkusTest +import io.restassured.http.ContentType +import io.restassured.module.kotlin.extensions.Then +import io.restassured.module.kotlin.extensions.When +import org.junit.jupiter.api.Test + +/** + * Test suite for [SchedulerResource] + */ +@QuarkusTest +class SchedulerResourceTest { + /** + * Test to verify whether we can obtain all schedulers. + */ + @Test + fun testGetSchedulers() { + When { + get("/schedulers") + } Then { + statusCode(200) + contentType(ContentType.JSON) + } + } +} diff --git a/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/TraceResourceTest.kt b/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/TraceResourceTest.kt new file mode 100644 index 00000000..2490cf46 --- /dev/null +++ b/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/TraceResourceTest.kt @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2022 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.web.server.rest + +import io.mockk.every +import io.quarkiverse.test.junit.mockk.InjectMock +import io.quarkus.test.common.http.TestHTTPEndpoint +import io.quarkus.test.junit.QuarkusMock +import io.quarkus.test.junit.QuarkusTest +import io.restassured.http.ContentType +import io.restassured.module.kotlin.extensions.Then +import io.restassured.module.kotlin.extensions.When +import org.hamcrest.Matchers +import org.hamcrest.Matchers.equalTo +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.opendc.web.proto.Trace +import org.opendc.web.server.service.TraceService + +/** + * Test suite for [TraceResource]. + */ +@QuarkusTest +@TestHTTPEndpoint(TraceResource::class) +class TraceResourceTest { + @InjectMock + private lateinit var traceService: TraceService + + @BeforeEach + fun setUp() { + QuarkusMock.installMockForType(traceService, TraceService::class.java) + } + + /** + * Test that tries to obtain all traces (empty response). + */ + @Test + fun testGetAllEmpy() { + every { traceService.findAll() } returns emptyList() + + When { + get() + } Then { + statusCode(200) + contentType(ContentType.JSON) + body("", Matchers.empty()) + } + } + + /** + * Test that tries to obtain a non-existent trace. + */ + @Test + fun testGetNonExisting() { + every { traceService.findById("bitbrains") } returns null + + When { + get("/bitbrains") + } Then { + statusCode(404) + contentType(ContentType.JSON) + } + } + + /** + * Test that tries to obtain an existing trace. + */ + @Test + fun testGetExisting() { + every { traceService.findById("bitbrains") } returns Trace("bitbrains", "Bitbrains", "VM") + + When { + get("/bitbrains") + } Then { + statusCode(200) + contentType(ContentType.JSON) + body("name", equalTo("Bitbrains")) + } + } +} diff --git a/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/runner/JobResourceTest.kt b/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/runner/JobResourceTest.kt new file mode 100644 index 00000000..c96788b0 --- /dev/null +++ b/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/runner/JobResourceTest.kt @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2022 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.web.server.rest.runner + +import io.mockk.every +import io.quarkiverse.test.junit.mockk.InjectMock +import io.quarkus.test.common.http.TestHTTPEndpoint +import io.quarkus.test.junit.QuarkusMock +import io.quarkus.test.junit.QuarkusTest +import io.quarkus.test.security.TestSecurity +import io.restassured.http.ContentType +import io.restassured.module.kotlin.extensions.Given +import io.restassured.module.kotlin.extensions.Then +import io.restassured.module.kotlin.extensions.When +import org.hamcrest.Matchers.equalTo +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.opendc.web.proto.* +import org.opendc.web.proto.Targets +import org.opendc.web.proto.runner.Job +import org.opendc.web.proto.runner.Portfolio +import org.opendc.web.proto.runner.Scenario +import org.opendc.web.proto.runner.Topology +import org.opendc.web.server.service.JobService +import java.time.Instant + +/** + * Test suite for [JobResource]. + */ +@QuarkusTest +@TestHTTPEndpoint(JobResource::class) +class JobResourceTest { + @InjectMock + private lateinit var jobService: JobService + + /** + * Dummy values + */ + private val dummyPortfolio = Portfolio(1, 1, "test", Targets(emptySet())) + private val dummyTopology = Topology(1, 1, "test", emptyList(), Instant.now(), Instant.now()) + private val dummyTrace = Trace("bitbrains", "Bitbrains", "vm") + private val dummyScenario = Scenario(1, 1, dummyPortfolio, "test", Workload(dummyTrace, 1.0), dummyTopology, OperationalPhenomena(false, false), "test",) + private val dummyJob = Job(1, dummyScenario, JobState.PENDING, Instant.now(), Instant.now()) + + @BeforeEach + fun setUp() { + QuarkusMock.installMockForType(jobService, JobService::class.java) + } + + /** + * Test that tries to query the pending jobs without token. + */ + @Test + fun testQueryWithoutToken() { + When { + get() + } Then { + statusCode(401) + } + } + + /** + * Test that tries to query the pending jobs for a user. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testQueryInvalidScope() { + When { + get() + } Then { + statusCode(403) + } + } + + /** + * Test that tries to query the pending jobs for a runner. + */ + @Test + @TestSecurity(user = "testUser", roles = ["runner"]) + fun testQuery() { + every { jobService.queryPending() } returns listOf(dummyJob) + + When { + get() + } Then { + statusCode(200) + contentType(ContentType.JSON) + body("get(0).id", equalTo(1)) + } + } + + /** + * Test that tries to obtain a non-existent job. + */ + @Test + @TestSecurity(user = "testUser", roles = ["runner"]) + fun testGetNonExisting() { + every { jobService.findById(1) } returns null + + When { + get("/1") + } Then { + statusCode(404) + contentType(ContentType.JSON) + } + } + + /** + * Test that tries to obtain a job. + */ + @Test + @TestSecurity(user = "testUser", roles = ["runner"]) + fun testGetExisting() { + every { jobService.findById(1) } returns dummyJob + + When { + get("/1") + } Then { + statusCode(200) + contentType(ContentType.JSON) + body("id", equalTo(1)) + } + } + + /** + * Test that tries to update a non-existent job. + */ + @Test + @TestSecurity(user = "testUser", roles = ["runner"]) + fun testUpdateNonExistent() { + every { jobService.updateState(1, any(), any()) } returns null + + Given { + body(Job.Update(JobState.PENDING)) + contentType(ContentType.JSON) + } When { + post("/1") + } Then { + statusCode(404) + contentType(ContentType.JSON) + } + } + + /** + * Test that tries to update a job. + */ + @Test + @TestSecurity(user = "testUser", roles = ["runner"]) + fun testUpdateState() { + every { jobService.updateState(1, any(), any()) } returns dummyJob.copy(state = JobState.CLAIMED) + + Given { + body(Job.Update(JobState.CLAIMED)) + contentType(ContentType.JSON) + } When { + post("/1") + } Then { + statusCode(200) + contentType(ContentType.JSON) + body("state", equalTo(JobState.CLAIMED.toString())) + } + } + + /** + * Test that tries to update a job with invalid input. + */ + @Test + @TestSecurity(user = "testUser", roles = ["runner"]) + fun testUpdateInvalidInput() { + Given { + body("""{ "test": "test" }""") + contentType(ContentType.JSON) + } When { + post("/1") + } Then { + statusCode(400) + contentType(ContentType.JSON) + } + } +} diff --git a/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/PortfolioResourceTest.kt b/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/PortfolioResourceTest.kt new file mode 100644 index 00000000..5798d2e7 --- /dev/null +++ b/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/PortfolioResourceTest.kt @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2022 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.web.server.rest.user + +import io.mockk.every +import io.quarkiverse.test.junit.mockk.InjectMock +import io.quarkus.test.common.http.TestHTTPEndpoint +import io.quarkus.test.junit.QuarkusMock +import io.quarkus.test.junit.QuarkusTest +import io.quarkus.test.security.TestSecurity +import io.restassured.http.ContentType +import io.restassured.module.kotlin.extensions.Given +import io.restassured.module.kotlin.extensions.Then +import io.restassured.module.kotlin.extensions.When +import org.hamcrest.Matchers +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.opendc.web.proto.Targets +import org.opendc.web.proto.user.Portfolio +import org.opendc.web.proto.user.Project +import org.opendc.web.proto.user.ProjectRole +import org.opendc.web.server.service.PortfolioService +import java.time.Instant + +/** + * Test suite for [PortfolioResource]. + */ +@QuarkusTest +@TestHTTPEndpoint(PortfolioResource::class) +class PortfolioResourceTest { + @InjectMock + private lateinit var portfolioService: PortfolioService + + /** + * Dummy project and portfolio + */ + private val dummyProject = Project(1, "test", Instant.now(), Instant.now(), ProjectRole.OWNER) + private val dummyPortfolio = Portfolio(1, 1, dummyProject, "test", Targets(emptySet(), 1), emptyList()) + + @BeforeEach + fun setUp() { + QuarkusMock.installMockForType(portfolioService, PortfolioService::class.java) + } + + /** + * Test that tries to obtain the list of portfolios belonging to a project. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testGetForProject() { + every { portfolioService.findAll("testUser", 1) } returns emptyList() + + Given { + pathParam("project", "1") + } When { + get() + } Then { + statusCode(200) + contentType(ContentType.JSON) + } + } + + /** + * Test that tries to create a topology for a project. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testCreateNonExistent() { + every { portfolioService.create("testUser", 1, any()) } returns null + + Given { + pathParam("project", "1") + + body(Portfolio.Create("test", Targets(emptySet(), 1))) + contentType(ContentType.JSON) + } When { + post() + } Then { + statusCode(404) + contentType(ContentType.JSON) + } + } + + /** + * Test that tries to create a portfolio for a scenario. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testCreate() { + every { portfolioService.create("testUser", 1, any()) } returns dummyPortfolio + + Given { + pathParam("project", "1") + + body(Portfolio.Create("test", Targets(emptySet(), 1))) + contentType(ContentType.JSON) + } When { + post() + } Then { + statusCode(200) + contentType(ContentType.JSON) + body("id", Matchers.equalTo(1)) + body("name", Matchers.equalTo("test")) + } + } + + /** + * Test to create a portfolio with an empty body. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testCreateEmpty() { + Given { + pathParam("project", "1") + + body("{}") + contentType(ContentType.JSON) + } When { + post() + } Then { + statusCode(400) + contentType(ContentType.JSON) + } + } + + /** + * Test to create a portfolio with a blank name. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testCreateBlankName() { + Given { + pathParam("project", "1") + + body(Portfolio.Create("", Targets(emptySet(), 1))) + contentType(ContentType.JSON) + } When { + post() + } Then { + statusCode(400) + contentType(ContentType.JSON) + } + } + + /** + * Test that tries to obtain a portfolio without token. + */ + @Test + fun testGetWithoutToken() { + Given { + pathParam("project", "1") + } When { + get("/1") + } Then { + statusCode(401) + } + } + + /** + * Test that tries to obtain a portfolio with an invalid scope. + */ + @Test + @TestSecurity(user = "testUser", roles = ["runner"]) + fun testGetInvalidToken() { + Given { + pathParam("project", "1") + } When { + get("/1") + } Then { + statusCode(403) + } + } + + /** + * Test that tries to obtain a non-existent portfolio. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testGetNonExisting() { + every { portfolioService.findOne("testUser", 1, 1) } returns null + + Given { + pathParam("project", "1") + } When { + get("/1") + } Then { + statusCode(404) + contentType(ContentType.JSON) + } + } + + /** + * Test that tries to obtain a portfolio. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testGetExisting() { + every { portfolioService.findOne("testUser", 1, 1) } returns dummyPortfolio + + Given { + pathParam("project", "1") + } When { + get("/1") + } Then { + statusCode(200) + contentType(ContentType.JSON) + body("id", Matchers.equalTo(1)) + } + } + + /** + * Test to delete a non-existent portfolio. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testDeleteNonExistent() { + every { portfolioService.delete("testUser", 1, 1) } returns null + + Given { + pathParam("project", "1") + } When { + delete("/1") + } Then { + statusCode(404) + } + } + + /** + * Test to delete a portfolio. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testDelete() { + every { portfolioService.delete("testUser", 1, 1) } returns dummyPortfolio + + Given { + pathParam("project", "1") + } When { + delete("/1") + } Then { + statusCode(200) + contentType(ContentType.JSON) + } + } +} diff --git a/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/PortfolioScenarioResourceTest.kt b/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/PortfolioScenarioResourceTest.kt new file mode 100644 index 00000000..13c47d19 --- /dev/null +++ b/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/PortfolioScenarioResourceTest.kt @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2022 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.web.server.rest.user + +import io.mockk.every +import io.quarkiverse.test.junit.mockk.InjectMock +import io.quarkus.test.common.http.TestHTTPEndpoint +import io.quarkus.test.junit.QuarkusMock +import io.quarkus.test.junit.QuarkusTest +import io.quarkus.test.security.TestSecurity +import io.restassured.http.ContentType +import io.restassured.module.kotlin.extensions.Given +import io.restassured.module.kotlin.extensions.Then +import io.restassured.module.kotlin.extensions.When +import org.hamcrest.Matchers +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.opendc.web.proto.* +import org.opendc.web.proto.user.* +import org.opendc.web.server.service.ScenarioService +import java.time.Instant + +/** + * Test suite for [PortfolioScenarioResource]. + */ +@QuarkusTest +@TestHTTPEndpoint(PortfolioScenarioResource::class) +class PortfolioScenarioResourceTest { + @InjectMock + private lateinit var scenarioService: ScenarioService + + /** + * Dummy values + */ + private val dummyProject = Project(0, "test", Instant.now(), Instant.now(), ProjectRole.OWNER) + private val dummyPortfolio = Portfolio.Summary(1, 1, "test", Targets(emptySet())) + private val dummyJob = Job(1, JobState.PENDING, Instant.now(), Instant.now(), null) + private val dummyTrace = Trace("bitbrains", "Bitbrains", "vm") + private val dummyTopology = Topology.Summary(1, 1, "test", Instant.now(), Instant.now()) + private val dummyScenario = Scenario( + 1, + 1, + dummyProject, + dummyPortfolio, + "test", + Workload(dummyTrace, 1.0), + dummyTopology, + OperationalPhenomena(false, false), + "test", + dummyJob + ) + + @BeforeEach + fun setUp() { + QuarkusMock.installMockForType(scenarioService, ScenarioService::class.java) + } + + /** + * Test that tries to obtain a portfolio without token. + */ + @Test + fun testGetWithoutToken() { + Given { + pathParam("project", "1") + pathParam("portfolio", "1") + } When { + get() + } Then { + statusCode(401) + } + } + + /** + * Test that tries to obtain a portfolio with an invalid scope. + */ + @Test + @TestSecurity(user = "testUser", roles = ["runner"]) + fun testGetInvalidToken() { + Given { + pathParam("project", "1") + pathParam("portfolio", "1") + } When { + get() + } Then { + statusCode(403) + } + } + + /** + * Test that tries to obtain a non-existent portfolio. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testGet() { + every { scenarioService.findAll("testUser", 1, 1) } returns emptyList() + + Given { + pathParam("project", "1") + pathParam("portfolio", "1") + } When { + get() + } Then { + statusCode(200) + contentType(ContentType.JSON) + } + } + + /** + * Test that tries to create a scenario for a portfolio. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testCreateNonExistent() { + every { scenarioService.create("testUser", 1, any(), any()) } returns null + + Given { + pathParam("project", "1") + pathParam("portfolio", "1") + + body(Scenario.Create("test", Workload.Spec("test", 1.0), 1, OperationalPhenomena(false, false), "test")) + contentType(ContentType.JSON) + } When { + post() + } Then { + statusCode(404) + contentType(ContentType.JSON) + } + } + + /** + * Test that tries to create a scenario for a portfolio. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testCreate() { + every { scenarioService.create("testUser", 1, 1, any()) } returns dummyScenario + + Given { + pathParam("project", "1") + pathParam("portfolio", "1") + + body(Scenario.Create("test", Workload.Spec("test", 1.0), 1, OperationalPhenomena(false, false), "test")) + contentType(ContentType.JSON) + } When { + post() + } Then { + statusCode(200) + contentType(ContentType.JSON) + body("id", Matchers.equalTo(1)) + body("name", Matchers.equalTo("test")) + } + } + + /** + * Test to create a project with an empty body. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testCreateEmpty() { + Given { + pathParam("project", "1") + pathParam("portfolio", "1") + + body("{}") + contentType(ContentType.JSON) + } When { + post() + } Then { + statusCode(400) + contentType(ContentType.JSON) + } + } + + /** + * Test to create a project with a blank name. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testCreateBlankName() { + Given { + pathParam("project", "1") + pathParam("portfolio", "1") + + body(Scenario.Create("", Workload.Spec("test", 1.0), 1, OperationalPhenomena(false, false), "test")) + contentType(ContentType.JSON) + } When { + post() + } Then { + statusCode(400) + contentType(ContentType.JSON) + } + } +} diff --git a/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/ProjectResourceTest.kt b/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/ProjectResourceTest.kt new file mode 100644 index 00000000..fec8759c --- /dev/null +++ b/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/ProjectResourceTest.kt @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2022 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.web.server.rest.user + +import io.mockk.every +import io.quarkiverse.test.junit.mockk.InjectMock +import io.quarkus.test.common.http.TestHTTPEndpoint +import io.quarkus.test.junit.QuarkusMock +import io.quarkus.test.junit.QuarkusTest +import io.quarkus.test.security.TestSecurity +import io.restassured.http.ContentType +import io.restassured.module.kotlin.extensions.Given +import io.restassured.module.kotlin.extensions.Then +import io.restassured.module.kotlin.extensions.When +import org.hamcrest.Matchers.equalTo +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.opendc.web.proto.user.Project +import org.opendc.web.proto.user.ProjectRole +import org.opendc.web.server.service.ProjectService +import java.time.Instant + +/** + * Test suite for [ProjectResource]. + */ +@QuarkusTest +@TestHTTPEndpoint(ProjectResource::class) +class ProjectResourceTest { + @InjectMock + private lateinit var projectService: ProjectService + + /** + * Dummy values. + */ + private val dummyProject = Project(0, "test", Instant.now(), Instant.now(), ProjectRole.OWNER) + + @BeforeEach + fun setUp() { + QuarkusMock.installMockForType(projectService, ProjectService::class.java) + } + + /** + * Test that tries to obtain all projects without token. + */ + @Test + fun testGetAllWithoutToken() { + When { + get() + } Then { + statusCode(401) + } + } + + /** + * Test that tries to obtain all projects with an invalid scope. + */ + @Test + @TestSecurity(user = "testUser", roles = ["runner"]) + fun testGetAllWithInvalidScope() { + When { + get() + } Then { + statusCode(403) + } + } + + /** + * Test that tries to obtain all project for a user. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testGetAll() { + val projects = listOf(dummyProject) + every { projectService.findWithUser("testUser") } returns projects + + When { + get() + } Then { + statusCode(200) + contentType(ContentType.JSON) + body("get(0).name", equalTo("test")) + } + } + + /** + * Test that tries to obtain a non-existent project. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testGetNonExisting() { + every { projectService.findWithUser("testUser", 1) } returns null + + When { + get("/1") + } Then { + statusCode(404) + contentType(ContentType.JSON) + } + } + + /** + * Test that tries to obtain a job. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testGetExisting() { + every { projectService.findWithUser("testUser", 1) } returns dummyProject + + When { + get("/1") + } Then { + statusCode(200) + contentType(ContentType.JSON) + body("id", equalTo(0)) + } + } + + /** + * Test that tries to create a project. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testCreate() { + every { projectService.createForUser("testUser", "test") } returns dummyProject + + Given { + body(Project.Create("test")) + contentType(ContentType.JSON) + } When { + post() + } Then { + statusCode(200) + contentType(ContentType.JSON) + body("id", equalTo(0)) + body("name", equalTo("test")) + } + } + + /** + * Test to create a project with an empty body. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testCreateEmpty() { + Given { + body("{}") + contentType(ContentType.JSON) + } When { + post() + } Then { + statusCode(400) + contentType(ContentType.JSON) + } + } + + /** + * Test to create a project with a blank name. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testCreateBlankName() { + Given { + body(Project.Create("")) + contentType(ContentType.JSON) + } When { + post() + } Then { + statusCode(400) + contentType(ContentType.JSON) + } + } + + /** + * Test to delete a non-existent project. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testDeleteNonExistent() { + every { projectService.deleteWithUser("testUser", 1) } returns null + + When { + delete("/1") + } Then { + statusCode(404) + contentType(ContentType.JSON) + } + } + + /** + * Test to delete a project. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testDelete() { + every { projectService.deleteWithUser("testUser", 1) } returns dummyProject + + When { + delete("/1") + } Then { + statusCode(200) + contentType(ContentType.JSON) + } + } + + /** + * Test to delete a project which the user does not own. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testDeleteNonOwner() { + every { projectService.deleteWithUser("testUser", 1) } throws IllegalArgumentException("User does not own project") + + When { + delete("/1") + } Then { + statusCode(403) + contentType(ContentType.JSON) + } + } +} diff --git a/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/ScenarioResourceTest.kt b/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/ScenarioResourceTest.kt new file mode 100644 index 00000000..1d63679e --- /dev/null +++ b/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/ScenarioResourceTest.kt @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2022 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.web.server.rest.user + +import io.mockk.every +import io.quarkiverse.test.junit.mockk.InjectMock +import io.quarkus.test.common.http.TestHTTPEndpoint +import io.quarkus.test.junit.QuarkusMock +import io.quarkus.test.junit.QuarkusTest +import io.quarkus.test.security.TestSecurity +import io.restassured.http.ContentType +import io.restassured.module.kotlin.extensions.Given +import io.restassured.module.kotlin.extensions.Then +import io.restassured.module.kotlin.extensions.When +import org.hamcrest.Matchers +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.opendc.web.proto.* +import org.opendc.web.proto.user.* +import org.opendc.web.server.service.ScenarioService +import java.time.Instant + +/** + * Test suite for [ScenarioResource]. + */ +@QuarkusTest +@TestHTTPEndpoint(ScenarioResource::class) +class ScenarioResourceTest { + @InjectMock + private lateinit var scenarioService: ScenarioService + + /** + * Dummy values + */ + private val dummyProject = Project(0, "test", Instant.now(), Instant.now(), ProjectRole.OWNER) + private val dummyPortfolio = Portfolio.Summary(1, 1, "test", Targets(emptySet())) + private val dummyJob = Job(1, JobState.PENDING, Instant.now(), Instant.now(), null) + private val dummyTrace = Trace("bitbrains", "Bitbrains", "vm") + private val dummyTopology = Topology.Summary(1, 1, "test", Instant.now(), Instant.now()) + private val dummyScenario = Scenario( + 1, + 1, + dummyProject, + dummyPortfolio, + "test", + Workload(dummyTrace, 1.0), + dummyTopology, + OperationalPhenomena(false, false), + "test", + dummyJob + ) + + @BeforeEach + fun setUp() { + QuarkusMock.installMockForType(scenarioService, ScenarioService::class.java) + } + + /** + * Test that tries to obtain a scenario without token. + */ + @Test + fun testGetWithoutToken() { + Given { + pathParam("project", "1") + } When { + get("/1") + } Then { + statusCode(401) + } + } + + /** + * Test that tries to obtain a scenario with an invalid scope. + */ + @Test + @TestSecurity(user = "testUser", roles = ["runner"]) + fun testGetInvalidToken() { + Given { + pathParam("project", "1") + } When { + get("/1") + } Then { + statusCode(403) + } + } + + /** + * Test that tries to obtain a non-existent scenario. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testGetNonExisting() { + every { scenarioService.findOne("testUser", 1, 1) } returns null + + Given { + pathParam("project", "1") + } When { + get("/1") + } Then { + statusCode(404) + contentType(ContentType.JSON) + } + } + + /** + * Test that tries to obtain a scenario. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testGetExisting() { + every { scenarioService.findOne("testUser", 1, 1) } returns dummyScenario + + Given { + pathParam("project", "1") + } When { + get("/1") + } Then { + statusCode(200) + contentType(ContentType.JSON) + body("id", Matchers.equalTo(1)) + } + } + + /** + * Test to delete a non-existent scenario. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testDeleteNonExistent() { + every { scenarioService.delete("testUser", 1, 1) } returns null + + Given { + pathParam("project", "1") + } When { + delete("/1") + } Then { + statusCode(404) + } + } + + /** + * Test to delete a scenario. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testDelete() { + every { scenarioService.delete("testUser", 1, 1) } returns dummyScenario + + Given { + pathParam("project", "1") + } When { + delete("/1") + } Then { + statusCode(200) + contentType(ContentType.JSON) + } + } +} diff --git a/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/TopologyResourceTest.kt b/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/TopologyResourceTest.kt new file mode 100644 index 00000000..8a542d33 --- /dev/null +++ b/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/TopologyResourceTest.kt @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2022 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.web.server.rest.user + +import io.mockk.every +import io.quarkiverse.test.junit.mockk.InjectMock +import io.quarkus.test.common.http.TestHTTPEndpoint +import io.quarkus.test.junit.QuarkusMock +import io.quarkus.test.junit.QuarkusTest +import io.quarkus.test.security.TestSecurity +import io.restassured.http.ContentType +import io.restassured.module.kotlin.extensions.Given +import io.restassured.module.kotlin.extensions.Then +import io.restassured.module.kotlin.extensions.When +import org.hamcrest.Matchers +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.opendc.web.proto.user.Project +import org.opendc.web.proto.user.ProjectRole +import org.opendc.web.proto.user.Topology +import org.opendc.web.server.service.TopologyService +import java.time.Instant + +/** + * Test suite for [TopologyResource]. + */ +@QuarkusTest +@TestHTTPEndpoint(TopologyResource::class) +class TopologyResourceTest { + @InjectMock + private lateinit var topologyService: TopologyService + + /** + * Dummy project and topology. + */ + private val dummyProject = Project(1, "test", Instant.now(), Instant.now(), ProjectRole.OWNER) + private val dummyTopology = Topology(1, 1, dummyProject, "test", emptyList(), Instant.now(), Instant.now()) + + @BeforeEach + fun setUp() { + QuarkusMock.installMockForType(topologyService, TopologyService::class.java) + } + + /** + * Test that tries to obtain the list of topologies belonging to a project. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testGetForProject() { + every { topologyService.findAll("testUser", 1) } returns emptyList() + + Given { + pathParam("project", "1") + } When { + get() + } Then { + statusCode(200) + contentType(ContentType.JSON) + } + } + + /** + * Test that tries to create a topology for a project. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testCreateNonExistent() { + every { topologyService.create("testUser", 1, any()) } returns null + + Given { + pathParam("project", "1") + + body(Topology.Create("test", emptyList())) + contentType(ContentType.JSON) + } When { + post() + } Then { + statusCode(404) + contentType(ContentType.JSON) + } + } + + /** + * Test that tries to create a topology for a project. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testCreate() { + every { topologyService.create("testUser", 1, any()) } returns dummyTopology + + Given { + pathParam("project", "1") + + body(Topology.Create("test", emptyList())) + contentType(ContentType.JSON) + } When { + post() + } Then { + statusCode(200) + contentType(ContentType.JSON) + body("id", Matchers.equalTo(1)) + body("name", Matchers.equalTo("test")) + } + } + + /** + * Test to create a topology with an empty body. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testCreateEmpty() { + Given { + pathParam("project", "1") + + body("{}") + contentType(ContentType.JSON) + } When { + post() + } Then { + statusCode(400) + contentType(ContentType.JSON) + } + } + + /** + * Test to create a topology with a blank name. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testCreateBlankName() { + Given { + pathParam("project", "1") + + body(Topology.Create("", emptyList())) + contentType(ContentType.JSON) + } When { + post() + } Then { + statusCode(400) + contentType(ContentType.JSON) + } + } + + /** + * Test that tries to obtain a topology without token. + */ + @Test + fun testGetWithoutToken() { + Given { + pathParam("project", "1") + } When { + get("/1") + } Then { + statusCode(401) + } + } + + /** + * Test that tries to obtain a topology with an invalid scope. + */ + @Test + @TestSecurity(user = "testUser", roles = ["runner"]) + fun testGetInvalidToken() { + Given { + pathParam("project", "1") + } When { + get("/1") + } Then { + statusCode(403) + } + } + + /** + * Test that tries to obtain a non-existent topology. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testGetNonExisting() { + every { topologyService.findOne("testUser", 1, 1) } returns null + + Given { + pathParam("project", "1") + } When { + get("/1") + } Then { + statusCode(404) + contentType(ContentType.JSON) + } + } + + /** + * Test that tries to obtain a topology. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testGetExisting() { + every { topologyService.findOne("testUser", 1, 1) } returns dummyTopology + + Given { + pathParam("project", "1") + } When { + get("/1") + } Then { + statusCode(200) + contentType(ContentType.JSON) + body("id", Matchers.equalTo(1)) + println(extract().asPrettyString()) + } + } + + /** + * Test to delete a non-existent topology. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testUpdateNonExistent() { + every { topologyService.update("testUser", any(), any(), any()) } returns null + + Given { + pathParam("project", "1") + body(Topology.Update(emptyList())) + contentType(ContentType.JSON) + } When { + put("/1") + } Then { + statusCode(404) + } + } + + /** + * Test to update a topology. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testUpdate() { + every { topologyService.update("testUser", any(), any(), any()) } returns dummyTopology + + Given { + pathParam("project", "1") + body(Topology.Update(emptyList())) + contentType(ContentType.JSON) + } When { + put("/1") + } Then { + statusCode(200) + contentType(ContentType.JSON) + } + } + + /** + * Test to delete a non-existent topology. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testDeleteNonExistent() { + every { topologyService.delete("testUser", 1, 1) } returns null + + Given { + pathParam("project", "1") + } When { + delete("/1") + } Then { + statusCode(404) + } + } + + /** + * Test to delete a topology. + */ + @Test + @TestSecurity(user = "testUser", roles = ["openid"]) + fun testDelete() { + every { topologyService.delete("testUser", 1, 1) } returns dummyTopology + + Given { + pathParam("project", "1") + } When { + delete("/1") + } Then { + statusCode(200) + contentType(ContentType.JSON) + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 170267a5..860cbda5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -36,7 +36,7 @@ include(":opendc-faas:opendc-faas-workload") include(":opendc-experiments:opendc-experiments-capelin") include(":opendc-experiments:opendc-experiments-tf20") include(":opendc-web:opendc-web-proto") -include(":opendc-web:opendc-web-api") +include(":opendc-web:opendc-web-server") include(":opendc-web:opendc-web-client") include(":opendc-web:opendc-web-ui") include(":opendc-web:opendc-web-ui-quarkus") -- cgit v1.2.3 From acfab9d4ca84e9331bb40c74d300c593915c542a Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Mon, 1 Aug 2022 21:51:18 +0200 Subject: feat(web/server): Implement database migrations using Flyway This change updates the Quarkus-based web server to use Flyway for migrating between schema versions. This enables us to evolve the schema and denote it in SQL. --- opendc-web/opendc-web-server/build.gradle.kts | 1 + .../src/main/resources/application-dev.properties | 4 +- .../main/resources/application-docker.properties | 1 - .../src/main/resources/application-prod.properties | 3 +- .../src/main/resources/application-test.properties | 4 +- .../src/main/resources/application.properties | 4 + .../main/resources/db/migration/V1.0.0__core.sql | 144 +++++++++++++++++++++ .../src/main/resources/import.sql | 3 - 8 files changed, 154 insertions(+), 10 deletions(-) create mode 100644 opendc-web/opendc-web-server/src/main/resources/db/migration/V1.0.0__core.sql delete mode 100644 opendc-web/opendc-web-server/src/main/resources/import.sql diff --git a/opendc-web/opendc-web-server/build.gradle.kts b/opendc-web/opendc-web-server/build.gradle.kts index d6b9164c..a55df2f7 100644 --- a/opendc-web/opendc-web-server/build.gradle.kts +++ b/opendc-web/opendc-web-server/build.gradle.kts @@ -48,6 +48,7 @@ dependencies { implementation(libs.quarkus.hibernate.orm) implementation(libs.quarkus.hibernate.validator) + implementation(libs.quarkus.flyway) implementation(libs.quarkus.jdbc.postgresql) implementation(libs.quarkus.jdbc.h2) diff --git a/opendc-web/opendc-web-server/src/main/resources/application-dev.properties b/opendc-web/opendc-web-server/src/main/resources/application-dev.properties index 3f30e9c4..4065f55f 100644 --- a/opendc-web/opendc-web-server/src/main/resources/application-dev.properties +++ b/opendc-web/opendc-web-server/src/main/resources/application-dev.properties @@ -20,11 +20,11 @@ # Datasource (H2) quarkus.datasource.db-kind=h2 -quarkus.datasource.jdbc.url=jdbc:h2:mem:default;DB_CLOSE_DELAY=-1;INIT=CREATE TYPE IF NOT EXISTS "JSONB" AS blob; +quarkus.datasource.jdbc.url=jdbc:h2:mem:default;DB_CLOSE_DELAY=-1;INIT=CREATE TYPE IF NOT EXISTS "JSONB" AS json; # Hibernate quarkus.hibernate-orm.dialect=org.hibernate.dialect.H2Dialect -quarkus.hibernate-orm.database.generation=drop-and-create +quarkus.flyway.clean-at-start=true # Disable authentication opendc.security.enabled=false diff --git a/opendc-web/opendc-web-server/src/main/resources/application-docker.properties b/opendc-web/opendc-web-server/src/main/resources/application-docker.properties index cd1f9ff3..eae9ee1e 100644 --- a/opendc-web/opendc-web-server/src/main/resources/application-docker.properties +++ b/opendc-web/opendc-web-server/src/main/resources/application-docker.properties @@ -28,7 +28,6 @@ quarkus.datasource.jdbc.url=${OPENDC_DB_URL} # Hibernate quarkus.hibernate-orm.dialect=org.hibernate.dialect.PostgreSQL95Dialect -quarkus.hibernate-orm.database.generation=validate # Disable OpenDC web UI quarkus.opendc-ui.include=false diff --git a/opendc-web/opendc-web-server/src/main/resources/application-prod.properties b/opendc-web/opendc-web-server/src/main/resources/application-prod.properties index 09653d59..8e6a9720 100644 --- a/opendc-web/opendc-web-server/src/main/resources/application-prod.properties +++ b/opendc-web/opendc-web-server/src/main/resources/application-prod.properties @@ -20,11 +20,10 @@ # Datasource (H2) quarkus.datasource.db-kind=h2 -quarkus.datasource.jdbc.url=jdbc:h2:file:./data/opendc;DB_CLOSE_DELAY=-1;INIT=CREATE TYPE IF NOT EXISTS "JSONB" AS blob; +quarkus.datasource.jdbc.url=jdbc:h2:file:./data/opendc;DB_CLOSE_DELAY=-1;INIT=CREATE TYPE IF NOT EXISTS "JSONB" AS json; # Hibernate quarkus.hibernate-orm.dialect=org.hibernate.dialect.H2Dialect -quarkus.hibernate-orm.database.generation=validate # Disable authentication opendc.security.enabled=false diff --git a/opendc-web/opendc-web-server/src/main/resources/application-test.properties b/opendc-web/opendc-web-server/src/main/resources/application-test.properties index 78512f3f..338a00b9 100644 --- a/opendc-web/opendc-web-server/src/main/resources/application-test.properties +++ b/opendc-web/opendc-web-server/src/main/resources/application-test.properties @@ -20,10 +20,10 @@ # Datasource configuration quarkus.datasource.db-kind = h2 -quarkus.datasource.jdbc.url=jdbc:h2:mem:default;DB_CLOSE_DELAY=-1;INIT=CREATE TYPE "JSONB" AS blob; +quarkus.datasource.jdbc.url=jdbc:h2:mem:default;DB_CLOSE_DELAY=-1;INIT=CREATE TYPE IF NOT EXISTS "JSONB" AS json; quarkus.hibernate-orm.dialect=org.hibernate.dialect.H2Dialect -quarkus.hibernate-orm.database.generation=drop-and-create +quarkus.flyway.clean-at-start=true # Disable security quarkus.oidc.enabled=false diff --git a/opendc-web/opendc-web-server/src/main/resources/application.properties b/opendc-web/opendc-web-server/src/main/resources/application.properties index d0b567e5..40933304 100644 --- a/opendc-web/opendc-web-server/src/main/resources/application.properties +++ b/opendc-web/opendc-web-server/src/main/resources/application.properties @@ -42,3 +42,7 @@ quarkus.smallrye-openapi.info-license-url=https://github.com/atlarge-research/op quarkus.swagger-ui.path=docs quarkus.swagger-ui.always-include=true + +# Flyway database migrations +quarkus.flyway.baseline-on-migrate=true +quarkus.flyway.migrate-at-start=true diff --git a/opendc-web/opendc-web-server/src/main/resources/db/migration/V1.0.0__core.sql b/opendc-web/opendc-web-server/src/main/resources/db/migration/V1.0.0__core.sql new file mode 100644 index 00000000..183a70ea --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/resources/db/migration/V1.0.0__core.sql @@ -0,0 +1,144 @@ +-- Hibernate sequence for unique identifiers +create sequence hibernate_sequence start with 1 increment by 1; + +-- Projects +create table projects +( + id bigint not null, + created_at timestamp not null, + name varchar(255) not null, + portfolios_created integer not null, + scenarios_created integer not null, + topologies_created integer not null, + updated_at timestamp not null, + primary key (id) +); + +-- Project authorizations authorize users specific permissions to a project. +create table project_authorizations +( + project_id bigint not null, + user_id varchar(255) not null, + role integer not null, + primary key (project_id, user_id) +); + +-- Topologies represent the datacenter designs created by users. +create table topologies +( + id bigint not null, + created_at timestamp not null, + name varchar(255) not null, + number integer not null, + rooms jsonb not null, + updated_at timestamp not null, + project_id bigint not null, + primary key (id) +); + +-- Portfolios +create table portfolios +( + id bigint not null, + name varchar(255) not null, + number integer not null, + targets jsonb not null, + project_id bigint not null, + primary key (id) +); + +create table scenarios +( + id bigint not null, + name varchar(255) not null, + number integer not null, + phenomena jsonb not null, + scheduler_name varchar(255) not null, + sampling_fraction double precision not null, + job_id bigint, + portfolio_id bigint not null, + project_id bigint not null, + topology_id bigint not null, + trace_id varchar(255) not null, + primary key (id) +); + +create table jobs +( + id bigint not null, + created_at timestamp not null, + repeats integer not null, + results jsonb, + state integer not null, + updated_at timestamp not null, + primary key (id) +); + +-- Workload traces available to the user. +create table traces +( + id varchar(255) not null, + name varchar(255) not null, + type varchar(255) not null, + primary key (id) +); + +-- Relations +alter table project_authorizations + add constraint FK824hw0npe6gwiamwb6vohsu19 + foreign key (project_id) + references projects; + +create index fn_topologies_number on topologies (project_id, number); + +alter table topologies + add constraint UK2s5na63qtu2of4g7odocmwi2a unique (project_id, number); + +alter table topologies + add constraint FK1kpw87pylq7m2ct9lq0ed1u3b + foreign key (project_id) + references projects; + +create index fn_portfolios_number on portfolios (project_id, number); + +alter table portfolios + add constraint FK31ytuaxb7aboxueng9hq7owwa + foreign key (project_id) + references projects; + +alter table portfolios + add constraint UK56dtskxruwj22dvxny2hfhks1 unique (project_id, number); + +create index fn_scenarios_number on scenarios (project_id, number); + +alter table scenarios + add constraint UKd0bk6fmtw5qiu9ty7t3g9crqd unique (project_id, number); + +alter table scenarios + add constraint FK9utvg0i5uu8db9pa17a1d77iy + foreign key (job_id) + references jobs; + +alter table scenarios + add constraint FK181y5hv0uibhj7fpbpkdy90s5 + foreign key (portfolio_id) + references portfolios; + +alter table scenarios + add constraint FKbvwyh4joavs444rj270o3b8fr + foreign key (project_id) + references projects; + +alter table scenarios + add constraint FKrk6ltvaf9lp0aukp9dq3qjujj + foreign key (topology_id) + references topologies; + +alter table scenarios + add constraint FK5m05tqeekqjkbbsaj3ehl6o8n + foreign key (trace_id) + references traces; + +-- Initial data +insert into traces (id, name, type) +values ('bitbrains-small', 'Bitbrains Small', 'vm'); diff --git a/opendc-web/opendc-web-server/src/main/resources/import.sql b/opendc-web/opendc-web-server/src/main/resources/import.sql deleted file mode 100644 index 756eff46..00000000 --- a/opendc-web/opendc-web-server/src/main/resources/import.sql +++ /dev/null @@ -1,3 +0,0 @@ - --- Add example traces -INSERT INTO traces (id, name, type) VALUES ('bitbrains-small', 'Bitbrains Small', 'vm'); -- cgit v1.2.3 From ec39ad82a9257c3aae11ef60c0c81a1362b73ead Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Sat, 30 Jul 2022 12:20:42 +0200 Subject: fix(web/ui): Fix Docker deployment This change fixes the Docker deployment of the OpenDC web UI. There have been several updates to the build process of the web UI, which have not taken into account the Docker deployment process. This change addresses these discrepancies. --- opendc-web/opendc-web-ui/Dockerfile | 12 ++++++------ opendc-web/opendc-web-ui/scripts/envsubst.sh | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/opendc-web/opendc-web-ui/Dockerfile b/opendc-web/opendc-web-ui/Dockerfile index 15a92068..5ff85092 100644 --- a/opendc-web/opendc-web-ui/Dockerfile +++ b/opendc-web/opendc-web-ui/Dockerfile @@ -2,8 +2,8 @@ FROM node:16 AS staging MAINTAINER OpenDC Maintainers # Copy package details -COPY ./package.json ./yarn.lock /opendc/ -RUN cd /opendc && yarn install --frozen-lockfile +COPY ./package.json ./package-lock.json /opendc/ +RUN cd /opendc && npm ci # Build frontend FROM node:16 AS build @@ -17,12 +17,12 @@ RUN cd /opendc/ \ NEXT_PUBLIC_AUTH0_DOMAIN="%%NEXT_PUBLIC_AUTH0_DOMAIN%%" \ NEXT_PUBLIC_AUTH0_CLIENT_ID="%%NEXT_PUBLIC_AUTH0_CLIENT_ID%%" \ NEXT_PUBLIC_AUTH0_AUDIENCE="%%NEXT_PUBLIC_AUTH0_AUDIENCE%%" \ - && yarn build \ - && yarn cache clean --all \ - && mv .next .next.template + && npm run build \ + && npm cache clean --force \ + && mv build/next build/next.template FROM node:16-slim COPY --from=build /opendc /opendc WORKDIR /opendc -CMD ./scripts/envsubst.sh; yarn start +CMD ./scripts/envsubst.sh; npm run start diff --git a/opendc-web/opendc-web-ui/scripts/envsubst.sh b/opendc-web/opendc-web-ui/scripts/envsubst.sh index d7ae9ecb..afc976ed 100755 --- a/opendc-web/opendc-web-ui/scripts/envsubst.sh +++ b/opendc-web/opendc-web-ui/scripts/envsubst.sh @@ -3,8 +3,8 @@ set -e auto_envsubst() { - input_path="/opendc/.next.template" - output_path="/opendc/.next" + input_path="build/next.template" + output_path="build/next" cp -r "$input_path" "$output_path" find "$output_path" -type f -name '*.js' -exec perl -pi -e 's/%%(NEXT_PUBLIC_[_A-Z0-9]+)%%/$ENV{$1}/g' {} \; -- cgit v1.2.3 From 1724ab032deedea0a9c54e827829632a9ba066aa Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Sat, 30 Jul 2022 12:22:45 +0200 Subject: ci: Build Docker image for OpenDC web UI This change adds a build step for building the OpenDC web UI as Docker image. We forgot this step and found that the image failed to build during previous commits. --- .github/workflows/build.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3f6ca4dd..324866e0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -62,11 +62,16 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v3 - - name: Build Runner + - name: Build UI uses: docker/build-push-action@v3 with: - file: opendc-web/opendc-web-runner/Dockerfile + context: opendc-web/opendc-web-ui + file: opendc-web/opendc-web-ui/Dockerfile - name: Build Web Server uses: docker/build-push-action@v3 with: file: opendc-web/opendc-web-server/Dockerfile + - name: Build Runner + uses: docker/build-push-action@v3 + with: + file: opendc-web/opendc-web-runner/Dockerfile -- cgit v1.2.3 From 87df18bb691260ee69d2e48cf1598e6a4acc329b Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Sat, 30 Jul 2022 12:56:26 +0200 Subject: docs: Update deployment guide This change updates the deployment guide based on the latest changes to the Quarkus application. Quite a few existing configuration options can be removed now. --- docs/deploy.md | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/docs/deploy.md b/docs/deploy.md index da622571..ac417def 100644 --- a/docs/deploy.md +++ b/docs/deploy.md @@ -1,7 +1,5 @@ # Deploying OpenDC -This document explains how you can deploy OpenDC in your local environment. -The official way to run OpenDC is using Docker. Other options include building and running locally, and building and -running to deploy on a server. +This document explains how you can deploy a multi-tenant instance of OpenDC using Docker. ## Contents @@ -30,13 +28,6 @@ create: Once your application has been created, you should have a _Domain_ and _Client ID_ which we need to pass to the frontend application (as `OPENDC_AUTH0_DOMAIN` and `OPENDC_AUTH0_CLIENT_ID` respectively). -3. **A Machine to Machine Application (M2M)** - You need to define a Machine to Machine application in Auth0 so that the simulator can communicate with the OpenDC API. - Please refer to the [following guide](https://auth0.com/docs/get-started/auth0-overview/create-applications/machine-to-machine-apps) - on how to create such an application. - - Once your application has been created, you should have a _Client ID_ and _Client Secret_ which we need to pass to the - simulator (as `OPENDC_AUTH0_CLIENT_ID_RUNNER` and `OPENDC_AUTH0_CLIENT_SECRET_RUNNER` respectively). ## Installing Docker @@ -61,23 +52,16 @@ called `.env` in the `opendc` folder. In this file, replace `your-auth0-*` with step. For a standard setup, you can leave the other settings as-is. ```.env -MONGO_INITDB_ROOT_USERNAME=root -MONGO_INITDB_ROOT_PASSWORD=rootpassword -MONGO_INITDB_DATABASE=admin -OPENDC_DB=opendc OPENDC_DB_USERNAME=opendc OPENDC_DB_PASSWORD=opendcpassword -OPENDC_FLASK_SECRET="This is a secret flask key, please change" OPENDC_AUTH0_DOMAIN=your-auth0-domain OPENDC_AUTH0_CLIENT_ID=your-auth0-client-id OPENDC_AUTH0_AUDIENCE=your-auth0-api-identifier -OPENDC_AUTH0_CLIENT_ID_RUNNER=your-auth0-client-id-for-runner -OPENDC_AUTH0_CLIENT_SECRET_RUNNER=your-auth0-client-secret-for-runner OPENDC_API_BASE_URL=http://web ``` We provide a set of default traces for you to experiment with. If you want to add others, place them in the `traces` -directory and add entries to the database (see also [the database folder](../database/mongo-init-opendc-db.sh)) +directory and add entries to the database (see also [the SQL init script](../opendc-web/opendc-web-server/src/main/resources/db/migration/V1.0.0__core.sql)) If you plan to deploy publicly, please also tweak the other settings. In that case, also check the `docker-compose.yml` and `docker-compose.prod.yml` for further instructions. @@ -93,4 +77,4 @@ docker-compose up ``` Wait a few seconds and open `http://localhost:8080` in your browser to use OpenDC. We recommend Google Chrome for the -best development experience. +best user experience. -- cgit v1.2.3