package de.is24.infrastructure.gridfs.http.repos; import com.mongodb.AggregationOutput; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; import de.is24.infrastructure.gridfs.http.domain.RepoEntry; import de.is24.infrastructure.gridfs.http.metadata.YumEntriesRepository; import de.is24.infrastructure.gridfs.http.rpm.version.CachingVersionDBObjectComparator; import de.is24.infrastructure.gridfs.http.storage.FileStorageService; import de.is24.util.monitoring.spring.TimeMeasurement; import org.bson.types.ObjectId; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.jmx.export.annotation.ManagedResource; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import static de.is24.infrastructure.gridfs.http.domain.RepoType.SCHEDULED; import static de.is24.infrastructure.gridfs.http.domain.RepoType.STATIC; import static de.is24.infrastructure.gridfs.http.mongo.MongoAggregationBuilder.field; import static de.is24.infrastructure.gridfs.http.mongo.MongoAggregationBuilder.groupBy; import static de.is24.infrastructure.gridfs.http.mongo.MongoAggregationBuilder.match; import static java.util.Arrays.asList; import static java.util.Collections.sort; import static org.springframework.data.mongodb.core.query.Criteria.where; @ManagedResource @Service @TimeMeasurement public class RepoCleaner { public static final String REPO_KEY = "repo"; private static final Logger LOG = LoggerFactory.getLogger(RepoCleaner.class); public static final String VERSION_KEY = "version"; public static final String TIME_KEY = "time"; public static final String TIME_BUILD_KEY = "build"; public static final String FILE_KEY = "file"; public static final String FILENAME_KEY = "filename"; public static final String ITEMS_KEY = "items"; private final MongoTemplate mongo; private final YumEntriesRepository entriesRepository; private final FileStorageService fileStorageService; private final RepoService repoService; private final CachingVersionDBObjectComparator comparatorVersion = new CachingVersionDBObjectComparator(); /* for CGLIB */ protected RepoCleaner() { mongo = null; entriesRepository = null; fileStorageService = null; repoService = null; } @Autowired public RepoCleaner(MongoTemplate mongo, YumEntriesRepository entriesRepository, FileStorageService fileStorageService, RepoService repoService) { this.mongo = mongo; this.entriesRepository = entriesRepository; this.fileStorageService = fileStorageService; this.repoService = repoService; } @ManagedOperation public boolean cleanByMaxNum(String reponame, int maxValue) { if (maxValue > 0) { LOG.info("Cleaning up repository {} and keep {} rpms at maximum ...", reponame, maxValue); AggregationOutput aggregation = aggregateAllRpmNamesInRepoThatHaveMoreThanMaxKeepEntries(reponame, maxValue); boolean filesDeleted = false; for (DBObject aggregatedArtifact : aggregation.results()) { for (DBObject itemToDelete : oldestItemsToDeleteByVersion(maxValue, getItemsFromAggregate(aggregatedArtifact))) { ObjectId fileId = (ObjectId) itemToDelete.get(FILE_KEY); if (fileId != null) { entriesRepository.delete(fileId); final String path = reponame + "/" + itemToDelete.get(FILENAME_KEY); fileStorageService.markForDeletionByPath(path); filesDeleted = true; LOG.info("Mark file {} as deleted during cleanup.", path); } } } LOG.info("Clean up for repository {} finished.", reponame); if (filesDeleted) { repoService.createOrUpdate(reponame); return true; } } return false; } @ManagedOperation public boolean cleanByMaxDays(String reponame, int maxValue) { if (maxValue > 0) { LOG.info("Cleaning up repository {} and keep rpms newer than {} days ...", reponame, maxValue); AggregationOutput aggregation = aggregateAllRpmNamesInRepoByTime(reponame); boolean filesDeleted = false; for (DBObject aggregatedArtifact : aggregation.results()) { for (DBObject itemToDelete : oldestItemsToDeleteByDays(maxValue, getItemsFromAggregate(aggregatedArtifact))) { ObjectId fileId = (ObjectId) itemToDelete.get(FILE_KEY); if (fileId != null) { entriesRepository.delete(fileId); final String path = reponame + "/" + itemToDelete.get(FILENAME_KEY); fileStorageService.markForDeletionByPath(path); filesDeleted = true; LOG.info("Mark file {} as deleted during cleanup.", path); } } } LOG.info("Clean up for repository {} finished.", reponame); if (filesDeleted) { repoService.createOrUpdate(reponame); return true; } } return false; } @SuppressWarnings("unchecked") private List<DBObject> getItemsFromAggregate(final DBObject aggregatedArtifact) { return (List<DBObject>) aggregatedArtifact.get(ITEMS_KEY); } @ManagedOperation public boolean cleanup(String reponame) { RepoEntry repoEntry = repoService.ensureEntry(reponame, STATIC, SCHEDULED); return (cleanByMaxNum(reponame, repoEntry.getMaxKeepRpms()) | cleanByMaxDays(reponame, repoEntry.getMaxDaysRpms())); } private AggregationOutput aggregateAllRpmNamesInRepoByTime(String reponame) { BasicDBObject repoMatch = match(where(REPO_KEY).is(reponame)); BasicDBObject groupArtifactNames = groupBy(field("name", "yumPackage.name"), field("arch", "yumPackage.arch")).push( ITEMS_KEY, field(TIME_KEY, "yumPackage.time"), field(FILE_KEY, "_id"), field(FILENAME_KEY, "yumPackage.location.href")) .build(); return mongo.getCollection("yum.entries").aggregate(asList(repoMatch, groupArtifactNames)); } private AggregationOutput aggregateAllRpmNamesInRepoThatHaveMoreThanMaxKeepEntries(String reponame, int maxKeepRpm) { List<DBObject> pipeline = new ArrayList<>(); pipeline.add(match(where(REPO_KEY).is(reponame))); pipeline.add(groupBy(field("name", "yumPackage.name"), field("arch", "yumPackage.arch")).push( ITEMS_KEY, field(VERSION_KEY, "yumPackage.version"), field(FILE_KEY, "_id"), field(FILENAME_KEY, "yumPackage.location.href")) .count() .build()); pipeline.add(match(where("count").gt(maxKeepRpm))); return mongo.getCollection("yum.entries").aggregate(pipeline); } private List<DBObject> oldestItemsToDeleteByVersion(int maxKeepRpm, List<DBObject> items) { sort(items, (DBObject obj1, DBObject obj2) -> comparatorVersion.compare(obj1.get(VERSION_KEY), obj2.get(VERSION_KEY))); return items.subList(0, items.size() - maxKeepRpm); } private List<DBObject> oldestItemsToDeleteByDays(int maxDaysRpm, List<DBObject> items) { DateTime startDate = new DateTime().minusDays(maxDaysRpm).withZoneRetainFields(DateTimeZone.UTC); List<DBObject> newList = new LinkedList<>(); for (DBObject item : items) { BasicDBObject aux = (BasicDBObject) item.get(TIME_KEY); Long itemTime = aux.getLong(TIME_BUILD_KEY); DateTime itemDateTime = new DateTime(itemTime * 1000L).withZoneRetainFields(DateTimeZone.UTC); if (itemDateTime.isBefore(startDate)) { newList.add(item); } } return newList; } }