diff options
| author | Fabian Mastenbroek <mail.fabianm@gmail.com> | 2021-10-26 16:19:55 +0200 |
|---|---|---|
| committer | Fabian Mastenbroek <mail.fabianm@gmail.com> | 2022-04-04 12:48:04 +0200 |
| commit | f0c472b1792779e63fdeb97a470b46300de00050 (patch) | |
| tree | 99646cf4448f4f73c2c98ede338df19a1497f9b5 /opendc-web/opendc-web-api/src/main/kotlin | |
| parent | 8f958c5a578dc11b890c96c0dc48e3e3f92a4d07 (diff) | |
feat(web/api): Initial API implementation in Kotlin
This change adds the initial implementation of the new API server in Kotlin,
replacing the old API written in Python. The implementation uses Quarkus,
RESTEasy, and Hibernate to implement the new API endpoints.
The reason for replacing the old API server is unifying the build and
deployment toolchains, reducing the number of technologies necessary to
work with OpenDC. Furthermore, we envision bundling the entire OpenDC
project into a single distributions, allowing users to launch their own
deployment trivially.
Diffstat (limited to 'opendc-web/opendc-web-api/src/main/kotlin')
36 files changed, 2757 insertions, 0 deletions
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 new file mode 100644 index 00000000..ddbd5390 --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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 new file mode 100644 index 00000000..23838e34 --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Job.kt @@ -0,0 +1,96 @@ +/* + * 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 io.quarkiverse.hibernate.types.json.JsonBinaryType +import io.quarkiverse.hibernate.types.json.JsonTypes +import org.hibernate.annotations.Type +import org.hibernate.annotations.TypeDef +import org.opendc.web.proto.JobState +import java.time.Instant +import javax.persistence.* + +/** + * A simulation job to be run by the simulator. + */ +@TypeDef(name = JsonTypes.JSON_BIN, typeClass = JsonBinaryType::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 = JsonTypes.JSON_BIN) + @Column(columnDefinition = JsonTypes.JSON_BIN) + var results: Map<String, Any>? = 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 new file mode 100644 index 00000000..c8b94daf --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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<Scenario> = 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 new file mode 100644 index 00000000..e0440bf4 --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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<Portfolio> = 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<Topology> = 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<Scenario> = 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<ProjectAuthorization> = 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 new file mode 100644 index 00000000..a72ff06a --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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 new file mode 100644 index 00000000..b5f66e70 --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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 new file mode 100644 index 00000000..9a383c7c --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Scenario.kt @@ -0,0 +1,108 @@ +/* + * 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 io.quarkiverse.hibernate.types.json.JsonBinaryType +import io.quarkiverse.hibernate.types.json.JsonTypes +import org.hibernate.annotations.Type +import org.hibernate.annotations.TypeDef +import org.opendc.web.proto.OperationalPhenomena +import javax.persistence.* + +/** + * A single scenario to be explored by the simulator. + */ +@TypeDef(name = JsonTypes.JSON_BIN, typeClass = JsonBinaryType::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 = JsonTypes.JSON_BIN) + @Column(columnDefinition = JsonTypes.JSON_BIN, 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 new file mode 100644 index 00000000..32bf799a --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/model/Topology.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.api.model + +import io.quarkiverse.hibernate.types.json.JsonBinaryType +import io.quarkiverse.hibernate.types.json.JsonTypes +import org.hibernate.annotations.Type +import org.hibernate.annotations.TypeDef +import org.opendc.web.proto.Room +import java.time.Instant +import javax.persistence.* + +/** + * A datacenter design in OpenDC. + */ +@TypeDef(name = JsonTypes.JSON_BIN, typeClass = JsonBinaryType::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 = JsonTypes.JSON_BIN) + @Column(columnDefinition = JsonTypes.JSON_BIN, nullable = false) + var rooms: List<Room> = 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 new file mode 100644 index 00000000..2e2d71f8 --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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 new file mode 100644 index 00000000..07fc096b --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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 new file mode 100644 index 00000000..558d7c38 --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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<Job> { + 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<String, Any>?): 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 new file mode 100644 index 00000000..34b3598c --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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<Portfolio> { + 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 new file mode 100644 index 00000000..6529f778 --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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<ProjectAuthorization> { + 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 new file mode 100644 index 00000000..de116ad6 --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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<Scenario> { + 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<Scenario> { + 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 new file mode 100644 index 00000000..cd8f666e --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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<Topology> { + 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 new file mode 100644 index 00000000..6652fc80 --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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<Trace> { + 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 new file mode 100644 index 00000000..735fdd9b --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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 new file mode 100644 index 00000000..e87fe602 --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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<Trace> { + 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 new file mode 100644 index 00000000..fb253758 --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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<Exception> { + 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 new file mode 100644 index 00000000..57cd35d1 --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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<MissingKotlinParameterException> { + 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 new file mode 100644 index 00000000..7e31e2c5 --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/rest/runner/JobResource.kt @@ -0,0 +1,65 @@ +/* + * 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<Job> { + 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 jobService.updateState(id, update.state, update.results) ?: throw WebApplicationException("Job not found", 404) + } +} 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 new file mode 100644 index 00000000..e720de75 --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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<Portfolio> { + 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 new file mode 100644 index 00000000..8d24b2eb --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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<Scenario> { + 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 new file mode 100644 index 00000000..a27d50e7 --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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<Project> { + 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 new file mode 100644 index 00000000..3690f987 --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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 new file mode 100644 index 00000000..52c5eaaa --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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<Topology> { + 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 new file mode 100644 index 00000000..1b33248d --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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<Job> { + 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<String, Any>?): 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 new file mode 100644 index 00000000..1f41c2d7 --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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<Portfolio> { + // 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 new file mode 100644 index 00000000..c3e43395 --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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<Project> { + 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 new file mode 100644 index 00000000..3722a641 --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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 new file mode 100644 index 00000000..dd51a929 --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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<Scenario> { + // 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 new file mode 100644 index 00000000..f3460496 --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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<Topology> { + // 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 new file mode 100644 index 00000000..a942696e --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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<Trace> { + 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 new file mode 100644 index 00000000..8612ee8c --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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 new file mode 100644 index 00000000..254be8b7 --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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/KotlinModuleCustomizer.kt b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/util/KotlinModuleCustomizer.kt new file mode 100644 index 00000000..8d91a00c --- /dev/null +++ b/opendc-web/opendc-web-api/src/main/kotlin/org/opendc/web/api/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.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()) + } +} |
