summaryrefslogtreecommitdiff
path: root/opendc-web/opendc-web-server/src/main/java/org
diff options
context:
space:
mode:
Diffstat (limited to 'opendc-web/opendc-web-server/src/main/java/org')
-rw-r--r--opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Job.java28
-rw-r--r--opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/runner/JobResource.java28
-rw-r--r--opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/runner/RunnerProtocol.java9
-rw-r--r--opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/JobService.java11
-rw-r--r--opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/util/runner/QuarkusJobManager.java27
5 files changed, 88 insertions, 15 deletions
diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Job.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Job.java
index 63982854..b78815da 100644
--- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Job.java
+++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Job.java
@@ -67,6 +67,12 @@ public class Job extends PanacheEntityBase {
public Instant createdAt;
/**
+ * The instant at which the job started running.
+ */
+ @Column(name = "started_at")
+ public Instant startedAt;
+
+ /**
* The number of simulation runs to perform.
*/
@Column(nullable = false, updatable = false)
@@ -99,6 +105,13 @@ public class Job extends PanacheEntityBase {
public Map<String, ?> results = null;
/**
+ * Job report containing warnings and errors in JSON
+ */
+ @Column(columnDefinition = "jsonb")
+ @Type(JsonType.class)
+ public Map<String, Object> report = null;
+
+ /**
* Construct a {@link Job} instance.
*/
public Job(Scenario scenario, String createdBy, Instant createdAt, int repeats) {
@@ -129,15 +142,28 @@ public class Job extends PanacheEntityBase {
*
* @param newState The new state to enter into.
* @param time The time at which the update occurs.
+ * @param startedAt The time at which the job started running (optional).
+ * @param runtime The runtime (in seconds) consumed by the simulation job so far.
* @param results The results to possible set.
+ * @param report The report to possible set.
* @return <code>true</code> when the update succeeded`, <code>false</code> when there was a conflict.
*/
- public boolean updateAtomically(JobState newState, Instant time, int runtime, Map<String, ?> results) {
+ public boolean updateAtomically(
+ JobState newState,
+ Instant time,
+ Instant startedAt,
+ int runtime,
+ Map<String, ?> results,
+ Map<String, Object> report) {
// Update entity fields directly - this uses the JsonType converter for proper JSON serialization
this.state = newState;
this.updatedAt = time;
+ if (startedAt != null) {
+ this.startedAt = startedAt;
+ }
this.runtime = runtime;
this.results = results;
+ this.report = report;
// Flush changes to database - JsonType will properly serialize the results map to JSON
Panache.getEntityManager().flush();
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
index 2b774082..b0b6274f 100644
--- 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
@@ -22,6 +22,7 @@
package org.opendc.web.server.rest.runner;
+import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.security.RolesAllowed;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
@@ -33,6 +34,7 @@ import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.WebApplicationException;
import java.util.List;
+import java.util.Map;
import org.opendc.web.proto.JobState;
import org.opendc.web.server.model.Job;
import org.opendc.web.server.service.JobService;
@@ -49,13 +51,17 @@ public final class JobResource {
*/
private final JobService jobService;
+ private final ObjectMapper objectMapper;
+
/**
* Construct a {@link JobResource} instance.
*
* @param jobService The {@link JobService} for managing the job lifecycle.
+ * @param objectMapper The {@link ObjectMapper} for JSON conversions.
*/
- public JobResource(JobService jobService) {
+ public JobResource(JobService jobService, ObjectMapper objectMapper) {
this.jobService = jobService;
+ this.objectMapper = objectMapper;
}
/**
@@ -98,7 +104,10 @@ public final class JobResource {
}
try {
- jobService.updateJob(job, update.state(), update.runtime(), update.results());
+ @SuppressWarnings("unchecked")
+ Map<String, Object> reportMap =
+ update.report() != null ? objectMapper.convertValue(update.report(), Map.class) : null;
+ jobService.updateJob(job, update.state(), update.runtime(), update.results(), reportMap);
} catch (IllegalArgumentException e) {
throw new WebApplicationException(e, 400);
} catch (IllegalStateException e) {
@@ -107,4 +116,19 @@ public final class JobResource {
return RunnerProtocol.toDto(job);
}
+
+ /**
+ * Get the report for a job.
+ */
+ @GET
+ @Path("{job}/report")
+ public Map<String, Object> getReport(@PathParam("job") long id) {
+ Job job = Job.findById(id);
+
+ if (job == null) {
+ throw new WebApplicationException("Job not found", 404);
+ }
+
+ return job.report != null ? job.report : Map.of();
+ }
}
diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/runner/RunnerProtocol.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/runner/RunnerProtocol.java
index 6bf65d97..4c03aeea 100644
--- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/runner/RunnerProtocol.java
+++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/runner/RunnerProtocol.java
@@ -42,7 +42,14 @@ public final class RunnerProtocol {
*/
public static org.opendc.web.proto.runner.Job toDto(Job job) {
return new org.opendc.web.proto.runner.Job(
- job.id, toDto(job.scenario), job.state, job.createdAt, job.updatedAt, job.runtime, job.results);
+ job.id,
+ toDto(job.scenario),
+ job.state,
+ job.createdAt,
+ job.startedAt,
+ job.updatedAt,
+ job.runtime,
+ job.results);
}
/**
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 70933520..1f8ac68e 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
@@ -55,10 +55,11 @@ public final class JobService {
* @param newState The new state to transition the job to.
* @param runtime The runtime (in seconds) consumed by the simulation jbo so far.
* @param results The results to attach to the job.
+ * @param report The report containing warnings and errors.
* @throws IllegalArgumentException if the state transition is invalid.
* @throws IllegalStateException if someone tries to update the job concurrently.
*/
- public void updateJob(Job job, JobState newState, int runtime, Map<String, ?> results) {
+ public void updateJob(Job job, JobState newState, int runtime, Map<String, ?> results, Map<String, Object> report) {
JobState state = job.state;
if (!job.canTransitionTo(newState)) {
@@ -69,12 +70,18 @@ public final class JobService {
JobState nextState = newState;
int consumedBudget = Math.min(1, runtime - job.runtime);
+ // Set startedAt when transitioning to RUNNING for the first time
+ Instant startedAt = job.startedAt;
+ if (newState == JobState.RUNNING && job.state != JobState.RUNNING && startedAt == null) {
+ startedAt = now;
+ }
+
// Check whether the user still has any simulation budget left
if (accountingService.consumeSimulationBudget(job.createdBy, consumedBudget) && nextState == JobState.RUNNING) {
nextState = JobState.FAILED; // User has consumed all their budget; cancel the job
}
- if (!job.updateAtomically(nextState, now, runtime, results)) {
+ if (!job.updateAtomically(nextState, now, startedAt, runtime, results, report)) {
throw new IllegalStateException("Conflicting update");
}
}
diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/util/runner/QuarkusJobManager.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/util/runner/QuarkusJobManager.java
index 47d397f3..df9f7d97 100644
--- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/util/runner/QuarkusJobManager.java
+++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/util/runner/QuarkusJobManager.java
@@ -22,12 +22,14 @@
package org.opendc.web.server.util.runner;
+import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.transaction.Transactional;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.opendc.web.proto.JobState;
+import org.opendc.web.proto.runner.Report;
import org.opendc.web.runner.JobManager;
import org.opendc.web.server.model.Job;
import org.opendc.web.server.rest.runner.RunnerProtocol;
@@ -43,13 +45,17 @@ public class QuarkusJobManager implements JobManager {
*/
private final JobService jobService;
+ private final ObjectMapper objectMapper;
+
/**
* Construct a {@link QuarkusJobManager}.
*
* @param jobService The {@link JobService} for managing the job's lifecycle.
+ * @param objectMapper The {@link ObjectMapper} for JSON conversions.
*/
- public QuarkusJobManager(JobService jobService) {
+ public QuarkusJobManager(JobService jobService, ObjectMapper objectMapper) {
this.jobService = jobService;
+ this.objectMapper = objectMapper;
}
@Transactional
@@ -67,25 +73,25 @@ public class QuarkusJobManager implements JobManager {
@Transactional
@Override
public boolean claim(long id) {
- return updateState(id, JobState.CLAIMED, 0, null);
+ return updateState(id, JobState.CLAIMED, 0, null, null);
}
@Transactional
@Override
public boolean heartbeat(long id, int runtime) {
- return updateState(id, JobState.RUNNING, runtime, null);
+ return updateState(id, JobState.RUNNING, runtime, null, null);
}
@Transactional
@Override
- public void fail(long id, int runtime) {
- updateState(id, JobState.FAILED, runtime, null);
+ public void fail(long id, int runtime, @Nullable Report report) {
+ updateState(id, JobState.FAILED, runtime, null, report);
}
@Transactional
@Override
- public void finish(long id, int runtime, @NotNull Map<String, ?> results) {
- updateState(id, JobState.FINISHED, runtime, results);
+ public void finish(long id, int runtime, @NotNull Map<String, ? extends Object> results, @Nullable Report report) {
+ updateState(id, JobState.FINISHED, runtime, results, report);
}
/**
@@ -95,9 +101,10 @@ public class QuarkusJobManager implements JobManager {
* @param newState The new state to transition to.
* @param runtime The runtime of the job.
* @param results The results of the job.
+ * @param report The report containing warnings and errors.
* @return <code>true</code> if the operation succeeded, <code>false</code> otherwise.
*/
- private boolean updateState(long id, JobState newState, int runtime, Map<String, ?> results) {
+ private boolean updateState(long id, JobState newState, int runtime, Map<String, ?> results, Report report) {
Job job = Job.findById(id);
if (job == null) {
@@ -105,7 +112,9 @@ public class QuarkusJobManager implements JobManager {
}
try {
- jobService.updateJob(job, newState, runtime, results);
+ @SuppressWarnings("unchecked")
+ Map<String, Object> reportMap = report != null ? objectMapper.convertValue(report, Map.class) : null;
+ jobService.updateJob(job, newState, runtime, results, reportMap);
return true;
} catch (IllegalArgumentException | IllegalStateException e) {
return false;