summaryrefslogtreecommitdiff
path: root/opendc-web/opendc-web-server/src/main/java/org/opendc
diff options
context:
space:
mode:
Diffstat (limited to 'opendc-web/opendc-web-server/src/main/java/org/opendc')
-rw-r--r--opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/SchedulerResource.java52
-rw-r--r--opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/TraceResource.java (renamed from opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/TraceService.java)39
-rw-r--r--opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/MissingKotlinParameterExceptionMapper.java46
-rw-r--r--opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/WebApplicationExceptionMapper.java51
-rw-r--r--opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/runner/JobResource.java103
-rw-r--r--opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioResource.java120
-rw-r--r--opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioScenarioResource.java89
-rw-r--r--opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ProjectResource.java118
-rw-r--r--opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ScenarioResource.java94
-rw-r--r--opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/TopologyResource.java146
-rw-r--r--opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/UserResource.java (renamed from opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/UserService.java)33
-rw-r--r--opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/JobService.java8
-rw-r--r--opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/ScenarioService.java9
-rw-r--r--opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/util/KotlinModuleCustomizer.java39
14 files changed, 924 insertions, 23 deletions
diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/SchedulerResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/SchedulerResource.java
new file mode 100644
index 00000000..0fd58182
--- /dev/null
+++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/SchedulerResource.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2023 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.web.server.rest;
+
+import java.util.List;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+/**
+ * A resource representing the available schedulers that can be used during experiments.
+ */
+@Produces("application/json")
+@Path("/schedulers")
+public final class SchedulerResource {
+ /**
+ * Obtain all available schedulers.
+ */
+ @GET
+ public List<String> getAll() {
+ return List.of(
+ "mem",
+ "mem-inv",
+ "core-mem",
+ "core-mem-inv",
+ "active-servers",
+ "active-servers-inv",
+ "provisioned-cores",
+ "provisioned-cores-inv",
+ "random");
+ }
+}
diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/TraceService.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/TraceResource.java
index 94b8340b..2b1efb02 100644
--- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/TraceService.java
+++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/TraceResource.java
@@ -20,30 +20,45 @@
* SOFTWARE.
*/
-package org.opendc.web.server.service;
+package org.opendc.web.server.rest;
import java.util.List;
-import javax.enterprise.context.ApplicationScoped;
+import java.util.stream.Stream;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
import org.opendc.web.server.model.Trace;
/**
- * Service for managing {@link Trace}s.
+ * A resource representing the workload traces available in the OpenDC instance.
*/
-@ApplicationScoped
-public final class TraceService {
+@Produces("application/json")
+@Path("/traces")
+public final class TraceResource {
/**
- * Obtain all available workload traces.
+ * Obtain all available traces.
*/
- public List<org.opendc.web.proto.Trace> findAll() {
- List<Trace> entities = Trace.listAll();
- return entities.stream().map(TraceService::toUserDto).toList();
+ @GET
+ public List<org.opendc.web.proto.Trace> getAll() {
+ Stream<Trace> entities = Trace.streamAll();
+ return entities.map(TraceResource::toUserDto).toList();
}
/**
- * Obtain a workload trace by identifier.
+ * Obtain trace information by identifier.
*/
- public org.opendc.web.proto.Trace findById(String id) {
- return toUserDto(Trace.findById(id));
+ @GET
+ @Path("{id}")
+ public org.opendc.web.proto.Trace get(@PathParam("id") String id) {
+ Trace trace = Trace.findById(id);
+
+ if (trace == null) {
+ throw new WebApplicationException("Trace not found", 404);
+ }
+
+ return toUserDto(trace);
}
/**
diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/MissingKotlinParameterExceptionMapper.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/MissingKotlinParameterExceptionMapper.java
new file mode 100644
index 00000000..3b6be42e
--- /dev/null
+++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/MissingKotlinParameterExceptionMapper.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2023 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.web.server.rest.error;
+
+import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+import org.opendc.web.proto.ProtocolError;
+
+/**
+ * An [ExceptionMapper] for [MissingKotlinParameterException] thrown by Jackson.
+ */
+@Provider
+public final class MissingKotlinParameterExceptionMapper implements ExceptionMapper<MissingKotlinParameterException> {
+ @Override
+ public Response toResponse(MissingKotlinParameterException exception) {
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(new ProtocolError(
+ Response.Status.BAD_REQUEST.getStatusCode(),
+ "Field " + exception.getParameter().getName() + " is missing from body."))
+ .type(MediaType.APPLICATION_JSON)
+ .build();
+ }
+}
diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/WebApplicationExceptionMapper.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/WebApplicationExceptionMapper.java
new file mode 100644
index 00000000..ad1bb05e
--- /dev/null
+++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/WebApplicationExceptionMapper.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2023 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.web.server.rest.error;
+
+import 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;
+import org.opendc.web.proto.ProtocolError;
+
+/**
+ * Helper class to transform a {@link WebApplicationException} into an JSON error response.
+ */
+@Provider
+public final class WebApplicationExceptionMapper implements ExceptionMapper<WebApplicationException> {
+ @Override
+ public Response toResponse(WebApplicationException exception) {
+ int code = exception.getResponse().getStatus();
+
+ String message = exception.getMessage();
+ if (message == null) {
+ message = "Unknown error";
+ }
+
+ return Response.status(code)
+ .entity(new ProtocolError(code, message))
+ .type(MediaType.APPLICATION_JSON)
+ .build();
+ }
+}
diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/runner/JobResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/runner/JobResource.java
new file mode 100644
index 00000000..134c6814
--- /dev/null
+++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/runner/JobResource.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2023 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.web.server.rest.runner;
+
+import java.util.List;
+import javax.annotation.security.RolesAllowed;
+import javax.transaction.Transactional;
+import javax.validation.Valid;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import org.opendc.web.server.service.JobService;
+
+/**
+ * A resource representing the available simulation jobs.
+ */
+@Produces("application/json")
+@Path("/jobs")
+@RolesAllowed("runner")
+public final class JobResource {
+ /**
+ * The {@link JobService} responsible for managing the jobs.
+ */
+ private final JobService jobService;
+
+ /**
+ * Construct a {@link JobResource} instance.
+ *
+ * @param jobService The {@link JobService} responsible for managing the jobs.
+ */
+ public JobResource(JobService jobService) {
+ this.jobService = jobService;
+ }
+
+ /**
+ * Obtain all pending simulation jobs.
+ */
+ @GET
+ public List<org.opendc.web.proto.runner.Job> queryPending() {
+ return jobService.listPending();
+ }
+
+ /**
+ * Get a job by identifier.
+ */
+ @GET
+ @Path("{job}")
+ public org.opendc.web.proto.runner.Job get(@PathParam("job") long id) {
+ org.opendc.web.proto.runner.Job job = jobService.findById(id);
+ if (job == null) {
+ throw new WebApplicationException("Job not found", 404);
+ }
+
+ return job;
+ }
+
+ /**
+ * Atomically update the state of a job.
+ */
+ @POST
+ @Path("{job}")
+ @Consumes("application/json")
+ @Transactional
+ public org.opendc.web.proto.runner.Job update(
+ @PathParam("job") long id, @Valid org.opendc.web.proto.runner.Job.Update update) {
+ try {
+ var job = jobService.updateState(id, update.getState(), update.getRuntime(), update.getResults());
+ if (job == null) {
+ throw new WebApplicationException("Job not found", 404);
+ }
+
+ return job;
+ } catch (IllegalArgumentException e) {
+ throw new WebApplicationException(e, 400);
+ } catch (IllegalStateException e) {
+ throw new WebApplicationException(e, 409);
+ }
+ }
+}
diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioResource.java
new file mode 100644
index 00000000..e8e05f97
--- /dev/null
+++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioResource.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2023 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.web.server.rest.user;
+
+import io.quarkus.security.identity.SecurityIdentity;
+import java.util.List;
+import javax.annotation.security.RolesAllowed;
+import javax.transaction.Transactional;
+import javax.validation.Valid;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import org.opendc.web.server.service.PortfolioService;
+
+/**
+ * A resource representing the portfolios of a project.
+ */
+@Produces("application/json")
+@Path("/projects/{project}/portfolios")
+@RolesAllowed("openid")
+public final class PortfolioResource {
+ /**
+ * The service for managing the user portfolios.
+ */
+ private final PortfolioService portfolioService;
+
+ /**
+ * The identity of the current user.
+ */
+ private final SecurityIdentity identity;
+
+ /**
+ * Construct a {@link PortfolioResource}.
+ *
+ * @param portfolioService The {@link PortfolioService} instance to use.
+ * @param identity The {@link SecurityIdentity} of the current user.
+ */
+ public PortfolioResource(PortfolioService portfolioService, SecurityIdentity identity) {
+ this.portfolioService = portfolioService;
+ this.identity = identity;
+ }
+
+ /**
+ * Get all portfolios that belong to the specified project.
+ */
+ @GET
+ public List<org.opendc.web.proto.user.Portfolio> getAll(@PathParam("project") long projectId) {
+ return portfolioService.findByUser(identity.getPrincipal().getName(), projectId);
+ }
+
+ /**
+ * Create a portfolio for this project.
+ */
+ @POST
+ @Transactional
+ public org.opendc.web.proto.user.Portfolio create(
+ @PathParam("project") long projectId, @Valid org.opendc.web.proto.user.Portfolio.Create request) {
+ var portfolio = portfolioService.create(identity.getPrincipal().getName(), projectId, request);
+ if (portfolio == null) {
+ throw new WebApplicationException("Project not found", 404);
+ }
+
+ return portfolio;
+ }
+
+ /**
+ * Obtain a portfolio by its identifier.
+ */
+ @GET
+ @Path("{portfolio}")
+ public org.opendc.web.proto.user.Portfolio get(
+ @PathParam("project") long projectId, @PathParam("portfolio") int number) {
+ var portfolio = portfolioService.findByUser(identity.getPrincipal().getName(), projectId, number);
+ if (portfolio == null) {
+ throw new WebApplicationException("Portfolio not found", 404);
+ }
+
+ return portfolio;
+ }
+
+ /**
+ * Delete a portfolio.
+ */
+ @DELETE
+ @Path("{portfolio}")
+ @Transactional
+ public org.opendc.web.proto.user.Portfolio delete(
+ @PathParam("project") long projectId, @PathParam("portfolio") int number) {
+ var portfolio = portfolioService.delete(identity.getPrincipal().getName(), projectId, number);
+ if (portfolio == null) {
+ throw new WebApplicationException("Portfolio not found", 404);
+ }
+
+ return portfolio;
+ }
+}
diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioScenarioResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioScenarioResource.java
new file mode 100644
index 00000000..a6db7c54
--- /dev/null
+++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioScenarioResource.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2023 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.web.server.rest.user;
+
+import io.quarkus.security.identity.SecurityIdentity;
+import java.util.List;
+import javax.annotation.security.RolesAllowed;
+import javax.transaction.Transactional;
+import javax.validation.Valid;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.WebApplicationException;
+import org.opendc.web.proto.user.Scenario;
+import org.opendc.web.server.service.ScenarioService;
+
+/**
+ * A resource representing the scenarios of a portfolio.
+ */
+@Path("/projects/{project}/portfolios/{portfolio}/scenarios")
+@RolesAllowed("openid")
+public final class PortfolioScenarioResource {
+ /**
+ * The service for managing the user scenarios.
+ */
+ private final ScenarioService scenarioService;
+
+ /**
+ * The identity of the current user.
+ */
+ private final SecurityIdentity identity;
+
+ /**
+ * Construct a {@link PortfolioScenarioResource}.
+ *
+ * @param scenarioService The {@link ScenarioService} instance to use.
+ * @param identity The {@link SecurityIdentity} of the current user.
+ */
+ public PortfolioScenarioResource(ScenarioService scenarioService, SecurityIdentity identity) {
+ this.scenarioService = scenarioService;
+ this.identity = identity;
+ }
+
+ /**
+ * Get all scenarios that belong to the specified portfolio.
+ */
+ @GET
+ public List<Scenario> get(@PathParam("project") long projectId, @PathParam("portfolio") int portfolioNumber) {
+ return scenarioService.findAll(identity.getPrincipal().getName(), projectId, portfolioNumber);
+ }
+
+ /**
+ * Create a scenario for this portfolio.
+ */
+ @POST
+ @Transactional
+ public org.opendc.web.proto.user.Scenario create(
+ @PathParam("project") long projectId,
+ @PathParam("portfolio") int portfolioNumber,
+ @Valid org.opendc.web.proto.user.Scenario.Create request) {
+ var scenario = scenarioService.create(identity.getPrincipal().getName(), projectId, portfolioNumber, request);
+ if (scenario == null) {
+ throw new WebApplicationException("Portfolio not found", 404);
+ }
+
+ return scenario;
+ }
+}
diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ProjectResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ProjectResource.java
new file mode 100644
index 00000000..b0b8eb4e
--- /dev/null
+++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ProjectResource.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2023 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.web.server.rest.user;
+
+import io.quarkus.security.identity.SecurityIdentity;
+import java.util.List;
+import javax.annotation.security.RolesAllowed;
+import javax.transaction.Transactional;
+import javax.validation.Valid;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import org.opendc.web.server.service.ProjectService;
+
+/**
+ * A resource representing the created projects.
+ */
+@Produces("application/json")
+@Path("/projects")
+@RolesAllowed("openid")
+public final class ProjectResource {
+ /**
+ * The service for managing the user projects.
+ */
+ private final ProjectService projectService;
+
+ /**
+ * The identity of the current user.
+ */
+ private final SecurityIdentity identity;
+
+ /**
+ * Construct a {@link ProjectResource}.
+ *
+ * @param projectService The {@link ProjectService} instance to use.
+ * @param identity The {@link SecurityIdentity} of the current user.
+ */
+ public ProjectResource(ProjectService projectService, SecurityIdentity identity) {
+ this.projectService = projectService;
+ this.identity = identity;
+ }
+
+ /**
+ * Obtain all the projects of the current user.
+ */
+ @GET
+ public List<org.opendc.web.proto.user.Project> getAll() {
+ return projectService.findByUser(identity.getPrincipal().getName());
+ }
+
+ /**
+ * Create a new project for the current user.
+ */
+ @POST
+ @Transactional
+ @Consumes("application/json")
+ public org.opendc.web.proto.user.Project create(@Valid org.opendc.web.proto.user.Project.Create request) {
+ return projectService.create(identity.getPrincipal().getName(), request.getName());
+ }
+
+ /**
+ * Obtain a single project by its identifier.
+ */
+ @GET
+ @Path("{project}")
+ public org.opendc.web.proto.user.Project get(@PathParam("project") long id) {
+ var project = projectService.findByUser(identity.getPrincipal().getName(), id);
+ if (project == null) {
+ throw new WebApplicationException("Project not found", 404);
+ }
+
+ return project;
+ }
+
+ /**
+ * Delete a project.
+ */
+ @DELETE
+ @Path("{project}")
+ @Transactional
+ public org.opendc.web.proto.user.Project delete(@PathParam("project") long id) {
+ try {
+ var project = projectService.delete(identity.getPrincipal().getName(), id);
+ if (project == null) {
+ throw new WebApplicationException("Project not found", 404);
+ }
+
+ return project;
+ } catch (IllegalArgumentException e) {
+ throw new WebApplicationException(e.getMessage(), 403);
+ }
+ }
+}
diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ScenarioResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ScenarioResource.java
new file mode 100644
index 00000000..a6838148
--- /dev/null
+++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ScenarioResource.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2023 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.web.server.rest.user;
+
+import io.quarkus.security.identity.SecurityIdentity;
+import javax.annotation.security.RolesAllowed;
+import javax.transaction.Transactional;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import org.opendc.web.server.service.ScenarioService;
+
+/**
+ * A resource representing the scenarios of a portfolio.
+ */
+@Produces("application/json")
+@Path("/projects/{project}/scenarios")
+@RolesAllowed("openid")
+public final class ScenarioResource {
+ /**
+ * The service for managing the user scenarios.
+ */
+ private final ScenarioService scenarioService;
+
+ /**
+ * The identity of the current user.
+ */
+ private final SecurityIdentity identity;
+
+ /**
+ * Construct a {@link ScenarioResource}.
+ *
+ * @param scenarioService The {@link ScenarioService} instance to use.
+ * @param identity The {@link SecurityIdentity} of the current user.
+ */
+ public ScenarioResource(ScenarioService scenarioService, SecurityIdentity identity) {
+ this.scenarioService = scenarioService;
+ this.identity = identity;
+ }
+
+ /**
+ * Obtain a scenario by its identifier.
+ */
+ @GET
+ @Path("{scenario}")
+ public org.opendc.web.proto.user.Scenario get(
+ @PathParam("project") long projectId, @PathParam("scenario") int number) {
+ var scenario = scenarioService.findOne(identity.getPrincipal().getName(), projectId, number);
+ if (scenario == null) {
+ throw new WebApplicationException("Scenario not found", 404);
+ }
+
+ return scenario;
+ }
+
+ /**
+ * Delete a scenario.
+ */
+ @DELETE
+ @Path("{scenario}")
+ @Transactional
+ public org.opendc.web.proto.user.Scenario delete(
+ @PathParam("project") long projectId, @PathParam("scenario") int number) {
+ var scenario = scenarioService.delete(identity.getPrincipal().getName(), projectId, number);
+ if (scenario == null) {
+ throw new WebApplicationException("Scenario not found", 404);
+ }
+
+ return scenario;
+ }
+}
diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/TopologyResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/TopologyResource.java
new file mode 100644
index 00000000..54afc1ce
--- /dev/null
+++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/TopologyResource.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2023 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.web.server.rest.user;
+
+import io.quarkus.security.identity.SecurityIdentity;
+import java.util.List;
+import javax.annotation.security.RolesAllowed;
+import javax.transaction.Transactional;
+import javax.validation.Valid;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import org.opendc.web.server.service.TopologyService;
+
+/**
+ * A resource representing the constructed datacenter topologies.
+ */
+@Produces("application/json")
+@Path("/projects/{project}/topologies")
+@RolesAllowed("openid")
+public final class TopologyResource {
+ /**
+ * The service for managing the user topologies.
+ */
+ private final TopologyService topologyService;
+
+ /**
+ * The identity of the current user.
+ */
+ private final SecurityIdentity identity;
+
+ /**
+ * Construct a {@link TopologyResource}.
+ *
+ * @param topologyService The {@link TopologyService} instance to use.
+ * @param identity The {@link SecurityIdentity} of the current user.
+ */
+ public TopologyResource(TopologyService topologyService, SecurityIdentity identity) {
+ this.topologyService = topologyService;
+ this.identity = identity;
+ }
+
+ /**
+ * Get all topologies that belong to the specified project.
+ */
+ @GET
+ public List<org.opendc.web.proto.user.Topology> getAll(@PathParam("project") long projectId) {
+ return topologyService.findAll(identity.getPrincipal().getName(), projectId);
+ }
+
+ /**
+ * Create a topology for this project.
+ */
+ @POST
+ @Consumes("application/json")
+ @Transactional
+ public org.opendc.web.proto.user.Topology create(
+ @PathParam("project") long projectId, @Valid org.opendc.web.proto.user.Topology.Create request) {
+ var topology = topologyService.create(identity.getPrincipal().getName(), projectId, request);
+
+ if (topology == null) {
+ throw new WebApplicationException("Topology not found", 404);
+ }
+
+ return topology;
+ }
+
+ /**
+ * Obtain a topology by its number.
+ */
+ @GET
+ @Path("{topology}")
+ public org.opendc.web.proto.user.Topology get(
+ @PathParam("project") long projectId, @PathParam("topology") int number) {
+ var topology = topologyService.findOne(identity.getPrincipal().getName(), projectId, number);
+
+ if (topology == null) {
+ throw new WebApplicationException("Topology not found", 404);
+ }
+
+ return topology;
+ }
+
+ /**
+ * Update the specified topology by its number.
+ */
+ @PUT
+ @Path("{topology}")
+ @Consumes("application/json")
+ @Transactional
+ public org.opendc.web.proto.user.Topology update(
+ @PathParam("project") long projectId,
+ @PathParam("topology") int number,
+ @Valid org.opendc.web.proto.user.Topology.Update request) {
+ var topology = topologyService.update(identity.getPrincipal().getName(), projectId, number, request);
+
+ if (topology == null) {
+ throw new WebApplicationException("Topology not found", 404);
+ }
+
+ return topology;
+ }
+
+ /**
+ * Delete the specified topology.
+ */
+ @Path("{topology}")
+ @DELETE
+ @Transactional
+ public org.opendc.web.proto.user.Topology delete(
+ @PathParam("project") long projectId, @PathParam("topology") int number) {
+ var topology = topologyService.delete(identity.getPrincipal().getName(), projectId, number);
+
+ if (topology == null) {
+ throw new WebApplicationException("Topology not found", 404);
+ }
+
+ return topology;
+ }
+}
diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/UserService.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/UserResource.java
index b46b799b..c3fb2866 100644
--- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/UserService.java
+++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/UserResource.java
@@ -20,36 +20,51 @@
* SOFTWARE.
*/
-package org.opendc.web.server.service;
+package org.opendc.web.server.rest.user;
import io.quarkus.security.identity.SecurityIdentity;
-import javax.enterprise.context.ApplicationScoped;
+import javax.annotation.security.RolesAllowed;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
import org.opendc.web.proto.user.User;
import org.opendc.web.proto.user.UserAccounting;
+import org.opendc.web.server.service.UserAccountingService;
/**
- * Service for managing {@link User}s.
+ * A resource representing the active user.
*/
-@ApplicationScoped
-public final class UserService {
+@Produces("application/json")
+@Path("/users")
+@RolesAllowed("openid")
+public final class UserResource {
/**
* The service for managing the user accounting.
*/
private final UserAccountingService accountingService;
/**
- * Construct a {@link UserService} instance.
+ * The identity of the current user.
+ */
+ private final SecurityIdentity identity;
+
+ /**
+ * Construct a {@link UserResource}.
*
* @param accountingService The {@link UserAccountingService} instance to use.
+ * @param identity The {@link SecurityIdentity} of the current user.
*/
- public UserService(UserAccountingService accountingService) {
+ public UserResource(UserAccountingService accountingService, SecurityIdentity identity) {
this.accountingService = accountingService;
+ this.identity = identity;
}
/**
- * Obtain the {@link User} object for the specified <code>identity</code>.
+ * Get the current active user data.
*/
- public User getUser(SecurityIdentity identity) {
+ @GET
+ @Path("me")
+ public User get() {
String userId = identity.getPrincipal().getName();
UserAccounting accounting = accountingService.getAccounting(userId);
diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/JobService.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/JobService.java
index eb0982ec..47f44d27 100644
--- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/JobService.java
+++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/JobService.java
@@ -61,7 +61,13 @@ public final class JobService {
* Find a job by its identifier.
*/
public org.opendc.web.proto.runner.Job findById(long id) {
- return toRunnerDto(Job.findById(id));
+ Job job = Job.findById(id);
+
+ if (job == null) {
+ return null;
+ }
+
+ return toRunnerDto(job);
}
/**
diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/ScenarioService.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/ScenarioService.java
index bf5206af..6a70db1e 100644
--- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/ScenarioService.java
+++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/ScenarioService.java
@@ -219,6 +219,13 @@ public final class ScenarioService {
* Convert a {@link Workload} entity into a DTO.
*/
public static org.opendc.web.proto.Workload toDto(Workload workload) {
- return new org.opendc.web.proto.Workload(TraceService.toUserDto(workload.trace), workload.samplingFraction);
+ return new org.opendc.web.proto.Workload(toDto(workload.trace), workload.samplingFraction);
+ }
+
+ /**
+ * Convert a {@link Trace] entity into a {@link org.opendc.web.proto.Trace} DTO.
+ */
+ public static org.opendc.web.proto.Trace toDto(Trace trace) {
+ return new org.opendc.web.proto.Trace(trace.id, trace.name, trace.type);
}
}
diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/util/KotlinModuleCustomizer.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/util/KotlinModuleCustomizer.java
new file mode 100644
index 00000000..c30edcbf
--- /dev/null
+++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/util/KotlinModuleCustomizer.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2023 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.web.server.util;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.module.kotlin.KotlinModule;
+import io.quarkus.jackson.ObjectMapperCustomizer;
+import javax.inject.Singleton;
+
+/**
+ * Helper class to register the Kotlin Jackson module.
+ */
+@Singleton
+public final class KotlinModuleCustomizer implements ObjectMapperCustomizer {
+ @Override
+ public void customize(ObjectMapper objectMapper) {
+ objectMapper.registerModule(new KotlinModule.Builder().build());
+ }
+}