package io.eguan.vvr.persistence.repository;
/*
* #%L
* Project eguan
* %%
* Copyright (C) 2012 - 2017 Oodrive
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import static io.eguan.utils.ByteBuffers.ALLOCATE_DIRECT;
import io.eguan.configuration.AbstractConfigKey;
import io.eguan.configuration.ConfigValidationException;
import io.eguan.configuration.MetaConfiguration;
import io.eguan.dtx.DtxResourceManagerContext;
import io.eguan.dtx.DtxTaskInfo;
import io.eguan.hash.HashAlgorithm;
import io.eguan.ibs.Ibs;
import io.eguan.ibs.IbsErrorCode;
import io.eguan.ibs.IbsFactory;
import io.eguan.ibs.IbsIOException;
import io.eguan.net.MsgClientStartpoint;
import io.eguan.net.MsgServerRemoteStatus;
import io.eguan.net.MsgServerTimeoutException;
import io.eguan.nrs.NrsConfigurationContext;
import io.eguan.nrs.NrsException;
import io.eguan.nrs.NrsFile;
import io.eguan.nrs.NrsFileHeader;
import io.eguan.nrs.NrsFileJanitor;
import io.eguan.nrs.NrsMsgEnhancer;
import io.eguan.nrs.NrsStorageConfigKey;
import io.eguan.proto.Common.OpCode;
import io.eguan.proto.Common.Type;
import io.eguan.proto.Common.Uuid;
import io.eguan.proto.nrs.NrsRemote.NrsFileHeaderMsg;
import io.eguan.proto.nrs.NrsRemote.NrsFileMapping;
import io.eguan.proto.nrs.NrsRemote.NrsFileUpdate;
import io.eguan.proto.nrs.NrsRemote.NrsVersion;
import io.eguan.proto.vvr.VvrRemote;
import io.eguan.proto.vvr.VvrRemote.Item;
import io.eguan.proto.vvr.VvrRemote.RemoteOperation;
import io.eguan.utils.SimpleIdentifierProvider;
import io.eguan.utils.UuidT;
import io.eguan.utils.mapper.FileMapperConfigurationContext;
import io.eguan.vvr.configuration.CommonConfigurationContext;
import io.eguan.vvr.configuration.IbsConfigurationContext;
import io.eguan.vvr.configuration.PersistenceConfigurationContext;
import io.eguan.vvr.configuration.keys.BlockSizeConfigKey;
import io.eguan.vvr.configuration.keys.DescriptionConfigkey;
import io.eguan.vvr.configuration.keys.DeviceFileDirectoryConfigKey;
import io.eguan.vvr.configuration.keys.HashAlgorithmConfigKey;
import io.eguan.vvr.configuration.keys.IbsIbpGenPathConfigKey;
import io.eguan.vvr.configuration.keys.IbsIbpPathConfigKey;
import io.eguan.vvr.configuration.keys.NameConfigKey;
import io.eguan.vvr.configuration.keys.SnapshotFileDirectoryConfigKey;
import io.eguan.vvr.configuration.keys.StartedConfigKey;
import io.eguan.vvr.remote.VvrDtxRmContext;
import io.eguan.vvr.remote.VvrRemoteUtils;
import io.eguan.vvr.repository.core.api.AbstractRepositoryImpl;
import io.eguan.vvr.repository.core.api.Device;
import io.eguan.vvr.repository.core.api.Snapshot;
import io.eguan.vvr.repository.core.api.VersionedVolumeRepository.ItemChangedEvent.VvrItemAttributeType;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.ConnectException;
import java.nio.ByteBuffer;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nonnull;
import javax.transaction.xa.XAException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.protobuf.ByteString;
import com.google.protobuf.GeneratedMessageLite;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.MessageLite;
/**
* {@link NrsFile} based implementation of the repository interface.
* <p>
*
* @author oodrive
* @author llambert
* @author ebredzinski
* @author jmcaba
* @author pwehrle
*
*/
public final class NrsRepository extends AbstractRepositoryImpl {
static final Logger LOGGER = LoggerFactory.getLogger(NrsRepository.class);
/**
* Name of the file containing the repository configuration.
*/
private static final String NRS_CONFIG = "vvr.cfg";
/**
* Name of the file containing the previous repository configuration.
*/
private static final String NRS_CONFIG_PREV = "vvr.cfg.bak";
/** Block size in bytes. */
private final int blockSize;
/**
* Association between the {@link NrsFile} and a {@link NrsSnapshot}. The key is the UuidT of the NrsFile, the value
* is the UUID of the snapshot.
*/
private BiMap<UuidT<NrsFile>, UUID> nrsToSnapshot;
/**
* Internal map of all snapshots.
*/
private ConcurrentHashMap<UUID, NrsSnapshot> snapshots;
/**
* Internal map of all parents (NrsFile). The value is the UUID of the parent of the key.
*/
private ConcurrentHashMap<UuidT<NrsFile>, UuidT<NrsFile>> parents;
/**
* Internal map of all devices.
*/
private ConcurrentHashMap<UUID, NrsDevice> devices;
/**
* The item at the root of the hierarchy.
*/
private NrsSnapshot rootItem = null;
/**
* The {@link Ibs} instance backing this repository.
*/
private Ibs ibsInstance;
/**
* The flag indicating the operation mode.
*/
private boolean started;
/**
* The configured {@link HashAlgorithm}.
*/
private HashAlgorithm hashAlgorithm;
/**
* The length in bytes of the {@link #hashAlgorithm}'s results.
*/
private int hashLength;
/**
* The temporary file to keep for IBS configuration.
*/
private File targetIbsConfigFile;
/**
* The initialization state of this instance.
*/
private boolean initialized;
/**
* The {@link NrsFileJanitor} used to construct {@link NrsVvrItem}s.
*/
private final NrsFileJanitor nrsFileJanitor;
private final NrsMsgEnhancer nrsMsgEnhancer = new NrsMsgEnhancer() {
@Override
public final void enhance(final GeneratedMessageLite.Builder<?, ?> genericBuilder) {
final RemoteOperation.Builder remoteBuilder = (RemoteOperation.Builder) genericBuilder;
enhanceMessage(remoteBuilder);
}
};
/**
* The directory for saving {@link NrsSnapshot}s.
*/
private File snapshotDir;
/**
* The directory for saving {@link NrsDevice}s.
*/
private File deviceDir;
/**
* Constructor to be invoked by builders.
*
* @param builder
* the {@link NrsRepository.Builder} instance to build this instance from
*/
private NrsRepository(final NrsRepository.Builder builder) {
super(builder);
LOGGER.trace("building new {} instance with uuid {}", NrsRepository.class.getSimpleName(), getUuid());
final MetaConfiguration config = builder.getConfiguration();
this.blockSize = BlockSizeConfigKey.getInstance().getTypedValue(config);
// Initializes the NRS persistence configuration
this.nrsFileJanitor = new NrsFileJanitor(config);
}
@Override
public final void init() {
if (initialized) {
throw new IllegalStateException();
}
// initializes the IBS configuration
this.initIbsConfiguration(false);
// initializes the hash configuration fields
this.initHashConfiguration();
// initializes tree of items
try {
this.initNrsTree();
}
catch (final NrsException e) {
throw new IllegalStateException(e);
}
// Inits the IBS instance. Need to start Ibs for remote messages handling
this.ibsInstance = IbsFactory.openIbs(targetIbsConfigFile);
this.ibsInstance.start();
this.initialized = true;
}
@Override
public final boolean isInitialized() {
return this.initialized;
}
@Override
public final void fini() {
// TODO: add more checking and manage state better
if (!this.initialized) {
LOGGER.warn("Repository not initialized");
return;
}
if (this.isStarted()) {
this.stop(false);
}
try {
this.ibsInstance.stop();
}
catch (final Throwable t) {
LOGGER.warn("Error while stopping IBS", t);
}
// Clean configurations initialized during init()
finiNrsTree();
finiHashConfiguration();
finiIbsConfiguration();
finiSnapDevConfig();
this.initialized = false;
}
@Override
public final void start(final boolean saveState) {
if (!this.isInitialized()) {
throw new IllegalStateException("Repository not initialized");
}
if (started) {
throw new IllegalStateException("Repository already started");
}
this.started = true;
// Save configuration
if (saveState) {
saveStartedState(Boolean.TRUE);
}
}
@Override
public final boolean isStarted() {
return this.started;
}
@Override
public final void stop(final boolean saveState) {
if (started) {
if (!this.isInitialized()) {
throw new IllegalStateException("repository not initialized");
}
this.started = false;
// Save configuration
if (saveState) {
saveStartedState(Boolean.FALSE);
}
}
}
@Override
public final void updateConfiguration(final Map<AbstractConfigKey, Object> newKeyValueMap) {
// Update configuration
super.updateConfiguration(newKeyValueMap);
// Store new configuration
final MetaConfiguration configuration = getConfiguration();
final File repositoryPath = NrsStorageConfigKey.getInstance().getTypedValue(configuration);
storeConfiguration(configuration, repositoryPath);
}
@Override
public final NrsSnapshot getSnapshot(final UUID snapshotId) {
return this.snapshots.get(snapshotId);
}
final NrsSnapshot getSnapshotFromFile(final UuidT<NrsFile> fileId) {
final UUID snapshotId = nrsToSnapshot.get(fileId);
return snapshotId == null ? null : getSnapshot(snapshotId);
}
@Override
public final Set<UUID> getSnapshots() {
return Collections.unmodifiableSet(this.snapshots.keySet());
}
@Override
public final NrsDevice getDevice(final UUID deviceId) {
return this.devices.get(Objects.requireNonNull(deviceId));
}
@Override
public final Set<UUID> getDevices() {
return Collections.unmodifiableSet(this.devices.keySet());
}
@Override
public final Snapshot getRootSnapshot() {
return rootItem;
}
// TODO not thread safe, not atomic
@Override
public final void delete() {
// Started?
if (isStarted()) {
throw new IllegalStateException("Started");
}
// Flag as 'to delete'
setDeleted();
}
public final Collection<UUID> getSnapshotChildrenUuid(final UUID snapshotId) {
final Iterator<NrsSnapshot> ite = snapshots.values().iterator();
final Collection<UUID> result = new ArrayList<>();
while (ite.hasNext()) {
final NrsSnapshot nrsSnapshot = ite.next();
if (nrsSnapshot.isRoot()) {
// Root is not a child
continue;
}
if (snapshotId.equals(nrsSnapshot.getParent())) {
result.add(nrsSnapshot.getUuid());
}
}
return Collections.unmodifiableCollection(result);
}
public final Collection<UUID> getSnapshotDevicesUuid(final UUID snapshotId) {
final Iterator<NrsDevice> ite = devices.values().iterator();
final Collection<UUID> result = new ArrayList<>();
while (ite.hasNext()) {
final Device nrsDevice = ite.next();
if (snapshotId.equals(nrsDevice.getParent())) {
result.add(nrsDevice.getUuid());
}
}
return Collections.unmodifiableCollection(result);
}
/**
* Gets the registered parent for the given UUID.
*
* @param parent
* @return the snapshot found
*/
final NrsSnapshot getParentSnapshot(final UuidT<NrsFile> parentNrsFile) {
UuidT<NrsFile> loopUuidNrsFile = parentNrsFile;
while (true) {
final UUID snapshotUuid = nrsToSnapshot.get(loopUuidNrsFile);
if (snapshotUuid != null) {
final NrsSnapshot snapshot = snapshots.get(snapshotUuid);
if (snapshot == null) {
throw new IllegalStateException("Snapshot not found, id=" + snapshotUuid);
}
return snapshot;
}
final UuidT<NrsFile> parentUuid = parents.get(loopUuidNrsFile);
if (parentUuid == null) {
throw new IllegalStateException("Parent not found, id=" + loopUuidNrsFile);
}
if (parentUuid.equals(loopUuidNrsFile)) {
// Reached root
return rootItem;
}
// Next loop
loopUuidNrsFile = parentUuid;
}
}
/**
* Gets the configured {@link HashProvider.HashAlgorithm}.
*
* @return the {@link HashProvider.HashAlgorithm} to apply to data blocks
*/
final HashAlgorithm getHashAlgorithm() {
// FIXME: this should only be necessary if initialization is not complete => hunt down the race condition and
// exterminate it!
if (this.hashAlgorithm == null) {
this.initHashConfiguration();
}
return this.hashAlgorithm;
}
/**
* Gets the {@link NrsFileJanitor} used to construct {@link NrsVvrItem}s.
*
* @return the {@link NrsFileJanitor} initialized by {@link #initNrsConfiguration()}.
*/
final NrsFileJanitor getNrsFileJanitor() {
return nrsFileJanitor;
}
/**
* Prepare the registration of a new snapshot.
*
* Backing items are automatically registered.
*
* @param newSnapshot
* the snapshot to register
*/
final void preRegisterSnapshot(@Nonnull final NrsSnapshot newSnapshot) {
// NPE if newSnapshot is null
if (newSnapshot.getUuid() == null) {
throw new IllegalArgumentException("snapshot to register has null id");
}
// Add NrsFile <> snapshot association
final UuidT<NrsFile> nrsFileId = newSnapshot.getNrsFileId();
final UuidT<NrsFile> parentFileId = newSnapshot.getParentFile();
final UUID snapshotId = newSnapshot.getUuid();
nrsToSnapshot.put(nrsFileId, snapshotId);
// Stores Nrs hierarchy and snapshots
this.parents.put(nrsFileId, parentFileId);
if (this.snapshots.containsKey(snapshotId)) {
return;
}
this.snapshots.put(snapshotId, newSnapshot);
}
/**
* Last operation(s) after the update of the device.
*
* @param newSnapshot
*/
final void postRegisterSnapshot(@Nonnull final NrsSnapshot newSnapshot) {
// Notify addition of the new snapshot
final ItemCreatedEvent event = new ItemCreatedEvent(this, Objects.requireNonNull(newSnapshot));
eventBus.post(event);
}
/**
* Unregisters the snapshot with the given ID.
*
* Backing items are not automatically unregistered.
*
* @param snapshotId
* the ID of the snapshot to unregister
*/
final void unregisterSnapshot(final UUID snapshotId) {
if (snapshotId == null) {
return;
}
if (getRootSnapshot().getUuid().equals(snapshotId)) {
throw new IllegalStateException("cannot unregister root snapshot");
}
this.snapshots.remove(snapshotId);
this.nrsToSnapshot.inverse().remove(snapshotId);
// Must reset parent cache for the children (snapshots and devices). Reset all (getParent() would
// init parent cache when it is not set...)
for (final NrsSnapshot snapshot : snapshots.values()) {
snapshot.resetParent();
}
for (final NrsDevice device : devices.values()) {
device.resetParent();
}
// Notify delete
final ItemDeletedEvent event = new ItemDeletedEvent(this, snapshotId, Snapshot.class);
eventBus.post(event);
}
/**
* Registers a new device.
*
* Backing items are automatically registered.
*
* @param newDevice
* the device to register
*/
final void registerDevice(@Nonnull final NrsDevice newDevice) {
if (newDevice == null) {
throw new NullPointerException("device to register is null");
}
if (newDevice.getUuid() == null) {
throw new IllegalArgumentException("device to register has null id");
}
if (this.devices.containsKey(newDevice.getUuid()) && this.devices.containsValue(newDevice)) {
return;
}
this.devices.put(newDevice.getUuid(), newDevice);
this.parents.put(newDevice.getNrsFileId(), newDevice.getParentFile());
// Notify addition
final ItemCreatedEvent event = new ItemCreatedEvent(this, newDevice);
eventBus.post(event);
}
/**
* Unregisters the device with the given ID.
*
* Backing items are not automatically unregistered.
*
* @param deviceId
* the ID of the device to unregister
*/
final void unregisterDevice(final UUID deviceId) {
if (deviceId == null) {
return;
}
this.devices.remove(deviceId);
// Notify delete
final ItemDeletedEvent event = new ItemDeletedEvent(this, deviceId, Device.class);
eventBus.post(event);
}
final void registerNrsFile(final UuidT<NrsFile> prevNrsFileUuid, final NrsFile nrsFile) {
final NrsFileHeader<NrsFile> header = nrsFile.getDescriptor();
assert prevNrsFileUuid.equals(header.getParentId());
this.parents.put(header.getFileId(), header.getParentId());
}
/**
* Gets the {@link Ibs} instance backing this repository.
*
* @return the current {@link Ibs}
*/
final Ibs getIbsInstance() {
return this.ibsInstance;
}
/**
* Gets the {@link #hashLength} value.
*
* @return the {@link #hashLength} in bytes as computed by {@link #initHashConfiguration()}
*/
final int getHashLength() {
// FIXME: this should only be necessary if initialization is not complete => hunt down the race condition and
// exterminate it!
if (this.hashLength == 0) {
this.initHashConfiguration();
}
return this.hashLength;
}
/**
* The repository block size.
*
* @return the block size
*/
final int getBlockSize() {
return blockSize;
}
final File getSnapshotDir() {
return snapshotDir;
}
final File getDeviceDir() {
return deviceDir;
}
/**
* Expose method locally to send remote messages on Nrs objects.
*/
@Override
protected final Collection<MsgServerRemoteStatus> sendMessage(final RemoteOperation.Builder opBuilder,
final Type type, final OpCode opCode, final boolean async, final UUID peer)
throws MsgServerTimeoutException, InterruptedException, ConnectException {
return super.sendMessage(opBuilder, type, opCode, async, peer);
}
/**
* Expose method locally to submit transactions.
*
* @see io.eguan.vvr.repository.core.api.AbstractRepositoryImpl#submitTransaction(io.eguan.proto.vvr.VvrRemote.Operation.Builder,
* io.eguan.proto.vvr.VvrRemote.Operation.Type,
* io.eguan.proto.vvr.VvrRemote.Operation.OpCode)
*/
@Override
protected final UUID submitTransaction(final RemoteOperation.Builder opBuilder, final Type type, final OpCode opCode) {
return super.submitTransaction(opBuilder, type, opCode);
}
@Override
public final MessageLite handleMsg(final RemoteOperation op) {
final Type type = op.getType();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Msg op=" + op.getOp() + ", type=" + type + " uuid=" + VvrRemoteUtils.fromUuid(op.getUuid()));
}
if (type == Type.VVR) {
handleMsgVvr(op);
return null;
}
else if (type == Type.DEVICE) {
handleMsgDevice(op);
return null;
}
else if (type == Type.SNAPSHOT) {
handleMsgSnapshot(op);
return null;
}
else if (type == Type.NRS) {
return handleMsgNrs(op);
}
else if (type == Type.IBS) {
return handleMsgIbs(op);
}
else {
throw new IllegalArgumentException("Unexpected message type=" + type);
}
}
private final void handleMsgVvr(final RemoteOperation op) {
final OpCode opCode = op.getOp();
if (opCode == OpCode.SET) {
if (op.hasItem()) {
final Item item = op.getItem();
final Map<AbstractConfigKey, Object> newKeyValueMap = new HashMap<>();
// TODO: name / description have already changed on the initiator node: cannot roll back!!
final String oldName = getName();
final String oldDescription = getDescription();
// Name
if (item.hasName()) {
final String nameNew = item.getName();
setNameLocal(nameNew);
newKeyValueMap.put(NameConfigKey.getInstance(), nameNew);
}
// Description
if (item.hasDescription()) {
final String descriptionNew = item.getDescription();
setDescriptionLocal(descriptionNew);
newKeyValueMap.put(DescriptionConfigkey.getInstance(), descriptionNew);
}
if (!newKeyValueMap.isEmpty()) {
// Local update
updateConfiguration(newKeyValueMap);
}
// Notify change
if (item.hasName()) {
eventBus.post(new ItemChangedEvent(this, getUuid(), VvrItemAttributeType.NAME, oldName, item
.getName()));
}
if (item.hasDescription()) {
eventBus.post(new ItemChangedEvent(this, getUuid(), VvrItemAttributeType.DESCRIPTION,
oldDescription, item.getDescription()));
}
}
}
}
private final void handleMsgDevice(final RemoteOperation op) {
final OpCode opCode = op.getOp();
final UUID uuid = VvrRemoteUtils.fromUuid(op.getUuid());
if (opCode == OpCode.CREATE) {
// Need parent, NrsFile header and item (name is not empty)
if (!op.hasSnapshot() || !(op.getNrsFileHeaderCount() == 1) || !op.hasItem() || !op.getItem().hasName()) {
throw new IllegalArgumentException("Create device " + uuid + ": missing attribute(s)");
}
final NrsSnapshot parent = getSnapshot(op.getSnapshot());
final Item item = op.getItem();
final String name = item.getName();
final String description = item.hasDescription() ? item.getDescription() : null;
final NrsFileHeaderMsg nrsFileHeaderMsg = op.getNrsFileHeader(0);
final NrsFileHeader<NrsFile> nrsFileHeader = NrsRemoteUtils.fromNrsFileHeaderMsg(nrsFileJanitor,
nrsFileHeaderMsg, null);
final long size = nrsFileHeaderMsg.getSize();
if (size < nrsFileHeaderMsg.getBlockSize()) {
throw new IllegalArgumentException("Create device " + uuid + ": size < "
+ nrsFileHeaderMsg.getBlockSize());
}
doCreateDevice(parent, name, description, size, uuid, nrsFileHeader);
return;
}
if (opCode == OpCode.CLONE) {
final UUID origDeviceUuid = VvrRemoteUtils.fromUuid(op.getSnapshot());
final UUID newDeviceUuid = VvrRemoteUtils.fromUuid(op.getUuid());
// Need device NrsFile header for both devices and item (name is not empty)
if (!op.hasSnapshot() || !(op.getNrsFileHeaderCount() == 2) || !op.hasItem() || !op.getItem().hasName()) {
throw new IllegalArgumentException("Clone device from " + origDeviceUuid + ": missing attribute(s)");
}
// Note: the NrsFile of the device may have changed since the submission of the transaction: need to reset
// the parentUuid of the header
final NrsDevice device = getDevice(origDeviceUuid);
final Item item = op.getItem();
final String name = item.getName();
final String description = item.hasDescription() ? item.getDescription() : null;
final UuidT<NrsFile> nrsFileUuid = device.getNrsFileId();
// Create NrsFile header for the cloned device
{
final NrsFileHeaderMsg newNrsFileHeaderMsg = op.getNrsFileHeader(0);
final NrsFileHeader<NrsFile> newNrsFileHeader = NrsRemoteUtils.fromNrsFileHeaderMsg(nrsFileJanitor,
newNrsFileHeaderMsg, nrsFileUuid);
device.doCloneDevice(newDeviceUuid, name, description, newNrsFileHeader);
}
// Create new NrsFile for the originate device
{
final NrsFileHeaderMsg origNrsFileHeaderMsg = op.getNrsFileHeader(1);
final NrsFileHeader<NrsFile> origNrsFileHeader = NrsRemoteUtils.fromNrsFileHeaderMsg(nrsFileJanitor,
origNrsFileHeaderMsg, nrsFileUuid);
device.newNrsFileLocal(origNrsFileHeader);
}
return;
}
// Find the device
final NrsDevice device = devices.get(uuid);
if (device == null) {
throw new IllegalArgumentException("Device " + uuid + " not found");
}
if (opCode == OpCode.SET) {
updateItem(device, op);
// New header?
if (op.getNrsFileHeaderCount() == 1) {
// The NrsFile of the device may have changed since the submission of the transaction
final NrsFileHeaderMsg nrsFileHeaderMsg = op.getNrsFileHeader(0);
final NrsFileHeader<NrsFile> nrsFileHeader = NrsRemoteUtils.fromNrsFileHeaderMsg(getNrsFileJanitor(),
nrsFileHeaderMsg, device.getNrsFileId());
device.newNrsFileLocal(nrsFileHeader);
}
}
else if (opCode == OpCode.DELETE) {
device.deleteDevice();
}
}
private final void handleMsgSnapshot(final RemoteOperation op) {
// Find the snapshot
final OpCode opCode = op.getOp();
if (opCode == OpCode.CREATE) {
// Need device NrsFile header and item (name is not empty)
final UUID uuidDevice = VvrRemoteUtils.fromUuid(op.getUuid());
if (!op.hasSnapshot() || !(op.getNrsFileHeaderCount() == 1) || !op.hasItem() || !op.getItem().hasName()) {
throw new IllegalArgumentException("Create snapshot from " + uuidDevice + ": missing attribute(s)");
}
// Note: the NrsFile of the device may have changed since the submission of the transaction: need to reset
// the parentUuid of the header
final NrsDevice device = getDevice(uuidDevice);
final UUID uuid = VvrRemoteUtils.fromUuid(op.getSnapshot());
final Item item = op.getItem();
final String name = item.getName();
final String description = item.hasDescription() ? item.getDescription() : null;
final NrsFileHeaderMsg nrsFileHeaderMsg = op.getNrsFileHeader(0);
final NrsFileHeader<NrsFile> nrsFileHeader = NrsRemoteUtils.fromNrsFileHeaderMsg(nrsFileJanitor,
nrsFileHeaderMsg, device.getNrsFileId());
device.doCreateSnapshot(uuid, name, description, nrsFileHeader);
return;
}
// Get snapshot
final NrsSnapshot snapshot = getSnapshot(op.getUuid());
if (opCode == OpCode.SET) {
updateItem(snapshot, op);
}
else if (opCode == OpCode.DELETE) {
snapshot.deleteSnapshot();
}
}
private final MessageLite handleMsgNrs(final RemoteOperation op) {
// Get NrsFile
final OpCode opCode = op.getOp();
try {
if (opCode == OpCode.SET) {
final UuidT<NrsFile> uuid = VvrRemoteUtils.fromUuidT(op.getUuid());
if (!op.hasNrsFileUpdate()) {
throw new IllegalArgumentException("Update NRS " + uuid + ": missing attribute(s)");
}
// Write updates in NrsFile
final NrsFileUpdate nrsFileUpdate = op.getNrsFileUpdate();
final NrsFile nrsFile = nrsFileJanitor.openNrsFile(uuid, false);
try {
nrsFile.handleNrsFileUpdate(nrsFileUpdate);
}
finally {
nrsFileJanitor.unlockNrsFile(nrsFile);
}
}
else if (opCode == OpCode.LIST) {
// Reply: list of NrsFiles
final List<NrsVersion> versions = listNrsFiles();
final RemoteOperation.Builder opBuilder = RemoteOperation.newBuilder();
opBuilder.addAllNrsVersions(versions);
return createMessageReply(opBuilder, Type.NRS, OpCode.LIST);
}
else if (opCode == OpCode.UPDATE) {
final UuidT<NrsFile> uuid = VvrRemoteUtils.fromUuidT(op.getUuid());
final NrsFileMapping mapping = op.getNrsFileMapping();
final UUID node = VvrRemoteUtils.fromUuid(op.getSource());
// Open the file read-only, but do not lock it (may be in use)
final NrsFile nrsFile = nrsFileJanitor.openNrsFile(uuid, true);
nrsFileJanitor.unlockNrsFile(nrsFile);
nrsFile.processNrsFileSync(mapping, node);
}
}
catch (final Exception e) {
LOGGER.warn("Failed to handle NRS message " + getUuid(), e);
}
return null;
}
private final MessageLite handleMsgIbs(final RemoteOperation op) {
final OpCode opCode = op.getOp();
try {
if (op.getIbsCount() < 0) {
throw new IllegalArgumentException("Update ibs: missing attribute(s)");
}
if (opCode == OpCode.SET) {
// Write value in IBS or notify put/replace
for (final VvrRemote.Ibs ibsMsg : op.getIbsList()) {
final byte[] key = ibsMsg.getKey().toByteArray();
// Has value?
final ByteBuffer value;
if (ibsMsg.hasValue()) {
final byte[] valueArray = ibsMsg.getValue().toByteArray();
value = ByteBuffer.wrap(valueArray);
}
else {
value = null;
}
// Replace?
if (ibsMsg.hasKeyOld()) {
final byte[] oldKey = ibsMsg.getKeyOld().toByteArray();
ibsInstance.replace(oldKey, key, value);
}
else {
ibsInstance.put(key, value);
}
}
}
else if (opCode == OpCode.GET) {
// Look for the requested block (for now, only on block at a time)
assert op.getIbsCount() == 1;
ByteBuffer result;
final VvrRemote.Ibs ibsMsg = op.getIbs(0);
final byte[] key = ibsMsg.getKey().toByteArray();
try {
result = ibsInstance.get(key, blockSize, ALLOCATE_DIRECT);
}
catch (final IbsIOException e) {
// Clean reply if the buffer is not found
if (e.getErrorCode() == IbsErrorCode.NOT_FOUND) {
result = null;
}
else {
throw e;
}
}
final VvrRemote.Ibs.Builder replyIbsBuilder = VvrRemote.Ibs.newBuilder();
if (result != null) {
result.rewind();
replyIbsBuilder.setValue(ByteString.copyFrom(result));
}
final RemoteOperation.Builder opBuilder = RemoteOperation.newBuilder();
opBuilder.addIbs(replyIbsBuilder);
return createMessageReply(opBuilder, Type.IBS, OpCode.GET);
}
}
catch (final Exception e) {
LOGGER.warn("Failed to update IBS", e);
}
return null;
}
/**
* Find the snapshot corresponding to the given uuid.
*
* @param snapshotUuid
* @return the snapshot found
* @throws IllegalArgumentException
* if the snapshot is not present
*/
private final NrsSnapshot getSnapshot(final Uuid snapshotUuid) throws IllegalArgumentException {
final UUID uuid = VvrRemoteUtils.fromUuid(snapshotUuid);
final NrsSnapshot snapshot = snapshots.get(uuid);
if (snapshot == null) {
throw new IllegalArgumentException("Snapshot " + uuid + " not found");
}
return snapshot;
}
/**
* Update the name, the description and/or the user properties of a {@link NrsVvrItem}.
*
* @param vvrItem
* @param op
*/
private final void updateItem(final NrsVvrItem vvrItem, final RemoteOperation op) {
if (op.hasItem()) {
final Item item = op.getItem();
boolean persistNeeded = false;
final String oldName = vvrItem.getName();
final String oldDescription = vvrItem.getDescription();
// Name
if (item.hasName()) {
vvrItem.setNameLocal(item.getName());
persistNeeded = true;
}
// Description
if (item.hasDescription()) {
vvrItem.setDescriptionLocal(item.getDescription());
persistNeeded = true;
}
// tries to persist all properties, as one has certainly changed
if (persistNeeded) {
try {
vvrItem.persist();
}
catch (final IOException e) {
LOGGER.warn("Failed to persist '" + getUuid() + "'", e);
}
}
// User properties
// Set
{
final int propertiesCount = item.getSetPropCount();
if (propertiesCount > 0) {
assert propertiesCount % 2 == 0;
final List<String> propertiesList = item.getSetPropList();
for (int i = 0; i < propertiesCount; i += 2) {
vvrItem.setUserPropertiesLocal(propertiesList.get(i), propertiesList.get(i + 1));
}
}
}
// Unset
{
final int propertiesCount = item.getDelPropCount();
if (propertiesCount > 0) {
for (final String property : item.getDelPropList()) {
vvrItem.unsetUserPropertiesLocal(property);
}
}
}
// Notify change
if (item.hasName()) {
eventBus.post(new ItemChangedEvent(this, vvrItem.getUuid(), VvrItemAttributeType.NAME, oldName, item
.getName()));
}
if (item.hasDescription()) {
eventBus.post(new ItemChangedEvent(this, vvrItem.getUuid(), VvrItemAttributeType.DESCRIPTION,
oldDescription, item.getDescription()));
}
}
}
/**
* List the {@link NrsFile}s of the repository.
*
* @return the list of {@link NrsFile}s.
* @throws IOException
*/
private final List<NrsVersion> listNrsFiles() throws IOException {
final List<NrsVersion> result = new ArrayList<>(parents.size());
nrsFileJanitor.visitImages(new SimpleFileVisitor<Path>() {
@Override
public final FileVisitResult visitFile(final Path headerPath, final BasicFileAttributes attrs)
throws IOException {
final File headerFile = headerPath.toFile();
assert headerFile.isFile();
// Get file information
final NrsFile nrsFile = nrsFileJanitor.loadNrsFile(headerPath);
final NrsFileHeader<NrsFile> nrsFileHeader = nrsFile.getDescriptor();
final long version = nrsFile.getVersion();
final NrsVersion.Builder versionBuilder = NrsVersion.newBuilder();
versionBuilder.setUuid(VvrRemoteUtils.newTUuid(nrsFileHeader.getFileId()));
versionBuilder.setSource(VvrRemoteUtils.newUuid(nrsFileHeader.getNodeId()));
versionBuilder.setVersion(version);
versionBuilder.setWritable(nrsFileJanitor.isNrsFileWritable(nrsFile));
// Add new version
result.add(versionBuilder.build());
return FileVisitResult.CONTINUE;
}
});
return result;
}
/**
* Repository instance builder adding parameter initialization and build implementation.
*
*
*/
public static final class Builder extends AbstractRepositoryImpl.Builder {
/**
* Location of the repository persistence for the load.
*/
private File repositoryPath;
/**
* UUID of the root snapshot.
*/
private UUID rootUuid;
/**
* Path of the repository. Necessary to load the repository and ignored for the creation.
*
* @param repositoryPath
* @return this
*/
public final Builder repositoryPath(final File repositoryPath) {
this.repositoryPath = repositoryPath;
return this;
}
/**
* Sets the uuid of the root snapshot.
*
* @param rootUuid
* @return this
*/
public final Builder rootUuid(final UUID rootUuid) {
this.rootUuid = Objects.requireNonNull(rootUuid);
return this;
}
@Override
protected MetaConfiguration getConfiguration() {
return super.getConfiguration();
}
/**
* Load an existing {@link NrsRepository}. The caller must set repositoryPath in the builder.
*
* @return a new loaded {@link NrsRepository}
*/
public final NrsRepository load() {
if (!repositoryPath.exists()) {
throw new IllegalStateException(repositoryPath + " does not exist");
}
final File[] contents = repositoryPath.listFiles();
if (contents == null) {
throw new IllegalStateException(repositoryPath + " is not a directory");
}
if (contents.length == 0) {
throw new IllegalStateException(repositoryPath + " is empty");
}
// Load meta configuration
final File config = new File(repositoryPath, NRS_CONFIG);
final MetaConfiguration configuration;
try {
configuration = MetaConfiguration.newConfiguration(config, CommonConfigurationContext.getInstance(),
FileMapperConfigurationContext.getInstance(), IbsConfigurationContext.getInstance(),
NrsConfigurationContext.getInstance(), PersistenceConfigurationContext.getInstance());
configuration(configuration);
}
catch (final IOException | NullPointerException | IllegalArgumentException | ConfigValidationException e) {
throw new IllegalStateException("Failed to load configuration in '" + config + "'", e);
}
// Update builder according to configuration
name(NameConfigKey.getInstance().getTypedValue(configuration));
description(DescriptionConfigkey.getInstance().getTypedValue(configuration));
final NrsRepository result = new NrsRepository(this);
initSnapDevConfig(result, true);
return result;
}
/**
* Create a NrsRepository. The NRS_STORAGE must exist and be empty.
*
* @return the new NrsRepository
*/
public final NrsRepository create() {
final MetaConfiguration configuration = getConfiguration();
repositoryPath = NrsStorageConfigKey.getInstance().getTypedValue(configuration);
if (!repositoryPath.exists()) {
throw new IllegalStateException(repositoryPath + " does not exist");
}
final File[] contents = repositoryPath.listFiles();
if (contents == null) {
throw new IllegalStateException(repositoryPath + " is not a directory");
}
if (contents.length != 0) {
throw new IllegalStateException(repositoryPath + " is not empty");
}
// Create object (init NrsConfiguration done)
final NrsRepository result = new NrsRepository(this);
// TODO try {... to cleanup repositoryPath on error
initSnapDevConfig(result, false);
// Check configurations / create IBS directories
result.initIbsConfiguration(true);
try {
result.initHashConfiguration();
try {
result.nrsFileJanitor.init();
try {
// Store configuration under repository persistence
storeConfiguration(configuration, repositoryPath);
// Create the root snapshot
final NrsSnapshot.BuilderRoot rootSnapshotBuilder = new NrsSnapshot.BuilderRoot();
rootSnapshotBuilder.vvr(result);
rootSnapshotBuilder.metadataDirectory(result.getSnapshotDir());
rootSnapshotBuilder.uuid(rootUuid);
rootSnapshotBuilder.create();
}
finally {
result.nrsFileJanitor.fini();
}
}
finally {
result.finiHashConfiguration();
}
}
finally {
result.finiIbsConfiguration();
}
return result;
}
}
/**
* Initializes the configuration needed for IBS operation.
*
* @param <code>true</code> if the IBP and IBPGen directories must be checked for emptiness
*/
private final void initIbsConfiguration(final boolean createIbs) {
final MetaConfiguration config = getConfiguration();
final ArrayList<File> configIbpPathList = IbsIbpPathConfigKey.getInstance().getTypedValue(config);
assert (configIbpPathList != null) && (!configIbpPathList.isEmpty());
// Fake Ibs (for unit test purpose)
final String fileName = configIbpPathList.size() == 1 ? configIbpPathList.get(0).getName() : null;
final boolean fake = fileName != null
&& configIbpPathList.get(0).getName().startsWith(Ibs.UNIT_TEST_IBS_HEADER);
if (!fake) {
// Check standard configuration
for (final File currDir : configIbpPathList) {
assert currDir.isDirectory();
if (createIbs && (currDir.list().length > 0)) {
throw new IllegalStateException("Ibp directory '" + currDir + "' is not empty");
}
}
final File configIbpGenPath = IbsIbpGenPathConfigKey.getInstance().getTypedValue(config);
assert (configIbpGenPath != null) && (configIbpGenPath.isDirectory());
if (createIbs && (configIbpGenPath.list().length > 0)) {
throw new IllegalStateException("IbpGen directory '" + configIbpGenPath + "' is not empty");
}
}
targetIbsConfigFile = null;
try {
if (fake) {
targetIbsConfigFile = new File(fileName);
}
else {
targetIbsConfigFile = File.createTempFile("vvr-ibsconfig", ".tmp");
try (FileOutputStream outputStream = new FileOutputStream(targetIbsConfigFile)) {
IbsConfigurationContext.getInstance().storeIbsConfig(config, outputStream);
}
}
// Create the IBS if necessary
if (createIbs) {
final Ibs ibs = IbsFactory.createIbs(targetIbsConfigFile);
ibs.close();
}
}
catch (final IOException ie) {
throw new IllegalStateException("Could not write IBS configuration file " + targetIbsConfigFile, ie);
}
}
private final void finiIbsConfiguration() {
if (targetIbsConfigFile != null) {
try {
LOGGER.debug("deleting temporary configuration file: " + targetIbsConfigFile);
Files.deleteIfExists(this.targetIbsConfigFile.toPath());
}
catch (final IOException e) {
LOGGER.warn("failed to delete temporary IBS configuration file " + targetIbsConfigFile);
}
targetIbsConfigFile = null;
}
}
/**
* Initializes the {@link #hashFactory} and {@link #hashAlgorithm} fields according to configuration.
*/
private final void initHashConfiguration() {
this.hashAlgorithm = HashAlgorithmConfigKey.getInstance().getTypedValue(getConfiguration());
assert hashAlgorithm != null;
this.hashLength = hashAlgorithm.getPersistedDigestLength();
}
private final void finiHashConfiguration() {
// No op
}
/**
* Initializes the directories in which {@link NrsSnapshot}s and {@link NrsDevice}s are saved.
*/
private static final void initSnapDevConfig(final NrsRepository repository, final boolean exists) {
final MetaConfiguration config = repository.getConfiguration();
final File baseDir = NrsStorageConfigKey.getInstance().getTypedValue(config);
final File relSnapshotDir = SnapshotFileDirectoryConfigKey.getInstance().getTypedValue(config);
assert relSnapshotDir != null;
repository.snapshotDir = initDirectory(baseDir, relSnapshotDir, exists);
final File relDeviceDir = DeviceFileDirectoryConfigKey.getInstance().getTypedValue(config);
assert relDeviceDir != null;
repository.deviceDir = initDirectory(baseDir, relDeviceDir, exists);
// Check operations supported by the directory and the file system
checkDirectory(baseDir);
}
private static final File initDirectory(final File baseDir, final File relPath, final boolean exists) {
final File result = new File(baseDir, relPath.getPath());
final boolean doesExist = result.exists();
if (exists ^ doesExist) {
throw new AssertionError(String.format("Storage directory '%s' does%s exist although it should%s", result,
(doesExist ? "" : " not"), (exists ? "" : " not")));
}
if (!exists && !result.mkdirs()) {
throw new IllegalStateException("Failed to create directory '" + result + "'");
}
return result;
}
private static final void checkDirectory(final File baseDir) {
final File temporary = new File(baseDir, ".tmpfile");
try {
try {
// Fail safe: delete file if any
io.eguan.utils.Files.deleteRecursive(temporary.toPath());
// Can create files? (FS mounted read-write?)
// Should not fail here, as the MetaConfiguration checks if the basedir is writable
temporary.createNewFile();
// Can set user defined attributes?
io.eguan.utils.Files.checkUserAttrSupported(temporary.toPath());
}
catch (final IOException e) {
throw new IllegalStateException("Invalid directory '" + baseDir.getAbsolutePath() + "'", e);
}
}
finally {
temporary.delete();
}
}
private final void finiSnapDevConfig() {
// nothing
}
private final void initNrsTree() throws NrsException {
// Initialize Nrs
nrsFileJanitor.setClientStartpoint(getMsgClientStartpoint(), nrsMsgEnhancer);
nrsFileJanitor.init();
// Reset previous objects
rootItem = null;
nrsToSnapshot = null;
snapshots = null;
parents = null;
devices = null;
// Load the NrsFile headers
final AtomicReference<NrsSnapshot> rootItemNew = new AtomicReference<NrsSnapshot>();
final BiMap<UuidT<NrsFile>, UUID> nrsToSnapshotNew = HashBiMap.create();
final ConcurrentHashMap<UUID, NrsSnapshot> snapshotsNew = new ConcurrentHashMap<>();
final ConcurrentHashMap<UuidT<NrsFile>, UuidT<NrsFile>> parentsNew = new ConcurrentHashMap<>();
final ConcurrentHashMap<UUID, NrsDevice> devicesNew = new ConcurrentHashMap<>();
try {
// Load NrsFile<>NrsSnapshot associations
Files.walkFileTree(snapshotDir.toPath(), new SimpleFileVisitor<Path>() {
@Override
public final FileVisitResult visitFile(final Path headerPath, final BasicFileAttributes attrs)
throws IOException {
final File snapshotFile = headerPath.toFile();
assert snapshotFile.isFile();
// Should be a property file, containing a snapshot or its user preferences (if any)
final Properties properties = new Properties();
try (FileInputStream fis = new FileInputStream(snapshotFile)) {
properties.load(fis);
}
final String uuidStr = properties.getProperty(NrsVvrItem.UUID_KEY);
final String nrsFileUuidStr = properties.getProperty(NrsSnapshot.NRSFILE_UUID_KEY);
assert (uuidStr == null && nrsFileUuidStr == null) || (uuidStr != null && nrsFileUuidStr != null);
if (uuidStr == null && nrsFileUuidStr == null) {
// Not a snapshot: ignored
return FileVisitResult.CONTINUE;
}
// New snapshot
final boolean deleted = Boolean.valueOf(properties.getProperty(NrsSnapshot.DELETED_KEY))
.booleanValue();
if (deleted) {
// Ignored
return FileVisitResult.CONTINUE;
}
final UUID uuid = UUID.fromString(uuidStr);
final UuidT<NrsFile> nrsFileUuid = SimpleIdentifierProvider.fromString(nrsFileUuidStr);
nrsToSnapshotNew.put(nrsFileUuid, uuid);
return FileVisitResult.CONTINUE;
}
});
// Visit images
nrsFileJanitor.visitImages(new SimpleFileVisitor<Path>() {
@Override
public final FileVisitResult visitFile(final Path headerPath, final BasicFileAttributes attrs)
throws IOException {
final File headerFile = headerPath.toFile();
assert headerFile.isFile();
final NrsFileHeader<NrsFile> header = nrsFileJanitor.loadNrsFileHeader(headerPath);
final UuidT<NrsFile> nrsFileId = header.getFileId();
final UUID snapshotId = nrsToSnapshotNew.get(nrsFileId);
// Stores hierarchy
parentsNew.put(nrsFileId, header.getParentId());
// Is root?
if (header.isRoot()) {
assert header.getParentId().equals(nrsFileId);
assert snapshotId != null;
if (rootItemNew.get() == null) {
final NrsSnapshot rootTmp = loadSnapshot(header, snapshotId, headerPath);
if (rootTmp == null) {
throw new NrsException("Persistence of root not found '" + snapshotId + "'");
}
assert rootTmp.isRoot();
// Check the UUID of the node is correct
if (!getNodeId().equals(rootTmp.getNodeId())) {
throw new NrsException("Persistence of wrong node found: '" + rootTmp.getNodeId()
+ "' instead of '" + getNodeId() + "'");
}
rootItemNew.set(rootTmp);
snapshotsNew.put(snapshotId, rootTmp);
}
else {
throw new NrsException("Duplicate root '" + rootItemNew.get().getUuid() + "' and '"
+ snapshotId + "'");
}
}
else if (nrsFileJanitor.isSealed(headerPath)) {
// Update file: set is read-only if was not done previously
if (headerFile.canWrite()) {
headerFile.setReadOnly();
}
if (snapshotId == null) {
// Should be an isolated item
LOGGER.debug("No snapshot found for '" + nrsFileId + "'");
}
else {
final NrsSnapshot snapshot = loadSnapshot(header, snapshotId, headerPath);
assert !snapshot.isRoot();
if (snapshot == null) {
// Should not happen
LOGGER.warn("Persistence of snapshot not found '" + snapshotId + "'");
}
else if (snapshot.isDeleted()) {
LOGGER.debug("Snapshot deleted '" + snapshotId + "'");
}
else {
snapshotsNew.put(snapshotId, snapshot);
}
}
}
else {
// TODO store item (current snapshot) ID in device persistence
final NrsDevice device = loadDevice(header, headerPath);
if (device == null) {
throw new NrsException("Persistence of device not found '" + header.getDeviceId() + "'");
}
devicesNew.put(device.getDeviceId(), device);
}
return FileVisitResult.CONTINUE;
}
});
}
catch (final NrsException e) {
throw e;
}
catch (final IOException e) {
throw new NrsException(e);
}
// Sanity check
rootItem = rootItemNew.get();
if (rootItem == null) {
throw new NrsException("root not found");
}
// New values loaded
nrsToSnapshot = nrsToSnapshotNew;
snapshots = snapshotsNew;
parents = parentsNew;
devices = devicesNew;
}
private final NrsSnapshot loadSnapshot(final NrsFileHeader<NrsFile> header, final UUID snapshotId,
final Path nrsFilePath) {
// Read snapshot persistence to get partial state
final Properties persistence = NrsVvrItem.loadPersistence(snapshotDir, snapshotId);
if (persistence == null) {
// Item, but not a snapshot
return null;
}
// Load NrsFile
final NrsFile nrsFile = loadNrsFile(nrsFilePath);
final NrsSnapshot.BuilderLoad builder = new NrsSnapshot.BuilderLoad();
builder.deleted(Boolean.valueOf(persistence.getProperty(NrsSnapshot.DELETED_KEY)).booleanValue())
.name(persistence.getProperty(NrsVvrItem.NAME_KEY))
.description(persistence.getProperty(NrsVvrItem.DESC_KEY))
.uuid(UUID.fromString(persistence.getProperty(NrsVvrItem.UUID_KEY)));
builder.header(header);
builder.sourceFile(nrsFile).vvr(this);
builder.metadataDirectory(getSnapshotDir());
return (NrsSnapshot) builder.build();
}
private final NrsDevice loadDevice(final NrsFileHeader<NrsFile> header, final Path nrsFilePath) {
// Read item persistence to get partial state
final Properties persistence = NrsVvrItem.loadPersistence(deviceDir, header.getDeviceId());
if (persistence == null) {
// Item, but not a device
return null;
}
// Load NrsFile
final NrsFile nrsFile = loadNrsFile(nrsFilePath);
final NrsDevice.Builder builder = new NrsDevice.Builder();
builder.name(persistence.getProperty(NrsVvrItem.NAME_KEY)).description(
persistence.getProperty(NrsVvrItem.DESC_KEY));
builder.header(header);
builder.sourceFile(nrsFile).vvr(this);
builder.metadataDirectory(getDeviceDir());
return (NrsDevice) builder.build();
}
private final NrsFile loadNrsFile(final Path nrsFilePath) {
try {
return nrsFileJanitor.loadNrsFile(nrsFilePath);
}
catch (final NrsException e) {
throw new IllegalStateException("Could not load persistent file '" + nrsFilePath + "' for " + getUuid(), e);
}
}
private final void finiNrsTree() {
snapshots = null;
parents = null;
devices = null;
rootItem = null;
nrsFileJanitor.fini();
}
/**
* Save the started flag according to the started argument.
*
* @param started
*/
private final void saveStartedState(final Boolean started) {
final Map<AbstractConfigKey, Object> newKeyValueMap = new HashMap<>();
newKeyValueMap.put(StartedConfigKey.getInstance(), started);
updateConfiguration(newKeyValueMap);
}
/**
* Store the given configuration in the VVR directory.
*
* @param configuration
* @param repositoryPath
*/
private final static void storeConfiguration(final MetaConfiguration configuration, final File repositoryPath) {
// Store configuration under repository persistence
final File config = new File(repositoryPath, NRS_CONFIG);
final File configPrev = new File(repositoryPath, NRS_CONFIG_PREV);
try {
configuration.storeConfiguration(config, configPrev, true);
}
catch (final IOException e) {
throw new IllegalStateException("Failed to write configuration in '" + config + "'", e);
}
}
/**
* Build a {@link NrsFileHeader} for a {@link NrsFile} to create.
*
* @param parentFileId
* {@link UuidT} of the parent of the {@link NrsFile} to create.
* @param size
* @param deviceUuid
* @param futureFileUuid
* @return the device file header
*/
final NrsFileHeader<NrsFile> doCreateFutureNrsFileHeader(@Nonnull final UuidT<NrsFile> parentFileId,
final long size, @Nonnull final UUID deviceUuid, @Nonnull final UuidT<NrsFile> futureFileUuid) {
final NrsDevice.Builder builder = newDeviceBuilder(parentFileId, null, null, size, deviceUuid);
builder.futureFileId(futureFileUuid);
return builder.createDefaultNrsFileHeader();
}
/**
* Create a device from the given snapshot.
*
* @param parent
* @param name
* @param description
* @param size
* @param uuid
* uuid of the device
* @param nrsFileHeader
* file header template
* @return the new Device
*/
final NrsDevice doCreateDevice(@Nonnull final NrsSnapshot parent, final String name, final String description,
final long size, @Nonnull final UUID uuid, @Nonnull final NrsFileHeader<NrsFile> nrsFileHeader) {
final NrsDevice.Builder builder = newDeviceBuilder(parent.getNrsFileId(), name, description, size, uuid);
return builder.create(nrsFileHeader);
}
final NrsDevice.Builder newDeviceBuilder(@Nonnull final UuidT<NrsFile> parentFileId, final String name,
final String description, final long size, @Nonnull final UUID uuid) {
final NrsDevice.Builder builder = new NrsDevice.Builder();
builder.size(size).name(name).description(description);
builder.vvr(this).parentFile(Objects.requireNonNull(parentFileId));
builder.metadataDirectory(getDeviceDir());
builder.nodeID(getNodeId());
builder.uuid(Objects.requireNonNull(uuid));
return builder;
}
@Override
public final UUID getId() {
return getUuid();
}
@Override
public final DtxResourceManagerContext start(final byte[] payload) throws XAException, NullPointerException {
try {
return VvrRemoteUtils.createDtxContext(getUuid(), payload);
}
catch (final InvalidProtocolBufferException e) {
LOGGER.error("Exception on start", e);
final XAException xaException = new XAException(XAException.XAER_INVAL);
xaException.initCause(xaException);
throw xaException;
}
}
@Override
public final Boolean prepare(final DtxResourceManagerContext context) throws XAException {
final VvrDtxRmContext vvrDtxRmContext = (VvrDtxRmContext) context;
// TODO handle errors
try {
handleMsg(vvrDtxRmContext.getOperation());
return Boolean.TRUE;
}
catch (final IllegalStateException e) {
// Most of the time, a pre-condition error
LOGGER.error("Exception on prepare", e);
final XAException xaException = new XAException(XAException.XA_RBROLLBACK);
xaException.initCause(e);
throw xaException;
}
}
@Override
public final void commit(final DtxResourceManagerContext context) throws XAException {
// TODO real commit
}
@Override
public final void rollback(final DtxResourceManagerContext context) throws XAException {
// TODO real rollback
}
@Override
public final void processPostSync() throws Exception {
// Update NrsFiles
// - Get status of remote nodes
final RemoteOperation.Builder builder = RemoteOperation.newBuilder();
final Collection<MsgServerRemoteStatus> status = sendMessage(builder, Type.NRS, OpCode.LIST, false, null);
if (status == null || status.isEmpty()) {
// Stand alone mode or alone
return;
}
// Get the version of NrsFile on each node
final Map<UUID, Map<UuidT<NrsFile>, NrsVersion>> remoteVersions = extractNrsVersions(status);
// Get local versions
final List<NrsVersion> localVersions = listNrsFiles();
for (int i = localVersions.size() - 1; i >= 0; i--) {
final NrsVersion nrsVersion = localVersions.get(i);
// Save some memory
localVersions.set(i, null);
processNrsFileCheck(nrsVersion, remoteVersions);
}
// If the transactions are handled correctly, there should not have remote NrsFile not found locally
for (final Map.Entry<UUID, Map<UuidT<NrsFile>, NrsVersion>> entry : remoteVersions.entrySet()) {
if (!entry.getValue().isEmpty()) {
// Must replay some transaction(s)
throw new NrsException("Extra NrsFile on node '" + entry.getKey() + "'");
}
}
}
/**
* Build a map that contains the version of the {@link NrsFile}s for each remote node.
*
* @param status
* @return {@link NrsFile} version for each node
* @throws InvalidProtocolBufferException
*/
private final Map<UUID, Map<UuidT<NrsFile>, NrsVersion>> extractNrsVersions(
final Collection<MsgServerRemoteStatus> status) throws InvalidProtocolBufferException {
final Map<UUID, Map<UuidT<NrsFile>, NrsVersion>> result = new HashMap<>();
for (final Iterator<MsgServerRemoteStatus> iterator = status.iterator(); iterator.hasNext();) {
final MsgServerRemoteStatus msgServerRemoteStatus = iterator.next();
final RemoteOperation reply = RemoteOperation.parseFrom(msgServerRemoteStatus.getReplyBytes());
assert reply.getOp() == OpCode.LIST;
assert reply.getType() == Type.NRS;
assert reply.getNrsVersionsCount() > 0;
final List<NrsVersion> versions = reply.getNrsVersionsList();
final Map<UuidT<NrsFile>, NrsVersion> versionsRes = new HashMap<>();
for (int i = versions.size() - 1; i >= 0; i--) {
final NrsVersion nrsVersion = versions.get(i);
final UuidT<NrsFile> uuidT = VvrRemoteUtils.fromUuidT(nrsVersion.getUuid());
versionsRes.put(uuidT, nrsVersion);
}
result.put(msgServerRemoteStatus.getNodeId(), versionsRes);
}
return result;
}
private final void processNrsFileCheck(final NrsVersion nrsVersion,
final Map<UUID, Map<UuidT<NrsFile>, NrsVersion>> remoteVersions) throws IllegalStateException, IOException,
MsgServerTimeoutException, InterruptedException {
final long version = nrsVersion.getVersion();
final UuidT<NrsFile> nrsFileUuid = VvrRemoteUtils.fromUuidT(nrsVersion.getUuid());
// Look for a more recent remote file
NrsVersion max = null;
UUID maxNodeUuid = null;
long versionMax = version;
final AtomicReference<Boolean> writable = new AtomicReference<>();
for (final Map.Entry<UUID, Map<UuidT<NrsFile>, NrsVersion>> entry : remoteVersions.entrySet()) {
final Map<UuidT<NrsFile>, NrsVersion> value = entry.getValue();
final NrsVersion remoteVersion = value.remove(nrsFileUuid); // remove to save memory
if (remoteVersion != null) {
if (!remoteVersion.getWritable()) {
writable.set(Boolean.FALSE);
}
final long versionFile = remoteVersion.getVersion();
if (versionFile > versionMax) {
max = remoteVersion;
versionMax = versionFile;
maxNodeUuid = entry.getKey();
}
}
}
if (max != null) {
updateNrsFile(nrsFileUuid, maxNodeUuid, max);
}
else {
// Check if the file should be read-only
if (writable.get() != null) {
assert writable.get() == Boolean.FALSE;
final NrsFile nrsFile = nrsFileJanitor.loadNrsFile(nrsFileUuid);
// File should not be in use
nrsFileJanitor.flushNrsFile(nrsFile);
nrsFileJanitor.setNrsFileNoWritable(nrsFile);
}
}
}
/**
* Update a file from the contents of a remote node.
*
* @param fileUuid
* UUID of the {@link NrsFile} to update
* @param nodeUuid
* remote node
* @param nrsVersion
* remote version information
* @return <code>true</code> if the update of the file was aborted
* @throws IOException
* @throws IllegalStateException
* @throws InterruptedException
* @throws MsgServerTimeoutException
*/
private final boolean updateNrsFile(final UuidT<NrsFile> fileUuid, final UUID nodeUuid, final NrsVersion nrsVersion)
throws IllegalStateException, IOException, MsgServerTimeoutException, InterruptedException {
final NrsFile nrsFile = nrsFileJanitor.loadNrsFile(fileUuid);
// Prefer update from the creator of the file, if it's online
final UUID nrsFileCreator = nrsFile.getDescriptor().getNodeId();
final MsgClientStartpoint clientStartpoint = getMsgClientStartpoint();
final UUID peer;
if (clientStartpoint.isPeerConnected(nrsFileCreator)) {
peer = nrsFileCreator;
}
else {
peer = nodeUuid;
}
final boolean aborted;
nrsFileJanitor.prepareNrsFileUpdate(nrsFile, nrsVersion);
try {
final RemoteOperation.Builder builder = nrsFile.getFileMapping(HashAlgorithm.TIGER);
// Set Uuid of the NrsFile
builder.setUuid(VvrRemoteUtils.newTUuid(nrsFile.getDescriptor().getFileId()));
sendMessage(builder, Type.NRS, OpCode.UPDATE, false, peer);
}
finally {
aborted = nrsFileJanitor.endNrsFileUpdate(nrsFile, nrsVersion);
}
return aborted;
}
@Override
public final DtxTaskInfo createTaskInfo(final byte[] payload) {
try {
final RemoteOperation operation = RemoteOperation.parseFrom(payload);
return createVvrTaskInfo(getUuid(), operation);
}
catch (final InvalidProtocolBufferException e) {
return null;
}
}
/**
* Constructs information for vvr task.
*
* @param resourceId
* The globally unique ID of the resourceId
* @param operation
* The complete operation used to construct the task info
*/
private final VvrTaskInfo createVvrTaskInfo(final UUID resourceId, final RemoteOperation operation) {
VvrTaskOperation op;
VvrTargetType targetType;
String targetId;
final String source = VvrRemoteUtils.fromUuid(operation.getSource()).toString();
switch (operation.getType()) {
case SNAPSHOT:
targetType = VvrTargetType.SNAPSHOT;
targetId = VvrRemoteUtils.fromUuid(operation.getSnapshot()).toString();
break;
case DEVICE:
targetType = VvrTargetType.DEVICE;
targetId = VvrRemoteUtils.fromUuid(operation.getUuid()).toString();
break;
case VVR:
targetType = VvrTargetType.VVR;
targetId = VvrRemoteUtils.fromUuid(operation.getUuid()).toString();
break;
default:
throw new AssertionError("type=" + operation.getType());
}
switch (operation.getOp()) {
case CREATE:
op = VvrTaskOperation.CREATE;
break;
case DELETE:
op = VvrTaskOperation.DELETE;
break;
case SET:
op = VvrTaskOperation.SET;
break;
case CLONE:
op = VvrTaskOperation.CLONE;
break;
default:
throw new AssertionError("type=" + operation.getOp());
}
return new VvrTaskInfo(source, op, targetType, targetId);
}
}