blob: bb1bf8a570dcab0457be11d35b05867fbcd38144 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package org.apache.geode.internal.statistics;
import static java.lang.String.valueOf;
import static java.lang.System.lineSeparator;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.apache.commons.io.FileUtils.moveFileToDirectory;
import static org.apache.commons.io.FileUtils.sizeOfDirectory;
import static org.apache.commons.lang3.StringUtils.leftPad;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import org.apache.commons.io.FileUtils;
import org.assertj.core.api.AbstractFileAssert;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.rules.TemporaryFolder;
import org.junit.rules.TestName;
import org.apache.geode.StatisticDescriptor;
import org.apache.geode.StatisticsType;
import org.apache.geode.distributed.ConfigurationProperties;
import org.apache.geode.internal.NanoTimer;
import org.apache.geode.internal.io.MainWithChildrenRollingFileHandler;
import org.apache.geode.internal.io.RollingFileHandler;
import org.apache.geode.internal.util.ArrayUtils;
import org.apache.geode.test.junit.categories.StatisticsTest;
/**
* Integration tests for rolling and deleting behavior of
* {@link ConfigurationProperties#ARCHIVE_FILE_SIZE_LIMIT} and
* {@link ConfigurationProperties#ARCHIVE_DISK_SPACE_LIMIT}.
*/
@Category(StatisticsTest.class)
public class DiskSpaceLimitIntegrationTest {
private static final long FILE_SIZE_LIMIT_BYTES = 1024 * 2;
private static final long DISK_SPACE_LIMIT_BYTES = FILE_SIZE_LIMIT_BYTES * 2;
private File dir;
private File dirOfDeletedFiles;
private String name;
private String archiveFileName;
private RollingFileHandler movingRollingFileHandler;
private SampleCollector sampleCollector;
private StatArchiveHandlerConfig config;
private long initTimeStamp;
private long nanosTimeStamp;
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Rule
public TestName testName = new TestName();
@Before
public void setUp() throws Exception {
dir = temporaryFolder.getRoot();
dirOfDeletedFiles = temporaryFolder.newFolder("deleted");
name = testName.getMethodName();
archiveFileName = new File(dir, name + ".gfs").getAbsolutePath();
LocalStatisticsFactory factory = new LocalStatisticsFactory(null);
StatisticDescriptor[] statisticDescriptors = new StatisticDescriptor[] {
factory.createIntCounter("stat1", "description of stat1", "units", true)};
StatisticsType statisticsType =
factory.createType("statisticsType1", "statisticsType1", statisticDescriptors);
factory.createAtomicStatistics(statisticsType, "statistics1", 1);
StatisticsSampler sampler = mock(StatisticsSampler.class);
when(sampler.getStatistics()).thenReturn(factory.getStatistics());
config = mock(StatArchiveHandlerConfig.class);
when(config.getArchiveFileName()).thenReturn(new File(archiveFileName));
when(config.getArchiveFileSizeLimit()).thenReturn(FILE_SIZE_LIMIT_BYTES);
when(config.getSystemId()).thenReturn(1L);
when(config.getSystemStartTime()).thenReturn(System.currentTimeMillis());
when(config.getSystemDirectoryPath()).thenReturn(dir.getAbsolutePath());
when(config.getProductDescription()).thenReturn(testName.getMethodName());
movingRollingFileHandler = new MovingRollingFileHandler();
sampleCollector = new SampleCollector(sampler);
NanoTimer timer = new NanoTimer();
initTimeStamp = timer.getConstructionTime() - getNanoRate();
nanosTimeStamp = timer.getConstructionTime();
validateNumberFiles(0);
}
@After
public void tearDown() throws Exception {
StatisticsTypeFactoryImpl.clear();
}
@Test
public void zeroKeepsAllFiles() throws Exception {
sampleCollector.initialize(config, initTimeStamp, movingRollingFileHandler);
when(config.getArchiveDiskSpaceLimit()).thenReturn(0L);
File fileWithChildId1 = archiveFile(1);
File fileWithChildId2 = archiveFile(2);
// sample until file with childId 1 exists
sampleUntilFileExists(fileWithChildId1);
// sample until filed with childId 2 exists
sampleUntilFileExists(fileWithChildId2);
// both files should still exist
assertThat(fileWithChildId1).withFailMessage(fileShouldExist(fileWithChildId1)).exists();
assertThat(fileWithChildId2).withFailMessage(fileShouldExist(fileWithChildId2)).exists();
}
@Test
public void aboveZeroDeletesOldestFile() throws Exception {
sampleCollector.initialize(config, initTimeStamp, movingRollingFileHandler);
when(config.getArchiveDiskSpaceLimit()).thenReturn(DISK_SPACE_LIMIT_BYTES);
File fileWithChildId1 = archiveFile(1);
File fileWithChildId2 = archiveFile(2);
// sample until file with childId 1 exists
sampleUntilFileExists(fileWithChildId1);
// sample until file with childId 2 exists
sampleUntilFileExists(fileWithChildId2);
// sample until file with childId 1 is deleted (moved to deleted dir)
sampleUntilFileDeleted(fileWithChildId1);
assertThat(fileWithChildId1).withFailMessage(fileShouldNotExist(fileWithChildId1))
.doesNotExist();
// different file systems may have different children created/deleted
int latestChildId = 2;
for (; latestChildId < 10; latestChildId++) {
if (archiveFile(latestChildId).exists()) {
break;
}
}
assertThat(latestChildId).isLessThan(10);
File fileWithLatestChildId = archiveFile(latestChildId);
assertThat(fileWithLatestChildId).withFailMessage(fileShouldExist(fileWithLatestChildId))
.exists();
assertThat(fileExisted(fileWithChildId1))
.withFailMessage(fileShouldHaveExisted(fileWithChildId1)).isTrue();
}
@Test
public void aboveZeroDeletesPreviousFiles() throws Exception {
int oldMainId = 1;
int newMainId = 2;
int numberOfPreviousFiles = 100;
int numberOfLines = 100;
createPreviousFiles(oldMainId, numberOfPreviousFiles, numberOfLines);
validateNumberFiles(numberOfPreviousFiles);
for (int childId = 1; childId <= numberOfPreviousFiles; childId++) {
File fileWithOldMainId = archiveFile(oldMainId, childId);
assertThatFileExists(fileWithOldMainId);
}
// current archive file does not exist yet
File archiveFile = archiveFile();
assertThatFileDoesNotExist(archiveFile);
// rolling files for mainId 2 do not exist yet
File markerFileWithNewMainId = markerFile(newMainId);
assertThatFileDoesNotExist(markerFileWithNewMainId);
File fileWithNewMainId = archiveFile(newMainId, 1);
assertThatFileDoesNotExist(fileWithNewMainId);
when(config.getArchiveDiskSpaceLimit())
.thenReturn(sizeOfDirectory(dir) / numberOfPreviousFiles);
sampleCollector.initialize(config, initTimeStamp, movingRollingFileHandler);
assertThatFileExists(archiveFile).hasParent(dir);
assertThatFileExists(markerFileWithNewMainId).hasParent(dir).hasBinaryContent(new byte[0]);
assertThatFileDoesNotExist(fileWithNewMainId);
sampleNumberOfTimes(1);
sampleUntilFileExists(fileWithNewMainId);
assertThatFileExists(fileWithNewMainId);
validateNumberFilesIsAtLeast(2);
for (int childId = 1; childId <= numberOfPreviousFiles; childId++) {
File fileWithOldMainId = archiveFile(oldMainId, childId);
assertThatFileDoesNotExist(fileWithOldMainId);
}
}
@Test
public void aboveZeroDeletesPreviousFiles_nameWithHyphen() throws Exception {
name = "psin8p724_cache1-statistics";
archiveFileName = new File(dir, name + ".gfs").getAbsolutePath();
when(config.getArchiveFileName()).thenReturn(new File(archiveFileName));
int oldMainId = 1;
int newMainId = 2;
int numberOfPreviousFiles = 100;
int numberOfLines = 100;
createPreviousFiles(oldMainId, numberOfPreviousFiles, numberOfLines);
validateNumberFiles(numberOfPreviousFiles);
for (int childId = 1; childId <= numberOfPreviousFiles; childId++) {
File fileWithOldMainId = archiveFile(oldMainId, childId);
assertThatFileExists(fileWithOldMainId);
}
// current archive file does not exist yet
File archiveFile = archiveFile();
assertThatFileDoesNotExist(archiveFile);
// rolling files for mainId 2 do not exist yet
File markerFileWithNewMainId = markerFile(newMainId);
assertThatFileDoesNotExist(markerFileWithNewMainId);
File fileWithNewMainId = archiveFile(newMainId, 1);
assertThatFileDoesNotExist(fileWithNewMainId);
when(config.getArchiveDiskSpaceLimit())
.thenReturn(sizeOfDirectory(dir) / numberOfPreviousFiles);
sampleCollector.initialize(config, initTimeStamp, movingRollingFileHandler);
assertThatFileExists(archiveFile).hasParent(dir);
assertThatFileExists(markerFileWithNewMainId).hasParent(dir).hasBinaryContent(new byte[0]);
assertThatFileDoesNotExist(fileWithNewMainId);
sampleNumberOfTimes(1);
sampleUntilFileExists(fileWithNewMainId);
assertThatFileExists(fileWithNewMainId);
validateNumberFilesIsAtLeast(2);
for (int childId = 1; childId <= numberOfPreviousFiles; childId++) {
File fileWithOldMainId = archiveFile(oldMainId, childId);
assertThatFileDoesNotExist(fileWithOldMainId);
}
}
private AbstractFileAssert<?> assertThatFileExists(File file) {
return assertThat(file).withFailMessage(fileShouldExist(file)).exists();
}
private void assertThatFileDoesNotExist(File file) {
assertThat(file).withFailMessage(fileShouldNotExist(file)).doesNotExist();
}
private String fileShouldExist(File file) {
return "Expecting:" + lineSeparator() + " " + file.getAbsolutePath() + lineSeparator() +
" to exist in:" + lineSeparator() + " " + dir.getAbsolutePath() + lineSeparator() +
" but only found:" + lineSeparator() + " " + filesAndSizes();
}
private String fileShouldNotExist(File file) {
return " Expecting:" + lineSeparator() + " " + file.getAbsolutePath() + lineSeparator() +
" to not exist in:" + lineSeparator() + " " + dir.getAbsolutePath() + lineSeparator() +
" but found:" + lineSeparator() + " " + filesAndSizes();
}
private String fileShouldHaveExisted(File file) {
return "Expecting:" + lineSeparator() + " " + file.getAbsolutePath() + lineSeparator() +
" to exist in:" + lineSeparator() + " " + dir.getAbsolutePath() + lineSeparator() +
" or moved to:" + lineSeparator() + " " + dirOfDeletedFiles.getAbsolutePath()
+ lineSeparator() +
" but only found:" + lineSeparator() + " " + filesAndSizes();
}
private Map<File, String> filesAndSizes() {
Collection<File> existingFiles = FileUtils.listFiles(dir, null, true);
Map<File, String> filesAndSizes = new HashMap<>();
for (File existingFile : existingFiles) {
filesAndSizes.put(existingFile, FileUtils.sizeOf(existingFile) + " bytes");
}
return filesAndSizes;
}
/**
* Validates number of files under this.dir while ignoring this.dirOfDeletedFiles.
*/
private void validateNumberFiles(final int expected) {
assertThat(numberOfFiles(dir)).as("Unexpected files: " + listFiles(dir))
.isEqualTo(expected);
}
/**
* Validates number of files under this.dir while ignoring this.dirOfDeletedFiles.
*/
private void validateNumberFilesIsAtLeast(final int expected) {
assertThat(numberOfFiles(dir)).as("Unexpected files: " + listFiles(dir))
.isGreaterThanOrEqualTo(expected);
}
private void sampleNumberOfTimes(final int value) throws InterruptedException {
long minutes = 1;
long timeout = System.nanoTime() + MINUTES.toNanos(minutes);
int count = 0;
do {
sample(advanceNanosTimeStamp());
count++;
Thread.sleep(10); // avoid hot thread loop
} while (count < value && System.nanoTime() < timeout);
System.out.println("Sampled " + count + " times.");
}
private void sampleUntilFileExists(final File file)
throws InterruptedException, TimeoutException {
long minutes = 1;
long timeout = System.nanoTime() + MINUTES.toNanos(minutes);
int count = 0;
do {
sample(advanceNanosTimeStamp());
count++;
Thread.sleep(10); // avoid hot thread loop
} while (!fileExisted(file) && System.nanoTime() < timeout);
if (!fileExisted(file)) {
throw new TimeoutException("File " + file + " does not exist after " + count
+ " samples within " + minutes + " " + MINUTES);
}
System.out.println("Sampled " + count + " times to create " + file);
}
private void sampleUntilFileDeleted(final File file)
throws InterruptedException, TimeoutException {
long minutes = 1;
long timeout = System.nanoTime() + MINUTES.toNanos(minutes);
int count = 0;
do {
sample(advanceNanosTimeStamp());
count++;
Thread.sleep(10); // avoid hot thread loop
} while (file.exists() && System.nanoTime() < timeout);
if (file.exists()) {
throw new TimeoutException("File " + file + " does not exist after " + count
+ " samples within " + minutes + " " + MINUTES);
}
System.out.println("Sampled " + count + " times to delete " + file);
}
private boolean fileExisted(final File file) {
if (file.exists()) {
return true;
} else { // check dirOfDeletedFiles
File deleted = new File(dirOfDeletedFiles, file.getName());
return deleted.exists();
}
}
private void sample(final long time) {
getSampleCollector().sample(time);
}
private SampleCollector getSampleCollector() {
return sampleCollector;
}
private long advanceNanosTimeStamp() {
nanosTimeStamp += getNanoRate();
return nanosTimeStamp;
}
private long getNanoRate() {
return NanoTimer.millisToNanos(getSampleRate());
}
private long getSampleRate() {
return 1000; // 1 second
}
private File archiveFile() {
return archiveFile(archiveName());
}
private String archiveName() {
return name;
}
private File archiveFile(final String name) {
return new File(dir, name + ".gfs");
}
private File archiveFile(final int childId) {
return archiveFile(1, childId);
}
private File archiveFile(final int mainId, final int childId) {
return archiveFile(archiveName(), mainId, childId);
}
private File archiveFile(final String name, final int mainId, final int childId) {
return new File(dir, archiveName(name, mainId, childId) + ".gfs");
}
private String archiveName(final String name, final int mainId, final int childId) {
return name + "-" + formatId(mainId) + "-" + formatId(childId);
}
private File markerFile(final int mainId) {
return markerFile(archiveName(), mainId);
}
private File markerFile(final String name, final int mainId) {
return new File(dir, markerName(name, mainId));
}
private String markerName(final String name, final int mainId) {
return archiveName(name, mainId, 0) + ".marker";
}
private String formatId(final int id) {
return String.format("%02d", id);
}
private List<File> listFiles(final File dir) {
List<File> files = ArrayUtils.asList(dir.listFiles());
files.remove(dirOfDeletedFiles);
return files;
}
private int numberOfFiles(final File dir) {
return listFiles(dir).size();
}
private void createPreviousFiles(final int mainId, final int fileCount, final int lineCount)
throws IOException {
createPreviousFiles(name, mainId, fileCount, lineCount);
}
private void createPreviousFiles(final String name, final int mainId, final int fileCount,
final int lineCount) throws IOException {
int childId = 1;
List<String> lines = lines(lineCount);
for (int i = 0; i < fileCount; i++) {
File file = createFile(name, mainId, childId);
writeFile(file, lines);
childId++;
}
}
private File createFile(final String name, final int mainId, final int childId) {
File file = new File(dir, name + "-" + leftPad(valueOf(mainId), 2, "0") + "-"
+ leftPad(valueOf(childId), 2, "0") + ".gfs");
return file;
}
private void writeFile(final File file, final List<String> lines) throws IOException {
PrintWriter writer = new PrintWriter(file, "UTF-8");
for (String line : lines) {
writer.println(line);
}
writer.close();
}
private List<String> lines(final int lineCount) {
List<String> lines = new ArrayList<>();
for (int i = 0; i < lineCount; i++) {
lines.add(testName.getMethodName());
}
return lines;
}
/**
* Override protected method to move file instead of deleting it.
*/
private class MovingRollingFileHandler extends MainWithChildrenRollingFileHandler {
@Override
protected boolean delete(final File file) {
try {
moveFileToDirectory(file, dirOfDeletedFiles, false);
return true;
} catch (IOException e) {
throw new Error(e);
}
}
}
}