Implemented saving to disk storage

This commit is contained in:
Andrey Terentev 2024-02-28 09:48:43 +07:00 committed by Andrey Terentev
parent 32553b63ff
commit b95d2f3953
17 changed files with 200 additions and 32 deletions

View File

@ -42,9 +42,9 @@ public class TestConfig implements WebMvcConfigurer {
@Bean
void createNodes() {
createNodeWithContent(new Node("game-servers", PickingMode.ROUND_ROBIN));
createNodeWithContent(new Node("game-lobbies", PickingMode.LEAST_PICKED));
createNodeWithContent(new Node("hub", PickingMode.SEQUENTIAL_FILLING));
createNodeWithContent(new Node("game-servers", PickingMode.ROUND_ROBIN, false));
createNodeWithContent(new Node("game-lobbies", PickingMode.LEAST_PICKED, false));
createNodeWithContent(new Node("hub", PickingMode.SEQUENTIAL_FILLING, false));
}
@SneakyThrows
@ -54,7 +54,7 @@ public class TestConfig implements WebMvcConfigurer {
for (int i = 1; i <= 5; i++) {
var slots = 5 * i;
var room = Room.create("test-" + i, node, SlotLimit.of(slots), json.writeValueAsString(generatePayload()));
var room = Room.create("test-" + i, node, SlotLimit.of(slots), json.writeValueAsString(generatePayload()), false);
roomRepository.create(room);
for (int j = 0, n = rand.nextInt(slots + 1); j < n; j++) {
@ -64,7 +64,7 @@ public class TestConfig implements WebMvcConfigurer {
}
for (int i = 0; i < 5; i++) {
var room = Room.create(randomUUID().toString(), node, SlotLimit.unlimited(), json.writeValueAsString(generatePayload()));
var room = Room.create(randomUUID().toString(), node, SlotLimit.unlimited(), json.writeValueAsString(generatePayload()), false);
room.setLocked((i & 1) == 0);
roomRepository.create(room);
}

View File

@ -41,10 +41,10 @@ public class NodeController {
@PostMapping
ResponseEntity<?> registerNode(
@Parameter(description = "Node identifier") @RequestParam(name = "nodeId") String nodeId,
@Parameter(description = "Picking mode method") @RequestParam(name = "method") PickingMode method
@Parameter(description = "Picking mode method") @RequestParam(name = "method") PickingMode method,
@Parameter(description = "Save node") @RequestParam(name = "persist", required = false, defaultValue = "false") boolean persist
) {
nodeService.create(new Node(nodeId, method));
nodeService.create(new Node(nodeId, method, persist));
return ResponseEntity.ok().build();
}

View File

@ -50,10 +50,11 @@ public class RoomController {
@Parameter(description = "Room identifier") @RequestParam(name = "roomId") String roomId,
@Parameter(description = "Maximum users count in room") @RequestParam(name = "slots") int slots,
@Parameter(description = "Payload. Some data") @RequestParam(name = "payload") String payload,
@Parameter(description = "Lock for picking") @RequestParam(name = "locked", defaultValue = "false") boolean locked
@Parameter(description = "Lock for picking") @RequestParam(name = "locked", required = false, defaultValue = "false") boolean locked,
@Parameter(description = "Save room") @RequestParam(name = "persist", required = false, defaultValue = "false") boolean persist
) {
var node = nodeService.find(nodeId).orElseThrow(() -> new NodeNotFoundException(nodeId));
var room = Room.create(roomId, node, SlotLimit.of(slots), payload);
var room = Room.create(roomId, node, SlotLimit.of(slots), payload, persist);
room.setLocked(locked);
roomService.create(room);
@ -105,6 +106,7 @@ public class RoomController {
var node = nodeService.find(nodeId).orElseThrow(() -> new NodeNotFoundException(nodeId));
var room = roomService.find(node, roomId).orElseThrow(() -> new RoomNotFoundException(nodeId, roomId));
room.setLocked(value);
roomService.updateState(room);
return ResponseEntity.ok(true);
}
}

View File

@ -4,7 +4,7 @@ import lombok.NonNull;
import ru.dragonestia.picker.api.repository.response.type.RNode;
import ru.dragonestia.picker.api.repository.response.type.type.PickingMode;
public record Node(@NonNull String id, @NonNull PickingMode mode) {
public record Node(@NonNull String id, @NonNull PickingMode mode, boolean persist) {
@Override
public int hashCode() {

View File

@ -16,10 +16,11 @@ public class Room {
private final String nodeId;
private final SlotLimit slots;
private final String payload;
private final boolean persist;
private boolean locked = false;
public static Room create(String roomId, Node node, SlotLimit limit, String payload) {
return new Room(roomId, node.id(), limit, payload);
public static Room create(String roomId, Node node, SlotLimit limit, String payload, boolean persist) {
return new Room(roomId, node.id(), limit, payload, persist);
}
public void setLocked(boolean value) {

View File

@ -9,7 +9,9 @@ public class SlotLimit {
public final static int UNLIMITED_VALUE = -1;
private final int slots;
private int slots;
private SlotLimit() {}
private SlotLimit(int slots) {
this.slots = slots;

View File

@ -2,6 +2,7 @@ package ru.dragonestia.picker.repository;
import ru.dragonestia.picker.api.exception.NodeAlreadyExistException;
import ru.dragonestia.picker.model.Node;
import ru.dragonestia.picker.model.Room;
import java.util.List;
import java.util.Optional;
@ -10,7 +11,7 @@ public interface NodeRepository {
void create(Node node) throws NodeAlreadyExistException;
void delete(Node node);
List<Room> delete(Node node);
Optional<Node> find(String nodeId);

View File

@ -29,5 +29,5 @@ public interface RoomRepository {
void onCreateNode(Node node);
void onRemoveNode(Node node);
List<Room> onRemoveNode(Node node);
}

View File

@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import ru.dragonestia.picker.api.exception.NodeAlreadyExistException;
import ru.dragonestia.picker.model.Node;
import ru.dragonestia.picker.model.Room;
import ru.dragonestia.picker.repository.RoomRepository;
import ru.dragonestia.picker.repository.NodeRepository;
import ru.dragonestia.picker.repository.impl.cache.NodeId2PickerModeCache;
@ -38,14 +39,14 @@ public class NodeRepositoryImpl implements NodeRepository {
}
@Override
public void delete(Node node) {
public List<Room> delete(Node node) {
synchronized (nodeMap) {
nodeMap.remove(node.id());
pickerRepository.remove(node.id());
nodeId2PickerModeCache.remove(node.id());
}
roomRepository.onRemoveNode(node);
return roomRepository.onRemoveNode(node);
}
@Override

View File

@ -122,10 +122,13 @@ public class RoomRepositoryImpl implements RoomRepository {
}
@Override
public void onRemoveNode(Node node) {
public List<Room> onRemoveNode(Node node) {
List<Room> deleted;
synchronized (node2roomsMap) {
deleted = node2roomsMap.get(node).values().stream().map(container -> container.room).toList();
node2roomsMap.remove(node);
}
return deleted;
}
private record RoomContainer(Room room, AtomicInteger used) {

View File

@ -24,11 +24,7 @@ public interface RoomService {
List<RRoom.Short> getAllRoomsWithDetailsResponse(Node node, Set<RoomDetails> details);
default int countAvailable(Node node) {
return countAvailable(node, 1);
}
int countAvailable(Node node, int requiredSlots);
Room pickAvailable(Node node, List<User> users);
void updateState(Room room);
}

View File

@ -9,6 +9,7 @@ import ru.dragonestia.picker.api.repository.response.type.RNode;
import ru.dragonestia.picker.model.Node;
import ru.dragonestia.picker.repository.NodeRepository;
import ru.dragonestia.picker.service.NodeService;
import ru.dragonestia.picker.storage.NodeAndRoomStorage;
import ru.dragonestia.picker.util.DetailsExtractor;
import ru.dragonestia.picker.util.NamingValidator;
@ -24,16 +25,21 @@ public class NodeServiceImpl implements NodeService {
private final NodeRepository nodeRepository;
private final DetailsExtractor detailsExtractor;
private final NamingValidator namingValidator;
private final NodeAndRoomStorage storage;
@Override
public void create(Node node) throws InvalidNodeIdentifierException, NodeAlreadyExistException {
namingValidator.validateNodeId(node.id());
nodeRepository.create(node);
storage.saveNode(node);
}
@Override
public void remove(Node node) {
nodeRepository.delete(node);
for (var room: nodeRepository.delete(node)) {
storage.removeRoom(room);
}
storage.removeNode(node);
}
@Override

View File

@ -12,6 +12,7 @@ import ru.dragonestia.picker.model.Node;
import ru.dragonestia.picker.model.User;
import ru.dragonestia.picker.repository.RoomRepository;
import ru.dragonestia.picker.service.RoomService;
import ru.dragonestia.picker.storage.NodeAndRoomStorage;
import ru.dragonestia.picker.util.DetailsExtractor;
import ru.dragonestia.picker.util.NamingValidator;
@ -28,16 +29,19 @@ public class RoomServiceImpl implements RoomService {
private final RoomRepository roomRepository;
private final DetailsExtractor detailsExtractor;
private final NamingValidator namingValidator;
private final NodeAndRoomStorage storage;
@Override
public void create(Room room) throws InvalidRoomIdentifierException, RoomAlreadyExistException {
namingValidator.validateRoomId(room.getNodeId(), room.getId());
roomRepository.create(room);
storage.saveRoom(room);
}
@Override
public void remove(Room room) {
roomRepository.remove(room);
storage.removeRoom(room);
}
@Override
@ -59,14 +63,14 @@ public class RoomServiceImpl implements RoomService {
return response;
}
@Override
public int countAvailable(Node node, int requiredSlots) {
throw new RuntimeException("Not implemented");
}
@Override
public Room pickAvailable(Node node, List<User> users) {
return roomRepository.pickFree(node, users)
.orElseThrow(() -> new RuntimeException("There are no rooms available"));
}
@Override
public void updateState(Room room) {
storage.saveRoom(room);
}
}

View File

@ -0,0 +1,17 @@
package ru.dragonestia.picker.storage;
import ru.dragonestia.picker.model.Node;
import ru.dragonestia.picker.model.Room;
public interface NodeAndRoomStorage {
void loadAll();
void saveNode(Node node);
void removeNode(Node node);
void saveRoom(Room room);
void removeRoom(Room room);
}

View File

@ -0,0 +1,108 @@
package ru.dragonestia.picker.storage.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import ru.dragonestia.picker.model.Node;
import ru.dragonestia.picker.model.Room;
import ru.dragonestia.picker.repository.NodeRepository;
import ru.dragonestia.picker.repository.RoomRepository;
import ru.dragonestia.picker.storage.NodeAndRoomStorage;
import java.io.File;
import java.util.Objects;
@Log4j2
@Profile("!test")
@Component
@RequiredArgsConstructor
public class FileStorageImpl implements NodeAndRoomStorage {
@Value("${ROOMPICKER_DATA_PATH:./appdata}")
private String path;
private final NodeRepository nodeRepository;
private final RoomRepository roomRepository;
private final ObjectMapper objectMapper;
@PostConstruct
@Override
public void loadAll() {
var dir = new File(path);
var reader = objectMapper.reader();
if (!dir.exists()) dir.mkdirs();
var nodeDir = new File(path + "/nodes");
if (!nodeDir.exists()) nodeDir.mkdir();
for (var file: Objects.requireNonNull(nodeDir.listFiles(File::isFile))) {
try {
var node = reader.readValue(file, Node.class);
nodeRepository.create(node);
} catch (Exception ex) {
log.error("Cannot read node file '%s'".formatted(file.getName()));
log.throwing(ex);
}
}
var roomDir = new File(path + "/rooms");
if (!roomDir.exists()) roomDir.mkdir();
for (var file: Objects.requireNonNull(roomDir.listFiles(File::isFile))) {
try {
var room = reader.readValue(file, Room.class);
roomRepository.create(room);
} catch (Exception ex) {
log.error("Cannot read room file '%s'".formatted(file.getName()));
log.throwing(ex);
}
}
}
@Override
public void saveNode(Node node) {
if (!node.persist()) return;
var nodeFile = new File(path + "/nodes/" + node.id() + ".json");
var writer = objectMapper.writer();
try {
writer.writeValue(nodeFile, node);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
@Override
public void removeNode(Node node) {
if (!node.persist()) return;
new File(path + "/nodes/" + node.id() + ".json").delete();
log.info("Removed node '%s' from disk storage".formatted(node.id()));
}
@SneakyThrows
@Override
public void saveRoom(Room room) {
if (!room.isPersist()) return;
var roomFile = new File(path + "/rooms/" + room.getNodeId() + "." + room.getId() + ".json");
var writer = objectMapper.writer();
try {
writer.writeValue(roomFile, room);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
@Override
public void removeRoom(Room room) {
if (!room.isPersist()) return;
new File(path + "/rooms/" + room.getNodeId() + "." + room.getId() + ".json").delete();
log.info("Removed room '%s/%s' from disk storage".formatted(room.getNodeId(), room.getId()));
}
}

View File

@ -0,0 +1,27 @@
package ru.dragonestia.picker.storage.impl;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import ru.dragonestia.picker.model.Node;
import ru.dragonestia.picker.model.Room;
import ru.dragonestia.picker.storage.NodeAndRoomStorage;
@Profile("test")
@Component
public class NullStorageImpl implements NodeAndRoomStorage {
@Override
public void loadAll() {}
@Override
public void saveNode(Node node) {}
@Override
public void removeNode(Node node) {}
@Override
public void saveRoom(Room room) {}
@Override
public void removeRoom(Room room) {}
}

View File

@ -9,7 +9,7 @@ import java.net.URI;
@Configuration
public class ServerConfig {
@Value("${DLB_HOST_URL:http://localhost:8080/}")
@Value("${ROOMPICKER_HOST_URL:http://localhost:8080/}")
private String serverUrl;
@Bean