package org.dcache.pool.repository;
import com.google.common.jimfs.Configuration;
import com.google.common.jimfs.Jimfs;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.FileSystem;
import java.nio.ByteBuffer;
import java.util.EnumSet;
import diskCacheV111.util.AccessLatency;
import diskCacheV111.util.CacheException;
import diskCacheV111.util.FileNotFoundCacheException;
import diskCacheV111.util.PnfsHandler;
import diskCacheV111.util.PnfsId;
import diskCacheV111.util.RetentionPolicy;
import diskCacheV111.vehicles.OSMStorageInfo;
import diskCacheV111.vehicles.StorageInfo;
import org.dcache.cells.CellStub;
import org.dcache.pool.classic.ALRPReplicaStatePolicy;
import org.dcache.pool.movers.IoMode;
import org.dcache.tests.repository.ReplicaStoreHelper;
import org.dcache.vehicles.FileAttributes;
import static diskCacheV111.util.AccessLatency.NEARLINE;
import static diskCacheV111.util.RetentionPolicy.CUSTODIAL;
import static org.dcache.pool.repository.ReplicaState.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;
import static org.mockito.BDDMockito.*;
public class ConsistentReplicaStoreTest
{
private final static String POOL = "test-pool";
private final static PnfsId PNFSID =
new PnfsId("000000000000000000000000000000000001");
private final static StorageInfo STORAGE_INFO = new OSMStorageInfo("h1", "rawd");
static {
STORAGE_INFO.addLocation(URI.create("osm://mystore/?store=mystore&group=mygroup&bdid=1"));
}
private PnfsHandler _pnfs;
private FlatFileStore _fileStore;
private ReplicaStore _replicaStore;
private ConsistentReplicaStore _consistentReplicaStore;
private CellStub _broadcast;
@Before
public void setup() throws Exception
{
_pnfs = mock(PnfsHandler.class);
_broadcast = mock(CellStub.class);
FileSystem fileSystem = Jimfs.newFileSystem(Configuration.unix());
_fileStore = new FlatFileStore(fileSystem.getPath("/"));
_replicaStore = new ReplicaStoreHelper(_fileStore);
_consistentReplicaStore =
new ConsistentReplicaStore(_pnfs, null, _replicaStore,
new ALRPReplicaStatePolicy());
_consistentReplicaStore.setPoolName(POOL);
}
private void givenStoreHasFileOfSize(PnfsId pnfsId, long size)
throws IOException
{
_fileStore.create(pnfsId);
try (RepositoryChannel channel = _fileStore.openDataChannel(pnfsId, IoMode.WRITE)) {
ByteBuffer buf = ByteBuffer.allocate((int)size);
buf.limit(buf.capacity());
channel.write(buf);
}
}
private void givenReplicaStoreHas(ReplicaState state, FileAttributes attributes)
throws DuplicateEntryException, CacheException
{
ReplicaRecord entry = _replicaStore.create(attributes.getPnfsId(), EnumSet.noneOf(Repository.OpenFlags.class));
entry.update(r -> {
r.setState(state);
r.setFileAttributes(attributes);
return null;
});
}
@Test
public void shouldMarkNonTransientReplicasWithWrongSizeAsBroken()
throws Exception
{
// Given a replica with one file size
givenStoreHasFileOfSize(PNFSID, 17);
// and given the meta data indicates a different size, but is
// otherwise in a valid non-transient state
FileAttributes info = FileAttributes.of().pnfsId(PNFSID).size(20).
storageInfo(STORAGE_INFO).accessLatency(NEARLINE).
retentionPolicy(CUSTODIAL).build();
givenReplicaStoreHas(CACHED, info);
// and given that the name space provides the same storage info
given(_pnfs.getFileAttributes(eq(PNFSID), Mockito.anySet())).willReturn(info);
// when reading the meta data record
ReplicaRecord record = _consistentReplicaStore.get(PNFSID);
// then the replica is marked broken
assertThat(record.getState(), is(ReplicaState.BROKEN));
// and the storage info size is unaltered
assertThat(record.getFileAttributes().getSize(), is(20L));
// and no attributes are updated in the name space
verify(_pnfs, never())
.setFileAttributes(eq(PNFSID), Mockito.any(FileAttributes.class));
verify(_pnfs, never())
.clearCacheLocation(PNFSID);
}
@Test
public void shouldRegisterFileSizeAndLocationOnIncompleteUpload()
throws Exception
{
// Given a replica
givenStoreHasFileOfSize(PNFSID, 17);
// and given the replica is an incomplete upload
FileAttributes info = FileAttributes.of().pnfsId(PNFSID).storageInfo(STORAGE_INFO).
accessLatency(NEARLINE).retentionPolicy(CUSTODIAL).build();
givenReplicaStoreHas(FROM_CLIENT, info);
// and given the name space entry exists without any size
given(_pnfs.getFileAttributes(eq(PNFSID), Mockito.anySet())).willReturn(info);
// when reading the meta data record
ReplicaRecord record = _consistentReplicaStore.get(PNFSID);
// then the correct size is set in file attributes info
assertThat(_replicaStore.get(PNFSID).getFileAttributes().getSize(), is(17L));
// and the correct file size and location is registered in
// the name space
ArgumentCaptor<FileAttributes> captor =
ArgumentCaptor.forClass(FileAttributes.class);
verify(_pnfs).setFileAttributes(eq(PNFSID), captor.capture());
assertThat(captor.getValue().getSize(), is(17L));
assertThat(captor.getValue().getLocations(), hasItem(POOL));
// and the record is no longer in an upload state
assertThat(record.getState(), is(not(ReplicaState.FROM_CLIENT)));
}
@Test
public void shouldDeleteIncompleteReplicasWithNoNameSpaceEntry()
throws Exception
{
// Given a replica
givenStoreHasFileOfSize(PNFSID, 17);
// and given the replica is in an incomplete upload
FileAttributes info = FileAttributes.of().pnfsId(PNFSID).size(0).
storageInfo(STORAGE_INFO).accessLatency(NEARLINE).
retentionPolicy(CUSTODIAL).build();
givenReplicaStoreHas(FROM_CLIENT, info);
// and given the name space entry does not exist
given(_pnfs.getFileAttributes(eq(PNFSID), Mockito.anySet()))
.willThrow(new FileNotFoundCacheException("No such file"));
// when reading the meta data record
ReplicaRecord record = _consistentReplicaStore.get(PNFSID);
// then nothing is returned
assertThat(record, is(nullValue()));
// and the replica is deleted
assertThat(_replicaStore.get(PNFSID), is(nullValue()));
assertThat(_fileStore.contains(PNFSID), is(false));
// and the name space entry is not touched
verify(_pnfs, never())
.setFileAttributes(eq(PNFSID), Mockito.any(FileAttributes.class));
}
@Test
public void shouldDeleteBrokenReplicasWithNoNameSpaceEntry()
throws Exception
{
// Given a replica with no meta data
givenStoreHasFileOfSize(PNFSID, 17);
// and given the name space entry does not exist
given(_pnfs.getFileAttributes(eq(PNFSID), Mockito.anySet()))
.willThrow(new FileNotFoundCacheException("No such file"));
// when reading the meta data record
ReplicaRecord record = _consistentReplicaStore.get(PNFSID);
// then recovery is attempted
verify(_pnfs).getFileAttributes(eq(PNFSID), Mockito.anySet());
// but nothing is returned
assertThat(record, is(nullValue()));
// and the replica is deleted
assertThat(_replicaStore.get(PNFSID), is(nullValue()));
assertThat(_fileStore.contains(PNFSID), is(false));
// and the name space entry is not touched
verify(_pnfs, never())
.setFileAttributes(eq(PNFSID), Mockito.any(FileAttributes.class));
}
@Test
public void shouldRecoverBrokenEntries()
throws Exception
{
// Given a replica
givenStoreHasFileOfSize(PNFSID, 17);
// and given the replica is marked broken
FileAttributes info = FileAttributes.of().pnfsId(PNFSID).storageInfo(STORAGE_INFO).
accessLatency(NEARLINE).retentionPolicy(CUSTODIAL).build();
givenReplicaStoreHas(BROKEN, info);
// and given the name space entry exists without any size
given(_pnfs.getFileAttributes(eq(PNFSID), Mockito.anySet())).willReturn(info);
// when reading the meta data record
ReplicaRecord record = _consistentReplicaStore.get(PNFSID);
// then the correct size is set in storage info
assertThat(_replicaStore.get(PNFSID).getFileAttributes().getSize(),
is(17L));
// and the correct file size and location is registered in
// the name space
ArgumentCaptor<FileAttributes> captor =
ArgumentCaptor.forClass(FileAttributes.class);
verify(_pnfs).setFileAttributes(eq(PNFSID), captor.capture());
assertThat(captor.getValue().getSize(), is(17L));
assertThat(captor.getValue().getLocations(), hasItem(POOL));
// and the record is no longer in a broken state
assertThat(record.getState(), is(ReplicaState.CACHED));
}
@Test
public void shouldDeleteIncompleteRestores()
throws Exception
{
// Given a replica with one file size
givenStoreHasFileOfSize(PNFSID, 17);
// and given the replica meta data indicates the file was
// being restored from tape and is supposed to have a
// different file size,
FileAttributes info = FileAttributes.of().pnfsId(PNFSID).size(20).
storageInfo(STORAGE_INFO).accessLatency(NEARLINE).
retentionPolicy(CUSTODIAL).build();
givenReplicaStoreHas(FROM_STORE, info);
// when reading the meta data record
ReplicaRecord record = _consistentReplicaStore.get(PNFSID);
// then nothing is returned
assertThat(record, is(nullValue()));
// and the replica is deleted
assertThat(_replicaStore.get(PNFSID), is(nullValue()));
assertThat(_fileStore.contains(PNFSID), is(false));
// and the location is cleared
verify(_pnfs).clearCacheLocation(PNFSID);
// and the name space entry is not touched
verify(_pnfs, never())
.setFileAttributes(eq(PNFSID), Mockito.any(FileAttributes.class));
}
@Test
public void shouldMarkMissingEntriesWithWrongSizeAsBroken()
throws Exception
{
// Given a replica with missing meta data
givenStoreHasFileOfSize(PNFSID, 17);
// and given the name space entry reports a different size
FileAttributes info = FileAttributes.of().pnfsId(PNFSID).size(20).
storageInfo(STORAGE_INFO).accessLatency(NEARLINE).
retentionPolicy(CUSTODIAL).build();
given(_pnfs.getFileAttributes(eq(PNFSID), Mockito.anySet())).willReturn(info);
// when reading the meta data record
ReplicaRecord record = _consistentReplicaStore.get(PNFSID);
// then the replica is marked broken
assertThat(record.getState(), is(ReplicaState.BROKEN));
// and the name space entry is untouched
verify(_pnfs, never())
.setFileAttributes(eq(PNFSID), Mockito.any(FileAttributes.class));
}
@Test
public void shouldNotTalkToNameSpaceForIntactEntries()
throws Exception
{
// Given a replica
givenStoreHasFileOfSize(PNFSID, 17);
// and given the replica has intact meta data
givenReplicaStoreHas(CACHED, FileAttributes.of().pnfsId(PNFSID).size(17).
storageInfo(STORAGE_INFO).accessLatency(NEARLINE).
retentionPolicy(CUSTODIAL).build());
// when reading the meta data record
ReplicaRecord record = _consistentReplicaStore.get(PNFSID);
// then there is no interaction with the name space
verifyNoMoreInteractions(_pnfs);
}
@Test
public void shouldSilentlyIgnoreRemoveOfNonExistingReplicas() throws CacheException
{
_consistentReplicaStore.remove(PNFSID);
}
}