package org.dcache.auth;
import com.google.common.collect.Lists;
import com.google.common.collect.Range;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import diskCacheV111.util.CacheException;
import diskCacheV111.util.FileExistsCacheException;
import diskCacheV111.util.FileNotFoundCacheException;
import diskCacheV111.util.NotFileCacheException;
import diskCacheV111.util.PnfsHandler;
import diskCacheV111.util.PnfsId;
import diskCacheV111.vehicles.GenericStorageInfo;
import diskCacheV111.vehicles.PnfsAddCacheLocationMessage;
import diskCacheV111.vehicles.PnfsClearCacheLocationMessage;
import diskCacheV111.vehicles.PnfsCreateEntryMessage;
import diskCacheV111.vehicles.PnfsDeleteEntryMessage;
import diskCacheV111.vehicles.PnfsFlagMessage;
import diskCacheV111.vehicles.PnfsFlagMessage.FlagOperation;
import diskCacheV111.vehicles.PnfsGetCacheLocationsMessage;
import diskCacheV111.vehicles.PnfsGetParentMessage;
import diskCacheV111.vehicles.PnfsMapPathMessage;
import diskCacheV111.vehicles.PnfsMessage;
import diskCacheV111.vehicles.StorageInfo;
import dmg.cells.nucleus.CellEndpoint;
import dmg.cells.nucleus.CellMessage;
import dmg.cells.nucleus.CellMessageAnswerable;
import dmg.cells.nucleus.CellPath;
import dmg.cells.nucleus.SerializationException;
import org.dcache.cells.CellStub;
import org.dcache.namespace.FileAttribute;
import org.dcache.namespace.FileType;
import org.dcache.namespace.ListHandler;
import org.dcache.util.ChecksumType;
import org.dcache.util.list.DirectoryEntry;
import org.dcache.util.list.ListDirectoryHandler;
import org.dcache.vehicles.FileAttributes;
import org.dcache.vehicles.PnfsGetFileAttributes;
import org.dcache.vehicles.PnfsListDirectoryMessage;
import org.dcache.vehicles.PnfsRemoveChecksumMessage;
import org.dcache.vehicles.PnfsSetFileAttributes;
import static com.google.common.base.Preconditions.checkState;
import static diskCacheV111.util.CacheException.*;
import static org.dcache.auth.Subjects.ROOT;
import static org.dcache.namespace.FileAttribute.SIZE;
import static org.dcache.namespace.FileAttribute.TYPE;
import static org.dcache.namespace.FileType.DIR;
import static org.dcache.namespace.FileType.REGULAR;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;
import static org.mockito.BDDMockito.any;
import static org.mockito.BDDMockito.*;
/**
* Check that the RemoteNameSpaceProvider implementation sends messages of
* the correct type and behaves as expected when receiving reply messages.
*
* The namespace operations come in two types: those that require a reply
* message (and so, will wait for one or more messages) and those that are
* single-shot (and so, do not wait for a reply message).
*
* For the single-shot operations, this class only checks that the outgoing
* message is correctly formed.
*
* For those operations that wait for a reply messages, there are two groups
* of tests: those that simulate a successful outcome and those that simulate
* various failure modes.
*
* For the successful outcomes, this class check both the outgoing message is
* correctly formed and that the reply message is processed correctly. If the
* tests could lead to false-success (e.g., works-by-accident) then the
* successful outcome tests may be repeated with different arguments.
*
* For the failure mode tests, this class checks only that the reply message
* is process correctly. This is because the outgoing message is checked
* as part of the test(s) that check this operation when it is successful.
*
* Tests will send a single message and receive zero or more messages. The
* reply messages are derived from the outgoing message by serialising and
* deserialising (similar to intra-domain communication). This allows the
* "given" statements in a test to describe the changes to the out-going
* PnfsMessage rather that describing a completely new PnfsMessage.
*/
public class RemoteNameSpaceProviderTests
{
private static final Range<Integer> ALL_ENTRIES = Range.all();
private static final CellPath CELLPATH_PNFSMANAGER =
new CellPath("PnfsManager");
private static final PnfsId A_PNFSID =
new PnfsId("0123456789abcdef0123456789abcdef0123");
private static final PnfsId ANOTHER_PNFSID =
new PnfsId("fedcba9876543210fedcba9876543210fedc");
private static final PnfsId PNFSID_3 =
new PnfsId("00112233445566778899aabbccddeeff0011");
private static final PnfsId PNFSID_4 =
new PnfsId("ffeeddccbbaa99887766554433221100ffee");
private static final Modifier SUCCESSFUL = new Modifier() {
@Override
public void modify(PnfsMessage message)
{
message.setSucceeded();
}
};
RemoteNameSpaceProvider _namespace;
CellEndpoint _endpoint;
ListDirectoryHandler _listHandler;
@Before
public void setup() throws NoSuchMethodException
{
_endpoint = mock(CellEndpoint.class);
CellStub stub = new CellStub(_endpoint, CELLPATH_PNFSMANAGER);
PnfsHandler pnfs = new PnfsHandler(stub);
_listHandler = new ListDirectoryHandler(pnfs);
_namespace = new RemoteNameSpaceProvider(pnfs, _listHandler);
}
@Test
public void shouldSucceedWhenAddCacheLocationSuccessfully() throws Exception
{
givenSuccessfulResponse();
_namespace.addCacheLocation(ROOT, A_PNFSID, "pool-1");
PnfsAddCacheLocationMessage sent =
getSingleSendAndWaitMessage(PnfsAddCacheLocationMessage.class);
assertThat(sent.getReplyRequired(), is(true));
assertThat(sent.getSubject(), is(ROOT));
assertThat(sent.getPnfsId(), is(A_PNFSID));
assertThat(sent.getPoolName(), is("pool-1"));
}
@Test(expected=FileNotFoundCacheException.class)
public void shouldThrowExceptionWhenAddCacheLocationForMissingFile()
throws Exception
{
givenFailureResponse(FILE_NOT_FOUND);
_namespace.addCacheLocation(ROOT, A_PNFSID, "pool-1");
}
@Test
public void shouldSucceedWhenClearCacheLocationRemoveLast() throws Exception
{
givenSuccessfulResponse();
_namespace.clearCacheLocation(ROOT, A_PNFSID, "pool-1", true);
PnfsClearCacheLocationMessage sent =
getSingleSendAndWaitMessage(PnfsClearCacheLocationMessage.class);
assertThat(sent.getReplyRequired(), is(true));
assertThat(sent.getSubject(), is(ROOT));
assertThat(sent.getPnfsId(), is(A_PNFSID));
assertThat(sent.getPoolName(), is("pool-1"));
assertThat(sent.removeIfLast(), is(true));
}
@Test
public void shouldSucceedWhenClearCacheLocationWithoutRemoveLast()
throws Exception
{
givenSuccessfulResponse();
_namespace.clearCacheLocation(ROOT, A_PNFSID, "pool-1", false);
PnfsClearCacheLocationMessage sent =
getSingleSendAndWaitMessage(PnfsClearCacheLocationMessage.class);
assertThat(sent.getReplyRequired(), is(true));
assertThat(sent.getSubject(), is(ROOT));
assertThat(sent.getPnfsId(), is(A_PNFSID));
assertThat(sent.getPoolName(), is("pool-1"));
assertThat(sent.removeIfLast(), is(false));
}
@Test
public void shouldSucceedWhenCreatingFile() throws Exception
{
givenSuccessfulResponse();
_namespace.createFile(ROOT, "/path/to/file",
FileAttributes.of().uid(100).gid(200).mode(0644).build(),
EnumSet.noneOf(FileAttribute.class));
PnfsCreateEntryMessage sent =
getSingleSendAndWaitMessage(PnfsCreateEntryMessage.class);
assertThat(sent.getReplyRequired(), is(true));
assertThat(sent.getSubject(), is(ROOT));
assertThat(sent.getPnfsPath(), is("/path/to/file"));
assertThat(sent.getFileAttributes().getOwner(), is(100));
assertThat(sent.getFileAttributes().getGroup(), is(200));
assertThat(sent.getFileAttributes().getMode(), is(0644));
}
@Test(expected=FileExistsCacheException.class)
public void shouldFailWhenCreatingExistingFile() throws Exception
{
givenFailureResponse(FILE_EXISTS);
_namespace.createFile(ROOT, "/path/to/file",
FileAttributes.of().uid(100).gid(200).mode(0644).build(),
EnumSet.noneOf(FileAttribute.class));
}
@Test
public void shouldSucceedWhenCreatingDirectory() throws Exception
{
givenSuccessfulResponse();
_namespace.createDirectory(ROOT, "/path/to/dir",
FileAttributes.of().uid(100).gid(200).mode(0755).build());
PnfsCreateEntryMessage sent =
getSingleSendAndWaitMessage(PnfsCreateEntryMessage.class);
assertThat(sent.getReplyRequired(), is(true));
assertThat(sent.getSubject(), is(ROOT));
assertThat(sent.getPnfsPath(), is("/path/to/dir"));
assertThat(sent.getFileAttributes().getOwner(), is(100));
assertThat(sent.getFileAttributes().getGroup(), is(200));
assertThat(sent.getFileAttributes().getMode(), is(0755));
}
@Test
public void shouldSucceedWhenDeletingEntryByPnfsid() throws Exception
{
givenSuccessfulResponse();
_namespace.deleteEntry(ROOT, EnumSet.allOf(FileType.class), A_PNFSID,
EnumSet.noneOf(FileAttribute.class));
PnfsDeleteEntryMessage sent =
getSingleSendAndWaitMessage(PnfsDeleteEntryMessage.class);
assertThat(sent.getReplyRequired(), is(true));
assertThat(sent.getSubject(), is(ROOT));
assertThat(sent.getPnfsPath(), nullValue());
assertThat(sent.getPnfsId(), is(A_PNFSID));
}
@Test
public void shouldSucceedWhenDeletingEntryByPath() throws Exception
{
givenSuccessfulResponse();
_namespace.deleteEntry(ROOT, EnumSet.allOf(FileType.class), "/path/to/entry",
EnumSet.noneOf(FileAttribute.class));
PnfsDeleteEntryMessage sent =
getSingleSendAndWaitMessage(PnfsDeleteEntryMessage.class);
assertThat(sent.getReplyRequired(), is(true));
assertThat(sent.getSubject(), is(ROOT));
assertThat(sent.getPnfsPath(), is("/path/to/entry"));
assertThat(sent.getPnfsId(), nullValue());
}
@Test(expected=FileNotFoundCacheException.class)
public void shouldFailWhenDeletingNonexistingEntryByPnfsid() throws Exception
{
givenFailureResponse(FILE_NOT_FOUND);
_namespace.deleteEntry(ROOT, EnumSet.allOf(FileType.class), A_PNFSID,
EnumSet.noneOf(FileAttribute.class));
}
@Test(expected=FileNotFoundCacheException.class)
public void shouldFailWhenDeletingNonexistingEntryByPath() throws Exception
{
givenFailureResponse(FILE_NOT_FOUND);
_namespace.deleteEntry(ROOT, EnumSet.allOf(FileType.class), "/path/to/file",
EnumSet.noneOf(FileAttribute.class));
}
@Test
public void shouldSucceedWhenGetCacheLocationForFileWithNoLocations()
throws Exception
{
givenSuccessfulResponse();
List<String> locations = _namespace.getCacheLocation(ROOT, A_PNFSID);
PnfsGetCacheLocationsMessage sent =
getSingleSendAndWaitMessage(PnfsGetCacheLocationsMessage.class);
assertThat(sent.getReplyRequired(), is(true));
assertThat(sent.getSubject(), is(ROOT));
assertThat(sent.getPnfsId(), is(A_PNFSID));
assertThat(locations, hasSize(0)); // The empty() Matcher is broken.
}
@Test(expected=FileNotFoundCacheException.class)
public void shouldFailWhenGetCacheLocationForFileThatDoesNotExist()
throws Exception
{
givenFailureResponse(FILE_NOT_FOUND);
_namespace.getCacheLocation(ROOT, A_PNFSID);
}
@Test(expected=NotFileCacheException.class)
public void shouldFailWhenGetCacheLocationForADirectory()
throws Exception
{
givenFailureResponse(NOT_FILE);
_namespace.getCacheLocation(ROOT, A_PNFSID);
}
@Test
public void shouldSucceedWhenGetCacheLocationForFileWithOneLocation()
throws Exception
{
givenSuccessfulResponse(new Modifier<PnfsGetCacheLocationsMessage>(){
@Override
public void modify(PnfsGetCacheLocationsMessage reply)
{
reply.setCacheLocations(Collections.singletonList("pool-1"));
}
});
List<String> locations = _namespace.getCacheLocation(ROOT, A_PNFSID);
PnfsGetCacheLocationsMessage sent =
getSingleSendAndWaitMessage(PnfsGetCacheLocationsMessage.class);
assertThat(sent.getReplyRequired(), is(true));
assertThat(sent.getSubject(), is(ROOT));
assertThat(sent.getPnfsId(), is(A_PNFSID));
assertThat(locations, hasSize(1));
assertThat(locations, hasItem("pool-1"));
}
@Test
public void shouldSucceedWhenGetCacheLocationForFileWithTwoLocations()
throws Exception
{
givenSuccessfulResponse(new Modifier<PnfsGetCacheLocationsMessage>(){
@Override
public void modify(PnfsGetCacheLocationsMessage reply)
{
reply.setCacheLocations(Lists.newArrayList("pool-1", "pool-2"));
}
});
List<String> locations = _namespace.getCacheLocation(ROOT, A_PNFSID);
PnfsGetCacheLocationsMessage sent =
getSingleSendAndWaitMessage(PnfsGetCacheLocationsMessage.class);
assertThat(sent.getReplyRequired(), is(true));
assertThat(sent.getSubject(), is(ROOT));
assertThat(sent.getPnfsId(), is(A_PNFSID));
assertThat(locations, hasSize(2));
assertThat(locations, hasItem("pool-1"));
assertThat(locations, hasItem("pool-2"));
}
@Test
public void shouldSucceedWhenGetFileAttributesForExistingEntry()
throws Exception
{
givenSuccessfulResponse(new Modifier<PnfsGetFileAttributes>(){
@Override
public void modify(PnfsGetFileAttributes reply)
{
reply.setFileAttributes(attributes().size(1234L).type(REGULAR).build());
}
});
FileAttributes attributes =
_namespace.getFileAttributes(ROOT, A_PNFSID, EnumSet.of(TYPE, SIZE));
PnfsGetFileAttributes sent =
getSingleSendAndWaitMessage(PnfsGetFileAttributes.class);
assertThat(sent.getReplyRequired(), is(true));
assertThat(sent.getSubject(), is(ROOT));
assertThat(sent.getPnfsId(), is(A_PNFSID));
assertThat(sent.getRequestedAttributes(), hasSize(2));
assertThat(sent.getRequestedAttributes(), hasItem(TYPE));
assertThat(sent.getRequestedAttributes(), hasItem(SIZE));
assertThat(attributes.getSize(), is(1234L));
assertThat(attributes.getFileType(), is(REGULAR));
}
@Test(expected=FileNotFoundCacheException.class)
public void shouldFaileWhenGetFileAttributesForMissingEntry()
throws Exception
{
givenFailureResponse(FILE_NOT_FOUND);
_namespace.getFileAttributes(ROOT, A_PNFSID, EnumSet.of(TYPE, SIZE));
}
@Test
public void shouldSucceedForGetParentOfExistingEntry() throws Exception
{
givenSuccessfulResponse(new Modifier<PnfsGetParentMessage>(){
@Override
public void modify(PnfsGetParentMessage reply)
{
reply.setParent(ANOTHER_PNFSID);
}
});
PnfsId parent = _namespace.getParentOf(ROOT, A_PNFSID);
PnfsGetParentMessage sent =
getSingleSendAndWaitMessage(PnfsGetParentMessage.class);
assertThat(sent.getReplyRequired(), is(true));
assertThat(sent.getSubject(), is(ROOT));
assertThat(sent.getPnfsId(), is(A_PNFSID));
assertThat(parent, is(ANOTHER_PNFSID));
}
@Test(expected=FileNotFoundCacheException.class)
public void shouldFailForGetParentOfMissingEntry() throws Exception
{
givenFailureResponse(FILE_NOT_FOUND);
_namespace.getParentOf(ROOT, A_PNFSID);
}
@Test(timeout=60_000)
public void shouldSucceedForSuccessfulListWithSingleReplyMessage() throws Exception
{
givenListResponses(
Lists.newArrayList(
entry().name("file-1").id(A_PNFSID).size(1000).build(),
entry().name("file-2").id(ANOTHER_PNFSID).size(2000).build()));
ListCapture capture = new ListCapture();
_namespace.list(ROOT, "/path/to/dir", null, ALL_ENTRIES,
EnumSet.of(SIZE), capture);
Map<String,FileAttributes> results = capture.getNames();
assertThat(results.keySet(), hasSize(2));
assertThat(results.keySet(), hasItem("file-1"));
assertThat(results.keySet(), hasItem("file-2"));
FileAttributes file1Attr = results.get("file-1");
assertThat(file1Attr.getSize(), is(1000L));
assertThat(file1Attr.getPnfsId(), is(A_PNFSID));
FileAttributes file2Attr = results.get("file-2");
assertThat(file2Attr.getSize(), is(2000L));
assertThat(file2Attr.getPnfsId(), is(ANOTHER_PNFSID));
}
@Test(timeout=60_000)
public void shouldSucceedForSuccessfulListWithTwoReplyMessage() throws Exception
{
givenListResponses(
Lists.newArrayList(
entry().name("file-1").id(A_PNFSID).size(1000).build(),
entry().name("file-2").id(ANOTHER_PNFSID).size(2000).build()),
Lists.newArrayList(
entry().name("file-3").id(PNFSID_3).size(3000).build(),
entry().name("file-4").id(PNFSID_4).size(4000).build()));
ListCapture capture = new ListCapture();
_namespace.list(ROOT, "/path/to/dir", null, ALL_ENTRIES,
EnumSet.of(SIZE), capture);
Map<String,FileAttributes> results = capture.getNames();
assertThat(results.keySet(), hasSize(4));
assertThat(results.keySet(), hasItem("file-1"));
assertThat(results.keySet(), hasItem("file-2"));
assertThat(results.keySet(), hasItem("file-3"));
assertThat(results.keySet(), hasItem("file-4"));
FileAttributes file1Attr = results.get("file-1");
assertThat(file1Attr.getSize(), is(1000L));
assertThat(file1Attr.getPnfsId(), is(A_PNFSID));
FileAttributes file2Attr = results.get("file-2");
assertThat(file2Attr.getSize(), is(2000L));
assertThat(file2Attr.getPnfsId(), is(ANOTHER_PNFSID));
FileAttributes file3Attr = results.get("file-3");
assertThat(file3Attr.getSize(), is(3000L));
assertThat(file3Attr.getPnfsId(), is(PNFSID_3));
FileAttributes file4Attr = results.get("file-4");
assertThat(file4Attr.getSize(), is(4000L));
assertThat(file4Attr.getPnfsId(), is(PNFSID_4));
}
@Test
public void shouldSucceedForPathToPnfsidWithKnownPathAndResolvingSymlinks()
throws Exception
{
givenSuccessfulResponse(new Modifier<PnfsMapPathMessage>(){
@Override
public void modify(PnfsMapPathMessage message)
{
message.setPnfsId(A_PNFSID);
}
});
PnfsId id = _namespace.pathToPnfsid(ROOT, "/path/to/entry", true);
PnfsMapPathMessage sent =
getSingleSendAndWaitMessage(PnfsMapPathMessage.class);
assertThat(sent.getReplyRequired(), is(true));
assertThat(sent.getSubject(), is(ROOT));
assertThat(sent.getGlobalPath(), is("/path/to/entry"));
assertThat(sent.getPnfsId(), nullValue());
assertThat(sent.shouldResolve(), is(true));
assertThat(id, is(A_PNFSID));
}
@Test
public void shouldSucceedForPathToPnfsidWithKnownPathAndNotResolvingSymlinks()
throws Exception
{
givenSuccessfulResponse(new Modifier<PnfsMapPathMessage>(){
@Override
public void modify(PnfsMapPathMessage message)
{
message.setPnfsId(A_PNFSID);
}
});
PnfsId id = _namespace.pathToPnfsid(ROOT, "/path/to/entry", false);
PnfsMapPathMessage sent =
getSingleSendAndWaitMessage(PnfsMapPathMessage.class);
assertThat(sent.getReplyRequired(), is(true));
assertThat(sent.getSubject(), is(ROOT));
assertThat(sent.getGlobalPath(), is("/path/to/entry"));
assertThat(sent.getPnfsId(), nullValue());
assertThat(sent.shouldResolve(), is(false));
assertThat(id, is(A_PNFSID));
}
@Test(expected=FileNotFoundCacheException.class)
public void shouldFailForPathToPnfsidWithUnknownPath()
throws Exception
{
givenFailureResponse(FILE_NOT_FOUND);
_namespace.pathToPnfsid(ROOT, "/path/to/entry", false);
}
@Test
public void shouldSucceedForPnfsidToPathWithKnownPnfsId()
throws Exception
{
givenSuccessfulResponse(new Modifier<PnfsMapPathMessage>(){
@Override
public void modify(PnfsMapPathMessage message)
{
message.setGlobalPath("/path/to/entry");
}
});
String path = _namespace.pnfsidToPath(ROOT, A_PNFSID);
PnfsMapPathMessage sent =
getSingleSendAndWaitMessage(PnfsMapPathMessage.class);
assertThat(sent.getReplyRequired(), is(true));
assertThat(sent.getSubject(), is(ROOT));
assertThat(sent.getGlobalPath(), nullValue());
assertThat(sent.getPnfsId(), is(A_PNFSID));
assertThat(path, is("/path/to/entry"));
}
@Test(expected=FileNotFoundCacheException.class)
public void shouldFailForPnfsidToPathWithUnknownPnfsId()
throws Exception
{
givenFailureResponse(FILE_NOT_FOUND);
_namespace.pnfsidToPath(ROOT, A_PNFSID);
}
@Test
public void shouldSucceedForRemoveChecksum() throws Exception
{
_namespace.removeChecksum(ROOT, A_PNFSID, ChecksumType.ADLER32);
PnfsRemoveChecksumMessage sent =
getSingleSentMessage(PnfsRemoveChecksumMessage.class);
assertThat(sent.getSubject(), is(ROOT));
assertThat(sent.getPnfsId(), is(A_PNFSID));
assertThat(sent.getType(), is(ChecksumType.ADLER32));
}
@Test
public void shouldSucceedForRemoveFileAttribute() throws Exception
{
_namespace.removeFileAttribute(ROOT, A_PNFSID, "flag-name");
PnfsFlagMessage sent = getSingleSentMessage(PnfsFlagMessage.class);
assertThat(sent.getSubject(), is(ROOT));
assertThat(sent.getPnfsId(), is(A_PNFSID));
assertThat(sent.getFlagName(), is("flag-name"));
assertThat(sent.getOperation(), is(FlagOperation.REMOVE));
}
@Test
public void shouldSucceedForSetFileAttributesForKnownFile() throws Exception
{
givenSuccessfulResponse();
_namespace.setFileAttributes(ROOT, A_PNFSID,
attributes().size(1000).build(), EnumSet.noneOf(FileAttribute.class));
PnfsSetFileAttributes sent =
getSingleSendAndWaitMessage(PnfsSetFileAttributes.class);
assertThat(sent.getSubject(), is(ROOT));
assertThat(sent.getPnfsId(), is(A_PNFSID));
FileAttributes attributes = sent.getFileAttributes();
assertThat(attributes.isDefined(SIZE), is(true));
assertThat(attributes.getSize(), is(1000L));
assertThat(attributes.isDefined(TYPE), is(false));
}
@Test(expected=FileNotFoundCacheException.class)
public void shouldFailForSetFileAttributesForUnknownFile() throws Exception
{
givenFailureResponse(FILE_NOT_FOUND);
_namespace.setFileAttributes(ROOT, A_PNFSID,
attributes().size(1000).build(), EnumSet.noneOf(FileAttribute.class));
}
/*
* SUPPORT METHODS AND CLASSES
*/
/**
* Build response profile to a PnfsListDirectoryMessage. The
* method takes a vararg list of collections of DirectoryEntry
* answers. Each collection represents a Message sent by the
* namespace.
*
* This method simulates the arrival of these Messages by calling
* the ListDirectoryHandler's messageArrived method. The flag
* that indicates whether the message is the last in the train of
* messages is set automatically.
*
* Delivery of these messages is triggered when the implementation
* next calls sendMessage (a sendAndWait will trigger no activity).
*
* Note:
*
* 1. the unit-test support framework knows some aspects of the
* implementation; if the implementation changes then these
* methods need to up updated accordingly.
*
* 2. this method will use a separate thread to deliver the
* replies to the messageArrived so there is *no*
* happen-before relationship between sendMessage returning
* and the first call to messageArrived.
*/
private void givenListResponses(final Collection<DirectoryEntry>... answers)
{
try {
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) {
CellMessage request =
(CellMessage) invocation.getArguments() [0];
List<PnfsListDirectoryMessage> replies =
buildMessages(request, answers);
backgroundDeliverMessages(replies);
return null;
}}).when(_endpoint).sendMessage(any(CellMessage.class));
} catch (SerializationException e) {
throw new RuntimeException(e);
}
}
private List<PnfsListDirectoryMessage> buildMessages(final CellMessage request,
final Collection<DirectoryEntry>... replies)
{
List<PnfsListDirectoryMessage> messages =
Lists.newArrayListWithExpectedSize(replies.length);
for(int i = 0; i < replies.length; i++) {
Collection<DirectoryEntry> entries = replies [i];
boolean isLast = i == (replies.length - 1);
CellMessage reply = buildListReply(request, entries, isLast, replies.length);
messages.add((PnfsListDirectoryMessage) reply.getMessageObject());
}
return messages;
}
private static CellMessage buildListReply(CellMessage request,
final Collection<DirectoryEntry> entries, final boolean isLast, final int cnt)
{
return buildReply(request, new Modifier<PnfsListDirectoryMessage>(){
@Override
public void modify(PnfsListDirectoryMessage reply)
{
reply.setEntries(entries);
if (isLast) {
reply.setSucceeded(cnt);
}
}
}, SUCCESSFUL);
}
private void backgroundDeliverMessages(final Collection<PnfsListDirectoryMessage> messages)
{
new Thread() {
@Override
public void run()
{
for(PnfsListDirectoryMessage message : messages) {
_listHandler.messageArrived(message);
}
}
}.start();
}
/**
* Configure the mock CellEndpoint so that any call to sendAndWait will
* return the same object but with setSuccessful method called.
*/
private void givenSuccessfulResponse()
{
givenResponse(SUCCESSFUL);
}
/**
* Configure the mock CellEndpoint so that any call to sendAndWait will
* return the same object but with custom modification (represented by
* modifier) and with the setSuccessful method called.
*/
private void givenSuccessfulResponse(final Modifier modifier)
{
givenResponse(modifier, SUCCESSFUL);
}
/**
* Configure the mock CellEndpoint so that any call to sendAndWait will
* return the same object but with with setFailed called with the supplied
* error code.
*/
private void givenFailureResponse(final int errorcode)
{
givenResponse(new Modifier(){
@Override
public void modify(PnfsMessage message)
{
message.setFailed(errorcode, messageFor(errorcode));
}
});
}
private void givenResponse(final Modifier... modifiers)
{
willAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable
{
CellMessage request = (CellMessage) invocation.getArguments()[0];
CellMessageAnswerable callback = (CellMessageAnswerable) invocation.getArguments()[1];
callback.answerArrived(request, buildReply(request, modifiers));
return null;
}
}).given(_endpoint).sendMessage(any(CellMessage.class), any(CellMessageAnswerable.class), any(Executor.class), anyLong());
}
private static CellMessage buildReply(CellMessage request,
Modifier... modifiers)
{
CellMessage reply = request.encode().decode();
PnfsMessage payload = (PnfsMessage) reply.getMessageObject();
for(Modifier modifier : modifiers) {
modifier.modify(payload);
}
return reply;
}
/**
* Classes that implement this interface can modify a PnfsMessage (or
* subclass thereof) in any arbitrary fashion.
*/
private interface Modifier<T extends PnfsMessage>
{
void modify(T message);
}
private static String messageFor(int errorcode)
{
switch(errorcode) {
case CacheException.FILE_NOT_FOUND:
return "file not found";
case CacheException.FILE_EXISTS:
return "file already exists";
case CacheException.NOT_FILE:
return "not a file";
default:
throw new IllegalArgumentException("Unknown code " + errorcode);
}
}
/**
* Obtain the PnfsMessage that was sent via the CellEndpoint.sendAndWait
* method.
*/
private <T extends PnfsMessage> T getSingleSendAndWaitMessage(Class<T> type)
{
ArgumentCaptor<CellMessage> argument =
ArgumentCaptor.forClass(CellMessage.class);
verify(_endpoint).sendMessage(argument.capture(), any(CellMessageAnswerable.class), any(Executor.class), anyLong());
verify(_endpoint, never()).sendMessage(any(CellMessage.class));
Object payload = argument.getValue().getMessageObject();
return type.cast(payload);
}
/**
* Obtain the PnfsMessage that was sent via the CellEndpoint.sendMessage
* method.
*/
private <T extends PnfsMessage> T getSingleSentMessage(Class<T> type)
{
ArgumentCaptor<CellMessage> argument =
ArgumentCaptor.forClass(CellMessage.class);
verify(_endpoint).sendMessage(argument.capture());
return type.cast(argument.getValue().getMessageObject());
}
/**
* Class to capture all list results.
*/
private static class ListCapture implements ListHandler
{
Map<String, FileAttributes> _items = new HashMap<>();
@Override
public void addEntry(String name, FileAttributes attrs) throws CacheException
{
_items.put(name, attrs);
}
public Map<String, FileAttributes> getNames()
{
return Collections.unmodifiableMap(_items);
}
}
private FileAttributesBuilder attributes()
{
return new FileAttributesBuilder();
}
private DirectoryEntryBuilder entry()
{
return new DirectoryEntryBuilder();
}
/**
* A fluent class to build a FileAttribute.
*/
private static class FileAttributesBuilder
{
private final FileAttributes _attributes = new FileAttributes();
public FileAttributesBuilder size(long size)
{
_attributes.setSize(size);
return this;
}
public FileAttributesBuilder type(FileType type)
{
_attributes.setFileType(type);
return this;
}
public FileAttributesBuilder id(PnfsId id)
{
_attributes.setPnfsId(id);
return this;
}
public FileAttributes build()
{
return _attributes;
}
}
/**
* A fluent class to build a DirectoryEntry
*/
private static class DirectoryEntryBuilder
{
private final FileAttributes _attributes = new FileAttributes();
private String _name;
public DirectoryEntryBuilder name(String name)
{
_name = name;
return this;
}
public DirectoryEntryBuilder size(long size)
{
_attributes.setSize(size);
return this;
}
public DirectoryEntryBuilder type(FileType type)
{
_attributes.setFileType(type);
return this;
}
public DirectoryEntryBuilder id(PnfsId id)
{
_attributes.setPnfsId(id);
return this;
}
public DirectoryEntry build()
{
checkState(_name != null, "need to specify a name");
return new DirectoryEntry(_name, _attributes);
}
}
private StorageInfoBuilder storageInfo()
{
return new StorageInfoBuilder();
}
/**
* Builder for a StorageInfo with fluent interface
*/
private static class StorageInfoBuilder
{
private StorageInfo _info = new GenericStorageInfo();
public StorageInfoBuilder at(URI location)
{
_info.addLocation(location);
return this;
}
public StorageInfoBuilder at(String location)
{
try {
return at(new URI(location));
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
public StorageInfoBuilder cacheClass(String cacheClass)
{
_info.setCacheClass(cacheClass);
return this;
}
public StorageInfoBuilder hsm(String hsm)
{
_info.setHsm(hsm);
return this;
}
public StorageInfoBuilder isNew(boolean isNew)
{
_info.setIsNew(isNew);
return this;
}
public StorageInfoBuilder key(String key, String value)
{
_info.setKey(key, value);
return this;
}
public StorageInfoBuilder storageClass(String storageClass)
{
_info.setStorageClass(storageClass);
return this;
}
public StorageInfo build()
{
return _info;
}
}
}