diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/SegmentedStoreUtils.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/SegmentedStoreUtils.java index a1ebf600348ca7d454bf390c4ad57e8052aa6f7d..2b08fc7daf91d2ef6a837963ea878b81677036bb 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/SegmentedStoreUtils.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/SegmentedStoreUtils.java @@ -25,6 +25,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -49,8 +50,8 @@ import ch.systemsx.cisd.common.logging.ISimpleLogger; import ch.systemsx.cisd.common.logging.LogLevel; import ch.systemsx.cisd.common.utilities.ITimeProvider; import ch.systemsx.cisd.common.utilities.SystemTimeProvider; -import ch.systemsx.cisd.openbis.dss.generic.shared.IDataSetDirectoryProvider; import ch.systemsx.cisd.openbis.dss.generic.shared.IChecksumProvider; +import ch.systemsx.cisd.openbis.dss.generic.shared.IDataSetDirectoryProvider; import ch.systemsx.cisd.openbis.dss.generic.shared.IEncapsulatedOpenBISService; import ch.systemsx.cisd.openbis.dss.generic.shared.IShareIdManager; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.AbstractExternalData; @@ -229,25 +230,56 @@ public class SegmentedStoreUtils List<DatasetDescription> dataSets, IDataSetDirectoryProvider dataSetDirectoryProvider, IShareIdManager shareIdManager, ISimpleLogger logger) { - long requestedSpace = calculateTotalSizeOfDataSetsToKeep(dataSets); + List<DatasetDescription> filteredDataSets = new ArrayList<DatasetDescription>(dataSets); + List<SimpleDataSetInformationDTO> filteredDataSetsInShare + = new ArrayList<SimpleDataSetInformationDTO>(unarchivingScratchShare.getDataSetsOrderedBySize()); + removeCommonDataSets(filteredDataSets, filteredDataSetsInShare); + long requestedSpace = calculateTotalSizeOfDataSetsToKeep(filteredDataSets); long actualFreeSpace = unarchivingScratchShare.calculateFreeSpace(); - if (actualFreeSpace > requestedSpace) + if (actualFreeSpace < requestedSpace) + { + Collections.sort(filteredDataSetsInShare, MODIFICATION_TIMESTAMP_COMPARATOR); + List<SimpleDataSetInformationDTO> dataSetsToRemoveFromShare = + listDataSetsToRemoveFromShare(filteredDataSetsInShare, requestedSpace, actualFreeSpace, + unarchivingScratchShare, logger); + logger.log(LogLevel.INFO, "Remove the following data sets from share '" + unarchivingScratchShare.getShareId() + + "' and set their archiving status back to ARCHIVED: " + + CollectionUtils.abbreviate(extractCodes(dataSetsToRemoveFromShare), 10)); + service.archiveDataSets(extractCodes(dataSetsToRemoveFromShare), false); + for (SimpleDataSetInformationDTO dataSet : dataSetsToRemoveFromShare) + { + deleteDataSet(dataSet, dataSetDirectoryProvider, shareIdManager, logger); + } + logger.log(LogLevel.INFO, "The following data sets have been successfully removed from share '" + + unarchivingScratchShare.getShareId() + "' and their archiving status has been successfully " + + "set back to ARCHIVED: " + CollectionUtils.abbreviate(extractCodes(dataSetsToRemoveFromShare), 10)); + actualFreeSpace = unarchivingScratchShare.calculateFreeSpace(); + } + logger.log(LogLevel.INFO, "Free space on unarchiving scratch share '" + + unarchivingScratchShare.getShareId() + "': " + FileUtilities.byteCountToDisplaySize(actualFreeSpace) + + ", requested space for unarchiving " + filteredDataSets.size() + " data sets: " + + FileUtilities.byteCountToDisplaySize(requestedSpace)); + } + + private static void removeCommonDataSets(List<DatasetDescription> dataSets, List<SimpleDataSetInformationDTO> dataSetsInShare) + { + Set<String> extractCodes = new HashSet<String>(extractCodes(dataSetsInShare)); + for (Iterator<DatasetDescription> iterator = dataSets.iterator(); iterator.hasNext();) { - return; + DatasetDescription dataSet = iterator.next(); + if (extractCodes.remove(dataSet.getDataSetCode())) + { + iterator.remove(); + } } - List<SimpleDataSetInformationDTO> dataSetsInShare = listRemovableDataSets(unarchivingScratchShare, dataSets); - Collections.sort(dataSetsInShare, MODIFICATION_TIMESTAMP_COMPARATOR); - List<SimpleDataSetInformationDTO> dataSetsToRemoveFromShare = - listDataSetsToRemoveFromShare(dataSetsInShare, requestedSpace, actualFreeSpace, - unarchivingScratchShare, logger); - service.archiveDataSets(extractCodes(dataSetsToRemoveFromShare), false); - for (SimpleDataSetInformationDTO dataSet : dataSetsToRemoveFromShare) - { - deleteDataSet(dataSet, dataSetDirectoryProvider, shareIdManager, logger); + for (Iterator<SimpleDataSetInformationDTO> iterator = dataSetsInShare.iterator(); iterator.hasNext();) + { + SimpleDataSetInformationDTO dataSet = iterator.next(); + if (extractCodes.contains(dataSet.getDataSetCode()) == false) + { + iterator.remove(); + } } - logger.log(LogLevel.INFO, "The following data sets have been successfully removed from share '" - + unarchivingScratchShare.getShareId() + "' and their archiving status has been successfully " - + "set back to ARCHIVED: " + CollectionUtils.abbreviate(extractCodes(dataSetsToRemoveFromShare), 10)); } private static long calculateTotalSizeOfDataSetsToKeep(List<DatasetDescription> dataSets) @@ -265,25 +297,6 @@ public class SegmentedStoreUtils return size; } - private static List<SimpleDataSetInformationDTO> listRemovableDataSets(Share share, - List<DatasetDescription> dataSets) - { - Set<String> dataSetsToBeKept = new HashSet<String>(); - for (DatasetDescription dataSet : dataSets) - { - dataSetsToBeKept.add(dataSet.getDataSetCode()); - } - List<SimpleDataSetInformationDTO> dataSetsInShare = new ArrayList<SimpleDataSetInformationDTO>(); - for (SimpleDataSetInformationDTO dataSetInShare : share.getDataSetsOrderedBySize()) - { - if (dataSetsToBeKept.contains(dataSetInShare.getDataStoreCode()) == false) - { - dataSetsInShare.add(dataSetInShare); - } - } - return dataSetsInShare; - } - private static List<String> extractCodes(List<SimpleDataSetInformationDTO> dataSets) { List<String> codes = new ArrayList<String>(); @@ -306,14 +319,11 @@ public class SegmentedStoreUtils freeSpace += dataSetInShare.getDataSetSize(); dataSetsToRemoveFromShare.add(dataSetInShare); } - logger.log(LogLevel.INFO, "Remove the following data sets from share '" + share.getShareId() - + "' and set their archiving status back to ARCHIVED: " - + CollectionUtils.abbreviate(extractCodes(dataSetsToRemoveFromShare), 10)); if (requestedSpace >= freeSpace) { - throw new EnvironmentFailureException("After removing all removable data sets from share '" - + share.getShareId() + "' there is still only " + FileUtilities.byteCountToDisplaySize(freeSpace) - + " free space less than the requested " + FileUtilities.byteCountToDisplaySize(requestedSpace) + "."); + throw new EnvironmentFailureException("Even after removing all removable data sets from share '" + + share.getShareId() + "' there would be still only " + FileUtilities.byteCountToDisplaySize(freeSpace) + + " free space which is less than the requested " + FileUtilities.byteCountToDisplaySize(requestedSpace) + "."); } return dataSetsToRemoveFromShare; } diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/SegmentedStoreUtilsTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/SegmentedStoreUtilsTest.java index d29d9411febd10f480a76b06b55f5c0e0922c71a..b8a6ce3b762e06ef3831895e37efaf7c106f53a0 100644 --- a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/SegmentedStoreUtilsTest.java +++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/SegmentedStoreUtilsTest.java @@ -27,6 +27,7 @@ import java.util.List; import org.apache.commons.io.FileUtils; import org.jmock.Expectations; import org.jmock.Mockery; +import org.jmock.Sequence; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -131,7 +132,22 @@ public class SegmentedStoreUtilsTest extends AbstractFileSystemTestCase } @Test - public void testFreeSpace() + public void testFreeSpaceNothingToDo() + { + SimpleDataSetInformationDTO ds1 = dataSet(1, 11 * FileUtils.ONE_KB); + Share share = new Share(shareFolder, 0, freeSpaceProvider); + RecordingMatcher<HostAwareFile> recordingFileMatcher = prepareFreeSpace(12L); + + SegmentedStoreUtils.freeSpace(share, service, asDatasetDescriptions(ds1), dataSetDirectoryProvider, + shareIdManager, log); + + assertEquals(shareFolder.getPath(), recordingFileMatcher.recordedObject().getPath()); + assertEquals("INFO: Free space on unarchiving scratch share '1': 12.00 KB, " + + "requested space for unarchiving 1 data sets: 11.00 KB\n", log.toString()); + } + + @Test + public void testFreeSpaceRemovingOneDataSet() { SimpleDataSetInformationDTO ds1 = dataSet(1, 10 * FileUtils.ONE_KB); SimpleDataSetInformationDTO ds2 = dataSet(2, 10 * FileUtils.ONE_KB); @@ -142,73 +158,93 @@ public class SegmentedStoreUtilsTest extends AbstractFileSystemTestCase share.addDataSet(ds5); share.addDataSet(ds3); share.addDataSet(ds1); - RecordingMatcher<HostAwareFile> recordingFileMatcher = prepareFreeSpace(12L); + RecordingMatcher<HostAwareFile> recordingFileMatcher = prepareFreeSpace(12L, 22L); prepareSetArchingStatus(ds1); File file = prepareDeleteFromShare(ds1); assertEquals(true, file.exists()); - + SegmentedStoreUtils.freeSpace(share, service, asDatasetDescriptions(ds2, ds4), dataSetDirectoryProvider, shareIdManager, log); - + assertEquals(false, file.exists()); - assertEquals(shareFolder.getPath(), recordingFileMatcher.recordedObject().getPath()); + assertEquals(shareFolder.getPath(), recordingFileMatcher.getRecordedObjects().get(0).getPath()); + assertEquals(shareFolder.getPath(), recordingFileMatcher.getRecordedObjects().get(1).getPath()); + assertEquals(2, recordingFileMatcher.getRecordedObjects().size()); assertEquals("INFO: Remove the following data sets from share '1' and set their archiving status " + "back to ARCHIVED: [ds-1]\n" + "INFO: Await for data set ds-1 to be unlocked.\n" + "INFO: Start deleting data set ds-1 at " + shareFolder + "/abc/ds-1\n" + "INFO: Data set ds-1 at " + shareFolder + "/abc/ds-1 has been successfully deleted.\n" + "INFO: The following data sets have been successfully removed from share '1' " - + "and their archiving status has been successfully set back to ARCHIVED: [ds-1]\n", log.toString()); - } - - private File prepareDeleteFromShare(final SimpleDataSetInformationDTO dataSet) - { - final File file = new File(shareFolder, dataSet.getDataSetLocation()); - context.checking(new Expectations() - { - { - one(shareIdManager).await(dataSet.getDataSetCode()); - one(dataSetDirectoryProvider).getDataSetDirectory(dataSet); - will(returnValue(file)); - } - }); - return file; + + "and their archiving status has been successfully set back to ARCHIVED: [ds-1]\n" + + "INFO: Free space on unarchiving scratch share '1': 22.00 KB, requested space for " + + "unarchiving 2 data sets: 21.00 KB\n", log.toString()); } - - private void prepareSetArchingStatus(final SimpleDataSetInformationDTO... dataSets) + + @Test + public void testFreeSpaceForThreeDataSetsOneAlreadyInShare() { - context.checking(new Expectations() - { - { - List<String> dataSetCodes = new ArrayList<String>(); - for (SimpleDataSetInformationDTO dataSet : dataSets) - { - dataSetCodes.add(dataSet.getDataSetCode()); - } - one(service).archiveDataSets(dataSetCodes, false); - } - }); + SimpleDataSetInformationDTO ds1 = dataSet(1, 10 * FileUtils.ONE_KB); + SimpleDataSetInformationDTO ds2 = dataSet(2, 10 * FileUtils.ONE_KB); + SimpleDataSetInformationDTO ds3 = dataSet(3, 12 * FileUtils.ONE_KB); + SimpleDataSetInformationDTO ds4 = dataSet(4, 11 * FileUtils.ONE_KB); + SimpleDataSetInformationDTO ds5 = dataSet(5, 14 * FileUtils.ONE_KB); + Share share = new Share(shareFolder, 0, freeSpaceProvider); + share.addDataSet(ds5); + share.addDataSet(ds3); + share.addDataSet(ds1); + RecordingMatcher<HostAwareFile> recordingFileMatcher = prepareFreeSpace(12L, 24L); + prepareSetArchingStatus(ds3); + File file = prepareDeleteFromShare(ds3); + assertEquals(true, file.exists()); + + SegmentedStoreUtils.freeSpace(share, service, asDatasetDescriptions(ds1, ds2, ds4), dataSetDirectoryProvider, + shareIdManager, log); + + assertEquals(false, file.exists()); + assertEquals(shareFolder.getPath(), recordingFileMatcher.getRecordedObjects().get(0).getPath()); + assertEquals(shareFolder.getPath(), recordingFileMatcher.getRecordedObjects().get(1).getPath()); + assertEquals(2, recordingFileMatcher.getRecordedObjects().size()); + assertEquals("INFO: Remove the following data sets from share '1' and set their archiving status " + + "back to ARCHIVED: [ds-3]\n" + + "INFO: Await for data set ds-3 to be unlocked.\n" + + "INFO: Start deleting data set ds-3 at " + shareFolder + "/abc/ds-3\n" + + "INFO: Data set ds-3 at " + shareFolder + "/abc/ds-3 has been successfully deleted.\n" + + "INFO: The following data sets have been successfully removed from share '1' " + + "and their archiving status has been successfully set back to ARCHIVED: [ds-3]\n" + + "INFO: Free space on unarchiving scratch share '1': 24.00 KB, requested space for " + + "unarchiving 2 data sets: 21.00 KB\n", log.toString()); } - - private RecordingMatcher<HostAwareFile> prepareFreeSpace(final long freeSpace) + + @Test + public void testFreeSpaceRemovingDataSetsButStillNotEnoughFreeSpace() { - final RecordingMatcher<HostAwareFile> recorder = new RecordingMatcher<HostAwareFile>(); - context.checking(new Expectations() - { - { - try - { - one(freeSpaceProvider).freeSpaceKb(with(recorder)); - will(returnValue(freeSpace)); - } catch (IOException ex) - { - ex.printStackTrace(); - } - } - }); - return recorder; + SimpleDataSetInformationDTO ds1 = dataSet(1, 10 * FileUtils.ONE_KB); + SimpleDataSetInformationDTO ds2 = dataSet(2, 10 * FileUtils.ONE_KB); + SimpleDataSetInformationDTO ds3 = dataSet(3, 12 * FileUtils.ONE_KB); + SimpleDataSetInformationDTO ds4 = dataSet(4, 11 * FileUtils.ONE_KB); + SimpleDataSetInformationDTO ds5 = dataSet(5, 14 * FileUtils.ONE_KB); + Share share = new Share(shareFolder, 0, freeSpaceProvider); + share.addDataSet(ds3); + share.addDataSet(ds2); + share.addDataSet(ds1); + RecordingMatcher<HostAwareFile> recordingFileMatcher = prepareFreeSpace(2L); + + try + { + SegmentedStoreUtils.freeSpace(share, service, asDatasetDescriptions(ds4, ds5, ds1), dataSetDirectoryProvider, + shareIdManager, log); + fail("EnvironmentFailureException expected"); + } catch (EnvironmentFailureException ex) + { + assertEquals("Even after removing all removable data sets from share '1' there would be " + + "still only 24.00 KB free space which is less than the requested 25.00 KB.", ex.getMessage()); + } + + assertEquals(shareFolder.getPath(), recordingFileMatcher.recordedObject().getPath()); + assertEquals("", log.toString()); } - + @Test public void testGetDataSetsPerShare() { @@ -637,6 +673,59 @@ public class SegmentedStoreUtilsTest extends AbstractFileSystemTestCase assertEquals(Arrays.asList(names).toString(), actualNames.toString()); } + private File prepareDeleteFromShare(final SimpleDataSetInformationDTO dataSet) + { + final File file = new File(shareFolder, dataSet.getDataSetLocation()); + context.checking(new Expectations() + { + { + one(shareIdManager).await(dataSet.getDataSetCode()); + one(dataSetDirectoryProvider).getDataSetDirectory(dataSet); + will(returnValue(file)); + } + }); + return file; + } + + private void prepareSetArchingStatus(final SimpleDataSetInformationDTO... dataSets) + { + context.checking(new Expectations() + { + { + List<String> dataSetCodes = new ArrayList<String>(); + for (SimpleDataSetInformationDTO dataSet : dataSets) + { + dataSetCodes.add(dataSet.getDataSetCode()); + } + one(service).archiveDataSets(dataSetCodes, false); + } + }); + } + + private RecordingMatcher<HostAwareFile> prepareFreeSpace(final long... freeSpaceValues) + { + final RecordingMatcher<HostAwareFile> recorder = new RecordingMatcher<HostAwareFile>(); + final Sequence sequence = context.sequence("free space"); + context.checking(new Expectations() + { + { + try + { + for (long freeSpace : freeSpaceValues) + { + one(freeSpaceProvider).freeSpaceKb(with(recorder)); + will(returnValue(freeSpace)); + inSequence(sequence); + } + } catch (IOException ex) + { + ex.printStackTrace(); + } + } + }); + return recorder; + } + private List<DatasetDescription> asDatasetDescriptions(SimpleDataSetInformationDTO... dataSets) { List<DatasetDescription> result = new ArrayList<DatasetDescription>();