mirror of
https://github.com/google/nomulus.git
synced 2025-04-30 12:07:51 +02:00
This change renames directories in preparation for the great package rename. The repository is now in a broken state because the code itself hasn't been updated. However this should ensure that git correctly preserves history for each file.
327 lines
13 KiB
Java
327 lines
13 KiB
Java
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
|
//
|
|
// 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.google.domain.registry.backup;
|
|
|
|
import static com.google.common.truth.Truth.assertThat;
|
|
import static com.google.domain.registry.model.ofy.CommitLogBucket.getBucketKey;
|
|
import static com.google.domain.registry.testing.DatastoreHelper.createTld;
|
|
import static com.google.domain.registry.util.DateTimeUtils.END_OF_TIME;
|
|
import static com.google.domain.registry.util.DateTimeUtils.START_OF_TIME;
|
|
|
|
import com.google.common.base.Supplier;
|
|
import com.google.common.collect.ImmutableMap;
|
|
import com.google.domain.registry.config.TestRegistryConfig;
|
|
import com.google.domain.registry.model.ofy.CommitLogBucket;
|
|
import com.google.domain.registry.model.ofy.CommitLogCheckpoint;
|
|
import com.google.domain.registry.model.ofy.Ofy;
|
|
import com.google.domain.registry.model.registry.Registry;
|
|
import com.google.domain.registry.model.registry.RegistryCursor;
|
|
import com.google.domain.registry.model.registry.RegistryCursor.CursorType;
|
|
import com.google.domain.registry.testing.AppEngineRule;
|
|
import com.google.domain.registry.testing.FakeClock;
|
|
import com.google.domain.registry.testing.InjectRule;
|
|
import com.google.domain.registry.testing.RegistryConfigRule;
|
|
|
|
import com.googlecode.objectify.VoidWork;
|
|
|
|
import org.joda.time.DateTime;
|
|
import org.joda.time.Duration;
|
|
import org.junit.Before;
|
|
import org.junit.Rule;
|
|
import org.junit.Test;
|
|
import org.junit.runner.RunWith;
|
|
import org.mockito.runners.MockitoJUnitRunner;
|
|
|
|
/** Unit tests for {@link CommitLogCheckpointStrategy}. */
|
|
@RunWith(MockitoJUnitRunner.class)
|
|
public class CommitLogCheckpointStrategyTest {
|
|
|
|
@Rule
|
|
public final AppEngineRule appEngine = AppEngineRule.builder()
|
|
.withDatastore()
|
|
.build();
|
|
|
|
@Rule
|
|
public final InjectRule inject = new InjectRule();
|
|
|
|
@Rule
|
|
public final RegistryConfigRule configRule = new RegistryConfigRule();
|
|
|
|
final FakeClock clock = new FakeClock(DateTime.parse("2000-01-01TZ"));
|
|
final Ofy ofy = new Ofy(clock);
|
|
final CommitLogCheckpointStrategy strategy = new CommitLogCheckpointStrategy();
|
|
|
|
/**
|
|
* Supplier to inject into CommitLogBucket for doling out predictable bucket IDs.
|
|
*
|
|
* <p>If not overridden, the supplier returns 1 so that other saves won't hit an NPE (since even
|
|
* if they use saveWithoutBackup() the transaction still selects a bucket key early).
|
|
*/
|
|
final FakeSupplier<Integer> fakeBucketIdSupplier = new FakeSupplier<>(1);
|
|
|
|
/** Gross but necessary supplier that can be modified to return the desired value. */
|
|
private static class FakeSupplier<T> implements Supplier<T> {
|
|
/** Default value to return if 'value' is not set. */
|
|
final T defaultValue;
|
|
|
|
/** Set this value field to make the supplier return this value. */
|
|
T value = null;
|
|
|
|
public FakeSupplier(T defaultValue) {
|
|
this.defaultValue = defaultValue;
|
|
}
|
|
|
|
@Override
|
|
public T get() {
|
|
return value == null ? defaultValue : value;
|
|
}
|
|
}
|
|
|
|
@Before
|
|
public void before() throws Exception {
|
|
strategy.clock = clock;
|
|
strategy.ofy = ofy;
|
|
|
|
// Use three commit log buckets for easier but sufficiently complex testing.
|
|
configRule.override(new TestRegistryConfig() {
|
|
@Override
|
|
public int getCommitLogBucketCount() {
|
|
return 3;
|
|
}});
|
|
|
|
// Need to inject clock into Ofy so that createTld() below will get the right time.
|
|
inject.setStaticField(Ofy.class, "clock", clock);
|
|
// Inject a fake bucket ID supplier so we can dole out specific bucket IDs to commit logs.
|
|
inject.setStaticField(CommitLogBucket.class, "bucketIdSupplier", fakeBucketIdSupplier);
|
|
|
|
// Create some fake TLDs to parent RegistryCursor test objects under.
|
|
createTld("tld1");
|
|
createTld("tld2");
|
|
createTld("tld3");
|
|
clock.advanceOneMilli();
|
|
}
|
|
|
|
@Test
|
|
public void test_readBucketTimestamps_noCommitLogs() throws Exception {
|
|
assertThat(strategy.readBucketTimestamps())
|
|
.containsExactly(1, START_OF_TIME, 2, START_OF_TIME, 3, START_OF_TIME);
|
|
}
|
|
|
|
@Test
|
|
public void test_readBucketTimestamps_withSomeCommitLogs() throws Exception {
|
|
DateTime startTime = clock.nowUtc();
|
|
writeCommitLogToBucket(1);
|
|
clock.advanceOneMilli();
|
|
writeCommitLogToBucket(2);
|
|
assertThat(strategy.readBucketTimestamps())
|
|
.containsExactly(1, startTime, 2, startTime.plusMillis(1), 3, START_OF_TIME);
|
|
}
|
|
|
|
@Test
|
|
public void test_readBucketTimestamps_againAfterUpdate_reflectsUpdate() throws Exception {
|
|
DateTime firstTime = clock.nowUtc();
|
|
writeCommitLogToBucket(1);
|
|
writeCommitLogToBucket(2);
|
|
writeCommitLogToBucket(3);
|
|
assertThat(strategy.readBucketTimestamps().values())
|
|
.containsExactly(firstTime, firstTime, firstTime);
|
|
clock.advanceOneMilli();
|
|
writeCommitLogToBucket(1);
|
|
DateTime secondTime = clock.nowUtc();
|
|
assertThat(strategy.readBucketTimestamps())
|
|
.containsExactly(1, secondTime, 2, firstTime, 3, firstTime);
|
|
}
|
|
|
|
@Test
|
|
public void test_readNewCommitLogsAndFindThreshold_noCommitsAtAll_returnsEndOfTime() {
|
|
ImmutableMap<Integer, DateTime> bucketTimes =
|
|
ImmutableMap.of(1, START_OF_TIME, 2, START_OF_TIME, 3, START_OF_TIME);
|
|
assertThat(strategy.readNewCommitLogsAndFindThreshold(bucketTimes)).isEqualTo(END_OF_TIME);
|
|
}
|
|
|
|
@Test
|
|
public void test_readNewCommitLogsAndFindThreshold_noNewCommits_returnsEndOfTime() {
|
|
DateTime now = clock.nowUtc();
|
|
writeCommitLogToBucket(1);
|
|
clock.advanceOneMilli();
|
|
writeCommitLogToBucket(2);
|
|
clock.advanceOneMilli();
|
|
writeCommitLogToBucket(3);
|
|
ImmutableMap<Integer, DateTime> bucketTimes =
|
|
ImmutableMap.of(1, now, 2, now.plusMillis(1), 3, now.plusMillis(2));
|
|
assertThat(strategy.readNewCommitLogsAndFindThreshold(bucketTimes)).isEqualTo(END_OF_TIME);
|
|
}
|
|
|
|
@Test
|
|
public void test_readNewCommitLogsAndFindThreshold_tiedNewCommits_returnsCommitTimeMinusOne() {
|
|
DateTime now = clock.nowUtc();
|
|
writeCommitLogToBucket(1);
|
|
writeCommitLogToBucket(2);
|
|
writeCommitLogToBucket(3);
|
|
assertThat(strategy.readNewCommitLogsAndFindThreshold(
|
|
ImmutableMap.of(1, START_OF_TIME, 2, START_OF_TIME, 3, START_OF_TIME)))
|
|
.isEqualTo(now.minusMillis(1));
|
|
}
|
|
|
|
@Test
|
|
public void test_readNewCommitLogsAndFindThreshold_someNewCommits_returnsEarliestTimeMinusOne() {
|
|
DateTime now = clock.nowUtc();
|
|
writeCommitLogToBucket(1); // 1A
|
|
writeCommitLogToBucket(2); // 2A
|
|
writeCommitLogToBucket(3); // 3A
|
|
clock.advanceBy(Duration.millis(5));
|
|
writeCommitLogToBucket(1); // 1B
|
|
writeCommitLogToBucket(2); // 2B
|
|
writeCommitLogToBucket(3); // 3B
|
|
clock.advanceBy(Duration.millis(5));
|
|
writeCommitLogToBucket(1); // 1C
|
|
writeCommitLogToBucket(2); // 2C
|
|
writeCommitLogToBucket(3); // 3C
|
|
// First pass times: 1 at T0, 2 at T+5, 3 at T+10.
|
|
// Commits 1A, 2B, 3C are the commits seen in the first pass.
|
|
// Commits 2A, 3A, 3B are all old prior commits that should be ignored.
|
|
// Commit 1B is the first new commit for bucket 1, at T+5.
|
|
// Commit 1C is the second new commit for bucket 1, at T+10, and should be ignored.
|
|
// Commit 2C is the first new commit for bucket 2, at T+10.
|
|
// Since 1B as a new commit is older than 1C, T+5 is the oldest new commit time.
|
|
// Therefore, expect T+4 as the threshold time.
|
|
assertThat(strategy.readNewCommitLogsAndFindThreshold(
|
|
ImmutableMap.of(1, now, 2, now.plusMillis(5), 3, now.plusMillis(10))))
|
|
.isEqualTo(now.plusMillis(4));
|
|
}
|
|
|
|
@Test
|
|
public void test_readNewCommitLogsAndFindThreshold_commitsAtBucketTimes() {
|
|
DateTime now = clock.nowUtc();
|
|
ImmutableMap<Integer, DateTime> bucketTimes =
|
|
ImmutableMap.of(1, now.minusMillis(1), 2, now, 3, now.plusMillis(1));
|
|
assertThat(strategy.readNewCommitLogsAndFindThreshold(bucketTimes)).isEqualTo(END_OF_TIME);
|
|
}
|
|
|
|
@Test
|
|
public void test_computeBucketCheckpointTimes_earlyThreshold_setsEverythingToThreshold() {
|
|
DateTime now = clock.nowUtc();
|
|
ImmutableMap<Integer, DateTime> bucketTimes =
|
|
ImmutableMap.of(1, now.minusMillis(1), 2, now, 3, now.plusMillis(1));
|
|
assertThat(strategy.computeBucketCheckpointTimes(bucketTimes, now.minusMillis(2)).values())
|
|
.containsExactly(now.minusMillis(2), now.minusMillis(2), now.minusMillis(2));
|
|
}
|
|
|
|
@Test
|
|
public void test_computeBucketCheckpointTimes_middleThreshold_clampsToThreshold() {
|
|
DateTime now = clock.nowUtc();
|
|
ImmutableMap<Integer, DateTime> bucketTimes =
|
|
ImmutableMap.of(1, now.minusMillis(1), 2, now, 3, now.plusMillis(1));
|
|
assertThat(strategy.computeBucketCheckpointTimes(bucketTimes, now))
|
|
.containsExactly(1, now.minusMillis(1), 2, now, 3, now);
|
|
}
|
|
|
|
@Test
|
|
public void test_computeBucketCheckpointTimes_lateThreshold_leavesBucketTimesAsIs() {
|
|
DateTime now = clock.nowUtc();
|
|
ImmutableMap<Integer, DateTime> bucketTimes =
|
|
ImmutableMap.of(1, now.minusMillis(1), 2, now, 3, now.plusMillis(1));
|
|
assertThat(strategy.computeBucketCheckpointTimes(bucketTimes, now.plusMillis(2)))
|
|
.isEqualTo(bucketTimes);
|
|
}
|
|
|
|
@Test
|
|
public void test_computeCheckpoint_noCommitsAtAll_bucketCheckpointTimesAreStartOfTime() {
|
|
assertThat(strategy.computeCheckpoint())
|
|
.isEqualTo(CommitLogCheckpoint.create(
|
|
clock.nowUtc(),
|
|
ImmutableMap.of(1, START_OF_TIME, 2, START_OF_TIME, 3, START_OF_TIME)));
|
|
}
|
|
|
|
@Test
|
|
public void test_computeCheckpoint_noNewCommitLogs_bucketCheckpointTimesAreBucketTimes() {
|
|
DateTime now = clock.nowUtc();
|
|
writeCommitLogToBucket(1);
|
|
clock.advanceOneMilli();
|
|
writeCommitLogToBucket(2);
|
|
clock.advanceOneMilli();
|
|
writeCommitLogToBucket(3);
|
|
clock.advanceOneMilli();
|
|
DateTime checkpointTime = clock.nowUtc();
|
|
assertThat(strategy.computeCheckpoint())
|
|
.isEqualTo(CommitLogCheckpoint.create(
|
|
checkpointTime,
|
|
ImmutableMap.of(1, now, 2, now.plusMillis(1), 3, now.plusMillis(2))));
|
|
}
|
|
|
|
@Test
|
|
public void test_computeCheckpoint_someNewCommits_bucketCheckpointTimesAreClampedToThreshold() {
|
|
DateTime now = clock.nowUtc();
|
|
writeCommitLogToBucket(1); // 1A
|
|
writeCommitLogToBucket(2); // 2A
|
|
writeCommitLogToBucket(3); // 3A
|
|
clock.advanceBy(Duration.millis(5));
|
|
writeCommitLogToBucket(1); // 1B
|
|
writeCommitLogToBucket(2); // 2B
|
|
writeCommitLogToBucket(3); // 3B
|
|
clock.advanceBy(Duration.millis(5));
|
|
writeCommitLogToBucket(1); // 1C
|
|
writeCommitLogToBucket(2); // 2C
|
|
writeCommitLogToBucket(3); // 3C
|
|
|
|
// Set first pass times: 1 at T0, 2 at T+5, 3 at T+10.
|
|
saveBucketWithLastWrittenTime(1, now);
|
|
saveBucketWithLastWrittenTime(2, now.plusMillis(5));
|
|
saveBucketWithLastWrittenTime(3, now.plusMillis(10));
|
|
|
|
// Commits 1A, 2B, 3C are the commits seen in the first pass.
|
|
// Commits 2A, 3A, 3B are all old prior commits that should be ignored.
|
|
// Commit 1B is the first new commit for bucket 1, at T+5.
|
|
// Commit 1C is the second new commit for bucket 1, at T+10, and should be ignored.
|
|
// Commit 2C is the first new commit for bucket 2, at T+10.
|
|
// Since 1B as a new commit is older than 1C, T+5 is the oldest new commit time.
|
|
// Therefore, expect T+4 as the threshold time.
|
|
DateTime threshold = now.plusMillis(4);
|
|
|
|
// Advance clock before taking checkpoint.
|
|
clock.advanceBy(Duration.millis(10));
|
|
DateTime checkpointTime = clock.nowUtc();
|
|
|
|
// Bucket checkpoint times should be clamped as expected.
|
|
assertThat(strategy.computeCheckpoint())
|
|
.isEqualTo(CommitLogCheckpoint.create(
|
|
checkpointTime,
|
|
ImmutableMap.of(1, now, 2, threshold, 3, threshold)));
|
|
}
|
|
|
|
private void writeCommitLogToBucket(final int bucketId) {
|
|
fakeBucketIdSupplier.value = bucketId;
|
|
ofy.transact(
|
|
new VoidWork() {
|
|
@Override
|
|
public void vrun() {
|
|
String tld = "tld" + bucketId;
|
|
RegistryCursor.save(Registry.get(tld), CursorType.RDE_REPORT, ofy.getTransactionTime());
|
|
}
|
|
});
|
|
fakeBucketIdSupplier.value = null;
|
|
}
|
|
|
|
private void saveBucketWithLastWrittenTime(final int bucketId, final DateTime lastWrittenTime) {
|
|
ofy.transact(new VoidWork() {
|
|
@Override
|
|
public void vrun() {
|
|
ofy.saveWithoutBackup().entity(
|
|
CommitLogBucket.loadBucket(getBucketKey(bucketId)).asBuilder()
|
|
.setLastWrittenTime(lastWrittenTime)
|
|
.build());
|
|
}});
|
|
}
|
|
}
|