Skip to content

Commit

Permalink
Merge pull request #103 from usnistgov/feature/cache-expiry-check
Browse files Browse the repository at this point in the history
Add CacheExpiryCheck for automated removal of expired cached data
  • Loading branch information
RayPlante authored Mar 12, 2024
2 parents 2496782 + de80e31 commit 2ca70f5
Show file tree
Hide file tree
Showing 12 changed files with 362 additions and 33 deletions.
82 changes: 82 additions & 0 deletions src/main/java/gov/nist/oar/distrib/cachemgr/CacheExpiryCheck.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package gov.nist.oar.distrib.cachemgr;

import gov.nist.oar.distrib.StorageVolumeException;

import java.time.Instant;

/**
* 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 StorageInventoryDB inventoryDB;

public CacheExpiryCheck(StorageInventoryDB inventoryDB) {
this.inventoryDB = inventoryDB;
}

/**
* Checks if a cache object is expired and removes it from the cache if it is.
* The method uses the {@code expires} metadata field to determine the expiration status.
* The expiration time is calculated based on the {@code LastModified} time plus the {@code expires} duration.
* If the current time is past the calculated expiry time, the object is removed from the inventory database.
*
* @param co The cache object to check for expiration.
* @throws IntegrityException If the object is found to be corrupted during the check.
* @throws StorageVolumeException If there's an error accessing the storage volume during the check.
* @throws CacheManagementException If there's an error managing the cache, including removing the expired object.
*/
@Override
public void check(CacheObject co) throws IntegrityException, StorageVolumeException, CacheManagementException {
if (co == null || inventoryDB == null) {
throw new IllegalArgumentException("CacheObject or StorageInventoryDB is null");
}

if (co.hasMetadatum("expires")) {
long expiresDuration = co.getMetadatumLong("expires", -1L);
if (expiresDuration == -1L) {
throw new IntegrityException("Invalid 'expires' metadata value");
}

long lastModified = co.getLastModified();
if (lastModified == -1L) {
throw new IntegrityException("CacheObject 'lastModified' time not available");
}

long expiryTime = lastModified + expiresDuration;
long currentTime = Instant.now().toEpochMilli();

// Check if the object is expired
if (expiryTime < currentTime) {
try {
boolean removed = removeObject(co);
if (!removed) {
throw new CacheManagementException("Failed to remove expired object: " + co.name);
}
} catch (InventoryException e) {
throw new CacheManagementException("Error removing expired object from inventory database: " + co.name, e);
}
}
}
}

