/** * 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 org.apache.aurora.scheduler.storage.backup; import java.io.File; import java.io.IOException; import java.util.concurrent.ScheduledExecutorService; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import org.apache.aurora.common.base.Command; import org.apache.aurora.common.quantity.Amount; import org.apache.aurora.common.quantity.Time; import org.apache.aurora.common.testing.easymock.EasyMockTest; import org.apache.aurora.common.util.testing.FakeBuildInfo; import org.apache.aurora.common.util.testing.FakeClock; import org.apache.aurora.gen.storage.SchedulerMetadata; import org.apache.aurora.gen.storage.Snapshot; import org.apache.aurora.scheduler.base.Query; import org.apache.aurora.scheduler.base.TaskTestUtil; import org.apache.aurora.scheduler.base.Tasks; import org.apache.aurora.scheduler.storage.DistributedSnapshotStore; import org.apache.aurora.scheduler.storage.SnapshotStore; import org.apache.aurora.scheduler.storage.Storage; import org.apache.aurora.scheduler.storage.Storage.MutableStoreProvider; import org.apache.aurora.scheduler.storage.Storage.MutateWork; import org.apache.aurora.scheduler.storage.backup.Recovery.RecoveryException; import org.apache.aurora.scheduler.storage.backup.Recovery.RecoveryImpl; import org.apache.aurora.scheduler.storage.backup.StorageBackup.StorageBackupImpl; import org.apache.aurora.scheduler.storage.backup.StorageBackup.StorageBackupImpl.BackupConfig; import org.apache.aurora.scheduler.storage.backup.TemporaryStorage.TemporaryStorageFactory; import org.apache.aurora.scheduler.storage.db.EnumBackfill; import org.apache.aurora.scheduler.storage.entities.IScheduledTask; import org.apache.aurora.scheduler.testing.FakeScheduledExecutor; import org.easymock.Capture; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import static org.easymock.EasyMock.capture; import static org.easymock.EasyMock.expect; import static org.junit.Assert.assertEquals; public class RecoveryTest extends EasyMockTest { private static final Amount<Long, Time> INTERVAL = Amount.of(1L, Time.HOURS); private static final IScheduledTask TASK1 = TaskTestUtil.makeTask("task1", TaskTestUtil.JOB); private static final IScheduledTask TASK2 = TaskTestUtil.makeTask("task2", TaskTestUtil.JOB); private static final Snapshot SNAPSHOT1 = makeSnapshot(TASK1, TASK2); private SnapshotStore<Snapshot> snapshotStore; private DistributedSnapshotStore distributedStore; private Storage primaryStorage; private MutableStoreProvider storeProvider; private Command shutDownNow; private FakeClock clock; private StorageBackupImpl storageBackup; private RecoveryImpl recovery; @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); @Before public void setUp() throws IOException { final File backupDir = temporaryFolder.newFolder(); snapshotStore = createMock(new Clazz<SnapshotStore<Snapshot>>() { }); distributedStore = createMock(DistributedSnapshotStore.class); primaryStorage = createMock(Storage.class); storeProvider = createMock(MutableStoreProvider.class); shutDownNow = createMock(Command.class); ScheduledExecutorService executor = createMock(ScheduledExecutorService.class); clock = FakeScheduledExecutor.scheduleExecutor(executor); final EnumBackfill enumBackfill = createMock(EnumBackfill.class); TemporaryStorageFactory factory = new TemporaryStorageFactory(TaskTestUtil.THRIFT_BACKFILL, enumBackfill); storageBackup = new StorageBackupImpl( snapshotStore, clock, new BackupConfig(backupDir, 5, INTERVAL), executor); recovery = new RecoveryImpl(backupDir, factory, primaryStorage, distributedStore, shutDownNow); } @Test public void testRecover() throws Exception { expect(snapshotStore.createSnapshot()).andReturn(SNAPSHOT1); Capture<MutateWork<Object, Exception>> transaction = createCapture(); expect(primaryStorage.write(capture(transaction))).andReturn(null); Capture<Snapshot> snapshot = createCapture(); distributedStore.persist(capture(snapshot)); shutDownNow.execute(); control.replay(); assertEquals(ImmutableSet.of(), recovery.listBackups()); clock.advance(INTERVAL); storageBackup.createSnapshot(); String backup1 = storageBackup.createBackupName(); assertEquals(ImmutableSet.of(backup1), recovery.listBackups()); recovery.stage(backup1); assertEquals( IScheduledTask.setFromBuilders(SNAPSHOT1.getTasks()), ImmutableSet.copyOf(recovery.query(Query.unscoped()))); recovery.commit(); transaction.getValue().apply(storeProvider); snapshot.getValue().unsetDbScript(); assertEquals(SNAPSHOT1, snapshot.getValue()); } @Test public void testModifySnapshotBeforeCommit() throws Exception { expect(snapshotStore.createSnapshot()).andReturn(SNAPSHOT1); Snapshot modified = SNAPSHOT1.deepCopy().setTasks(ImmutableSet.of(TASK1.newBuilder())); Capture<MutateWork<Object, Exception>> transaction = createCapture(); expect(primaryStorage.write(capture(transaction))).andReturn(null); Capture<Snapshot> snapshot = createCapture(); distributedStore.persist(capture(snapshot)); shutDownNow.execute(); control.replay(); clock.advance(INTERVAL); storageBackup.createSnapshot(); String backup1 = storageBackup.createBackupName(); recovery.stage(backup1); assertEquals( IScheduledTask.setFromBuilders(SNAPSHOT1.getTasks()), ImmutableSet.copyOf(recovery.query(Query.unscoped()))); recovery.deleteTasks(Query.taskScoped(Tasks.id(TASK2))); assertEquals( IScheduledTask.setFromBuilders(modified.getTasks()), ImmutableSet.copyOf(recovery.query(Query.unscoped()))); recovery.commit(); transaction.getValue().apply(storeProvider); snapshot.getValue().unsetDbScript(); assertEquals(modified, snapshot.getValue()); } @Test(expected = RecoveryException.class) public void testLoadUnknownBackup() throws Exception { control.replay(); recovery.stage("foo"); } @Test(expected = RecoveryException.class) public void backupNotLoaded() throws Exception { control.replay(); recovery.commit(); } private static Snapshot makeSnapshot(IScheduledTask... tasks) { SchedulerMetadata metadata = new SchedulerMetadata() .setDetails(ImmutableMap.of( FakeBuildInfo.DATE, FakeBuildInfo.DATE, FakeBuildInfo.GIT_REVISION, FakeBuildInfo.GIT_REVISION, FakeBuildInfo.GIT_TAG, FakeBuildInfo.GIT_TAG)); return new Snapshot() .setCronJobs(ImmutableSet.of()) .setSchedulerMetadata(metadata) .setTasks(IScheduledTask.toBuildersSet(ImmutableSet.copyOf(tasks))); } }