/* * Licensed 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 com.addthis.hydra.job; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.List; import com.addthis.basis.util.JitterClock; import com.addthis.hydra.job.backup.DailyBackup; import com.addthis.hydra.job.backup.GoldBackup; import com.addthis.hydra.job.backup.HourlyBackup; import com.addthis.hydra.job.backup.ScheduledBackupType; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class ScheduledBackupTypeTest { private static final Collection<ScheduledBackupType> testedTypes = ScheduledBackupType.getBackupTypes().values(); private static final Date backupsValidStartDate = new Date(1325419200000l); // Jan 1, 2012 private static final long now = JitterClock.globalTime(); private static long daysAgo(int numDays) { return now - numDays * 86400000l; } @Test public void shouldMakeNewBackupTest() throws Exception { // For each backup type, ensure that a new backup will _not_ be made if there is a current backup available // Also ensure that a new backup _will_ be made if the existing backup is old / doesn't exist for (ScheduledBackupType type : testedTypes) { String currBackupName = type.generateCurrentName(true); String oldBackupName = type.generateNameForTime(daysAgo(60), true); String[] oldAndNewBackup = new String[]{oldBackupName, currBackupName}; String[] anOldBackup = new String[]{oldBackupName}; String desc = type.getDescription(); boolean isGold = type instanceof GoldBackup; // Gold is a special case; we always make gold backups assertEquals("shouldn't make a " + desc + " backup if a current one exists, unless gold", isGold, type.shouldMakeNewBackup(oldAndNewBackup)); assertEquals("should make a " + desc + " backup if other backup is old", true, type.shouldMakeNewBackup(anOldBackup)); assertEquals("should make a " + desc + " backup if no backups", true, type.shouldMakeNewBackup(new String[]{})); } } @Test public void oldBackupsToDeleteTest() throws Exception { // For each backup type, suppose we have 4 backups from that type and some other directories mixed in. // Then if our max number of backups is n, ensure 4-n of the oldest backups are deleted. // Also make sure we don't touch any of the other directories. for (ScheduledBackupType type : testedTypes) { String currBackupName = type.generateCurrentName(true); String monthOldBackupName = type.generateNameForTime(daysAgo(31), true); String twoMonthOldBackupName = type.generateNameForTime(daysAgo(62), true); String threeMonthOldBackupName = type.generateNameForTime(daysAgo(93), true); String[] existingBackups = new String[]{currBackupName, monthOldBackupName, twoMonthOldBackupName, threeMonthOldBackupName, "live", "replica", "otherdirectory"}; String desc = type.getDescription(); for (int max = 1; max < 4; max++) { List<String> backupsToDelete = type.oldBackupsToDelete(existingBackups, existingBackups, max); assertEquals("should delete all but " + max + " backups for type " + desc, 4 - max, backupsToDelete.size()); for (String backupToDelete : backupsToDelete) { assertTrue("should delete a backup from type " + desc, type.isValidName(backupToDelete)); assertTrue("shouldn't delete the current backup for type " + desc, !backupToDelete.equals(currBackupName)); } } } } @Test public void invalidBackupsDeleteTest() throws Exception { // For each backup type, suppose we have four valid backups from that type and one invalid backup // If the max number of backups for this type is two, we should delete the invalid backup and also the two oldest valid backups for (ScheduledBackupType type : testedTypes) { String currBackupName = type.generateCurrentName(true); String invalidBackupName = type.generateNameForTime(daysAgo(31), true); String twoMonthOldBackupName = type.generateNameForTime(daysAgo(62), true); String threeMonthOldBackupName = type.generateNameForTime(daysAgo(93), true); String fourMonthOldBackupName = type.generateNameForTime(daysAgo(124), true); String[] allBackups = new String[]{currBackupName, invalidBackupName, twoMonthOldBackupName, threeMonthOldBackupName, fourMonthOldBackupName, "live", "replica", "otherdirectory"}; String[] validBackups = new String[]{currBackupName, twoMonthOldBackupName, threeMonthOldBackupName, fourMonthOldBackupName, "live", "replica", "otherdirectory"}; List<String> backupsToDelete = type.oldBackupsToDelete(allBackups, validBackups, 2); String desc = type.getDescription(); assertEquals("should delete three old backups", 3, backupsToDelete.size()); for (String backupToDelete : backupsToDelete) { assertTrue("should delete a backup from type " + desc, type.isValidName(backupToDelete)); assertTrue("should delete an invalid backup or one of the two oldest backups", backupToDelete.equals(invalidBackupName) || backupToDelete.equals(threeMonthOldBackupName) || backupToDelete.equals(fourMonthOldBackupName)); } } } @Test public void parsingTest() throws Exception { // Make sure that each backup type can recognize and parse dates from its own generated names for (ScheduledBackupType type : testedTypes) { String desc = type.getDescription(); String currBackup = type.generateCurrentName(true); assertEquals("current " + desc + " backup should be valid name", true, type.isValidName(currBackup)); Date date = type.parseDateFromName(currBackup); assertEquals("parsed " + desc + " backup date should be after start date", true, date.after(backupsValidStartDate)); } } @Test public void noCollisionsTest() throws Exception { // Make sure all backup names are valid for their originating types and invalid for all other types for (ScheduledBackupType type1 : testedTypes) { String currBackupName = type1.generateCurrentName(true); for (ScheduledBackupType type2 : testedTypes) { if (type1.getDescription().equals(type2.getDescription())) { assertEquals("name from same type " + type1.getDescription() + " should be valid", true, type2.isValidName(currBackupName)); } else { assertEquals("name from different type should be invalid", false, type2.isValidName(currBackupName)); } } } } @Test public void sortBackupsByTimeTest() throws Exception { // Make sure that the sortBackupsByTime method correctly sorts backups from most recent to earliest long now = System.currentTimeMillis(); String newGold = new GoldBackup().generateCurrentName(true); String olderHourly = new HourlyBackup().generateNameForTime(now - 100_000, true); String evenOlderGold = new GoldBackup().generateNameForTime(now - 200_000, true); String oldestDaily = new DailyBackup().generateNameForTime(now - 400_000, true); ArrayList<String> backupsRandomOrder = new ArrayList<>(Arrays.asList(olderHourly, newGold, evenOlderGold, oldestDaily)); ScheduledBackupType.sortBackupsByTime(backupsRandomOrder); List<String> expected = Arrays.asList(newGold, olderHourly, evenOlderGold, oldestDaily); for (int i=0; i<expected.size(); i++) { assertEquals("should get list in expected order", expected.get(i), backupsRandomOrder.get(i)); } } }