/**
* Attempts to remove a cache object from both its physical volume and the inventory database.
* Synchronization ensures thread-safe removal operations.
*
* @param co The cache object to be removed.
* @return true if the object was successfully removed from its volume, false otherwise.
* @throws StorageVolumeException if an error occurs accessing the storage volume.
* @throws InventoryException if an error occurs updating the inventory database.
*/
protected boolean removeObject(CacheObject co) throws StorageVolumeException, InventoryException {
synchronized (inventoryDB) {
boolean out = co.volume.remove(co.name);
inventoryDB.removeObject(co.volname, co.name);
return out;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,9 @@ protected void cacheFromBagUsingStore(String bagfile, Collection<String> need, C
md.put("ediid", resmd.get("ediid"));
md.put("cachePrefs", prefs);

// a hook for handling the expiration logic
updateMetadata(md, prefs);

// find space in the cache, and copy the data file into it
try {
resv = into.reserveSpace(ze.getSize(), prefs);
Expand Down Expand Up @@ -687,6 +690,18 @@ protected void cacheFromBagUsingStore(String bagfile, Collection<String> need, C
fixMissingChecksums(into, fix, manifest);
}

/**
* Method intended for customization of metadata before caching. This method can be overridden
* by subclasses to implement specific metadata customization logic as needed.
*
* @param md The metadata JSONObject to be customized.
* @param prefs flags for data roles
*/
protected void updateMetadata(JSONObject md, int prefs) {
// Default implementation does nothing.
// Subclasses can override this to implement specific logic.
}

/**
* helper method to generate an ID for the object to be cached
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,44 +20,23 @@
import gov.nist.oar.distrib.ObjectNotFoundException;
import gov.nist.oar.distrib.ResourceNotFoundException;
import gov.nist.oar.distrib.BagStorage;
import gov.nist.oar.distrib.Checksum;
import gov.nist.oar.distrib.cachemgr.Restorer;
import gov.nist.oar.distrib.cachemgr.Reservation;
import gov.nist.oar.distrib.cachemgr.IntegrityMonitor;
import gov.nist.oar.distrib.cachemgr.BasicCache;
import gov.nist.oar.distrib.cachemgr.Cache;
import gov.nist.oar.distrib.cachemgr.CacheObject;
import gov.nist.oar.distrib.cachemgr.CacheObjectCheck;
import gov.nist.oar.distrib.cachemgr.StorageInventoryDB;
import gov.nist.oar.distrib.cachemgr.CacheManagementException;
import gov.nist.oar.distrib.cachemgr.RestorationException;
import gov.nist.oar.distrib.cachemgr.InventoryException;

import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.Map;
import java.util.HashSet;
import java.util.HashMap;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipEntry;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.FileNotFoundException;
import java.text.ParseException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.json.JSONObject;
import org.json.JSONException;

import org.apache.commons.io.FilenameUtils;

/**
* A {@link gov.nist.oar.distrib.cachemgr.Restorer} for restoring "restricted public" datasets from the
Expand All @@ -82,6 +61,7 @@
*/
public class RestrictedDatasetRestorer extends PDRDatasetRestorer {
BagStorage restrictedLtstore = null;
long expiryTime = 1209600000L; // 2 weeks in milliseconds

/**
* create the restorer
Expand Down Expand Up @@ -125,6 +105,25 @@ public RestrictedDatasetRestorer(BagStorage publicLtstore, BagStorage restricted
this.restrictedLtstore = restrictedLtstore;
}

/**
* Retrieves the expiry time for data.
* <p>
* This value represents the duration in milliseconds after which the data is considered expired.
*
* @return the expiry time in milliseconds.
*/
public long getExpiryTime() {
return expiryTime;
}

/**
* Sets the expiry time for restricted/public access content.
*
* @param expiryTime the expiry time in milliseconds to set.
*/
public void setExpiryTime(long expiryTime) {
this.expiryTime = expiryTime;
}

/**
* return true if an object does <i>not</i> exist in the long term storage system. Returning
Expand Down Expand Up @@ -285,4 +284,19 @@ protected void cacheFromBag(String bagfile, Collection<String> need, Collection<
target, ltstore);
}
}

/**
* Updates the metadata for files marked as restricted, by adding an expiration time.
*
* @param md The metadata JSONObject to be customized.
* @param prefs flags for data roles
*/
@Override
protected void updateMetadata(JSONObject md, int prefs) {
if ((prefs & ROLE_RESTRICTED_DATA) != 0) {
// Calculate the expiration time as current time + expiryTime
long expires = System.currentTimeMillis() + expiryTime;
md.put("expires", expires);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
package gov.nist.oar.distrib.web;

import gov.nist.oar.distrib.cachemgr.BasicCache;
import gov.nist.oar.distrib.cachemgr.CacheExpiryCheck;
import gov.nist.oar.distrib.cachemgr.ConfigurableCache;
import gov.nist.oar.distrib.cachemgr.CacheManagementException;
import gov.nist.oar.distrib.cachemgr.CacheVolume;
import gov.nist.oar.distrib.cachemgr.StorageInventoryDB;
import gov.nist.oar.distrib.cachemgr.storage.AWSS3CacheVolume;
import gov.nist.oar.distrib.cachemgr.storage.FilesystemCacheVolume;
import gov.nist.oar.distrib.cachemgr.VolumeStatus;
Expand Down Expand Up @@ -484,7 +486,11 @@ public PDRCacheManager createCacheManager(BasicCache cache, PDRDatasetRestorer r
throw new ConfigurationException(rootdir+": Not an existing directory");

List<CacheObjectCheck> checks = new ArrayList<CacheObjectCheck>();
// Get the StorageInventoryDB from the cache and add the CacheExpiryCheck to the list of checks
StorageInventoryDB inventoryDB = cache.getInventoryDB();
checks.add(new CacheExpiryCheck(inventoryDB));
checks.add(new ChecksumCheck(false, true));

PDRCacheManager out = new PDRCacheManager(cache, rstr, checks, getCheckDutyCycle()*1000,
getCheckGracePeriod()*1000, -1, rootdir, logger);
if (getMonitorAutoStart()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,8 @@
import gov.nist.oar.distrib.service.DefaultPreservationBagService;
import gov.nist.oar.distrib.service.FileDownloadService;
import gov.nist.oar.distrib.service.PreservationBagService;
import gov.nist.oar.distrib.service.RPACachingService;
import gov.nist.oar.distrib.service.rpa.DefaultRPARequestHandlerService;
import gov.nist.oar.distrib.service.rpa.JKSKeyRetriever;
import gov.nist.oar.distrib.service.rpa.KeyRetriever;
import gov.nist.oar.distrib.service.rpa.RPARequestHandlerService;
import gov.nist.oar.distrib.storage.AWSS3LongTermStorage;
import gov.nist.oar.distrib.storage.FilesystemLongTermStorage;
import gov.nist.oar.distrib.cachemgr.CacheManagementException;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
Expand All @@ -37,7 +31,6 @@
import java.util.List;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import javax.activation.MimetypesFileTypeMap;

import org.springframework.boot.SpringApplication;
Expand All @@ -46,7 +39,6 @@
import org.springframework.context.annotation.Bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
Expand Down Expand Up @@ -205,6 +197,7 @@ else if (mode.equals("local"))
}
}


/**
* the client for access S3 storage
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,9 @@ else if (mode.equals("local"))
public RestrictedDatasetRestorer createRPDatasetRestorer()
throws ConfigurationException, IOException, CacheManagementException
{
return new RestrictedDatasetRestorer(pubstore, getRPBagStorage(), getHeadBagCacheManager());
RestrictedDatasetRestorer rdr = new RestrictedDatasetRestorer(pubstore, getRPBagStorage(), getHeadBagCacheManager());
rdr.setExpiryTime(rpacfg.getExpiresAfterMillis());
return rdr;
// return new PDRDatasetRestorer(getRPBagStorage(), getHeadBagCacheManager());
}

Expand Down
11 changes: 10 additions & 1 deletion src/main/java/gov/nist/oar/distrib/web/RPAConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ public class RPAConfiguration {
String bagStore = null;
@JsonProperty("bagstore-mode")
String mode = null;

@JsonProperty("expiresAfterMillis")
long expiresAfterMillis = 0L;

public long getHeadbagCacheSize() {
return hbCacheSize;
Expand All @@ -76,6 +77,14 @@ public void setBagstoreMode(String mode) {
this.mode = mode;
}

public long getExpiresAfterMillis() {
return expiresAfterMillis;
}

public void setExpiresAfterMillis(long expiresAfterMillis) {
this.expiresAfterMillis = expiresAfterMillis;
}

public SalesforceJwt getSalesforceJwt() {
return salesforceJwt;
}
Expand Down
Loading

0 comments on commit 2ca70f5

Please sign in to comment.