summaryrefslogtreecommitdiff
path: root/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server
diff options
context:
space:
mode:
authorvincent van beek <vincent@vlogic.nl>2026-03-27 16:49:40 +0100
committerGitHub <noreply@github.com>2026-03-27 15:49:40 +0000
commit048bf777997bdbf599240645fc66612c98abf3c2 (patch)
treec04e999cb981c98ae9dc0fd83ea70aec9eaa419c /opendc-web/opendc-web-server/src/main/java/org/opendc/web/server
parent235057cd170f1583db14bf93ea7d2de39e492356 (diff)
Add import topology (#393)
* add a the posibility to import and export topogies in JSON format * fix web-runner integration, there were several bugs and mismatches between new implementations in OpenDC and the UI
Diffstat (limited to 'opendc-web/opendc-web-server/src/main/java/org/opendc/web/server')
-rw-r--r--opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Job.java33
-rw-r--r--opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Topology.java19
-rw-r--r--opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/SchedulerResource.java23
-rw-r--r--opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/TopologyResource.java126
4 files changed, 167 insertions, 34 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 ef342e5f..63982854 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
@@ -26,7 +26,6 @@ import io.hypersistence.utils.hibernate.type.json.JsonType;
import io.quarkus.hibernate.orm.panache.Panache;
import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
import io.quarkus.hibernate.orm.panache.PanacheQuery;
-import io.quarkus.panache.common.Parameters;
import jakarta.persistence.*;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
@@ -36,8 +35,6 @@ import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
-import jakarta.persistence.NamedQueries;
-import jakarta.persistence.NamedQuery;
import jakarta.persistence.Table;
import java.time.Instant;
import java.util.Map;
@@ -49,16 +46,6 @@ import org.opendc.web.proto.JobState;
*/
@Entity
@Table
-@NamedQueries({
- @NamedQuery(
- name = "Job.updateOne",
- query =
- """
- UPDATE Job j
- SET j.state = :newState, j.updatedAt = :updatedAt, j.runtime = :runtime, j.results = :results
- WHERE j.id = :id AND j.state = :oldState
- """)
-})
public class Job extends PanacheEntityBase {
/**
* The main ID of a project.
@@ -146,16 +133,16 @@ public class Job extends PanacheEntityBase {
* @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) {
- long count = update(
- "#Job.updateOne",
- Parameters.with("id", id)
- .and("oldState", state)
- .and("newState", newState)
- .and("updatedAt", time)
- .and("runtime", runtime)
- .and("results", results));
- Panache.getEntityManager().refresh(this);
- return count > 0;
+ // Update entity fields directly - this uses the JsonType converter for proper JSON serialization
+ this.state = newState;
+ this.updatedAt = time;
+ this.runtime = runtime;
+ this.results = results;
+
+ // Flush changes to database - JsonType will properly serialize the results map to JSON
+ Panache.getEntityManager().flush();
+
+ return true;
}
/**
diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Topology.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Topology.java
index ff8b4416..50402ab9 100644
--- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Topology.java
+++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Topology.java
@@ -59,7 +59,10 @@ import org.opendc.web.proto.topology.Room;
@NamedQuery(name = "Topology.findByProject", query = "SELECT t FROM Topology t WHERE t.project.id = :projectId"),
@NamedQuery(
name = "Topology.findOneByProject",
- query = "SELECT t FROM Topology t WHERE t.project.id = :projectId AND t.number = :number")
+ query = "SELECT t FROM Topology t WHERE t.project.id = :projectId AND t.number = :number"),
+ @NamedQuery(
+ name = "Topology.findOneByName",
+ query = "SELECT t FROM Topology t WHERE t.project.id = :projectId AND t.name = :name")
})
public class Topology extends PanacheEntityBase {
/**
@@ -149,4 +152,18 @@ public class Topology extends PanacheEntityBase {
Parameters.with("projectId", projectId).and("number", number))
.firstResult();
}
+
+ /**
+ * Find the [Topology] with the specified [name] belonging to [project][projectId].
+ *
+ * @param projectId The unique identifier of the project.
+ * @param name The name of the topology.
+ * @return The topology or `null` if it does not exist.
+ */
+ public static Topology findByName(long projectId, String name) {
+ return find(
+ "#Topology.findOneByName",
+ Parameters.with("projectId", projectId).and("name", name))
+ .firstResult();
+ }
}
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
index 3e839040..527c04b1 100644
--- 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
@@ -39,14 +39,19 @@ public final class SchedulerResource {
@GET
public List<String> getAll() {
return List.of(
- "mem",
- "mem-inv",
- "core-mem",
- "core-mem-inv",
- "active-tasks",
- "active-tasks-inv",
- "provisioned-cores",
- "provisioned-cores-inv",
- "random");
+ "Mem",
+ "MemInv",
+ "CoreMem",
+ "CoreMemInv",
+ "ActiveServers",
+ "ActiveServersInv",
+ "ProvisionedCores",
+ "ProvisionedCoresInv",
+ "Random",
+ "TaskNumMemorizing",
+ "Timeshift",
+ "ProvisionedCpuGpuCores",
+ "ProvisionedCpuGpuCoresInv",
+ "GpuTaskMemorizing");
}
}
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
index 25819e32..79290e26 100644
--- 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
@@ -39,6 +39,15 @@ import jakarta.ws.rs.Produces;
import jakarta.ws.rs.WebApplicationException;
import java.time.Instant;
import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import org.opendc.web.proto.topology.Machine;
+import org.opendc.web.proto.topology.MemoryUnit;
+import org.opendc.web.proto.topology.ProcessingUnit;
+import org.opendc.web.proto.topology.Rack;
+import org.opendc.web.proto.topology.Room;
+import org.opendc.web.proto.topology.RoomTile;
import org.opendc.web.server.model.Project;
import org.opendc.web.server.model.ProjectAuthorization;
import org.opendc.web.server.model.Topology;
@@ -102,9 +111,14 @@ public final class TopologyResource {
Instant now = Instant.now();
Project project = auth.project;
+
+ if (Topology.findByName(projectId, request.name()) != null) {
+ throw new WebApplicationException("Topology name already exists", 409);
+ }
+
int number = project.allocateTopology(now);
- Topology topology = new Topology(project, number, request.name(), now, request.rooms());
+ Topology topology = new Topology(project, number, request.name(), now, createNewInstances(request.rooms()));
project.topologies.add(topology);
topology.persist();
@@ -205,4 +219,114 @@ public final class TopologyResource {
return UserProtocol.toDto(entity, auth);
}
+
+ /**
+ * Create new instances of the specified rooms.
+ */
+ private List<Room> createNewInstances(List<Room> rooms) {
+ if (rooms == null) {
+ return null;
+ }
+
+ return rooms.stream().map(this::createNewInstance).toList();
+ }
+
+ /**
+ * Create a new instance of the specified room.
+ */
+ private Room createNewInstance(Room room) {
+ String roomId = UUID.randomUUID().toString();
+ Set<RoomTile> tiles = room.tiles();
+ if (tiles != null) {
+ tiles = tiles.stream().map(tile -> createNewInstance(tile, roomId)).collect(Collectors.toSet());
+ }
+
+ return new Room(roomId, room.name(), tiles, room.topologyId());
+ }
+
+ /**
+ * Create a new instance of the specified room tile.
+ */
+ private RoomTile createNewInstance(RoomTile tile, String roomId) {
+ String tileId = UUID.randomUUID().toString();
+ return new RoomTile(
+ tileId,
+ tile.positionX(),
+ tile.positionY(),
+ tile.rack() != null ? createNewInstance(tile.rack()) : null,
+ roomId);
+ }
+
+ /**
+ * Create a new instance of the specified rack.
+ */
+ private Rack createNewInstance(Rack rack) {
+ String rackId = UUID.randomUUID().toString();
+ List<Machine> machines = rack.machines();
+ if (machines != null) {
+ machines = machines.stream()
+ .map(machine -> createNewInstance(machine, rackId))
+ .toList();
+ }
+
+ return new Rack(rackId, rack.name(), rack.capacity(), rack.powerCapacityW(), machines);
+ }
+
+ /**
+ * Create a new instance of the specified machine.
+ */
+ private Machine createNewInstance(Machine machine, String rackId) {
+ String machineId = UUID.randomUUID().toString();
+ List<ProcessingUnit> cpus = machine.cpus();
+ if (cpus != null) {
+ cpus = cpus.stream().map(this::createNewInstance).toList();
+ }
+
+ List<ProcessingUnit> gpus = machine.gpus();
+ if (gpus != null) {
+ gpus = gpus.stream().map(this::createNewInstance).toList();
+ }
+
+ List<MemoryUnit> memory = machine.memory();
+ if (memory != null) {
+ memory = memory.stream().map(this::createNewInstance).toList();
+ }
+
+ List<MemoryUnit> storage = machine.storage();
+ if (storage != null) {
+ storage = storage.stream().map(this::createNewInstance).toList();
+ }
+
+ return new Machine(machineId, machine.position(), cpus, gpus, memory, storage, rackId);
+ }
+
+ /**
+ * Create a new instance of the specified processing unit.
+ */
+ private ProcessingUnit createNewInstance(ProcessingUnit unit) {
+ if (unit != null && (unit.id() == null || unit.id().isBlank())) {
+ return new ProcessingUnit(
+ UUID.randomUUID().toString(),
+ unit.name(),
+ unit.clockRateMhz(),
+ unit.numberOfCores(),
+ unit.energyConsumptionW());
+ }
+ return unit;
+ }
+
+ /**
+ * Create a new instance of the specified memory unit.
+ */
+ private MemoryUnit createNewInstance(MemoryUnit unit) {
+ if (unit != null && (unit.id() == null || unit.id().isBlank())) {
+ return new MemoryUnit(
+ UUID.randomUUID().toString(),
+ unit.name(),
+ unit.speedMbPerS(),
+ unit.sizeMb(),
+ unit.energyConsumptionW());
+ }
+ return unit;
+ }
}