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