diff --git a/core/src/main/java/google/registry/model/rde/RdeRevision.java b/core/src/main/java/google/registry/model/rde/RdeRevision.java index 701f05318..2b50840d7 100644 --- a/core/src/main/java/google/registry/model/rde/RdeRevision.java +++ b/core/src/main/java/google/registry/model/rde/RdeRevision.java @@ -48,6 +48,10 @@ public final class RdeRevision extends ImmutableObject { */ int revision; + public int getRevision() { + return revision; + } + /** * Returns next revision ID to use when staging a new deposit file for the given triplet. * diff --git a/core/src/main/java/google/registry/rde/RdeResourceType.java b/core/src/main/java/google/registry/rde/RdeResourceType.java index d22383ab1..e692b79a0 100644 --- a/core/src/main/java/google/registry/rde/RdeResourceType.java +++ b/core/src/main/java/google/registry/rde/RdeResourceType.java @@ -29,7 +29,7 @@ public enum RdeResourceType { DOMAIN("urn:ietf:params:xml:ns:rdeDomain-1.0", EnumSet.of(FULL, THIN)), HOST("urn:ietf:params:xml:ns:rdeHost-1.0", EnumSet.of(FULL)), REGISTRAR("urn:ietf:params:xml:ns:rdeRegistrar-1.0", EnumSet.of(FULL, THIN)), - IDN("urn:ietf:params:xml:ns:rdeIDN-1.0", EnumSet.of(FULL, THIN)), + IDN("urn:ietf:params:xml:ns:rdeIDN-1.0", EnumSet.of(FULL)), HEADER("urn:ietf:params:xml:ns:rdeHeader-1.0", EnumSet.of(FULL, THIN)); private final String uri; diff --git a/core/src/main/java/google/registry/rde/RdeStagingReducer.java b/core/src/main/java/google/registry/rde/RdeStagingReducer.java index e0a23ccf9..5da7d924e 100644 --- a/core/src/main/java/google/registry/rde/RdeStagingReducer.java +++ b/core/src/main/java/google/registry/rde/RdeStagingReducer.java @@ -77,7 +77,7 @@ public final class RdeStagingReducer extends Reducer RdeRevision.getNextRevision(tld, watermark, mode)); String id = RdeUtil.timestampToId(watermark); String prefix = RdeNamingUtils.makeRydeFilename(tld, watermark, mode, 1, revision); if (key.manual()) { @@ -168,9 +168,13 @@ public final class RdeStagingReducer extends Reducer\n", ""), + DepositFragment.create( + RdeResourceType.REGISTRAR, "\n", ""))); + + private Fragments rdeFragments = + new Fragments( + ImmutableList.of( + DepositFragment.create(RdeResourceType.DOMAIN, "\n", ""), + DepositFragment.create(RdeResourceType.REGISTRAR, "\n", ""), + DepositFragment.create(RdeResourceType.CONTACT, "\n", ""), + DepositFragment.create(RdeResourceType.HOST, "\n", ""))); + + private PendingDeposit key; + + private RdeStagingReducer reducer = + new RdeStagingReducer( + new TaskQueueUtils(new Retrier(new FakeSleeper(new FakeClock()), 1)), + new FakeLockHandler(true), + 1024, + GCS_BUCKET, + Duration.ZERO, + PgpHelper.convertPublicKeyToBytes(encryptionKey), + ValidationMode.STRICT); + + @BeforeEach + public void setUp() { + createTld("soy"); + CursorDao.saveCursor(Cursor.create(CursorType.BRDA, now, Registry.get("soy")), "soy"); + CursorDao.saveCursor(Cursor.create(CursorType.RDE_STAGING, now, Registry.get("soy")), "soy"); + tm().transact( + () -> { + RdeRevision.saveRevision("soy", now, THIN, 0); + RdeRevision.saveRevision("soy", now, FULL, 0); + }); + } + + @Test + public void testSuccess_BRDA() throws Exception { + key = PendingDeposit.create("soy", now, THIN, CursorType.BRDA, Duration.standardDays(1)); + reducer.reduce(key, brdaFragments); + String outputFile = decryptGhostrydeGcsFile("soy_2000-01-01_thin_S1_R1.xml.ghostryde"); + assertThat(outputFile) + .isEqualTo( + readResourceUtf8(RdeStagingReducerTest.class, "reducer_brda.xml") + .replace("%RESEND%", " resend=\"1\"")); + compareLength(outputFile, "soy_2000-01-01_thin_S1_R1.xml.length"); + // BRDA doesn't write a report file. + assertThrows( + IOException.class, + () -> + readGcsFile( + gcsService, + new GcsFilename(GCS_BUCKET, "soy_2000-01-01_thin_S1_R1-report.xml.ghostryde"))); + assertThat(loadCursorTime(CursorType.BRDA)) + .isEquivalentAccordingToCompareTo(now.plus(Duration.standardDays(1))); + assertThat(loadRevision(THIN)).isEqualTo(1); + assertTasksEnqueued( + "brda", + new TaskMatcher() + .url(BrdaCopyAction.PATH) + .param(RequestParameters.PARAM_TLD, "soy") + .param(RdeModule.PARAM_WATERMARK, now.toString())); + } + + @Test + public void testSuccess_BRDA_manual() throws Exception { + key = PendingDeposit.createInManualOperation("soy", now, THIN, "", 0); + reducer.reduce(key, brdaFragments); + String outputFile = decryptGhostrydeGcsFile("manual/soy_2000-01-01_thin_S1_R0.xml.ghostryde"); + assertThat(outputFile) + .isEqualTo( + readResourceUtf8(RdeStagingReducerTest.class, "reducer_brda.xml") + .replace("%RESEND%", "")); + compareLength(outputFile, "manual/soy_2000-01-01_thin_S1_R0.xml.length"); + // BRDA doesn't write a report file. + assertThrows( + IOException.class, + () -> + readGcsFile( + gcsService, + new GcsFilename( + GCS_BUCKET, "manual/soy_2000-01-01_thin_S1_R0-report.xml.ghostryde"))); + // No extra operations in manual mode. + assertThat(loadCursorTime(CursorType.BRDA)).isEquivalentAccordingToCompareTo(now); + assertThat(loadRevision(THIN)).isEqualTo(0); + assertNoTasksEnqueued("brda"); + } + + @Test + public void testSuccess_RDE() throws Exception { + key = PendingDeposit.create("soy", now, FULL, CursorType.RDE_STAGING, Duration.standardDays(1)); + reducer.reduce(key, rdeFragments); + String outputFile = decryptGhostrydeGcsFile("soy_2000-01-01_full_S1_R1.xml.ghostryde"); + assertThat(outputFile) + .isEqualTo( + readResourceUtf8(RdeStagingReducerTest.class, "reducer_rde.xml") + .replace("%RESEND%", " resend=\"1\"")); + compareLength(outputFile, "soy_2000-01-01_full_S1_R1.xml.length"); + assertThat(decryptGhostrydeGcsFile("soy_2000-01-01_full_S1_R1-report.xml.ghostryde")) + .isEqualTo( + readResourceUtf8(RdeStagingReducerTest.class, "reducer_rde_report.xml") + .replace("%RESEND%", "1")); + assertThat(loadCursorTime(CursorType.RDE_STAGING)) + .isEquivalentAccordingToCompareTo(now.plus(Duration.standardDays(1))); + assertThat(loadRevision(FULL)).isEqualTo(1); + assertTasksEnqueued( + "rde-upload", + new TaskMatcher().url(RdeUploadAction.PATH).param(RequestParameters.PARAM_TLD, "soy")); + } + + @Test + public void testSuccess_RDE_manual() throws Exception { + key = PendingDeposit.createInManualOperation("soy", now, FULL, "", 0); + reducer.reduce(key, rdeFragments); + String outputFile = decryptGhostrydeGcsFile("manual/soy_2000-01-01_full_S1_R0.xml.ghostryde"); + assertThat(outputFile) + .isEqualTo( + readResourceUtf8(RdeStagingReducerTest.class, "reducer_rde.xml") + .replace("%RESEND%", "")); + compareLength(outputFile, "manual/soy_2000-01-01_full_S1_R0.xml.length"); + assertThat(decryptGhostrydeGcsFile("manual/soy_2000-01-01_full_S1_R0-report.xml.ghostryde")) + .isEqualTo( + readResourceUtf8(RdeStagingReducerTest.class, "reducer_rde_report.xml") + .replace("%RESEND%", "0")); + // No extra operations in manual mode. + assertThat(loadCursorTime(CursorType.RDE_STAGING)).isEquivalentAccordingToCompareTo(now); + assertThat(loadRevision(FULL)).isEqualTo(0); + assertNoTasksEnqueued("rde-upload"); + } + + private static void compareLength(String outputFile, String lengthFilename) throws IOException { + assertThat(String.valueOf(outputFile.getBytes(UTF_8).length)) + .isEqualTo( + new String( + readGcsFile(gcsService, new GcsFilename(GCS_BUCKET, lengthFilename)), UTF_8)); + } + + private static DateTime loadCursorTime(CursorType type) { + return ofy().load().key(Cursor.createKey(type, Registry.get("soy"))).now().getCursorTime(); + } + + private static int loadRevision(RdeMode mode) { + return ofy() + .load() + .type(RdeRevision.class) + .id("soy_2000-01-01_" + mode.getFilenameComponent()) + .now() + .getRevision(); + } + + private static String decryptGhostrydeGcsFile(String filename) throws IOException, PGPException { + return new String( + Ghostryde.decode( + readGcsFile(gcsService, new GcsFilename(GCS_BUCKET, filename)), decryptionKey), + UTF_8); + } + + private static class Fragments extends ReducerInput { + private final Iterator iterator; + + Fragments(Iterable iterable) { + this.iterator = iterable.iterator(); + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public DepositFragment next() { + return iterator.next(); + } + } +} diff --git a/core/src/test/resources/google/registry/rde/reducer_brda.xml b/core/src/test/resources/google/registry/rde/reducer_brda.xml new file mode 100644 index 000000000..c95789c9c --- /dev/null +++ b/core/src/test/resources/google/registry/rde/reducer_brda.xml @@ -0,0 +1,21 @@ + + + 2000-01-01T00:00:00Z + + 1.0 + urn:ietf:params:xml:ns:rdeDomain-1.0 + urn:ietf:params:xml:ns:rdeHeader-1.0 + urn:ietf:params:xml:ns:rdeRegistrar-1.0 + + + + + + + soy + 1 + 1 + + + + diff --git a/core/src/test/resources/google/registry/rde/reducer_rde.xml b/core/src/test/resources/google/registry/rde/reducer_rde.xml new file mode 100644 index 000000000..df14ad1e3 --- /dev/null +++ b/core/src/test/resources/google/registry/rde/reducer_rde.xml @@ -0,0 +1,39 @@ + + + 2000-01-01T00:00:00Z + + 1.0 + urn:ietf:params:xml:ns:rdeContact-1.0 + urn:ietf:params:xml:ns:rdeDomain-1.0 + urn:ietf:params:xml:ns:rdeHeader-1.0 + urn:ietf:params:xml:ns:rdeHost-1.0 + urn:ietf:params:xml:ns:rdeIDN-1.0 + urn:ietf:params:xml:ns:rdeRegistrar-1.0 + + + + + + + + + https://www.iana.org/domains/idn-tables/tables/google_latn_1.0.txt + https://www.registry.google/about/policies/domainabuse/ + + + + https://www.iana.org/domains/idn-tables/tables/google_ja_1.0.txt + https://www.registry.google/about/policies/domainabuse/ + + + + soy + 1 + 1 + 1 + 1 + 2 + + + + diff --git a/core/src/test/resources/google/registry/rde/reducer_rde_report.xml b/core/src/test/resources/google/registry/rde/reducer_rde_report.xml new file mode 100644 index 000000000..851c3b765 --- /dev/null +++ b/core/src/test/resources/google/registry/rde/reducer_rde_report.xml @@ -0,0 +1,19 @@ + + + AAAABXDKZ6WAA + 1 + draft-arias-noguchi-registry-data-escrow-06 + draft-arias-noguchi-dnrd-objects-mapping-05 + %RESEND% + 2000-01-01T00:00:00Z + FULL + 2000-01-01T00:00:00Z + + soy + 1 + 1 + 1 + 1 + 2 + +