package org.dcache.tests.repository; import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; import com.sleepycat.je.DatabaseException; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.stream.Stream; import diskCacheV111.util.CacheException; import diskCacheV111.util.DiskErrorCacheException; import diskCacheV111.util.DiskSpace; import diskCacheV111.util.FileInCacheException; import diskCacheV111.util.FileNotInCacheException; import diskCacheV111.util.LockedCacheException; import diskCacheV111.util.PnfsHandler; import diskCacheV111.util.PnfsId; import diskCacheV111.vehicles.GenericStorageInfo; import diskCacheV111.vehicles.PnfsAddCacheLocationMessage; import diskCacheV111.vehicles.PnfsClearCacheLocationMessage; import diskCacheV111.vehicles.StorageInfo; import dmg.cells.nucleus.CellAddressCore; import dmg.cells.nucleus.CellPath; import org.dcache.namespace.FileAttribute; import org.dcache.pool.classic.FairQueueAllocation; import org.dcache.pool.classic.SpaceSweeper2; import org.dcache.pool.repository.AbstractStateChangeListener; import org.dcache.pool.repository.Account; import org.dcache.pool.repository.CacheEntry; import org.dcache.pool.repository.ReplicaState; import org.dcache.pool.repository.FileStore; import org.dcache.pool.repository.FlatFileStore; import org.dcache.pool.repository.IllegalTransitionException; import org.dcache.pool.repository.ReplicaStore; import org.dcache.pool.repository.ReplicaDescriptor; import org.dcache.pool.repository.Repository.OpenFlags; import org.dcache.pool.repository.RepositoryChannel; import org.dcache.pool.repository.SpaceRecord; import org.dcache.pool.repository.StateChangeEvent; import org.dcache.pool.repository.StickyRecord; import org.dcache.pool.repository.meta.file.FileMetaDataRepository; import org.dcache.pool.repository.v5.ReplicaRepository; import org.dcache.tests.cells.CellEndpointHelper; import org.dcache.tests.cells.CellStubHelper; import org.dcache.tests.cells.Message; import org.dcache.vehicles.FileAttributes; import org.dcache.vehicles.PnfsSetFileAttributes; import static org.dcache.pool.repository.ReplicaState.*; import static org.junit.Assert.*; public class RepositorySubsystemTest extends AbstractStateChangeListener { private long size1 = 1024; private long size2 = 1024; private long size3 = 1024; private long size4 = 1024; private long size5 = 1024; private PnfsId id1; // Precious private PnfsId id2; // Cached private PnfsId id3; // Cached + Sticky private PnfsId id4; // Non existing entry private PnfsId id5; // Non existing entry private StorageInfo info1; private StorageInfo info2; private StorageInfo info3; private StorageInfo info4; private StorageInfo info5; private FileAttributes attributes1; private FileAttributes attributes2; private FileAttributes attributes3; private FileAttributes attributes4; private FileAttributes attributes5; private PnfsHandler pnfs; private Account account; private ReplicaRepository repository; private SpaceSweeper2 sweeper; private ReplicaStore replicaStore; private Path metaRoot; private Path dataRoot; private Path metaDir; private Path dataDir; private BlockingQueue<StateChangeEvent> stateChangeEvents = new LinkedBlockingQueue<>(); private CellEndpointHelper cell; private final CellAddressCore address = new CellAddressCore("pool", "test"); private void createFile(ReplicaDescriptor descriptor, long size) throws IOException { try (RepositoryChannel channel = descriptor.createChannel()) { channel.write(ByteBuffer.allocate((int) size)); } } private void createEntry(final FileAttributes attributes, final ReplicaState state, final List<StickyRecord> sticky) throws Throwable { new CellStubHelper(cell) { @Message(cell="pnfs") public Object message(PnfsSetFileAttributes msg) { msg.setSucceeded(); return msg; } @Override protected void run() throws CacheException, IOException, InterruptedException { ReplicaDescriptor handle = repository.createEntry(attributes, ReplicaState.FROM_CLIENT, state, sticky, EnumSet.noneOf(OpenFlags.class)); try { handle.allocate(attributes.getSize()); createFile(handle, attributes.getSize()); handle.commit(); } finally { handle.close(); } } }; } private FileAttributes createFileAttributes(PnfsId pnfsId, long size, StorageInfo info) { FileAttributes attributes = new FileAttributes(); attributes.setPnfsId(pnfsId); attributes.setStorageInfo(info); attributes.setSize(size); attributes.setAccessLatency(StorageInfo.DEFAULT_ACCESS_LATENCY); attributes.setRetentionPolicy(StorageInfo.DEFAULT_RETENTION_POLICY); return attributes; } private void deleteDirectory(Path dir) throws IOException { Path[] fileArray; try (Stream<Path> list = Files.list(dir)) { fileArray = list.toArray(Path[]::new); } for (Path file : fileArray) { if (Files.isDirectory(file)) { deleteDirectory(file); } else { Files.delete(file); } } if (dir.getParent() != null) { Files.delete(dir); } } private void initRepository() throws IOException, DatabaseException { FairQueueAllocation allocator = new FairQueueAllocation(); FileStore fileStore = new FlatFileStore(dataRoot); replicaStore = new FileMetaDataRepository(fileStore, metaRoot); account = new Account(); sweeper = new SpaceSweeper2(); repository = new ReplicaRepository(); allocator.setAccount(account); repository.setCellAddress(address); repository.setAllocator(allocator); repository.setPnfsHandler(pnfs); repository.setAccount(account); repository.setReplicaStore(replicaStore); repository.setExecutor(Executors.newSingleThreadScheduledExecutor()); repository.setSynchronousNotification(true); repository.addListener(this); repository.setSpaceSweeperPolicy(sweeper); repository.setMaxDiskSpace(new DiskSpace(5120)); repository.addFaultListener(event -> System.err.println(event.getMessage() + ": " + event.getCause())); } @Before public void setUp() throws Throwable { id1 = new PnfsId("000000000001"); id2 = new PnfsId("000000000002"); id3 = new PnfsId("000000000003"); id4 = new PnfsId("000000000004"); id5 = new PnfsId("000000000005"); info1 = new GenericStorageInfo(); info2 = new GenericStorageInfo(); info3 = new GenericStorageInfo(); info4 = new GenericStorageInfo(); info5 = new GenericStorageInfo(); attributes1 = createFileAttributes(id1, size1, info1); attributes2 = createFileAttributes(id2, size2, info2); attributes3 = createFileAttributes(id3, size3, info3); attributes4 = createFileAttributes(id4, 0, info4); attributes5 = createFileAttributes(id5, size5, info5); dataRoot = Jimfs.newFileSystem(Configuration.unix()).getPath("/"); metaRoot = Files.createTempDirectory("dtest"); dataDir = dataRoot.resolve("data"); metaDir = metaRoot.resolve("meta"); Files.createDirectory(dataDir); Files.createDirectory(metaDir); cell = new CellEndpointHelper(address); pnfs = new PnfsHandler(new CellPath("pnfs"), "pool"); pnfs.setCellEndpoint(cell); /* Create test data. Notice that the repository automatically * applies a short lived sticky record if we don't request * one. That is fine for normal operation, but for testing it * is not what we want. So we explicitly specify an expired * sticky record to avoid that the automatic sticky record is * created. */ initRepository(); repository.init(); repository.load(); createEntry(attributes1, ReplicaState.PRECIOUS, Arrays.asList(new StickyRecord("system", 0))); createEntry(attributes2, ReplicaState.CACHED, Arrays.asList(new StickyRecord("system", 0))); createEntry(attributes3, ReplicaState.CACHED, Arrays.asList(new StickyRecord("system", -1))); repository.shutdown(); replicaStore.close(); /* Create repository. */ initRepository(); sweeper.setAccount(account); sweeper.setRepository(repository); sweeper.start(); } @After public void tearDown() throws InterruptedException, IOException { sweeper.stop(); repository.shutdown(); replicaStore.close(); if (metaRoot != null) { deleteDirectory(metaRoot); } if (dataRoot != null) { deleteDirectory(dataRoot); } } @Override public void stateChanged(StateChangeEvent event) { stateChangeEvents.add(event); } public void expectStateChangeEvent(PnfsId id, ReplicaState oldState, ReplicaState newState) { StateChangeEvent event = stateChangeEvents.poll(); assertNotNull(event); assertEquals(id, event.getPnfsId()); assertEquals(oldState, event.getOldState()); assertEquals(newState, event.getNewState()); } public void assertNoStateChangeEvent() { if (stateChangeEvents.size() > 0) { fail("Unexpected state change event: " + stateChangeEvents.remove()); } } private void assertSpaceRecord(long total, long free, long precious, long removable) { SpaceRecord space = repository.getSpaceRecord(); assertEquals(total, space.getTotalSpace()); assertEquals(free, space.getFreeSpace()); assertEquals(precious, space.getPreciousSpace()); assertEquals(removable, space.getRemovableSpace()); } private void assertCacheEntry(CacheEntry entry, PnfsId id, long size, ReplicaState state) { assertEquals(id, entry.getPnfsId()); assertEquals(size, entry.getReplicaSize()); assertEquals(state, entry.getState()); } private void assertCanOpen(PnfsId id, long size, ReplicaState state) { try { ReplicaDescriptor handle = repository.openEntry(id, EnumSet.noneOf(OpenFlags.class)); try { try (RepositoryChannel channel = handle.createChannel()) { } assertCacheEntry(repository.getEntry(id), id, size, state); } finally { handle.close(); } } catch (FileNotInCacheException e) { fail("Expected entry " + id + " not found"); } catch (CacheException | InterruptedException | IOException e) { fail("Failed to open " + id + ": " + e.getMessage()); } } @Test(expected=IllegalStateException.class) public void testInitTwiceFails() throws IOException, CacheException, InterruptedException { repository.init(); repository.init(); } @Test(expected=IllegalStateException.class) public void testLoadTwiceFails() throws IOException, CacheException, InterruptedException { repository.init(); repository.load(); repository.load(); } @Test(expected=IllegalStateException.class) public void testLoadBeforeInitFail() throws IOException, CacheException, InterruptedException { repository.load(); } @Test(expected=IllegalStateException.class) public void testCreateEntryFailsBeforeLoad() throws Exception { repository.init(); List<StickyRecord> stickyRecords = Collections.emptyList(); repository.createEntry(FileAttributes.of().pnfsId(id1).storageInfo(info1).build(), FROM_CLIENT, PRECIOUS, stickyRecords, EnumSet.noneOf(OpenFlags.class)); } @Test(expected=IllegalStateException.class) public void testOpenEntryFailsBeforeInit() throws Exception { repository.openEntry(id1, EnumSet.noneOf(OpenFlags.class)); } @Test(expected=IllegalStateException.class) public void testGetEntryFailsBeforeInit() throws Exception { repository.getEntry(id1); } @Test(expected=IllegalStateException.class) public void testSetStickyFailsBeforeInit() throws Exception { repository.setSticky(id2, "system", 0, true); } @Test(expected=IllegalStateException.class) public void testGetStateFailsBeforeInit() throws Exception { repository.getState(id1); } @Test(expected=IllegalStateException.class) public void testSetStateFailsBeforeLoad() throws Exception { repository.init(); repository.setState(id1, CACHED); } @Test public void testGetSpaceRecord() throws IOException, CacheException, InterruptedException { assertSpaceRecord(0, 0, 0, 0); repository.init(); assertSpaceRecord(0, 0, 0, 0); repository.load(); assertSpaceRecord(5120, 2048, 1024, 1024); } @Test public void testOpenEntryBeforeLoad() throws IOException, CacheException, InterruptedException { repository.init(); stateChangeEvents.clear(); assertCanOpen(id1, size1, PRECIOUS); assertCanOpen(id2, size2, CACHED); assertCanOpen(id3, size3, CACHED); } @Test public void testOpenEntry() throws IOException, CacheException, InterruptedException { repository.init(); repository.load(); stateChangeEvents.clear(); assertCanOpen(id1, size1, PRECIOUS); assertCanOpen(id2, size2, CACHED); assertCanOpen(id3, size3, CACHED); } @Test(expected=FileNotInCacheException.class) public void testOpenEntryFileNotFound() throws Throwable { repository.init(); repository.load(); stateChangeEvents.clear(); new CellStubHelper(cell) { /* Attempting to open a non-existing entry triggers a * clear cache location message. */ @Message(required=true,step=1,cell="pnfs") public Object message(PnfsClearCacheLocationMessage msg) { msg.setSucceeded(); return msg; } @Override protected void run() throws CacheException, InterruptedException { repository.openEntry(id4, EnumSet.noneOf(OpenFlags.class)); } }; } @Test public void testCreateEntryFromStore() throws Throwable { repository.init(); repository.load(); stateChangeEvents.clear(); new CellStubHelper(cell) { @Message(required = true, step = 1, cell = "pnfs") public Object message(PnfsSetFileAttributes msg) { if( msg.getFileAttributes().isDefined(FileAttribute.SIZE) ) { return new CacheException(""); } msg.setSucceeded(); return msg; } @Override protected void run() throws CacheException, InterruptedException { List<StickyRecord> stickyRecords = Collections.emptyList(); ReplicaDescriptor handle = repository.createEntry(attributes5, FROM_STORE, CACHED, stickyRecords, EnumSet.noneOf(OpenFlags.class)); try { handle.allocate(attributes5.getSize()); createFile(handle, attributes5.getSize()); handle.commit(); }catch( IOException e) { throw new DiskErrorCacheException(e.getMessage()); } finally { handle.close(); } } }; } @Test public void testSetState() throws IOException, IllegalTransitionException, CacheException, InterruptedException { repository.init(); repository.load(); stateChangeEvents.clear(); assertCanOpen(id1, size1, PRECIOUS); repository.setState(id1, CACHED); expectStateChangeEvent(id1, PRECIOUS, CACHED); assertCanOpen(id1, size1, CACHED); repository.setState(id1, PRECIOUS); expectStateChangeEvent(id1, CACHED, PRECIOUS); assertCanOpen(id1, size1, PRECIOUS); } @Test(expected=IllegalTransitionException.class) public void testSetStateFileNotFound() throws IOException, IllegalTransitionException, CacheException, InterruptedException { repository.init(); repository.load(); stateChangeEvents.clear(); repository.setState(id4, CACHED); } @Test(expected=IllegalTransitionException.class) public void testSetStateToNew() throws IOException, IllegalTransitionException, CacheException, InterruptedException { repository.init(); repository.load(); stateChangeEvents.clear(); repository.setState(id1, NEW); } @Test(expected=IllegalTransitionException.class) public void testSetStateToDestroyed() throws IOException, IllegalTransitionException, CacheException, InterruptedException { repository.init(); repository.load(); stateChangeEvents.clear(); repository.setState(id1, DESTROYED); } @Test(expected=IllegalStateException.class) public void testClosedReadHandleClose() throws IOException, CacheException, InterruptedException { repository.init(); repository.load(); stateChangeEvents.clear(); ReplicaDescriptor handle = repository.openEntry(id1, EnumSet.noneOf(OpenFlags.class)); handle.close(); handle.close(); } @Test(expected=IllegalStateException.class) public void testClosedReadHandleGetFile() throws IOException, CacheException, InterruptedException { repository.init(); repository.load(); stateChangeEvents.clear(); ReplicaDescriptor handle = repository.openEntry(id1, EnumSet.noneOf(OpenFlags.class)); handle.close(); handle.getReplicaFile(); } @Test public void testClosedReadHandleGetFileAttributes() throws IOException, CacheException, InterruptedException { repository.init(); repository.load(); stateChangeEvents.clear(); ReplicaDescriptor handle = repository.openEntry(id1, EnumSet.noneOf(OpenFlags.class)); handle.close(); FileAttributes fileAttributes = handle.getFileAttributes(); assertEquals(id1, fileAttributes.getPnfsId()); assertEquals(size1, fileAttributes.getSize()); assertEquals(info1, fileAttributes.getStorageInfo()); } @Test public void testSetSize() throws IOException, CacheException, InterruptedException { repository.init(); repository.load(); stateChangeEvents.clear(); repository.setMaxDiskSpace(new DiskSpace(3072)); assertSpaceRecord(3072, 0, 1024, 1024); } @Test(expected=IllegalArgumentException.class) public void testSetSizeNegative() throws IOException, CacheException, InterruptedException { repository.init(); repository.load(); stateChangeEvents.clear(); repository.setMaxDiskSpace(new DiskSpace(-1)); } @Test public void testRemoval() throws Throwable { repository.init(); repository.load(); stateChangeEvents.clear(); new CellStubHelper(cell) { @Message(required=true,step=1,cell="pnfs") public Object message(PnfsClearCacheLocationMessage msg) { msg.setSucceeded(); return msg; } @Override protected void run() throws IllegalTransitionException, CacheException, InterruptedException { repository.setState(id1, REMOVED); expectStateChangeEvent(id1, PRECIOUS, REMOVED); expectStateChangeEvent(id1, REMOVED, DESTROYED); assertStep("Cache location cleared", 1); assertEquals(repository.getState(id1), NEW); } }; } @Test public void testRemoveWhileReading() throws Throwable { repository.init(); repository.load(); stateChangeEvents.clear(); new CellStubHelper(cell) { @Message(required=true,step=0,cell="pnfs") public Object message(PnfsClearCacheLocationMessage msg) { msg.setSucceeded(); return msg; } @Override protected void run() throws CacheException, InterruptedException, IllegalTransitionException { ReplicaDescriptor handle1 = repository.openEntry(id1, EnumSet.noneOf(OpenFlags.class)); repository.setState(id1, REMOVED); expectStateChangeEvent(id1, PRECIOUS, REMOVED); assertNoStateChangeEvent(); assertStep("Cache location cleared", 1); handle1.close(); expectStateChangeEvent(id1, REMOVED, DESTROYED); } }; } @Test(expected= LockedCacheException.class) public void testRemoveOpenAgain() throws Throwable { repository.init(); repository.load(); stateChangeEvents.clear(); new CellStubHelper(cell) { @Message(required=true,step=1,cell="pnfs") public Object message(PnfsClearCacheLocationMessage msg) { msg.setSucceeded(); return msg; } @Override protected void run() throws CacheException, InterruptedException, IllegalTransitionException { ReplicaDescriptor h1 = repository.openEntry(id1, EnumSet.noneOf(OpenFlags.class)); repository.setState(id1, REMOVED); expectStateChangeEvent(id1, PRECIOUS, REMOVED); assertStep("Cache location should have been cleared", 1); ReplicaDescriptor h2 = repository.openEntry(id1, EnumSet.noneOf(OpenFlags.class)); } }; } @Test(expected=FileInCacheException.class) public void testCreateEntryFileExists() throws Throwable { repository.init(); repository.load(); stateChangeEvents.clear(); new CellStubHelper(cell) { /* Attempting to create an existing entry triggers a * add cache location message. */ @Message(required=true,step=1,cell="pnfs") public Object message(PnfsAddCacheLocationMessage msg) { msg.setSucceeded(); return msg; } @Override protected void run() throws CacheException, InterruptedException { List<StickyRecord> stickyRecords = Collections.emptyList(); repository.createEntry(attributes1, FROM_CLIENT, PRECIOUS, stickyRecords, EnumSet.noneOf(OpenFlags.class)); } }; } /* Helper method for creating a fourth entry in the repository. */ private void createEntry4(final long overallocation, final boolean failSetAttributes, final boolean cancel, final ReplicaState transferState, final ReplicaState finalState) throws Throwable { new CellStubHelper(cell) { boolean setAttr; boolean addCache; @Message(required=false,step=1,cell="pnfs") public Object whenFileIsGarbageCollected(PnfsClearCacheLocationMessage msg) { msg.setSucceeded(); return msg; } @Message(required=false,step=3,cell="pnfs") public Object whenDescriptorIsCommitted(PnfsSetFileAttributes msg) { assertEquals(size4, msg.getFileAttributes().getSize()); if (failSetAttributes) { msg.setFailed(1, null); } else { msg.setSucceeded(); } setAttr = true; return msg; } @Message(required=false,step=5,cell="pnfs") public Object whenDescriptorFails(PnfsAddCacheLocationMessage msg) { assertTrue(failSetAttributes || cancel); msg.setSucceeded(); addCache = true; return msg; } @Message(required=false,step=5,cell="pnfs") public Object whenDescriptorFails(PnfsSetFileAttributes msg) { assertTrue(failSetAttributes || cancel); msg.setSucceeded(); return msg; } @Override protected void run() throws FileInCacheException, CacheException, InterruptedException, IOException { List<StickyRecord> stickyRecords = Collections.emptyList(); ReplicaDescriptor handle = repository.createEntry(attributes4, transferState, finalState, stickyRecords, EnumSet.noneOf(OpenFlags.class)); try { handle.allocate(size4 + overallocation); assertStep("No clear after this point", 2); createFile(handle, size4); if (!cancel) { handle.commit(); } } finally { assertStep("Only failure registration after this point", 4); handle.close(); } assertEquals("SetFileAttributes must be sent unless we don't try to commit", !cancel, setAttr); assertEquals("AddCacheLocation must be sent if not committed", cancel || failSetAttributes, addCache); } }; } @Test public void testCreateEntry() throws Throwable { repository.init(); repository.load(); stateChangeEvents.clear(); createEntry4(0, false, false, FROM_CLIENT, PRECIOUS); assertCanOpen(id4, size4, PRECIOUS); assertSpaceRecord(5120, 1024, 2048, 1024); } @Test(expected=CacheException.class) public void testCreateEntrySetAttributesFailed() throws Throwable { repository.init(); repository.load(); stateChangeEvents.clear(); try { createEntry4(100, true, false, FROM_CLIENT, PRECIOUS); } finally { assertCacheEntry(repository.getEntry(id4), id4, size4, BROKEN); assertSpaceRecord(5120, 1024, 1024, 1024); } // TODO: Check notification } @Test public void testCreateEntryUnderallocation() throws Throwable { repository.init(); repository.load(); stateChangeEvents.clear(); createEntry4(-100, false, false, FROM_CLIENT, PRECIOUS); assertCanOpen(id4, size4, PRECIOUS); assertSpaceRecord(5120, 1024, 2048, 1024); // TODO: Check notification } @Test public void testCreateEntryOverallocation() throws Throwable { repository.init(); repository.load(); stateChangeEvents.clear(); createEntry4(100, false, false, FROM_CLIENT, PRECIOUS); assertCanOpen(id4, size4, PRECIOUS); assertSpaceRecord(5120, 1024, 2048, 1024); // TODO: Check notification } @Test public void testCreateEntryOverallocationFail() throws Throwable { repository.init(); repository.load(); stateChangeEvents.clear(); createEntry4(100, false, true, FROM_CLIENT, PRECIOUS); assertCacheEntry(repository.getEntry(id4), id4, size4, BROKEN); assertSpaceRecord(5120, 1024, 1024, 1024); // TODO: Check notification } @Test(expected=IllegalArgumentException.class) public void testCreateEntryNegativeAllocation() throws Throwable { repository.init(); repository.load(); stateChangeEvents.clear(); ReplicaDescriptor handle = repository.createEntry(attributes4, FROM_CLIENT, PRECIOUS, null, EnumSet.noneOf(OpenFlags.class)); handle.allocate(-1); } @Test public void testCreateEntryOutOfSpace() throws Throwable { repository.init(); repository.load(); stateChangeEvents.clear(); repository.setMaxDiskSpace(new DiskSpace(3072)); createEntry4(0, false, false, FROM_CLIENT, PRECIOUS); assertCanOpen(id4, size4, PRECIOUS); assertSpaceRecord(3072, 0, 2048, 0); // TODO: Check notification } // See http://rt.dcache.org/Ticket/Display.html?id=7337 @Ignore("Time-critical test; may fail under extreme load") @Test public void testStickyExpiration() throws IOException, CacheException, InterruptedException { repository.init(); repository.load(); stateChangeEvents.clear(); long now = System.currentTimeMillis(); assertFalse(repository.getEntry(id2).isSticky()); repository.setSticky(id2, "system", now + 500, true); assertTrue(repository.getEntry(id2).isSticky()); Thread.currentThread().sleep(600 + ReplicaRepository.EXPIRATION_CLOCKSHIFT_EXTRA_TIME); assertFalse(repository.getEntry(id2).isSticky()); } @Test public void testStickyClear() throws IOException, CacheException, InterruptedException { repository.init(); repository.load(); stateChangeEvents.clear(); long now = System.currentTimeMillis(); assertFalse(repository.getEntry(id2).isSticky()); repository.setSticky(id2, "system", now + 500, true); assertTrue(repository.getEntry(id2).isSticky()); repository.setSticky(id2, "system", 0, true); assertFalse(repository.getEntry(id2).isSticky()); } @Test public void testDoubleAccountingOnCache() throws IOException, CacheException, InterruptedException, IllegalTransitionException { repository.init(); repository.load(); stateChangeEvents.clear(); assertSpaceRecord(5120, 2048, 1024, 1024); repository.setState(id1, CACHED); assertSpaceRecord(5120, 2048, 0, 2048); repository.setState(id1, CACHED); assertSpaceRecord(5120, 2048, 0, 2048); } @Test public void testDoubleAccountingOnPrecious() throws IOException, CacheException, InterruptedException, IllegalTransitionException { repository.init(); repository.load(); stateChangeEvents.clear(); assertSpaceRecord(5120, 2048, 1024, 1024); repository.setState(id2, PRECIOUS); assertSpaceRecord(5120, 2048, 2048, 0); repository.setState(id2, PRECIOUS); assertSpaceRecord(5120, 2048, 2048, 0); } }