Release ObjectStore 0.0.4

This commit is contained in:
admin committed 2026-10-10 10:01:43 +02:00
1 parent 00d2af2fb0
commit ed7712a8af
19 files changed
+1025 -114

No files matched your search

+80
View File
@@ -15,7 +15,9 @@ import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HexFormat;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Executors;
@@ -84,11 +86,18 @@ public final class ClusterNode implements AutoCloseable {
return;
}
if (!expectedNodeAndRepairAuthorized(exchange)) return;
if (path.equals("/segments") && exchange.getRequestMethod().equals("GET")) {
if (maintenanceAuthorized(exchange)) inventory(exchange);
return;
}
String id = segmentId(exchange, path);
if (id == null) return;
switch (exchange.getRequestMethod()) {
case "PUT" -> put(exchange, segmentPath(id, true));
case "GET" -> get(exchange, segmentPath(id, false));
case "DELETE" -> {
if (maintenanceAuthorized(exchange)) delete(exchange, segmentPath(id, false));
}
default -> respond(exchange, 405, "Method not allowed");
}
} catch (IllegalArgumentException error) {
@@ -130,6 +139,77 @@ public final class ClusterNode implements AutoCloseable {
return true;
}
private boolean maintenanceAuthorized(HttpExchange exchange) throws IOException {
String supplied = exchange.getRequestHeaders().getFirst("X-Cluster-Repair-Token");
byte[] value = supplied == null ? new byte[0] : supplied.getBytes(java.nio.charset.StandardCharsets.UTF_8);
if (MessageDigest.isEqual(repairToken, value)) return true;
respond(exchange, 403, "Repair authority required");
return false;
}
private void inventory(HttpExchange exchange) throws IOException {
String query = exchange.getRequestURI().getRawQuery();
if (query == null || !query.matches("shard=[0-9a-f]{2}(&after=[0-9a-f-]{36})?")) {
respond(exchange, 400, "Invalid inventory request");
return;
}
String shard = query.substring(6, 8);
String after = query.length() > 8 ? query.substring(15) : "";
if (!after.isEmpty() && (!after.startsWith(shard) || !UUID.fromString(after).toString().equals(after))) {
respond(exchange, 400, "Invalid inventory cursor");
return;
}
Path directory = segments.resolve(shard);
if (!Files.isDirectory(directory)) {
respond(exchange, 200, "");
return;
}
List<Path> files;
try (var entries = Files.list(directory)) {
files = entries.filter(Files::isRegularFile).sorted(Comparator.comparing(path ->
path.getFileName().toString())).toList();
}
StringBuilder body = new StringBuilder();
int count = 0;
for (Path file : files) {
String id = file.getFileName().toString();
if (id.compareTo(after) <= 0 || !id.matches("[0-9a-f-]{36}")) continue;
if (!UUID.fromString(id).toString().equals(id)) continue;
body.append(id).append(' ').append(Files.getLastModifiedTime(file).toMillis()).append('\n');
if (++count == 1000) break;
}
respond(exchange, 200, body.toString());
}
private synchronized void delete(HttpExchange exchange, Path target) throws IOException {
String expected = exchange.getRequestHeaders().getFirst("X-Cluster-Expected-Mtime");
String age = exchange.getRequestHeaders().getFirst("X-Cluster-Gc-Min-Age-Millis");
long expectedTime, minimumAge;
try {
expectedTime = Long.parseLong(expected);
minimumAge = Long.parseLong(age);
} catch (NumberFormatException error) {
respond(exchange, 400, "Invalid deletion guard");
return;
}
if (minimumAge < 0 || minimumAge > System.currentTimeMillis()) {
respond(exchange, 400, "Invalid deletion age");
return;
}
if (!Files.isRegularFile(target)) {
exchange.sendResponseHeaders(404, -1);
return;
}
long modified = Files.getLastModifiedTime(target).toMillis();
if (modified != expectedTime || modified > System.currentTimeMillis() - minimumAge) {
exchange.sendResponseHeaders(409, -1);
return;
}
Files.delete(target);
DiskStore.syncDirectory(target.getParent());
exchange.sendResponseHeaders(204, -1);
}
private static String segmentId(HttpExchange exchange, String path) throws IOException {
if (!path.matches("/segments/[0-9a-f-]{36}")) {
respond(exchange, 404, "Not found");