From 048bf777997bdbf599240645fc66612c98abf3c2 Mon Sep 17 00:00:00 2001 From: vincent van beek Date: Fri, 27 Mar 2026 16:49:40 +0100 Subject: 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 --- .../main/java/org/opendc/web/server/model/Job.java | 33 ++---- .../java/org/opendc/web/server/model/Topology.java | 19 +++- .../opendc/web/server/rest/SchedulerResource.java | 23 ++-- .../web/server/rest/user/TopologyResource.java | 126 ++++++++++++++++++++- 4 files changed, 167 insertions(+), 34 deletions(-) (limited to 'opendc-web/opendc-web-server/src/main/java/org/opendc/web/server') 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 true when the update succeeded`, false when there was a conflict. */ public boolean updateAtomically(JobState newState, Instant time, int runtime, Map 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 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 createNewInstances(List 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 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 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 cpus = machine.cpus(); + if (cpus != null) { + cpus = cpus.stream().map(this::createNewInstance).toList(); + } + + List gpus = machine.gpus(); + if (gpus != null) { + gpus = gpus.stream().map(this::createNewInstance).toList(); + } + + List memory = machine.memory(); + if (memory != null) { + memory = memory.stream().map(this::createNewInstance).toList(); + } + + List 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; + } } -- cgit v1.2.3