From b5cf324105f9eaa0ef43335e1e84fc6f6d468d8b Mon Sep 17 00:00:00 2001 From: Stepan Usatiuk Date: Sat, 22 Jun 2024 23:46:24 +0200 Subject: [PATCH] maybe working file conflict resolution --- server/pom.xml | 1 + .../conflicts/DirectoryConflictResolver.java | 124 ++++++++++++++ .../files/conflicts/FileConflictResolver.java | 151 ++++++++++++++++++ .../NoOpConflictResolver.java | 2 +- .../NotImplementedConflictResolver.java | 2 +- .../dhfs/storage/files/objects/ChunkData.java | 1 + .../dhfs/storage/files/objects/ChunkInfo.java | 1 + .../dhfs/storage/files/objects/Directory.java | 1 + .../objects/DirectoryConflictResolver.java | 121 -------------- .../dhfs/storage/files/objects/File.java | 11 +- .../dhfs/storage/files/objects/FsNode.java | 1 + .../files/service/DhfsFileServiceImpl.java | 33 +++- .../distributed/InvalidationQueueService.java | 2 +- .../dhfs/files/DhfsFileServiceSimpleTest.java | 19 ++- 14 files changed, 336 insertions(+), 134 deletions(-) create mode 100644 server/src/main/java/com/usatiuk/dhfs/storage/files/conflicts/DirectoryConflictResolver.java create mode 100644 server/src/main/java/com/usatiuk/dhfs/storage/files/conflicts/FileConflictResolver.java rename server/src/main/java/com/usatiuk/dhfs/storage/files/{objects => conflicts}/NoOpConflictResolver.java (90%) rename server/src/main/java/com/usatiuk/dhfs/storage/files/{objects => conflicts}/NotImplementedConflictResolver.java (91%) delete mode 100644 server/src/main/java/com/usatiuk/dhfs/storage/files/objects/DirectoryConflictResolver.java diff --git a/server/pom.xml b/server/pom.xml index d4d991b0..8f8368ff 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -14,6 +14,7 @@ quarkus-bom io.quarkus.platform 3.11.3 + uber-jar true 3.2.5 diff --git a/server/src/main/java/com/usatiuk/dhfs/storage/files/conflicts/DirectoryConflictResolver.java b/server/src/main/java/com/usatiuk/dhfs/storage/files/conflicts/DirectoryConflictResolver.java new file mode 100644 index 00000000..cda19cdc --- /dev/null +++ b/server/src/main/java/com/usatiuk/dhfs/storage/files/conflicts/DirectoryConflictResolver.java @@ -0,0 +1,124 @@ +package com.usatiuk.dhfs.storage.files.conflicts; + +import com.usatiuk.dhfs.storage.SerializationHelper; +import com.usatiuk.dhfs.storage.files.objects.Directory; +import com.usatiuk.dhfs.storage.objects.jrepository.JObject; +import com.usatiuk.dhfs.storage.objects.repository.distributed.ConflictResolver; +import com.usatiuk.dhfs.storage.objects.repository.distributed.ObjectMetadata; +import com.usatiuk.dhfs.storage.objects.repository.distributed.PersistentRemoteHostsService; +import com.usatiuk.dhfs.storage.objects.repository.distributed.RemoteObjectServiceClient; +import io.quarkus.logging.Log; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.apache.commons.lang3.NotImplementedException; + +import java.util.LinkedHashMap; +import java.util.Objects; +import java.util.UUID; + +@ApplicationScoped +public class DirectoryConflictResolver implements ConflictResolver { + @Inject + PersistentRemoteHostsService persistentRemoteHostsService; + + @Inject + RemoteObjectServiceClient remoteObjectServiceClient; + + @Override + public ConflictResolutionResult resolve(UUID conflictHost, JObject ours) { + var theirsData = remoteObjectServiceClient.getSpecificObject(conflictHost, ours.getName()); + + var theirsDir = (Directory) SerializationHelper.deserialize(theirsData.getRight()); + if (!theirsDir.getClass().equals(Directory.class)) { + Log.error("Object type mismatch!"); + throw new NotImplementedException(); + } + + if (!ours.isOf(Directory.class)) + throw new NotImplementedException("Type conflict for " + ours.getName() + ", directory was expected"); + + var oursAsDir = (JObject) ours; + oursAsDir.runWriteLockedMeta((a, b, c) -> { + // FIXME: + if (!ours.tryLocalResolve()) + throw new NotImplementedException("Conflict but we don't have local copy for " + ours.getName()); + + oursAsDir.runWriteLocked((m, oursDir, bump) -> { + + LinkedHashMap mergedChildren = new LinkedHashMap<>(); + ObjectMetadata newMetadata; + + var oursHeader = m.toRpcHeader(); + var theirsHeader = theirsData.getLeft(); + + Directory first; + Directory second; + UUID otherHostname; + if (oursDir.getMtime() >= theirsDir.getMtime()) { + first = oursDir; + second = theirsDir; + otherHostname = conflictHost; + } else { + second = oursDir; + first = theirsDir; + otherHostname = persistentRemoteHostsService.getSelfUuid(); + } + + mergedChildren.putAll(first.getChildren()); + for (var entry : second.getChildren().entrySet()) { + if (mergedChildren.containsKey(entry.getKey()) && + !Objects.equals(mergedChildren.get(entry.getKey()), entry.getValue())) { + int i = 0; + do { + String name = entry.getKey() + ".conflict." + i + "." + otherHostname; + if (mergedChildren.containsKey(name)) { + i++; + continue; + } + mergedChildren.put(name, entry.getValue()); + break; + } while (true); + } else { + mergedChildren.put(entry.getKey(), entry.getValue()); + } + } + + newMetadata = new ObjectMetadata(ours.getName(), oursHeader.getConflictResolver(), m.getType()); + + for (var entry : oursHeader.getChangelog().getEntriesList()) { + newMetadata.getChangelog().put(UUID.fromString(entry.getHost()), entry.getVersion()); + } + for (var entry : theirsHeader.getChangelog().getEntriesList()) { + newMetadata.getChangelog().merge(UUID.fromString(entry.getHost()), entry.getVersion(), Long::max); + } + + boolean wasChanged = mergedChildren.size() != first.getChildren().size() + || oursDir.getMtime() != first.getMtime() + || oursDir.getCtime() != first.getCtime(); + + oursDir.setMtime(first.getMtime()); + oursDir.setCtime(first.getCtime()); + + if (wasChanged) { + newMetadata.getChangelog().merge(persistentRemoteHostsService.getSelfUuid(), 1L, Long::sum); + } + + if (wasChanged) + if (m.getBestVersion() >= newMetadata.getOurVersion()) + throw new NotImplementedException("Race when conflict resolving"); + + if (m.getBestVersion() > newMetadata.getOurVersion()) + throw new NotImplementedException("Race when conflict resolving"); + oursDir.getChildren().clear(); + oursDir.getChildren().putAll(mergedChildren); + + m.getChangelog().clear(); + m.getChangelog().putAll(newMetadata.getChangelog()); + return null; + }); + return null; + }); + + return ConflictResolutionResult.RESOLVED; + } +} diff --git a/server/src/main/java/com/usatiuk/dhfs/storage/files/conflicts/FileConflictResolver.java b/server/src/main/java/com/usatiuk/dhfs/storage/files/conflicts/FileConflictResolver.java new file mode 100644 index 00000000..4557633e --- /dev/null +++ b/server/src/main/java/com/usatiuk/dhfs/storage/files/conflicts/FileConflictResolver.java @@ -0,0 +1,151 @@ +package com.usatiuk.dhfs.storage.files.conflicts; + +import com.usatiuk.dhfs.storage.SerializationHelper; +import com.usatiuk.dhfs.storage.files.objects.Directory; +import com.usatiuk.dhfs.storage.files.objects.File; +import com.usatiuk.dhfs.storage.objects.jrepository.JObject; +import com.usatiuk.dhfs.storage.objects.jrepository.JObjectManager; +import com.usatiuk.dhfs.storage.objects.repository.distributed.ConflictResolver; +import com.usatiuk.dhfs.storage.objects.repository.distributed.ObjectMetadata; +import com.usatiuk.dhfs.storage.objects.repository.distributed.PersistentRemoteHostsService; +import com.usatiuk.dhfs.storage.objects.repository.distributed.RemoteObjectServiceClient; +import io.quarkus.logging.Log; +import jakarta.inject.Inject; +import org.apache.commons.lang3.NotImplementedException; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.Objects; +import java.util.UUID; + +public class FileConflictResolver implements ConflictResolver { + @Inject + PersistentRemoteHostsService persistentRemoteHostsService; + + @Inject + RemoteObjectServiceClient remoteObjectServiceClient; + + @Inject + JObjectManager jObjectManager; + + @Override + public ConflictResolutionResult resolve(UUID conflictHost, JObject ours) { + var theirsData = remoteObjectServiceClient.getSpecificObject(conflictHost, ours.getName()); + + var theirsFile = (File) SerializationHelper.deserialize(theirsData.getRight()); + if (!theirsFile.getClass().equals(File.class)) { + Log.error("Object type mismatch!"); + throw new NotImplementedException(); + } + + var oursAsFile = (JObject) ours; + + var _oursDir = jObjectManager.get(oursAsFile.runReadLocked((m, d) -> d.getParent().toString()), Directory.class) + .orElseThrow(() -> new NotImplementedException("Could not find parent directory for file " + oursAsFile.getName())); + + _oursDir.runWriteLockedMeta((a, b, c) -> { + // FIXME: + if (!_oursDir.tryLocalResolve()) + throw new NotImplementedException("Conflict but we don't have local copy for " + _oursDir.getName()); + + _oursDir.runWriteLocked((mD, oursDir, bumpDir) -> { + oursAsFile.runWriteLockedMeta((a2, b2, c2) -> { + // FIXME: + if (!ours.tryLocalResolve()) + throw new NotImplementedException("Conflict but we don't have local copy for " + ours.getName()); + + oursAsFile.runWriteLocked((m, oursFile, bumpFile) -> { + + // TODO: dedup + ObjectMetadata newMetadata; + + var oursHeader = m.toRpcHeader(); + var theirsHeader = theirsData.getLeft(); + + File first; + File second; + UUID otherHostname; + if (oursFile.getMtime() >= theirsFile.getMtime()) { + first = oursFile; + second = theirsFile; + otherHostname = conflictHost; + } else { + second = oursFile; + first = theirsFile; + otherHostname = persistentRemoteHostsService.getSelfUuid(); + } + + newMetadata = new ObjectMetadata(ours.getName(), oursHeader.getConflictResolver(), m.getType()); + + for (var entry : oursHeader.getChangelog().getEntriesList()) { + newMetadata.getChangelog().put(UUID.fromString(entry.getHost()), entry.getVersion()); + } + for (var entry : theirsHeader.getChangelog().getEntriesList()) { + newMetadata.getChangelog().merge(UUID.fromString(entry.getHost()), entry.getVersion(), Long::max); + } + + boolean chunksDiff = !Objects.equals(first.getChunks(), second.getChunks()); + + var firstChunksCopy = first.getChunks().entrySet().stream().map(e -> Pair.of(e.getKey(), e.getValue())).toList(); + var secondChunksCopy = second.getChunks().entrySet().stream().map(e -> Pair.of(e.getKey(), e.getValue())).toList(); + + boolean wasChanged = oursFile.getMtime() != first.getMtime() + || oursFile.getCtime() != first.getCtime() + || chunksDiff; + + if (wasChanged) { + oursFile.getChunks().clear(); + for (var e : firstChunksCopy) { + oursFile.getChunks().put(e.getLeft(), e.getValue()); + } + oursFile.setMtime(first.getMtime()); + oursFile.setCtime(first.getCtime()); + + var newFile = new File(UUID.randomUUID(), second.getMode(), oursDir.getUuid()); + newFile.setMtime(second.getMtime()); + newFile.setCtime(second.getCtime()); + for (var e : secondChunksCopy) { + newFile.getChunks().put(e.getLeft(), e.getValue()); + } + + var theName = oursDir.getChildren().entrySet().stream().filter(p -> p.getValue().equals(oursFile.getUuid())).findAny().orElseThrow( + () -> new NotImplementedException("Could not find our file in directory " + oursDir.getName()) + ); + + jObjectManager.put(newFile); + + int i = 0; + do { + String name = theName.getKey() + ".conflict." + i + "." + otherHostname; + if (oursDir.getChildren().containsKey(name)) { + i++; + continue; + } + oursDir.getChildren().put(name, newFile.getUuid()); + break; + } while (true); + + newMetadata.getChangelog().merge(persistentRemoteHostsService.getSelfUuid(), 1L, Long::sum); + bumpDir.apply(); + } + + if (wasChanged) + if (m.getBestVersion() >= newMetadata.getOurVersion()) + throw new NotImplementedException("Race when conflict resolving"); + + if (m.getBestVersion() > newMetadata.getOurVersion()) + throw new NotImplementedException("Race when conflict resolving"); + + m.getChangelog().clear(); + m.getChangelog().putAll(newMetadata.getChangelog()); + return null; + }); + return null; + }); + return null; + }); + return null; + }); + + return ConflictResolutionResult.RESOLVED; + } +} diff --git a/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/NoOpConflictResolver.java b/server/src/main/java/com/usatiuk/dhfs/storage/files/conflicts/NoOpConflictResolver.java similarity index 90% rename from server/src/main/java/com/usatiuk/dhfs/storage/files/objects/NoOpConflictResolver.java rename to server/src/main/java/com/usatiuk/dhfs/storage/files/conflicts/NoOpConflictResolver.java index 92a12758..14279c13 100644 --- a/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/NoOpConflictResolver.java +++ b/server/src/main/java/com/usatiuk/dhfs/storage/files/conflicts/NoOpConflictResolver.java @@ -1,4 +1,4 @@ -package com.usatiuk.dhfs.storage.files.objects; +package com.usatiuk.dhfs.storage.files.conflicts; import com.usatiuk.dhfs.storage.objects.jrepository.JObject; import com.usatiuk.dhfs.storage.objects.repository.distributed.ConflictResolver; diff --git a/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/NotImplementedConflictResolver.java b/server/src/main/java/com/usatiuk/dhfs/storage/files/conflicts/NotImplementedConflictResolver.java similarity index 91% rename from server/src/main/java/com/usatiuk/dhfs/storage/files/objects/NotImplementedConflictResolver.java rename to server/src/main/java/com/usatiuk/dhfs/storage/files/conflicts/NotImplementedConflictResolver.java index 08a18887..d850a3ac 100644 --- a/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/NotImplementedConflictResolver.java +++ b/server/src/main/java/com/usatiuk/dhfs/storage/files/conflicts/NotImplementedConflictResolver.java @@ -1,4 +1,4 @@ -package com.usatiuk.dhfs.storage.files.objects; +package com.usatiuk.dhfs.storage.files.conflicts; import com.usatiuk.dhfs.storage.objects.jrepository.JObject; import com.usatiuk.dhfs.storage.objects.repository.distributed.ConflictResolver; diff --git a/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/ChunkData.java b/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/ChunkData.java index 8501ca9d..79ff513c 100644 --- a/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/ChunkData.java +++ b/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/ChunkData.java @@ -1,6 +1,7 @@ package com.usatiuk.dhfs.storage.files.objects; import com.google.protobuf.ByteString; +import com.usatiuk.dhfs.storage.files.conflicts.NoOpConflictResolver; import com.usatiuk.dhfs.storage.objects.jrepository.JObjectData; import com.usatiuk.dhfs.storage.objects.repository.distributed.ConflictResolver; import lombok.Getter; diff --git a/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/ChunkInfo.java b/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/ChunkInfo.java index 906e4a03..96edb544 100644 --- a/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/ChunkInfo.java +++ b/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/ChunkInfo.java @@ -1,5 +1,6 @@ package com.usatiuk.dhfs.storage.files.objects; +import com.usatiuk.dhfs.storage.files.conflicts.NoOpConflictResolver; import com.usatiuk.dhfs.storage.objects.jrepository.JObjectData; import com.usatiuk.dhfs.storage.objects.repository.distributed.ConflictResolver; import lombok.Getter; diff --git a/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/Directory.java b/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/Directory.java index 65f46af0..ef020bf9 100644 --- a/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/Directory.java +++ b/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/Directory.java @@ -1,5 +1,6 @@ package com.usatiuk.dhfs.storage.files.objects; +import com.usatiuk.dhfs.storage.files.conflicts.DirectoryConflictResolver; import com.usatiuk.dhfs.storage.objects.repository.distributed.ConflictResolver; import lombok.Getter; diff --git a/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/DirectoryConflictResolver.java b/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/DirectoryConflictResolver.java deleted file mode 100644 index 7fb4e7c5..00000000 --- a/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/DirectoryConflictResolver.java +++ /dev/null @@ -1,121 +0,0 @@ -package com.usatiuk.dhfs.storage.files.objects; - -import com.usatiuk.dhfs.storage.SerializationHelper; -import com.usatiuk.dhfs.storage.objects.jrepository.JObject; -import com.usatiuk.dhfs.storage.objects.repository.distributed.ConflictResolver; -import com.usatiuk.dhfs.storage.objects.repository.distributed.ObjectMetadata; -import com.usatiuk.dhfs.storage.objects.repository.distributed.PersistentRemoteHostsService; -import com.usatiuk.dhfs.storage.objects.repository.distributed.RemoteObjectServiceClient; -import io.quarkus.logging.Log; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; -import org.apache.commons.lang3.NotImplementedException; - -import java.util.LinkedHashMap; -import java.util.Objects; -import java.util.UUID; - -@ApplicationScoped -public class DirectoryConflictResolver implements ConflictResolver { - @Inject - PersistentRemoteHostsService persistentRemoteHostsService; - - @Inject - RemoteObjectServiceClient remoteObjectServiceClient; - - @Override - public ConflictResolutionResult resolve(UUID conflictHost, JObject ours) { - var theirsData = remoteObjectServiceClient.getSpecificObject(conflictHost, ours.getName()); - - if (!ours.isOf(Directory.class)) - throw new NotImplementedException("Type conflict for " + ours.getName() + ", directory was expected"); - - var oursAsDir = (JObject) ours; - - oursAsDir.runWriteLocked((m, oursDir, bump) -> { - if (!ours.tryLocalResolve()) - throw new NotImplementedException("Conflict but we don't have local copy for " + ours.getName()); - - LinkedHashMap mergedChildren = new LinkedHashMap<>(); - ObjectMetadata newMetadata; - long newMtime; - long newCtime; - - var oursHeader = m.toRpcHeader(); - var theirsHeader = theirsData.getLeft(); - - var theirsDir = (Directory) SerializationHelper.deserialize(theirsData.getRight()); - if (!theirsDir.getClass().equals(Directory.class)) { - Log.error("Object type mismatch!"); - throw new NotImplementedException(); - } - - Directory first; - Directory second; - UUID otherHostname; - if (oursDir.getMtime() >= theirsDir.getMtime()) { - first = oursDir; - second = theirsDir; - otherHostname = conflictHost; - } else { - second = oursDir; - first = theirsDir; - otherHostname = persistentRemoteHostsService.getSelfUuid(); - } - - mergedChildren.putAll(first.getChildren()); - for (var entry : second.getChildren().entrySet()) { - if (mergedChildren.containsKey(entry.getKey()) && - !Objects.equals(mergedChildren.get(entry.getKey()), entry.getValue())) { - int i = 0; - do { - String name = entry.getKey() + ".conflict." + i + "." + otherHostname; - if (mergedChildren.containsKey(name)) { - i++; - continue; - } - mergedChildren.put(name, entry.getValue()); - break; - } while (true); - } else { - mergedChildren.put(entry.getKey(), entry.getValue()); - } - } - - newMetadata = new ObjectMetadata(ours.getName(), oursHeader.getConflictResolver(), m.getType()); - - for (var entry : oursHeader.getChangelog().getEntriesList()) { - newMetadata.getChangelog().put(UUID.fromString(entry.getHost()), entry.getVersion()); - } - for (var entry : theirsHeader.getChangelog().getEntriesList()) { - newMetadata.getChangelog().merge(UUID.fromString(entry.getHost()), entry.getVersion(), Long::max); - } - - boolean wasChanged = mergedChildren.size() != first.getChildren().size(); - if (wasChanged) { - newMetadata.getChangelog().merge(persistentRemoteHostsService.getSelfUuid(), 1L, Long::sum); - } - - newMtime = first.getMtime(); - newCtime = first.getCtime(); - - if (wasChanged) - if (m.getBestVersion() >= newMetadata.getOurVersion()) - throw new NotImplementedException("Race when conflict resolving"); - - if (m.getBestVersion() > newMetadata.getOurVersion()) - throw new NotImplementedException("Race when conflict resolving"); - - oursDir.setMtime(newMtime); - oursDir.setCtime(newCtime); - oursDir.getChildren().clear(); - oursDir.getChildren().putAll(mergedChildren); - - m.getChangelog().clear(); - m.getChangelog().putAll(newMetadata.getChangelog()); - return null; - }); - - return ConflictResolutionResult.RESOLVED; - } -} diff --git a/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/File.java b/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/File.java index 7ad72063..a148f90c 100644 --- a/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/File.java +++ b/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/File.java @@ -1,20 +1,21 @@ package com.usatiuk.dhfs.storage.files.objects; import lombok.Getter; +import lombok.Setter; import java.util.NavigableMap; import java.util.TreeMap; import java.util.UUID; public class File extends FsNode { - public File(UUID uuid) { - super(uuid); - } - - public File(UUID uuid, long mode) { + public File(UUID uuid, long mode, UUID parent) { super(uuid, mode); + _parent = parent; } @Getter private final NavigableMap _chunks = new TreeMap<>(); + + @Getter + private final UUID _parent; } diff --git a/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/FsNode.java b/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/FsNode.java index 93f36903..64034da0 100644 --- a/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/FsNode.java +++ b/server/src/main/java/com/usatiuk/dhfs/storage/files/objects/FsNode.java @@ -1,5 +1,6 @@ package com.usatiuk.dhfs.storage.files.objects; +import com.usatiuk.dhfs.storage.files.conflicts.NotImplementedConflictResolver; import com.usatiuk.dhfs.storage.objects.jrepository.JObjectData; import com.usatiuk.dhfs.storage.objects.repository.distributed.ConflictResolver; import lombok.Getter; diff --git a/server/src/main/java/com/usatiuk/dhfs/storage/files/service/DhfsFileServiceImpl.java b/server/src/main/java/com/usatiuk/dhfs/storage/files/service/DhfsFileServiceImpl.java index 6058c9d3..1f59dddf 100644 --- a/server/src/main/java/com/usatiuk/dhfs/storage/files/service/DhfsFileServiceImpl.java +++ b/server/src/main/java/com/usatiuk/dhfs/storage/files/service/DhfsFileServiceImpl.java @@ -54,7 +54,15 @@ public class DhfsFileServiceImpl implements DhfsFileService { return Optional.empty(); } - if (path.getNameCount() == 1) return ref; + if (path.getNameCount() == 1) { + if (ref.get().isOf(File.class)) { + var f = (JObject) ref.get(); + if (!Objects.equals(f.runReadLocked((m, d) -> d.getParent()).toString(), from.getName())) { + throw new StatusRuntimeException(Status.DATA_LOSS.withDescription("Parent mismatch for file " + path)); + } + } + return ref; + } return traverse(ref.get(), path.subpath(1, path.getNameCount())); @@ -99,8 +107,7 @@ public class DhfsFileServiceImpl implements DhfsFileService { var dir = (JObject) found.get(); var fuuid = UUID.randomUUID(); - File f = new File(fuuid); - f.setMode(mode); + File f = new File(fuuid, mode, UUID.fromString(dir.getName())); //FIXME: jObjectManager.put(f); @@ -147,6 +154,12 @@ public class DhfsFileServiceImpl implements DhfsFileService { if (!(found.get().isOf(Directory.class))) return false; + var kidId = ((JObject) found.get()).runReadLocked((m, d) -> d.getKid(Path.of(name).getFileName().toString())); + + var kid = jObjectManager.get(kidId.get().toString()); + + if (kid.isEmpty()) return false; + var dir = (JObject) found.get(); return dir.runWriteLocked((m, d, bump) -> { bump.apply(); @@ -171,6 +184,8 @@ public class DhfsFileServiceImpl implements DhfsFileService { if (dent.isEmpty()) return false; if (!rmdent(from)) return false; + var dentGot = dent.get(); + // FIXME: var root = getRoot(); var found = traverse(root, Path.of(to).getParent()); @@ -180,10 +195,20 @@ public class DhfsFileServiceImpl implements DhfsFileService { var dir = (JObject) found.get(); + var putDent = dentGot.isOf(File.class) ? + jObjectManager.put(((JObject) dentGot).runWriteLocked((m, d, b) -> { + var cpy = new File(UUID.randomUUID(), d.getMode(), UUID.fromString(dir.getName())); + cpy.setMtime(d.getMtime()); + cpy.setCtime(d.getCtime()); + cpy.getChunks().putAll(d.getChunks()); + return cpy; + })) : dentGot; + dir.runWriteLocked((m, d, bump) -> { bump.apply(); + d.setMtime(System.currentTimeMillis()); - d.getChildren().put(Path.of(to).getFileName().toString(), UUID.fromString(dent.get().getName())); + d.getChildren().put(Path.of(to).getFileName().toString(), UUID.fromString(putDent.getName())); return null; }); diff --git a/server/src/main/java/com/usatiuk/dhfs/storage/objects/repository/distributed/InvalidationQueueService.java b/server/src/main/java/com/usatiuk/dhfs/storage/objects/repository/distributed/InvalidationQueueService.java index fad80eaa..81628916 100644 --- a/server/src/main/java/com/usatiuk/dhfs/storage/objects/repository/distributed/InvalidationQueueService.java +++ b/server/src/main/java/com/usatiuk/dhfs/storage/objects/repository/distributed/InvalidationQueueService.java @@ -53,7 +53,7 @@ public class InvalidationQueueService { private void sender() { try { while (!Thread.interrupted()) { - Thread.sleep(100); + Thread.sleep(1000); var data = pullAll(); String stats = "Sent invalidation: "; for (var forHost : data.entrySet()) { diff --git a/server/src/test/java/com/usatiuk/dhfs/files/DhfsFileServiceSimpleTest.java b/server/src/test/java/com/usatiuk/dhfs/files/DhfsFileServiceSimpleTest.java index 859285a9..7ac180c5 100644 --- a/server/src/test/java/com/usatiuk/dhfs/files/DhfsFileServiceSimpleTest.java +++ b/server/src/test/java/com/usatiuk/dhfs/files/DhfsFileServiceSimpleTest.java @@ -39,7 +39,7 @@ public class DhfsFileServiceSimpleTest { ChunkInfo c2i = new ChunkInfo(c2.getHash(), c2.getBytes().size()); ChunkData c3 = new ChunkData(ByteString.copyFrom("91011".getBytes())); ChunkInfo c3i = new ChunkInfo(c3.getHash(), c3.getBytes().size()); - File f = new File(fuuid); + File f = new File(fuuid, 777, null); f.getChunks().put(0L, c1.getHash()); f.getChunks().put((long) c1.getBytes().size(), c2.getHash()); f.getChunks().put((long) c1.getBytes().size() + c2.getBytes().size(), c3.getHash()); @@ -129,4 +129,21 @@ public class DhfsFileServiceSimpleTest { fileService.truncate(uuid, 7); Assertions.assertArrayEquals(new byte[]{0, 1, 2, 3, 4, 5, 6,}, fileService.read(uuid, 0, 20).get().toByteArray()); } + + @Test + void moveTest() { + var ret = fileService.create("/moveTest", 777); + Assertions.assertTrue(ret.isPresent()); + var uuid = ret.get(); + + fileService.write(uuid, 0, new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + Assertions.assertArrayEquals(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, fileService.read(uuid, 0, 10).get().toByteArray()); + + Assertions.assertTrue(fileService.rename("/moveTest", "/movedTest")); + Assertions.assertFalse(fileService.open("/moveTest").isPresent()); + Assertions.assertTrue(fileService.open("/movedTest").isPresent()); + + Assertions.assertArrayEquals(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + fileService.read(fileService.open("/movedTest").get(), 0, 10).get().toByteArray()); + } }