From 9a4f79bde47dfa374119f74eba25723ab0e8bf5b Mon Sep 17 00:00:00 2001 From: elmiomar Date: Wed, 31 Jan 2024 11:41:25 -0500 Subject: [PATCH] create CacheExpiryCheck for automated removal of expired cached data --- .../distrib/cachemgr/CacheExpiryCheck.java | 62 +++++++++++ .../cachemgr/CacheExpiryCheckTest.java | 102 ++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 src/main/java/gov/nist/oar/distrib/cachemgr/CacheExpiryCheck.java create mode 100644 src/test/java/gov/nist/oar/distrib/cachemgr/CacheExpiryCheckTest.java diff --git a/src/main/java/gov/nist/oar/distrib/cachemgr/CacheExpiryCheck.java b/src/main/java/gov/nist/oar/distrib/cachemgr/CacheExpiryCheck.java new file mode 100644 index 00000000..64a1bd56 --- /dev/null +++ b/src/main/java/gov/nist/oar/distrib/cachemgr/CacheExpiryCheck.java @@ -0,0 +1,62 @@ +package gov.nist.oar.distrib.cachemgr; + +import gov.nist.oar.distrib.StorageVolumeException; + +/** + * Implements a cache object check to identify and remove objects that have been in the cache + * longer than a specified duration, specifically two weeks. This check helps in + * managing cache integrity by ensuring that stale or outdated data are removed + * from the cache. + */ +public class CacheExpiryCheck implements CacheObjectCheck { + + private static final long TWO_WEEKS_MILLIS = 14 * 24 * 60 * 60 * 1000; // 14 days in milliseconds + private StorageInventoryDB inventoryDB; + + public CacheExpiryCheck(StorageInventoryDB inventoryDB) { + this.inventoryDB = inventoryDB; + } + + /** + * Checks whether a cache object has expired based on its last modified time and removes it if expired. + * An object is considered expired if it has been in the cache for more than two weeks. + * + * @param co The CacheObject to be checked for expiry. + * @throws IntegrityException if the cache object's last modified time is unknown. + * @throws StorageVolumeException if there is an issue removing the expired object from the cache volume. + */ + @Override + public void check(CacheObject co) throws IntegrityException, StorageVolumeException { + long currentTime = System.currentTimeMillis(); + long objectLastModifiedTime = co.getLastModified(); + + // Throw an exception if the last modified time is unknown + if (objectLastModifiedTime == -1) { + throw new IntegrityException("Last modified time of cache object is unknown: " + co.name); + } + + // If the cache object is expired, remove it from the cache + if ((currentTime - objectLastModifiedTime) > TWO_WEEKS_MILLIS) { + removeExpiredObject(co); + } + } + + /** + * Removes an expired object from the cache. + * + * @param co The expired CacheObject to be removed. + * @throws StorageVolumeException if there is an issue removing the object from the cache volume. + */ + protected void removeExpiredObject(CacheObject co) throws StorageVolumeException { + CacheVolume volume = co.volume; + if (volume != null && volume.remove(co.name)) { + try { + inventoryDB.removeObject(co.volname, co.name); + } catch (InventoryException e) { + throw new StorageVolumeException("Failed to remove object from inventory database: " + co.name, e); + } + } else { + throw new StorageVolumeException("Failed to remove expired object from cache volume: " + co.name); + } + } +} diff --git a/src/test/java/gov/nist/oar/distrib/cachemgr/CacheExpiryCheckTest.java b/src/test/java/gov/nist/oar/distrib/cachemgr/CacheExpiryCheckTest.java new file mode 100644 index 00000000..dad89ab7 --- /dev/null +++ b/src/test/java/gov/nist/oar/distrib/cachemgr/CacheExpiryCheckTest.java @@ -0,0 +1,102 @@ +package gov.nist.oar.distrib.cachemgr; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class CacheExpiryCheckTest { + + @Mock + private StorageInventoryDB mockInventoryDB; + @Mock + private CacheVolume mockVolume; + @Mock + private CacheObject cacheObject; + private CacheExpiryCheck expiryCheck; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + expiryCheck = new CacheExpiryCheck(mockInventoryDB); + } + + /** + * Test to verify that {@link CacheExpiryCheck} correctly identifies and processes an expired cache object. + * An object is considered expired if its last modified time is more than two weeks ago. + * This test checks that the expired object is appropriately removed from both the cache volume and the inventory + * database. + * + * @throws Exception to handle any exceptions thrown during the test execution + */ + @Test + public void testExpiredObject() throws Exception { + // Setup mock + cacheObject.name = "testObject"; + cacheObject.volname = "testVolume"; + cacheObject.volume = mockVolume; + long lastModified = System.currentTimeMillis() - (15 * 24 * 60 * 60 * 1000); // 15 days in milliseconds + when(cacheObject.getLastModified()).thenReturn(lastModified); + + + when(mockVolume.remove("testObject")).thenReturn(true); + + // Perform the check + expiryCheck.check(cacheObject); + + // Verify the interactions + verify(mockVolume).remove("testObject"); + verify(mockInventoryDB).removeObject(anyString(), anyString()); + } + + + /** + * Test to ensure that {@link CacheExpiryCheck} does not flag a cache object as expired if it has been + * modified within the last two weeks. This test checks that no removal action is taken for a non-expired object. + * + * @throws Exception to handle any exceptions thrown during the test execution + */ + @Test + public void testNonExpiredObject() throws Exception { + // Setup mock + cacheObject.name = "nonExpiredObject"; + cacheObject.volname = "testVolume"; + cacheObject.volume = mockVolume; + + long lastModified = System.currentTimeMillis() - (5 * 24 * 60 * 60 * 1000); // 5 days in milliseconds + when(cacheObject.getLastModified()).thenReturn(lastModified); + + // Perform the check + expiryCheck.check(cacheObject); + + // Verify that the remove method was not called as the object is not expired + verify(mockVolume, never()).remove("nonExpiredObject"); + verify(mockInventoryDB, never()).removeObject("testVolume", "nonExpiredObject"); + } + + /** + * Test to verify that {@link CacheExpiryCheck} throws an {@link IntegrityException} when the last modified time + * of a cache object is unknown (indicated by a value of -1). This situation should be flagged as an error + * as the expiry status of the object cannot be determined. + * + * @throws Exception to handle any exceptions thrown during the test execution + */ + @Test(expected = IntegrityException.class) + public void testUnknownLastModifiedTime() throws Exception { + // Setup mock + cacheObject.name = "unknownLastModifiedObject"; + cacheObject.volname = "testVolume"; + cacheObject.volume = mockVolume; + long lastModified = -1; // Unknown last modified time + when(cacheObject.getLastModified()).thenReturn(lastModified); + + // Perform the check, expecting an IntegrityException + expiryCheck.check(cacheObject); + } + +}