diff --git a/java/google/registry/env/common/backend/WEB-INF/web.xml b/java/google/registry/env/common/backend/WEB-INF/web.xml index 0f82e765b..517a7f8eb 100644 --- a/java/google/registry/env/common/backend/WEB-INF/web.xml +++ b/java/google/registry/env/common/backend/WEB-INF/web.xml @@ -275,6 +275,12 @@ /_dr/task/importRdeHosts + + + backend-servlet + /_dr/task/importRdeDomains + + diff --git a/java/google/registry/module/backend/BackendRequestComponent.java b/java/google/registry/module/backend/BackendRequestComponent.java index a4ca07e03..5b492bbba 100644 --- a/java/google/registry/module/backend/BackendRequestComponent.java +++ b/java/google/registry/module/backend/BackendRequestComponent.java @@ -59,6 +59,7 @@ import google.registry.rde.RdeReporter; import google.registry.rde.RdeStagingAction; import google.registry.rde.RdeUploadAction; import google.registry.rde.imports.RdeContactImportAction; +import google.registry.rde.imports.RdeDomainImportAction; import google.registry.rde.imports.RdeHostImportAction; import google.registry.rde.imports.RdeImportsModule; import google.registry.request.RequestComponentBuilder; @@ -114,6 +115,7 @@ interface BackendRequestComponent { PublishDnsUpdatesAction publishDnsUpdatesAction(); ReadDnsQueueAction readDnsQueueAction(); RdeContactImportAction rdeContactImportAction(); + RdeDomainImportAction rdeDomainImportAction(); RdeHostImportAction rdeHostImportAction(); RdeReportAction rdeReportAction(); RdeStagingAction rdeStagingAction(); diff --git a/java/google/registry/rde/imports/BUILD b/java/google/registry/rde/imports/BUILD index b68e3bdaf..1ed15d94d 100644 --- a/java/google/registry/rde/imports/BUILD +++ b/java/google/registry/rde/imports/BUILD @@ -15,6 +15,7 @@ java_library( "//java/google/registry/request", "//java/google/registry/util", "//java/google/registry/xjc", + "//java/google/registry/xml", "//third_party/java/objectify:objectify-v4_1", "@com_google_appengine_api_1_0_sdk", "@com_google_appengine_tools_appengine_gcs_client", diff --git a/java/google/registry/rde/imports/RdeContactImportAction.java b/java/google/registry/rde/imports/RdeContactImportAction.java index c39caf5a9..d5e45bac8 100644 --- a/java/google/registry/rde/imports/RdeContactImportAction.java +++ b/java/google/registry/rde/imports/RdeContactImportAction.java @@ -24,6 +24,7 @@ import com.google.appengine.tools.cloudstorage.RetryParams; import com.google.appengine.tools.mapreduce.Mapper; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; +import com.googlecode.objectify.VoidWork; import google.registry.config.RegistryConfig.Config; import google.registry.config.RegistryConfig.ConfigModule; import google.registry.gcs.GcsUtils; @@ -32,7 +33,11 @@ import google.registry.model.contact.ContactResource; import google.registry.request.Action; import google.registry.request.Parameter; import google.registry.request.Response; +import google.registry.util.FormattingLogger; import google.registry.util.SystemClock; +import google.registry.xjc.JaxbFragment; +import google.registry.xjc.rdecontact.XjcRdeContact; +import google.registry.xjc.rdecontact.XjcRdeContactElement; import javax.inject.Inject; /** @@ -43,6 +48,7 @@ import javax.inject.Inject; @Action(path = "/_dr/task/importRdeContacts") public class RdeContactImportAction implements Runnable { + private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass(); private static final GcsService GCS_SERVICE = GcsServiceFactory.createGcsService(RetryParams.getDefaultInstance()); @@ -91,9 +97,11 @@ public class RdeContactImportAction implements Runnable { } /** Mapper to import contacts from an escrow file. */ - public static class RdeContactImportMapper extends Mapper { + public static class RdeContactImportMapper + extends Mapper, Void, Void> { private static final long serialVersionUID = -7645091075256589374L; + private final String importBucketName; private transient RdeImportUtils importUtils; @@ -120,8 +128,39 @@ public class RdeContactImportAction implements Runnable { } @Override - public void map(ContactResource contact) { - getImportUtils().importContact(contact); + public void map(JaxbFragment fragment) { + final XjcRdeContact xjcContact = fragment.getInstance().getValue(); + try { + logger.infofmt("Converting xml for contact %s", xjcContact.getId()); + // Record number of attempted map operations + getContext().incrementCounter("contact imports attempted"); + logger.infofmt("Saving contact %s", xjcContact.getId()); + ofy().transact(new VoidWork() { + @Override + public void vrun() { + ContactResource contact = + XjcToContactResourceConverter.convertContact(xjcContact); + getImportUtils().importContact(contact); + } + }); + // Record number of contacts imported + getContext().incrementCounter("contacts saved"); + logger.infofmt("Contact %s was imported successfully", xjcContact.getId()); + } catch (ResourceExistsException e) { + // Record the number of contacts already in the registry + getContext().incrementCounter("contacts skipped"); + logger.infofmt("Contact %s already exists", xjcContact.getId()); + } catch (Exception e) { + // Record the number of contacts with unexpected errors + getContext().incrementCounter("contact import errors"); + throw new ContactImportException(xjcContact.getId(), xjcContact.toString(), e); + } + } + } + + private static class ContactImportException extends RuntimeException { + ContactImportException(String contactId, String xml, Throwable cause) { + super(String.format("Error importing contact %s; xml=%s", contactId, xml), cause); } } } diff --git a/java/google/registry/rde/imports/RdeContactInput.java b/java/google/registry/rde/imports/RdeContactInput.java index 4318b1821..3e974dcf9 100644 --- a/java/google/registry/rde/imports/RdeContactInput.java +++ b/java/google/registry/rde/imports/RdeContactInput.java @@ -30,6 +30,8 @@ import google.registry.config.RegistryConfig.ConfigModule; import google.registry.gcs.GcsUtils; import google.registry.model.contact.ContactResource; import google.registry.rde.imports.RdeParser.RdeHeader; +import google.registry.xjc.JaxbFragment; +import google.registry.xjc.rdecontact.XjcRdeContactElement; import java.io.IOException; import java.io.InputStream; import java.util.List; @@ -41,7 +43,7 @@ import java.util.List; * so that each map shard has one reader. If a mapShards parameter has not been specified, a * default number of readers will be created. */ -public class RdeContactInput extends Input { +public class RdeContactInput extends Input> { private static final long serialVersionUID = -366966393494008712L; private static final GcsService GCS_SERVICE = @@ -64,13 +66,6 @@ public class RdeContactInput extends Input { private final String importBucketName; private final String importFileName; - /** - * Creates a new {@link RdeContactInput} - * - * @param mapShards Number of readers that should be created - * @param importBucketName Name of GCS bucket for escrow file imports - * @param importFileName Name of escrow file in GCS - */ public RdeContactInput(Optional mapShards, String importBucketName, String importFileName) { this.numReaders = mapShards.or(DEFAULT_READERS); @@ -79,7 +74,8 @@ public class RdeContactInput extends Input { } @Override - public List> createReaders() throws IOException { + public List>> createReaders() + throws IOException { int numReaders = this.numReaders; RdeHeader header = newParser().getHeader(); int numberOfContacts = header.getContactCount().intValue(); @@ -99,16 +95,10 @@ public class RdeContactInput extends Input { return builder.build(); } - /** - * Creates a new instance of {@link RdeContactReader} - */ private RdeContactReader newReader(int offset, int maxResults) { return new RdeContactReader(importBucketName, importFileName, offset, maxResults); } - /** - * Creates a new instance of {@link RdeParser} - */ private RdeParser newParser() { GcsUtils utils = new GcsUtils(GCS_SERVICE, ConfigModule.provideGcsBufferSize()); GcsFilename filename = new GcsFilename(importBucketName, importFileName); diff --git a/java/google/registry/rde/imports/RdeContactReader.java b/java/google/registry/rde/imports/RdeContactReader.java index f498469b5..04e1b40e2 100644 --- a/java/google/registry/rde/imports/RdeContactReader.java +++ b/java/google/registry/rde/imports/RdeContactReader.java @@ -21,8 +21,9 @@ import com.google.appengine.tools.cloudstorage.RetryParams; import com.google.appengine.tools.mapreduce.InputReader; import google.registry.config.RegistryConfig.ConfigModule; import google.registry.gcs.GcsUtils; -import google.registry.model.contact.ContactResource; import google.registry.util.FormattingLogger; +import google.registry.xjc.JaxbFragment; +import google.registry.xjc.rdecontact.XjcRdeContactElement; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; @@ -31,7 +32,8 @@ import javax.annotation.concurrent.NotThreadSafe; /** Mapreduce {@link InputReader} for reading contacts from escrow files */ @NotThreadSafe -public class RdeContactReader extends InputReader implements Serializable { +public class RdeContactReader extends InputReader> + implements Serializable { private static final long serialVersionUID = -3688793834175577691L; @@ -80,23 +82,26 @@ public class RdeContactReader extends InputReader implements Se } @Override - public ContactResource next() throws IOException { + public JaxbFragment next() throws IOException { if (count < maxResults) { if (parser == null) { parser = newParser(); if (parser.isAtContact()) { - count++; - return XjcToContactResourceConverter.convertContact(parser.getContact()); + return readContact(); } } if (parser.nextContact()) { - count++; - return XjcToContactResourceConverter.convertContact(parser.getContact()); + return readContact(); } } throw new NoSuchElementException(); } + private JaxbFragment readContact() { + count++; + return JaxbFragment.create(new XjcRdeContactElement(parser.getContact())); + } + @Override public void endSlice() throws IOException { super.endSlice(); diff --git a/java/google/registry/rde/imports/RdeDomainImportAction.java b/java/google/registry/rde/imports/RdeDomainImportAction.java new file mode 100644 index 000000000..f954d8595 --- /dev/null +++ b/java/google/registry/rde/imports/RdeDomainImportAction.java @@ -0,0 +1,170 @@ +// Copyright 2017 The Nomulus 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 google.registry.rde.imports; + +import static google.registry.mapreduce.MapreduceRunner.PARAM_MAP_SHARDS; +import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.rde.imports.RdeImportsModule.PATH; +import static google.registry.util.PipelineUtils.createJobPath; + +import com.google.appengine.tools.cloudstorage.GcsService; +import com.google.appengine.tools.cloudstorage.GcsServiceFactory; +import com.google.appengine.tools.cloudstorage.RetryParams; +import com.google.appengine.tools.mapreduce.Mapper; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableList; +import com.googlecode.objectify.VoidWork; +import google.registry.config.RegistryConfig.Config; +import google.registry.config.RegistryConfig.ConfigModule; +import google.registry.gcs.GcsUtils; +import google.registry.mapreduce.MapreduceRunner; +import google.registry.model.domain.DomainResource; +import google.registry.request.Action; +import google.registry.request.Parameter; +import google.registry.request.Response; +import google.registry.util.FormattingLogger; +import google.registry.util.SystemClock; +import google.registry.xjc.JaxbFragment; +import google.registry.xjc.rdedomain.XjcRdeDomain; +import google.registry.xjc.rdedomain.XjcRdeDomainElement; +import javax.inject.Inject; + +/** + * A mapreduce that imports domains from an escrow file. + * + *

Specify the escrow file to import with the "path" parameter. + */ +@Action(path = "/_dr/task/importRdeDomains") +public class RdeDomainImportAction implements Runnable { + + private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass(); + private static final GcsService GCS_SERVICE = + GcsServiceFactory.createGcsService(RetryParams.getDefaultInstance()); + + protected final MapreduceRunner mrRunner; + protected final Response response; + protected final String importBucketName; + protected final String importFileName; + protected final Optional mapShards; + + @Inject + public RdeDomainImportAction( + MapreduceRunner mrRunner, + Response response, + @Config("rdeImportBucket") String importBucketName, + @Parameter(PATH) String importFileName, + @Parameter(PARAM_MAP_SHARDS) Optional mapShards) { + this.mrRunner = mrRunner; + this.response = response; + this.importBucketName = importBucketName; + this.importFileName = importFileName; + this.mapShards = mapShards; + } + + @Override + public void run() { + logger.infofmt( + "Launching domains import mapreduce: bucket=%s, filename=%s", + this.importBucketName, + this.importFileName); + response.sendJavaScriptRedirect(createJobPath(mrRunner + .setJobName("Import domains from escrow file") + .setModuleName("backend") + .runMapOnly( + createMapper(), + ImmutableList.of(createInput())))); + } + + /** + * Creates a new {@link RdeDomainInput} + */ + private RdeDomainInput createInput() { + return new RdeDomainInput(mapShards, importBucketName, importFileName); + } + + /** + * Creates a new {@link RdeDomainImportMapper} + */ + private RdeDomainImportMapper createMapper() { + return new RdeDomainImportMapper(importBucketName); + } + + /** Mapper to import domains from an escrow file. */ + public static class RdeDomainImportMapper + extends Mapper, Void, Void> { + + private static final long serialVersionUID = -7645091075256589374L; + + private final String importBucketName; + private transient RdeImportUtils importUtils; + + public RdeDomainImportMapper(String importBucketName) { + this.importBucketName = importBucketName; + } + + private RdeImportUtils getImportUtils() { + if (importUtils == null) { + importUtils = createRdeImportUtils(); + } + return importUtils; + } + + /** + * Creates a new instance of RdeImportUtils. + */ + private RdeImportUtils createRdeImportUtils() { + return new RdeImportUtils( + ofy(), + new SystemClock(), + importBucketName, + new GcsUtils(GCS_SERVICE, ConfigModule.provideGcsBufferSize())); + } + + @Override + public void map(JaxbFragment fragment) { + final XjcRdeDomain xjcDomain = fragment.getInstance().getValue(); + try { + logger.infofmt("Converting xml for domain %s", xjcDomain.getName()); + // Record number of attempted map operations + getContext().incrementCounter("domain imports attempted"); + logger.infofmt("Saving domain %s", xjcDomain.getName()); + ofy().transact(new VoidWork() { + @Override + public void vrun() { + DomainResource domain = + XjcToDomainResourceConverter.convertDomain(xjcDomain); + getImportUtils().importDomain(domain); + } + }); + // Record the number of domains imported + getContext().incrementCounter("domains saved"); + logger.infofmt("Domain %s was imported successfully", xjcDomain.getName()); + } catch (ResourceExistsException e) { + // Record the number of domains already in the registry + getContext().incrementCounter("domains skipped"); + logger.infofmt("Domain %s already exists", xjcDomain.getName()); + } catch (Exception e) { + getContext().incrementCounter("domain import errors"); + throw new DomainImportException(xjcDomain.getName(), xjcDomain.toString(), e); + } + } + } + + private static class DomainImportException extends RuntimeException { + DomainImportException(String domainName, String xml, Throwable cause) { + super(String.format("Error processing domain %s; xml=%s", domainName, xml), cause); + } + } +} diff --git a/java/google/registry/rde/imports/RdeDomainInput.java b/java/google/registry/rde/imports/RdeDomainInput.java new file mode 100644 index 000000000..bc49145aa --- /dev/null +++ b/java/google/registry/rde/imports/RdeDomainInput.java @@ -0,0 +1,122 @@ +// Copyright 2017 The Nomulus 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 google.registry.rde.imports; + +import static com.google.common.math.IntMath.divide; +import static java.math.RoundingMode.CEILING; +import static java.math.RoundingMode.FLOOR; + +import com.google.appengine.tools.cloudstorage.GcsFilename; +import com.google.appengine.tools.cloudstorage.GcsService; +import com.google.appengine.tools.cloudstorage.GcsServiceFactory; +import com.google.appengine.tools.cloudstorage.RetryParams; +import com.google.appengine.tools.mapreduce.Input; +import com.google.appengine.tools.mapreduce.InputReader; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableList; +import google.registry.config.RegistryConfig.ConfigModule; +import google.registry.gcs.GcsUtils; +import google.registry.model.domain.DomainResource; +import google.registry.rde.imports.RdeParser.RdeHeader; +import google.registry.xjc.JaxbFragment; +import google.registry.xjc.rdedomain.XjcRdeDomainElement; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +/** + * A MapReduce {@link Input} that imports {@link DomainResource} objects from an escrow file. + * + *

If a mapShards parameter has been specified, up to that many readers will be created + * so that each map shard has one reader. If a mapShards parameter has not been specified, a + * default number of readers will be created. + */ +public class RdeDomainInput extends Input> { + + private static final long serialVersionUID = -366966393494008712L; + private static final GcsService GCS_SERVICE = + GcsServiceFactory.createGcsService(RetryParams.getDefaultInstance()); + + /** + * Default number of readers if map shards are not specified. + */ + private static final int DEFAULT_READERS = 50; + + /** + * Minimum number of records per reader. + */ + private static final int MINIMUM_RECORDS_PER_READER = 100; + + /** + * Optional argument to explicitly specify the number of readers. + */ + private final int numReaders; + private final String importBucketName; + private final String importFileName; + + public RdeDomainInput( + Optional mapShards, String importBucketName, String importFileName) { + this.numReaders = mapShards.or(DEFAULT_READERS); + this.importBucketName = importBucketName; + this.importFileName = importFileName; + } + + @Override + public List>> createReaders() + throws IOException { + int numReaders = this.numReaders; + RdeHeader header = newParser().getHeader(); + int numberOfDomains = header.getDomainCount().intValue(); + if (numberOfDomains / numReaders < MINIMUM_RECORDS_PER_READER) { + numReaders = divide(numberOfDomains, MINIMUM_RECORDS_PER_READER, FLOOR); + // use at least one reader + numReaders = Math.max(numReaders, 1); + } + ImmutableList.Builder builder = new ImmutableList.Builder<>(); + int domainsPerReader = + Math.max(MINIMUM_RECORDS_PER_READER, divide(numberOfDomains, numReaders, CEILING)); + int offset = 0; + for (int i = 0; i < numReaders; i++) { + builder = builder.add(newReader(offset, domainsPerReader)); + offset += domainsPerReader; + } + return builder.build(); + } + + private RdeDomainReader newReader(int offset, int maxResults) { + return new RdeDomainReader(importBucketName, importFileName, offset, maxResults); + } + + private RdeParser newParser() { + GcsUtils utils = new GcsUtils(GCS_SERVICE, ConfigModule.provideGcsBufferSize()); + GcsFilename filename = new GcsFilename(importBucketName, importFileName); + try (InputStream xmlInput = utils.openInputStream(filename)) { + return new RdeParser(xmlInput); + } catch (Exception e) { + throw new InitializationException( + String.format("Error opening rde file %s/%s", importBucketName, importFileName), e); + } + } + + /** + * Thrown when the input cannot initialize properly. + */ + private static class InitializationException extends RuntimeException { + + public InitializationException(String message, Throwable cause) { + super(message, cause); + } + } +} diff --git a/java/google/registry/rde/imports/RdeDomainReader.java b/java/google/registry/rde/imports/RdeDomainReader.java new file mode 100644 index 000000000..630a27840 --- /dev/null +++ b/java/google/registry/rde/imports/RdeDomainReader.java @@ -0,0 +1,109 @@ +// Copyright 2017 The Nomulus 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 google.registry.rde.imports; + +import com.google.appengine.tools.cloudstorage.GcsFilename; +import com.google.appengine.tools.cloudstorage.GcsService; +import com.google.appengine.tools.cloudstorage.GcsServiceFactory; +import com.google.appengine.tools.cloudstorage.RetryParams; +import com.google.appengine.tools.mapreduce.InputReader; +import google.registry.config.RegistryConfig.ConfigModule; +import google.registry.gcs.GcsUtils; +import google.registry.util.FormattingLogger; +import google.registry.xjc.JaxbFragment; +import google.registry.xjc.rdedomain.XjcRdeDomainElement; +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.util.NoSuchElementException; + +/** Mapreduce {@link InputReader} for reading domains from escrow files */ +public class RdeDomainReader extends InputReader> + implements Serializable { + + private static final long serialVersionUID = -2175777052970160122L; + private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass(); + private static final GcsService GCS_SERVICE = + GcsServiceFactory.createGcsService(RetryParams.getDefaultInstance()); + + final String importBucketName; + final String importFileName; + final int offset; + final int maxResults; + + private int count = 0; + + transient RdeParser parser; + + /** + * Creates a new instance of {@link RdeParser} + */ + private RdeParser newParser() { + GcsUtils utils = new GcsUtils(GCS_SERVICE, ConfigModule.provideGcsBufferSize()); + GcsFilename filename = new GcsFilename(importBucketName, importFileName); + InputStream xmlInput = utils.openInputStream(filename); + try { + RdeParser parser = new RdeParser(xmlInput); + // skip the file offset and count + // if count is greater than 0, the reader has been rehydrated after doing some work. + // skip any already processed records. + parser.skipDomains(offset + count); + return parser; + } catch (Exception e) { + logger.severefmt(e, "Error opening rde file %s/%s", importBucketName, importFileName); + throw new RuntimeException(e); + } + } + + public RdeDomainReader( + String importBucketName, + String importFileName, + int offset, + int maxResults) { + this.importBucketName = importBucketName; + this.importFileName = importFileName; + this.offset = offset; + this.maxResults = maxResults; + } + + @Override + public JaxbFragment next() throws IOException { + if (count < maxResults) { + if (parser == null) { + parser = newParser(); + if (parser.isAtDomain()) { + return readDomain(); + } + } + if (parser.nextDomain()) { + return readDomain(); + } + } + throw new NoSuchElementException(); + } + + private JaxbFragment readDomain() { + count++; + return JaxbFragment.create(new XjcRdeDomainElement(parser.getDomain())); + } + + @Override + public void endSlice() throws IOException { + super.endSlice(); + if (parser != null) { + parser.close(); + } + } +} diff --git a/java/google/registry/rde/imports/RdeHostImportAction.java b/java/google/registry/rde/imports/RdeHostImportAction.java index efd9466c0..cbdeb3e2d 100644 --- a/java/google/registry/rde/imports/RdeHostImportAction.java +++ b/java/google/registry/rde/imports/RdeHostImportAction.java @@ -24,6 +24,7 @@ import com.google.appengine.tools.cloudstorage.RetryParams; import com.google.appengine.tools.mapreduce.Mapper; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; +import com.googlecode.objectify.VoidWork; import google.registry.config.RegistryConfig.Config; import google.registry.config.RegistryConfig.ConfigModule; import google.registry.gcs.GcsUtils; @@ -32,7 +33,11 @@ import google.registry.model.host.HostResource; import google.registry.request.Action; import google.registry.request.Parameter; import google.registry.request.Response; +import google.registry.util.FormattingLogger; import google.registry.util.SystemClock; +import google.registry.xjc.JaxbFragment; +import google.registry.xjc.rdehost.XjcRdeHost; +import google.registry.xjc.rdehost.XjcRdeHostElement; import javax.inject.Inject; /** @@ -43,6 +48,7 @@ import javax.inject.Inject; @Action(path = "/_dr/task/importRdeHosts") public class RdeHostImportAction implements Runnable { + private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass(); private static final GcsService GCS_SERVICE = GcsServiceFactory.createGcsService(RetryParams.getDefaultInstance()); @@ -77,9 +83,11 @@ public class RdeHostImportAction implements Runnable { } /** Mapper to import hosts from an escrow file. */ - public static class RdeHostImportMapper extends Mapper { + public static class RdeHostImportMapper + extends Mapper, Void, Void> { private static final long serialVersionUID = -2898753709127134419L; + private final String importBucketName; private transient RdeImportUtils importUtils; @@ -106,8 +114,39 @@ public class RdeHostImportAction implements Runnable { } @Override - public void map(HostResource host) { - getImportUtils().importHost(host); + public void map(JaxbFragment fragment) { + final XjcRdeHost xjcHost = fragment.getInstance().getValue(); + try { + logger.infofmt("Converting xml for host %s", xjcHost.getName()); + // Record number of attempted map operations + getContext().incrementCounter("host imports attempted"); + logger.infofmt("Saving host %s", xjcHost.getName()); + ofy().transact(new VoidWork() { + + @Override + public void vrun() { + HostResource host = XjcToHostResourceConverter.convert(xjcHost); + getImportUtils().importHost(host); + } + }); + // Record number of hosts imported + getContext().incrementCounter("hosts saved"); + logger.infofmt("Host %s was imported successfully", xjcHost.getName()); + } catch (ResourceExistsException e) { + // Record the number of hosts already in the registry + getContext().incrementCounter("hosts skipped"); + logger.infofmt("Host %s already exists", xjcHost.getName()); + } catch (Exception e) { + // Record the number of hosts with unexpected errors + getContext().incrementCounter("host import errors"); + throw new HostImportException(xjcHost.getName(), xjcHost.toString(), e); + } + } + } + + private static class HostImportException extends RuntimeException { + HostImportException(String hostName, String xml, Throwable cause) { + super(String.format("Error processing host %s; xml=%s", hostName, xml), cause); } } } diff --git a/java/google/registry/rde/imports/RdeHostInput.java b/java/google/registry/rde/imports/RdeHostInput.java index 9ee4e9c5b..4b080c99b 100644 --- a/java/google/registry/rde/imports/RdeHostInput.java +++ b/java/google/registry/rde/imports/RdeHostInput.java @@ -28,6 +28,8 @@ import google.registry.config.RegistryConfig.ConfigModule; import google.registry.gcs.GcsUtils; import google.registry.model.host.HostResource; import google.registry.rde.imports.RdeParser.RdeHeader; +import google.registry.xjc.JaxbFragment; +import google.registry.xjc.rdehost.XjcRdeHostElement; import java.io.IOException; import java.io.InputStream; import java.util.List; @@ -39,7 +41,7 @@ import java.util.List; * so that each map shard has one reader. If a mapShards parameter has not been specified, a * default number of readers will be created. */ -public class RdeHostInput extends Input { +public class RdeHostInput extends Input> { private static final long serialVersionUID = 9218225041307602452L; @@ -63,13 +65,6 @@ public class RdeHostInput extends Input { private final String importBucketName; private final String importFileName; - /** - * Creates a new {@link RdeHostInput} - * - * @param mapShards Number of readers that should be created - * @param importBucketName Name of GCS bucket for escrow file imports - * @param importFileName Name of escrow file in GCS - */ public RdeHostInput(Optional mapShards, String importBucketName, String importFileName) { this.numReaders = mapShards.or(DEFAULT_READERS); @@ -79,7 +74,8 @@ public class RdeHostInput extends Input { } @Override - public List> createReaders() throws IOException { + public List>> createReaders() + throws IOException { int numReaders = this.numReaders; RdeHeader header = createParser().getHeader(); int numberOfHosts = header.getHostCount().intValue(); @@ -99,16 +95,10 @@ public class RdeHostInput extends Input { return builder.build(); } - /** - * Creates a new instance of {@link RdeHostReader} - */ private RdeHostReader createReader(int offset, int maxResults) { return new RdeHostReader(importBucketName, importFileName, offset, maxResults); } - /** - * Creates a new instance of {@link RdeParser} - */ private RdeParser createParser() { GcsUtils utils = new GcsUtils(GCS_SERVICE, ConfigModule.provideGcsBufferSize()); GcsFilename filename = new GcsFilename(importBucketName, importFileName); diff --git a/java/google/registry/rde/imports/RdeHostReader.java b/java/google/registry/rde/imports/RdeHostReader.java index 6a86f4e8e..d996b99b4 100644 --- a/java/google/registry/rde/imports/RdeHostReader.java +++ b/java/google/registry/rde/imports/RdeHostReader.java @@ -21,8 +21,9 @@ import com.google.appengine.tools.cloudstorage.RetryParams; import com.google.appengine.tools.mapreduce.InputReader; import google.registry.config.RegistryConfig.ConfigModule; import google.registry.gcs.GcsUtils; -import google.registry.model.host.HostResource; import google.registry.util.FormattingLogger; +import google.registry.xjc.JaxbFragment; +import google.registry.xjc.rdehost.XjcRdeHostElement; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; @@ -31,7 +32,8 @@ import javax.annotation.concurrent.NotThreadSafe; /** Mapreduce {@link InputReader} for reading hosts from escrow files */ @NotThreadSafe -public class RdeHostReader extends InputReader implements Serializable { +public class RdeHostReader extends InputReader> + implements Serializable { private static final long serialVersionUID = 3037264959150412846L; @@ -80,23 +82,26 @@ public class RdeHostReader extends InputReader implements Serializ } @Override - public HostResource next() throws IOException { + public JaxbFragment next() throws IOException { if (count < maxResults) { if (parser == null) { parser = newParser(); if (parser.isAtHost()) { - count++; - return XjcToHostResourceConverter.convert(parser.getHost()); + return readHost(); } } if (parser.nextHost()) { - count++; - return XjcToHostResourceConverter.convert(parser.getHost()); + return readHost(); } } throw new NoSuchElementException(); } + private JaxbFragment readHost() { + count++; + return JaxbFragment.create(new XjcRdeHostElement(parser.getHost())); + } + @Override public void endSlice() throws IOException { super.endSlice(); diff --git a/java/google/registry/rde/imports/RdeImportUtils.java b/java/google/registry/rde/imports/RdeImportUtils.java index 92587e489..32a7385ed 100644 --- a/java/google/registry/rde/imports/RdeImportUtils.java +++ b/java/google/registry/rde/imports/RdeImportUtils.java @@ -16,14 +16,17 @@ package google.registry.rde.imports; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; +import static google.registry.util.DateTimeUtils.START_OF_TIME; import com.google.appengine.tools.cloudstorage.GcsFilename; +import com.google.common.io.BaseEncoding; import com.googlecode.objectify.Key; -import com.googlecode.objectify.Work; import google.registry.config.RegistryConfig.Config; import google.registry.gcs.GcsUtils; import google.registry.model.EppResource; import google.registry.model.contact.ContactResource; +import google.registry.model.domain.DomainResource; +import google.registry.model.eppcommon.Trid; import google.registry.model.host.HostResource; import google.registry.model.index.EppResourceIndex; import google.registry.model.index.ForeignKeyIndex; @@ -37,6 +40,7 @@ import google.registry.util.FormattingLogger; import google.registry.xjc.rderegistrar.XjcRdeRegistrar; import java.io.IOException; import java.io.InputStream; +import java.util.UUID; import javax.inject.Inject; import javax.xml.bind.JAXBException; import javax.xml.stream.XMLStreamException; @@ -62,41 +66,29 @@ public class RdeImportUtils { this.escrowBucketName = escrowBucketName; } - private boolean importEppResource(final T resource, final String type) { + private void importEppResource(final T resource, final String type) { @SuppressWarnings("unchecked") - final Class resourceClass = (Class) resource.getClass(); - return ofy.transact( - new Work() { - @Override - public Boolean run() { - EppResource existing = ofy.load().key(Key.create(resource)).now(); - if (existing == null) { - ForeignKeyIndex existingForeignKeyIndex = - ForeignKeyIndex.load( - resourceClass, resource.getForeignKey(), clock.nowUtc()); - // foreign key index should not exist, since existing resource was not found. - checkState( - existingForeignKeyIndex == null, - "New %s resource has existing foreign key index; foreignKey=%s, repoId=%s", - type, - resource.getForeignKey(), - resource.getRepoId()); - ofy.save().entity(resource); - ofy.save().entity(ForeignKeyIndex.create(resource, resource.getDeletionTime())); - ofy.save().entity(EppResourceIndex.create(Key.create(resource))); - logger.infofmt( - "Imported %s resource - ROID=%s, id=%s", - type, resource.getRepoId(), resource.getForeignKey()); - return true; - } else if (!existing.getRepoId().equals(resource.getRepoId())) { - logger.warningfmt( - "Existing %s with same id but different ROID. " - + "id=%s, existing ROID=%s, new ROID=%s", - type, resource.getForeignKey(), existing.getRepoId(), resource.getRepoId()); - } - return false; - } - }); + Class resourceClass = (Class) resource.getClass(); + EppResource existing = ofy.load().key(Key.create(resource)).now(); + if (existing != null) { + // This will roll back the transaction and prevent duplicate history entries from being saved. + throw new ResourceExistsException(); + } + ForeignKeyIndex existingForeignKeyIndex = + ForeignKeyIndex.load(resourceClass, resource.getForeignKey(), START_OF_TIME); + // ForeignKeyIndex should never have existed, since existing resource was not found. + checkState( + existingForeignKeyIndex == null, + "New %s resource has existing foreign key index; foreignKey=%s, repoId=%s", + type, + resource.getForeignKey(), + resource.getRepoId()); + ofy.save().entity(resource); + ofy.save().entity(ForeignKeyIndex.create(resource, resource.getDeletionTime())); + ofy.save().entity(EppResourceIndex.create(Key.create(resource))); + logger.infofmt( + "Imported %s resource - ROID=%s, id=%s", + type, resource.getRepoId(), resource.getForeignKey()); } /** @@ -106,11 +98,9 @@ public class RdeImportUtils { * *

If the host is imported, {@link ForeignKeyIndex} and {@link EppResourceIndex} are also * created. - * - * @return true if the host was created or updated, false otherwise. */ - public boolean importHost(final HostResource resource) { - return importEppResource(resource, "host"); + public void importHost(final HostResource resource) { + importEppResource(resource, "host"); } /** @@ -120,11 +110,21 @@ public class RdeImportUtils { * *

If the contact is imported, {@link ForeignKeyIndex} and {@link EppResourceIndex} are also * created. - * - * @return true if the contact was created or updated, false otherwise. */ - public boolean importContact(final ContactResource resource) { - return importEppResource(resource, "contact"); + public void importContact(final ContactResource resource) { + importEppResource(resource, "contact"); + } + + /** + * Imports a domain from an escrow file. + * + *

The domain will only be imported if it has not been previously imported. + * + *

If the domain is imported, {@link ForeignKeyIndex} and {@link EppResourceIndex} are also + * created. + */ + public void importDomain(final DomainResource resource) { + importEppResource(resource, "domain"); } /** @@ -146,7 +146,7 @@ public class RdeImportUtils { * @throws IllegalArgumentException if the escrow file cannot be imported */ public void validateEscrowFileForImport(String escrowFilePath) throws IOException { - // TODO (wolfgang): Add validation method for IDN tables + // TODO (wolfgang@donuts.co): Add validation method for IDN tables try (InputStream input = gcsUtils.openInputStream(new GcsFilename(escrowBucketName, escrowFilePath))) { try (RdeParser parser = new RdeParser(input)) { @@ -176,4 +176,12 @@ public class RdeImportUtils { } } } + + /** Generates a random {@link Trid} for rde import. */ + public static Trid generateTridForImport() { + // Client trids must be a token between 3 and 64 characters long + // Base64 encoded UUID string meets this requirement + return Trid.create( + "Import_" + BaseEncoding.base64().encode(UUID.randomUUID().toString().getBytes())); + } } diff --git a/java/google/registry/rde/imports/RdeImportsModule.java b/java/google/registry/rde/imports/RdeImportsModule.java index 01e1d58df..6241a0a5b 100644 --- a/java/google/registry/rde/imports/RdeImportsModule.java +++ b/java/google/registry/rde/imports/RdeImportsModule.java @@ -28,9 +28,11 @@ import javax.servlet.http.HttpServletRequest; @Module public final class RdeImportsModule { + static final String PATH = "path"; + @Provides - @Parameter("path") + @Parameter(PATH) static String providePath(HttpServletRequest req) { - return RequestParameters.extractRequiredParameter(req, "path"); + return RequestParameters.extractRequiredParameter(req, PATH); } } diff --git a/java/google/registry/rde/imports/ResourceExistsException.java b/java/google/registry/rde/imports/ResourceExistsException.java new file mode 100644 index 000000000..8a2e701b5 --- /dev/null +++ b/java/google/registry/rde/imports/ResourceExistsException.java @@ -0,0 +1,23 @@ +// Copyright 2017 The Nomulus 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 google.registry.rde.imports; + +/** + * Indicates that a resource already exists and was not imported. + */ +public class ResourceExistsException extends RuntimeException { + + private static final long serialVersionUID = -9180381693364904061L; +} diff --git a/java/google/registry/rde/imports/XjcToContactResourceConverter.java b/java/google/registry/rde/imports/XjcToContactResourceConverter.java index f9f9b7552..43dc77a9f 100644 --- a/java/google/registry/rde/imports/XjcToContactResourceConverter.java +++ b/java/google/registry/rde/imports/XjcToContactResourceConverter.java @@ -16,11 +16,14 @@ package google.registry.rde.imports; import static com.google.common.base.Predicates.equalTo; import static com.google.common.base.Predicates.not; +import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.rde.imports.RdeImportUtils.generateTridForImport; import com.google.common.base.Function; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; +import com.googlecode.objectify.Key; import google.registry.model.contact.ContactAddress; import google.registry.model.contact.ContactPhoneNumber; import google.registry.model.contact.ContactResource; @@ -28,6 +31,7 @@ import google.registry.model.contact.Disclose; import google.registry.model.contact.Disclose.PostalInfoChoice; import google.registry.model.contact.PostalInfo; import google.registry.model.eppcommon.StatusValue; +import google.registry.model.reporting.HistoryEntry; import google.registry.model.transfer.TransferData; import google.registry.model.transfer.TransferStatus; import google.registry.util.XmlToEnumMapper; @@ -39,11 +43,12 @@ import google.registry.xjc.contact.XjcContactPostalInfoEnumType; import google.registry.xjc.contact.XjcContactPostalInfoType; import google.registry.xjc.contact.XjcContactStatusType; import google.registry.xjc.rdecontact.XjcRdeContact; +import google.registry.xjc.rdecontact.XjcRdeContactElement; import google.registry.xjc.rdecontact.XjcRdeContactTransferDataType; import javax.annotation.Nullable; /** Utility class that converts an {@link XjcRdeContact} into a {@link ContactResource}. */ -final class XjcToContactResourceConverter { +final class XjcToContactResourceConverter extends XjcToEppResourceConverter { private static final XmlToEnumMapper POSTAL_INFO_TYPE_MAPPER = XmlToEnumMapper.create(PostalInfo.Type.values()); @@ -68,6 +73,18 @@ final class XjcToContactResourceConverter { /** Converts {@link XjcRdeContact} to {@link ContactResource}. */ static ContactResource convertContact(XjcRdeContact contact) { + ofy().save().entity( + new HistoryEntry.Builder() + .setType(HistoryEntry.Type.RDE_IMPORT) + .setClientId(contact.getClID()) + .setTrid(generateTridForImport()) + .setModificationTime(ofy().getTransactionTime()) + .setXmlBytes(getObjectXml(new XjcRdeContactElement(contact))) + .setBySuperuser(true) + .setReason("RDE Import") + .setRequestedByRegistrar(false) + .setParent(Key.create(null, ContactResource.class, contact.getRoid())) + .build()); return new ContactResource.Builder() .setRepoId(contact.getRoid()) .setStatusValues( diff --git a/java/google/registry/rde/imports/XjcToDomainResourceConverter.java b/java/google/registry/rde/imports/XjcToDomainResourceConverter.java index c88062b68..fa37952f1 100644 --- a/java/google/registry/rde/imports/XjcToDomainResourceConverter.java +++ b/java/google/registry/rde/imports/XjcToDomainResourceConverter.java @@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.transform; import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.rde.imports.RdeImportUtils.generateTridForImport; import static google.registry.util.DateTimeUtils.END_OF_TIME; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; @@ -38,7 +39,6 @@ import google.registry.model.domain.GracePeriod; import google.registry.model.domain.rgp.GracePeriodStatus; import google.registry.model.domain.secdns.DelegationSignerData; import google.registry.model.eppcommon.StatusValue; -import google.registry.model.eppcommon.Trid; import google.registry.model.host.HostResource; import google.registry.model.index.ForeignKeyIndex; import google.registry.model.poll.PollMessage; @@ -120,14 +120,12 @@ final class XjcToDomainResourceConverter extends XjcToEppResourceConverter { @Override public GracePeriod apply(XjcRgpStatusType gracePeriodStatus) { - // TODO: (wolfgang) address these items in code review: - // verify that this logic is correct switch (gracePeriodStatus.getS()) { case ADD_PERIOD: return GracePeriod.createWithoutBillingEvent( GracePeriodStatus.ADD, domain.getCrDate().plus(this.tld.getAddGracePeriodLength()), - domain.getCrRr().getClient()); + domain.getCrRr().getValue()); case AUTO_RENEW_PERIOD: return GracePeriod.createForRecurring( GracePeriodStatus.AUTO_RENEW, @@ -180,6 +178,8 @@ final class XjcToDomainResourceConverter extends XjcToEppResourceConverter { .setCurrentSponsorClientId(domain.getClID()) .setCreationClientId(domain.getCrRr().getValue()) .setCreationTime(domain.getCrDate()) + .setAutorenewPollMessage(Key.create(pollMessage)) + .setAutorenewBillingEvent(Key.create(autoRenewBillingEvent)) .setRegistrationExpirationTime(domain.getExDate()) .setLastEppUpdateTime(domain.getUpDate()) .setLastEppUpdateClientId(domain.getUpRr() == null ? null : domain.getUpRr().getValue()) @@ -235,7 +235,7 @@ final class XjcToDomainResourceConverter extends XjcToEppResourceConverter { new HistoryEntry.Builder() .setType(HistoryEntry.Type.RDE_IMPORT) .setClientId(domain.getClID()) - .setTrid(Trid.create(null)) + .setTrid(generateTridForImport()) .setModificationTime(DateTime.now()) .setXmlBytes(getObjectXml(element)) .setBySuperuser(true) diff --git a/java/google/registry/rde/imports/XjcToEppResourceConverter.java b/java/google/registry/rde/imports/XjcToEppResourceConverter.java index 5f809d11a..329f9a94f 100644 --- a/java/google/registry/rde/imports/XjcToEppResourceConverter.java +++ b/java/google/registry/rde/imports/XjcToEppResourceConverter.java @@ -14,49 +14,24 @@ package google.registry.rde.imports; -import com.google.common.base.Joiner; +import static java.nio.charset.StandardCharsets.UTF_8; + import google.registry.model.EppResource; +import google.registry.xjc.XjcXmlTransformer; +import google.registry.xml.XmlException; import java.io.ByteArrayOutputStream; -import java.util.Arrays; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBElement; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Marshaller; /** * Base class for Jaxb object to {@link EppResource} converters */ public abstract class XjcToEppResourceConverter { - /** List of packages to initialize JAXBContext. **/ - private static final String JAXB_CONTEXT_PACKAGES = Joiner.on(":") - .join(Arrays.asList( - "google.registry.xjc.contact", - "google.registry.xjc.domain", - "google.registry.xjc.host", - "google.registry.xjc.mark", - "google.registry.xjc.rde", - "google.registry.xjc.rdecontact", - "google.registry.xjc.rdedomain", - "google.registry.xjc.rdeeppparams", - "google.registry.xjc.rdeheader", - "google.registry.xjc.rdeidn", - "google.registry.xjc.rdenndn", - "google.registry.xjc.rderegistrar", - "google.registry.xjc.smd")); - - /** Creates a {@link Marshaller} for serializing Jaxb objects */ - private static Marshaller createMarshaller() throws JAXBException { - return JAXBContext.newInstance(JAXB_CONTEXT_PACKAGES).createMarshaller(); - } - - protected static byte[] getObjectXml(JAXBElement jaxbElement) { + protected static byte[] getObjectXml(Object jaxbElement) { try { - Marshaller marshaller = createMarshaller(); ByteArrayOutputStream bout = new ByteArrayOutputStream(); - marshaller.marshal(jaxbElement, bout); + XjcXmlTransformer.marshalLenient(jaxbElement, bout, UTF_8); return bout.toByteArray(); - } catch (JAXBException e) { + } catch (XmlException e) { throw new RuntimeException(e); } } diff --git a/java/google/registry/rde/imports/XjcToHostResourceConverter.java b/java/google/registry/rde/imports/XjcToHostResourceConverter.java index eb7d547ce..40253cc17 100644 --- a/java/google/registry/rde/imports/XjcToHostResourceConverter.java +++ b/java/google/registry/rde/imports/XjcToHostResourceConverter.java @@ -16,21 +16,27 @@ package google.registry.rde.imports; import static com.google.common.base.Predicates.equalTo; import static com.google.common.base.Predicates.not; +import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.rde.imports.RdeImportUtils.generateTridForImport; import com.google.common.base.Function; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.net.InetAddresses; +import com.googlecode.objectify.Key; import google.registry.model.eppcommon.StatusValue; import google.registry.model.host.HostResource; +import google.registry.model.reporting.HistoryEntry; import google.registry.xjc.host.XjcHostAddrType; import google.registry.xjc.host.XjcHostStatusType; import google.registry.xjc.rdehost.XjcRdeHost; +import google.registry.xjc.rdehost.XjcRdeHostElement; import java.net.InetAddress; +import org.joda.time.DateTime; /** Utility class that converts an {@link XjcRdeHost} into a {@link HostResource}. */ -public class XjcToHostResourceConverter { +public class XjcToHostResourceConverter extends XjcToEppResourceConverter { private static final Function STATUS_VALUE_CONVERTER = new Function() { @@ -49,6 +55,19 @@ public class XjcToHostResourceConverter { }; static HostResource convert(XjcRdeHost host) { + // First create and save history entry + ofy().save().entity( + new HistoryEntry.Builder() + .setType(HistoryEntry.Type.RDE_IMPORT) + .setClientId(host.getClID()) + .setTrid(generateTridForImport()) + .setModificationTime(DateTime.now()) + .setXmlBytes(getObjectXml(new XjcRdeHostElement(host))) + .setBySuperuser(true) + .setReason("RDE Import") + .setRequestedByRegistrar(false) + .setParent(Key.create(null, HostResource.class, host.getRoid())) + .build()); return new HostResource.Builder() .setFullyQualifiedHostName(host.getName()) .setRepoId(host.getRoid()) diff --git a/java/google/registry/xjc/JaxbFragment.java b/java/google/registry/xjc/JaxbFragment.java new file mode 100644 index 000000000..f6f6dd64a --- /dev/null +++ b/java/google/registry/xjc/JaxbFragment.java @@ -0,0 +1,106 @@ +// Copyright 2017 The Nomulus 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 google.registry.xjc; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import google.registry.xml.XmlException; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +/** + * JAXB element wrapper for java object serialization. + * + * Instances of {@link JaxbFragment} wrap a non-serializable JAXB element instance, and provide + * hooks into the java object serialization process that allow the elements to be safely + * marshalled and unmarshalled using {@link ObjectOutputStream} and {@link ObjectInputStream}, + * respectively. + */ +public class JaxbFragment implements Serializable { + + private static final long serialVersionUID = 5651243983008818813L; + + private T instance; + + /** Stores a JAXB element in a {@link JaxbFragment} */ + public static JaxbFragment create(T object) { + JaxbFragment fragment = new JaxbFragment<>(); + fragment.instance = object; + return fragment; + } + + /** Serializes a JAXB element into xml bytes. */ + private static byte[] freezeInstance(T instance) throws IOException { + try { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + XjcXmlTransformer.marshalLenient(instance, bout, UTF_8); + return bout.toByteArray(); + } catch (XmlException e) { + throw new IOException(e); + } + } + + /** Deserializes a JAXB element from xml bytes. */ + private static T unfreezeInstance(byte[] instanceData, Class instanceType) + throws IOException { + try { + ByteArrayInputStream bin = new ByteArrayInputStream(instanceData); + @SuppressWarnings("unchecked") + T instance = (T) XjcXmlTransformer.unmarshal(instanceType, bin); + return instance; + } catch (XmlException e) { + throw new IOException(e); + } + } + + /** + * Retrieves the JAXB element that is wrapped by this fragment. + */ + public T getInstance() { + return instance; + } + + @Override + public String toString() { + try { + return new String(freezeInstance(instance), UTF_8); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void writeObject(ObjectOutputStream out) throws IOException { + // write instanceType, then instanceData + out.writeObject(instance.getClass()); + out.writeObject(freezeInstance(instance)); + } + + private void readObject(ObjectInputStream in) throws IOException { + // read instanceType, then instanceData + Class instanceType; + byte[] instanceData; + try { + instanceType = (Class) in.readObject(); + instanceData = (byte[]) in.readObject(); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + instance = unfreezeInstance(instanceData, instanceType); + } +} diff --git a/javatests/google/registry/rde/imports/RdeContactImportActionTest.java b/javatests/google/registry/rde/imports/RdeContactImportActionTest.java index f50132726..72450f4a5 100644 --- a/javatests/google/registry/rde/imports/RdeContactImportActionTest.java +++ b/javatests/google/registry/rde/imports/RdeContactImportActionTest.java @@ -16,6 +16,10 @@ package google.registry.rde.imports; import static com.google.common.truth.Truth.assertThat; import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.testing.DatastoreHelper.getHistoryEntries; +import static google.registry.testing.DatastoreHelper.newContactResource; +import static google.registry.testing.DatastoreHelper.persistResource; +import static google.registry.testing.DatastoreHelper.persistSimpleResource; import com.google.appengine.tools.cloudstorage.GcsFilename; import com.google.appengine.tools.cloudstorage.GcsService; @@ -24,10 +28,13 @@ import com.google.appengine.tools.cloudstorage.RetryParams; import com.google.common.base.Optional; import com.google.common.io.ByteSource; import com.google.common.io.ByteStreams; +import com.googlecode.objectify.Key; import google.registry.config.RegistryConfig.ConfigModule; import google.registry.gcs.GcsUtils; import google.registry.mapreduce.MapreduceRunner; import google.registry.model.contact.ContactResource; +import google.registry.model.eppcommon.Trid; +import google.registry.model.reporting.HistoryEntry; import google.registry.request.Response; import google.registry.testing.FakeResponse; import google.registry.testing.mapreduce.MapreduceTestCase; @@ -35,6 +42,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.List; +import org.joda.time.DateTime; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -77,6 +85,69 @@ public class RdeContactImportActionTest extends MapreduceTestCase contacts = ofy().load().type(ContactResource.class).list(); + ContactResource contact = contacts.get(0); + // verify history entry + List historyEntries = getHistoryEntries(contact); + assertThat(historyEntries).hasSize(1); + checkHistoryEntry(historyEntries.get(0), contact); + } + + /** Ensures that a second pass on a contact does not import a new contact. */ + @Test + public void test_mapreduceTwiceDoesNotDuplicateResources() throws Exception { + pushToGcs(DEPOSIT_1_CONTACT); + // Create contact and history entry first + ContactResource existingContact = persistResource( + newContactResource("contact1") + .asBuilder() + .setRepoId("contact1-TEST") + .build()); + persistSimpleResource(createHistoryEntry( + existingContact.getRepoId(), + existingContact.getCurrentSponsorClientId(), + loadContactXml(DEPOSIT_1_CONTACT))); + // Simulate running a second import and verify that the resources + // aren't imported twice (only one host, and one history entry) + runMapreduce(); + List contacts = ofy().load().type(ContactResource.class).list(); + assertThat(contacts).hasSize(1); + ContactResource contact = contacts.get(0); + // verify history entry + List historyEntries = getHistoryEntries(contact); + assertThat(historyEntries).hasSize(1); + checkHistoryEntry(historyEntries.get(0), contact); + } + + private static HistoryEntry createHistoryEntry(String roid, String clid, byte[] objectXml) { + return new HistoryEntry.Builder() + .setType(HistoryEntry.Type.RDE_IMPORT) + .setClientId(clid) + .setTrid(Trid.create(null)) + .setModificationTime(DateTime.now()) + .setXmlBytes(objectXml) + .setBySuperuser(true) + .setReason("RDE Import") + .setRequestedByRegistrar(false) + .setParent(Key.create(null, ContactResource.class, roid)) + .build(); + } + + /** Verify history entry fields are correct */ + private void checkHistoryEntry(HistoryEntry entry, ContactResource parent) { + assertThat(entry.getType()).isEqualTo(HistoryEntry.Type.RDE_IMPORT); + assertThat(entry.getClientId()).isEqualTo(parent.getCurrentSponsorClientId()); + assertThat(entry.getXmlBytes().length).isGreaterThan(0); + assertThat(entry.getBySuperuser()).isTrue(); + assertThat(entry.getReason()).isEqualTo("RDE Import"); + assertThat(entry.getRequestedByRegistrar()).isEqualTo(false); + assertThat(entry.getParent()).isEqualTo(Key.create(parent)); + } + /** Verifies that contact id and ROID match expected values */ private void checkContact(ContactResource contact) { assertThat(contact.getContactId()).isEqualTo("contact1"); @@ -96,4 +167,12 @@ public class RdeContactImportActionTest extends MapreduceTestCase fragment, String contactId, String repoId) + throws Exception { + assertThat(fragment).isNotNull(); + XjcRdeContact contact = fragment.getInstance().getValue(); + assertThat(contact.getId()).isEqualTo(contactId); + assertThat(contact.getRoid()).isEqualTo(repoId); } /** Gets a new {@link RdeContactReader} with specified offset and maxResults */ diff --git a/javatests/google/registry/rde/imports/RdeDomainImportActionTest.java b/javatests/google/registry/rde/imports/RdeDomainImportActionTest.java new file mode 100644 index 000000000..b63e5c3be --- /dev/null +++ b/javatests/google/registry/rde/imports/RdeDomainImportActionTest.java @@ -0,0 +1,181 @@ +// Copyright 2017 The Nomulus 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 google.registry.rde.imports; + +import static com.google.common.truth.Truth.assertThat; +import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.testing.DatastoreHelper.createTld; +import static google.registry.testing.DatastoreHelper.getHistoryEntries; +import static google.registry.testing.DatastoreHelper.newDomainResource; +import static google.registry.testing.DatastoreHelper.persistActiveContact; +import static google.registry.testing.DatastoreHelper.persistResource; +import static google.registry.testing.DatastoreHelper.persistSimpleResource; + +import com.google.appengine.tools.cloudstorage.GcsFilename; +import com.google.appengine.tools.cloudstorage.GcsService; +import com.google.appengine.tools.cloudstorage.GcsServiceFactory; +import com.google.appengine.tools.cloudstorage.RetryParams; +import com.google.common.base.Optional; +import com.google.common.io.ByteSource; +import com.google.common.io.ByteStreams; +import com.googlecode.objectify.Key; +import google.registry.config.RegistryConfig.ConfigModule; +import google.registry.gcs.GcsUtils; +import google.registry.mapreduce.MapreduceRunner; +import google.registry.model.domain.DomainResource; +import google.registry.model.eppcommon.Trid; +import google.registry.model.reporting.HistoryEntry; +import google.registry.request.Response; +import google.registry.testing.FakeResponse; +import google.registry.testing.mapreduce.MapreduceTestCase; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; +import org.joda.time.DateTime; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; + +/** Unit tests for {@link RdeDomainImportAction}. */ +@RunWith(MockitoJUnitRunner.class) +public class RdeDomainImportActionTest extends MapreduceTestCase { + + private static final ByteSource DEPOSIT_1_DOMAIN = RdeImportsTestData.get("deposit_1_domain.xml"); + private static final String IMPORT_BUCKET_NAME = "import-bucket"; + private static final String IMPORT_FILE_NAME = "escrow-file.xml"; + + private static final GcsService GCS_SERVICE = + GcsServiceFactory.createGcsService(RetryParams.getDefaultInstance()); + + private MapreduceRunner mrRunner; + + private Response response; + + @Before + public void before() throws Exception { + createTld("test"); + persistActiveContact("jd1234"); + persistActiveContact("sh8013"); + response = new FakeResponse(); + mrRunner = makeDefaultRunner(); + action = new RdeDomainImportAction( + mrRunner, + response, + IMPORT_BUCKET_NAME, + IMPORT_FILE_NAME, + Optional.of(3)); + } + + @Test + public void test_mapreduceSuccessfullyImportsDomain() throws Exception { + pushToGcs(DEPOSIT_1_DOMAIN); + runMapreduce(); + List domains = ofy().load().type(DomainResource.class).list(); + assertThat(domains).hasSize(1); + checkDomain(domains.get(0)); + } + + @Test + public void test_mapreduceSuccessfullyCreatesHistoryEntry() throws Exception { + pushToGcs(DEPOSIT_1_DOMAIN); + runMapreduce(); + List domains = ofy().load().type(DomainResource.class).list(); + DomainResource domain = domains.get(0); + // verify history entry + List historyEntries = getHistoryEntries(domain); + assertThat(historyEntries).hasSize(1); + checkHistoryEntry(historyEntries.get(0), domain); + } + + /** Ensures that a second pass on a domain does not import a new domain. */ + @Test + public void test_mapreduceTwiceDoesNotDuplicateResources() throws Exception { + pushToGcs(DEPOSIT_1_DOMAIN); + // Create domain and history entry first + DomainResource existingDomain = + persistResource( + newDomainResource("example1.test").asBuilder().setRepoId("Dexample1-TEST").build()); + persistSimpleResource(createHistoryEntry( + existingDomain.getRepoId(), + existingDomain.getCurrentSponsorClientId(), + loadDomainXml(DEPOSIT_1_DOMAIN))); + // Simulate running a second import and verify that the resources + // aren't imported twice (only one domain, and one history entry) + pushToGcs(DEPOSIT_1_DOMAIN); + runMapreduce(); + List domains = ofy().load().type(DomainResource.class).list(); + assertThat(domains.size()).isEqualTo(1); + DomainResource domain = domains.get(0); + // verify history entry + List historyEntries = getHistoryEntries(domain); + assertThat(historyEntries).hasSize(1); + checkHistoryEntry(historyEntries.get(0), domain); + } + + /** Verify history entry fields are correct */ + private void checkHistoryEntry(HistoryEntry entry, DomainResource parent) { + assertThat(entry.getType()).isEqualTo(HistoryEntry.Type.RDE_IMPORT); + assertThat(entry.getClientId()).isEqualTo(parent.getCurrentSponsorClientId()); + assertThat(entry.getXmlBytes().length).isGreaterThan(0); + assertThat(entry.getBySuperuser()).isTrue(); + assertThat(entry.getReason()).isEqualTo("RDE Import"); + assertThat(entry.getRequestedByRegistrar()).isEqualTo(false); + assertThat(entry.getParent()).isEqualTo(Key.create(parent)); + } + + /** Verifies that domain id and ROID match expected values */ + private void checkDomain(DomainResource domain) { + assertThat(domain.getFullyQualifiedDomainName()).isEqualTo("example1.test"); + assertThat(domain.getRepoId()).isEqualTo("Dexample1-TEST"); + } + + private void runMapreduce() throws Exception { + action.run(); + executeTasksUntilEmpty("mapreduce"); + } + + private void pushToGcs(ByteSource source) throws IOException { + try (OutputStream outStream = + new GcsUtils(GCS_SERVICE, ConfigModule.provideGcsBufferSize()) + .openOutputStream(new GcsFilename(IMPORT_BUCKET_NAME, IMPORT_FILE_NAME)); + InputStream inStream = source.openStream()) { + ByteStreams.copy(inStream, outStream); + } + } + + private static byte[] loadDomainXml(ByteSource source) throws IOException { + byte[] result = new byte[((int) source.size())]; + try (InputStream inStream = source.openStream()) { + ByteStreams.readFully(inStream, result); + } + return result; + } + + private static HistoryEntry createHistoryEntry(String roid, String clid, byte[] objectXml) { + return new HistoryEntry.Builder() + .setType(HistoryEntry.Type.RDE_IMPORT) + .setClientId(clid) + .setTrid(Trid.create(null)) + .setModificationTime(DateTime.now()) + .setXmlBytes(objectXml) + .setBySuperuser(true) + .setReason("RDE Import") + .setRequestedByRegistrar(false) + .setParent(Key.create(null, DomainResource.class, roid)) + .build(); + } +} diff --git a/javatests/google/registry/rde/imports/RdeDomainReaderTest.java b/javatests/google/registry/rde/imports/RdeDomainReaderTest.java new file mode 100644 index 000000000..409e4af70 --- /dev/null +++ b/javatests/google/registry/rde/imports/RdeDomainReaderTest.java @@ -0,0 +1,230 @@ +// Copyright 2017 The Nomulus 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 google.registry.rde.imports; + +import static com.google.common.truth.Truth.assertThat; +import static google.registry.testing.DatastoreHelper.createTld; +import static google.registry.testing.DatastoreHelper.persistActiveContact; + +import com.google.appengine.tools.cloudstorage.GcsFilename; +import com.google.appengine.tools.cloudstorage.GcsService; +import com.google.appengine.tools.cloudstorage.GcsServiceFactory; +import com.google.appengine.tools.cloudstorage.RetryParams; +import com.google.common.io.ByteSource; +import com.google.common.io.ByteStreams; +import google.registry.config.RegistryConfig.ConfigModule; +import google.registry.gcs.GcsUtils; +import google.registry.testing.AppEngineRule; +import google.registry.testing.ExceptionRule; +import google.registry.xjc.JaxbFragment; +import google.registry.xjc.rdedomain.XjcRdeDomain; +import google.registry.xjc.rdedomain.XjcRdeDomainElement; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.util.NoSuchElementException; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link RdeDomainReader} */ +@RunWith(JUnit4.class) +public class RdeDomainReaderTest { + + private static final ByteSource DEPOSIT_1_DOMAIN = RdeImportsTestData.get("deposit_1_domain.xml"); + private static final ByteSource DEPOSIT_3_DOMAIN = RdeImportsTestData.get("deposit_3_domain.xml"); + private static final ByteSource DEPOSIT_4_DOMAIN = RdeImportsTestData.get("deposit_4_domain.xml"); + private static final ByteSource DEPOSIT_10_DOMAIN = + RdeImportsTestData.get("deposit_10_domain.xml"); + private static final String IMPORT_BUCKET_NAME = "rde-import"; + private static final String IMPORT_FILE_NAME = "escrow-file.xml"; + + private static final GcsService GCS_SERVICE = + GcsServiceFactory.createGcsService(RetryParams.getDefaultInstance()); + + @Rule + public final AppEngineRule appEngine = AppEngineRule.builder() + .withDatastore() + .build(); + + @Rule + public final ExceptionRule thrown = new ExceptionRule(); + + @Before + public void before() { + createTld("test"); + persistActiveContact("jd1234"); + persistActiveContact("sh8013"); + } + + /** Reads at least one result at 0 offset 1 maxResults */ + @Test + public void testZeroOffsetOneResult_readsOne() throws Exception { + pushToGcs(DEPOSIT_1_DOMAIN); + RdeDomainReader reader = getReader(0, 1); + checkDomain(reader.next(), "example1.test", "Dexample1-TEST"); + } + + /** Reads at most one at 0 offset 1 maxResults */ + @Test + public void testZeroOffsetOneResult_stopsAfterOne() throws Exception { + pushToGcs(DEPOSIT_3_DOMAIN); + RdeDomainReader reader = getReader(0, 1); + reader.next(); + thrown.expect(NoSuchElementException.class); + reader.next(); + } + + /** Skips already-processed records after rehydration */ + @Test + public void testZeroOffsetOneResult_skipsOneAfterRehydration() throws Exception { + pushToGcs(DEPOSIT_3_DOMAIN); + RdeDomainReader reader = getReader(0, 1); + reader.next(); + reader.endSlice(); + + reader = cloneObject(reader); + reader.beginSlice(); + // reader will not advance any further + thrown.expect(NoSuchElementException.class); + reader.next(); + } + + /** Reads three domains */ + @Test + public void testZeroOffsetThreeResult_readsThree() throws Exception { + pushToGcs(DEPOSIT_3_DOMAIN); + RdeDomainReader reader = getReader(0, 3); + checkDomain(reader.next(), "example1.test", "Dexample1-TEST"); + checkDomain(reader.next(), "example2.test", "Dexample2-TEST"); + checkDomain(reader.next(), "example3.test", "Dexample3-TEST"); + } + + /** Stops reading at 3 maxResults */ + @Test + public void testZeroOffsetThreeResult_stopsAtThree() throws Exception { + pushToGcs(DEPOSIT_4_DOMAIN); + RdeDomainReader reader = getReader(0, 3); + for (int i = 0; i < 3; i++) { + reader.next(); + } + thrown.expect(NoSuchElementException.class); + reader.next(); + } + + /** Reads one domain from file then stops at end of file */ + @Test + public void testZeroOffsetThreeResult_endOfFile() throws Exception { + pushToGcs(DEPOSIT_1_DOMAIN); + RdeDomainReader reader = getReader(0, 3); + reader.next(); + thrown.expect(NoSuchElementException.class); + reader.next(); + } + + /** Skips three domains with offset of three */ + @Test + public void testThreeOffsetOneResult_skipsThree() throws Exception { + pushToGcs(DEPOSIT_4_DOMAIN); + RdeDomainReader reader = getReader(3, 1); + checkDomain(reader.next(), "example4.test", "Dexample4-TEST"); + } + + /** Skips four domains after advancing once at three offset, then rehydrating */ + @Test + public void testThreeOffsetTwoResult_skipsFourAfterRehydration() throws Exception { + pushToGcs(DEPOSIT_10_DOMAIN); + RdeDomainReader reader = getReader(3, 2); + reader.next(); + reader.endSlice(); + reader = cloneObject(reader); + reader.beginSlice(); + checkDomain(reader.next(), "example5.test", "Dexample5-TEST"); + } + + /** Reads three at zero offset three results with rehydration in the middle */ + @Test + public void testZeroOffsetThreeResult_readsThreeWithRehydration() throws Exception { + pushToGcs(DEPOSIT_4_DOMAIN); + RdeDomainReader reader = getReader(0, 3); + checkDomain(reader.next(), "example1.test", "Dexample1-TEST"); + reader.endSlice(); + reader = cloneObject(reader); + reader.beginSlice(); + checkDomain(reader.next(), "example2.test", "Dexample2-TEST"); + checkDomain(reader.next(), "example3.test", "Dexample3-TEST"); + } + + /** Stops reading at three with zero offset three results with rehydration in the middle */ + @Test + public void testZeroOffsetThreeResult_stopsAtThreeWithRehydration() throws Exception { + pushToGcs(DEPOSIT_4_DOMAIN); + RdeDomainReader reader = getReader(0, 3); + reader.next(); + reader.endSlice(); + reader = cloneObject(reader); + reader.beginSlice(); + reader.next(); + reader.next(); + thrown.expect(NoSuchElementException.class); + reader.next(); + } + + private void pushToGcs(ByteSource source) throws IOException { + try (OutputStream outStream = + new GcsUtils(GCS_SERVICE, ConfigModule.provideGcsBufferSize()) + .openOutputStream(new GcsFilename(IMPORT_BUCKET_NAME, IMPORT_FILE_NAME)); + InputStream inStream = source.openStream()) { + ByteStreams.copy(inStream, outStream); + } + } + + /** Creates a deep copy of the {@link T} */ + public T cloneObject( + T object) throws Exception { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + ObjectOutputStream oout = new ObjectOutputStream(bout); + oout.writeObject(object); + ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray()); + ObjectInputStream oin = new ObjectInputStream(bin); + @SuppressWarnings("unchecked") + T result = (T) oin.readObject(); + return result; + } + + /** Verifies that domain name and ROID match expected values */ + private void checkDomain( + JaxbFragment fragment, String domainName, String repoId) + throws Exception { + assertThat(fragment).isNotNull(); + XjcRdeDomain domain = fragment.getInstance().getValue(); + assertThat(domain.getName()).isEqualTo(domainName); + assertThat(domain.getRoid()).isEqualTo(repoId); + } + + /** Gets a new {@link RdeDomainReader} with specified offset and maxResults */ + private RdeDomainReader getReader(int offset, int maxResults) throws Exception { + RdeDomainReader reader = + new RdeDomainReader(IMPORT_BUCKET_NAME, IMPORT_FILE_NAME, offset, maxResults); + reader.beginSlice(); + return reader; + } +} diff --git a/javatests/google/registry/rde/imports/RdeHostImportActionTest.java b/javatests/google/registry/rde/imports/RdeHostImportActionTest.java index 52bbf46ea..d1645be59 100644 --- a/javatests/google/registry/rde/imports/RdeHostImportActionTest.java +++ b/javatests/google/registry/rde/imports/RdeHostImportActionTest.java @@ -16,6 +16,10 @@ package google.registry.rde.imports; import static com.google.common.truth.Truth.assertThat; import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.testing.DatastoreHelper.getHistoryEntries; +import static google.registry.testing.DatastoreHelper.newHostResource; +import static google.registry.testing.DatastoreHelper.persistResource; +import static google.registry.testing.DatastoreHelper.persistSimpleResource; import com.google.appengine.tools.cloudstorage.GcsFilename; import com.google.appengine.tools.cloudstorage.GcsService; @@ -24,10 +28,13 @@ import com.google.appengine.tools.cloudstorage.RetryParams; import com.google.common.base.Optional; import com.google.common.io.ByteSource; import com.google.common.io.ByteStreams; +import com.googlecode.objectify.Key; import google.registry.config.RegistryConfig.ConfigModule; import google.registry.gcs.GcsUtils; import google.registry.mapreduce.MapreduceRunner; +import google.registry.model.eppcommon.Trid; import google.registry.model.host.HostResource; +import google.registry.model.reporting.HistoryEntry; import google.registry.request.Response; import google.registry.testing.FakeResponse; import google.registry.testing.mapreduce.MapreduceTestCase; @@ -35,6 +42,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.List; +import org.joda.time.DateTime; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -52,7 +60,6 @@ public class RdeHostImportActionTest extends MapreduceTestCase mapShards = Optional.absent(); @@ -78,6 +85,55 @@ public class RdeHostImportActionTest extends MapreduceTestCase hosts = ofy().load().type(HostResource.class).list(); + HostResource host = hosts.get(0); + // verify history entry + List historyEntries = getHistoryEntries(host); + assertThat(historyEntries).hasSize(1); + checkHistoryEntry(historyEntries.get(0), host); + } + + /** Ensures that a second pass on a host does not import a new host. */ + @Test + public void test_mapreduceTwiceDoesNotDuplicateResources() throws Exception { + pushToGcs(DEPOSIT_1_HOST); + // Create host and history entry first + HostResource existingHost = persistResource( + newHostResource("ns1.example1.test") + .asBuilder() + .setRepoId("Hns1_example1_test-TEST") + .build()); + persistSimpleResource(createHistoryEntry( + existingHost.getRepoId(), + existingHost.getCurrentSponsorClientId(), + loadHostXml(DEPOSIT_1_HOST))); + // Simulate running a second import and verify that the resources + // aren't imported twice (only one host, and one history entry) + runMapreduce(); + List hosts = ofy().load().type(HostResource.class).list(); + assertThat(hosts).hasSize(1); + HostResource host = hosts.get(0); + // verify history entry + List historyEntries = getHistoryEntries(host); + assertThat(historyEntries).hasSize(1); + checkHistoryEntry(historyEntries.get(0), host); + } + + /** Verify history entry fields are correct */ + private void checkHistoryEntry(HistoryEntry entry, HostResource parent) { + assertThat(entry.getType()).isEqualTo(HistoryEntry.Type.RDE_IMPORT); + assertThat(entry.getClientId()).isEqualTo(parent.getCurrentSponsorClientId()); + assertThat(entry.getXmlBytes().length).isGreaterThan(0); + assertThat(entry.getBySuperuser()).isTrue(); + assertThat(entry.getReason()).isEqualTo("RDE Import"); + assertThat(entry.getRequestedByRegistrar()).isEqualTo(false); + assertThat(entry.getParent()).isEqualTo(Key.create(parent)); + } + /** Verifies that host id and ROID match expected values */ private void checkHost(HostResource host) { assertThat(host.getFullyQualifiedHostName()).isEqualTo("ns1.example1.test"); @@ -91,10 +147,32 @@ public class RdeHostImportActionTest extends MapreduceTestCase fragment, String domainName, String repoId) { + assertThat(fragment).isNotNull(); + XjcRdeHost host = fragment.getInstance().getValue(); + assertThat(host.getName()).isEqualTo(domainName); + assertThat(host.getRoid()).isEqualTo(repoId); } - /** Gets a new {@link RdeHostReader} with specified offset and maxResults */ private RdeHostReader getReader(int offset, int maxResults) throws Exception { RdeHostReader reader = diff --git a/javatests/google/registry/rde/imports/RdeImportTestUtils.java b/javatests/google/registry/rde/imports/RdeImportTestUtils.java new file mode 100644 index 000000000..de5887b1c --- /dev/null +++ b/javatests/google/registry/rde/imports/RdeImportTestUtils.java @@ -0,0 +1,42 @@ +// Copyright 2017 The Nomulus 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 google.registry.rde.imports; + +import static com.google.common.truth.Truth.assertThat; + +import google.registry.model.eppcommon.Trid; + +/** + * Collection of test utility logic for RDE import. + */ +public class RdeImportTestUtils { + + /** + * Verifies that the {@link Trid} clientTransactionId meets system requirements. + * + *

    + *
  1. clientTransactionId cannot be null + *
  2. clientTransactionId must be a token between 3 and 64 characters long + *
  3. clientTransactionId must be prefixed with Import__ + *
+ */ + public static void checkTrid(Trid trid) { + assertThat(trid).isNotNull(); + assertThat(trid.getClientTransactionId()).isNotNull(); + assertThat(trid.getClientTransactionId().length()).isAtLeast(3); + assertThat(trid.getClientTransactionId().length()).isAtMost(64); + assertThat(trid.getClientTransactionId()).startsWith("Import_"); + } +} diff --git a/javatests/google/registry/rde/imports/RdeImportUtilsTest.java b/javatests/google/registry/rde/imports/RdeImportUtilsTest.java index 9fde58911..ac5291650 100644 --- a/javatests/google/registry/rde/imports/RdeImportUtilsTest.java +++ b/javatests/google/registry/rde/imports/RdeImportUtilsTest.java @@ -17,9 +17,11 @@ package google.registry.rde.imports; import static com.google.common.truth.Truth.assertThat; import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.testing.DatastoreHelper.createTld; +import static google.registry.testing.DatastoreHelper.persistActiveContact; import static google.registry.testing.DatastoreHelper.persistNewRegistrar; import static google.registry.testing.DatastoreHelper.persistResource; import static org.joda.time.DateTimeZone.UTC; +import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -31,10 +33,15 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.io.ByteSource; import com.googlecode.objectify.Key; +import com.googlecode.objectify.VoidWork; import com.googlecode.objectify.Work; import google.registry.gcs.GcsUtils; import google.registry.model.EppResource; import google.registry.model.contact.ContactResource; +import google.registry.model.domain.DesignatedContact; +import google.registry.model.domain.DesignatedContact.Type; +import google.registry.model.domain.DomainResource; +import google.registry.model.eppcommon.StatusValue; import google.registry.model.host.HostResource; import google.registry.model.index.EppResourceIndex; import google.registry.model.index.EppResourceIndexBucket; @@ -103,8 +110,12 @@ public class RdeImportUtilsTest extends ShardableTestCase { /** Verifies import of a contact that has not been previously imported */ @Test public void testImportNewContact() { - ContactResource newContact = buildNewContact(); - assertThat(rdeImportUtils.importContact(newContact)).isTrue(); + final ContactResource newContact = buildNewContact(); + ofy().transact(new VoidWork() { + @Override + public void vrun() { + rdeImportUtils.importContact(newContact); + }}); assertEppResourceIndexEntityFor(newContact); assertForeignKeyIndexFor(newContact); @@ -121,36 +132,46 @@ public class RdeImportUtilsTest extends ShardableTestCase { public void testImportExistingContact() { ContactResource newContact = buildNewContact(); persistResource(newContact); - ContactResource updatedContact = + final ContactResource updatedContact = newContact .asBuilder() .setLastEppUpdateTime(newContact.getLastEppUpdateTime().plusSeconds(1)) .build(); - assertThat(rdeImportUtils.importContact(updatedContact)).isFalse(); - - // verify the updated contact was saved - ContactResource saved = getContact("TEST-123"); - assertThat(saved).isNotNull(); - assertThat(saved.getContactId()).isEqualTo(newContact.getContactId()); - assertThat(saved.getEmailAddress()).isEqualTo(newContact.getEmailAddress()); - assertThat(saved.getLastEppUpdateTime()).isEqualTo(newContact.getLastEppUpdateTime()); + try { + ofy().transact(new VoidWork() { + @Override + public void vrun() { + rdeImportUtils.importContact(updatedContact); + }}); + fail("Expected ResourceExistsException"); + } catch (ResourceExistsException expected) { + // verify the updated contact was not saved + ContactResource saved = getContact("TEST-123"); + assertThat(saved).isNotNull(); + assertThat(saved.getContactId()).isEqualTo(newContact.getContactId()); + assertThat(saved.getEmailAddress()).isEqualTo(newContact.getEmailAddress()); + assertThat(saved.getLastEppUpdateTime()).isEqualTo(newContact.getLastEppUpdateTime()); + } } /** Verifies import of a host that has not been previously imported */ @Test public void testImportNewHost() throws UnknownHostException { - HostResource newHost = buildNewHost(); - assertThat(rdeImportUtils.importHost(newHost)).isTrue(); + final HostResource newHost = buildNewHost(); + ofy().transact(new VoidWork() { + @Override + public void vrun() { + rdeImportUtils.importHost(newHost); + }}); + assertEppResourceIndexEntityFor(newHost); assertForeignKeyIndexFor(newHost); // verify the new contact was saved HostResource saved = getHost("FOO_ROID"); assertThat(saved).isNotNull(); - assertThat(saved.getFullyQualifiedHostName()).isEqualTo( - newHost.getFullyQualifiedHostName()); - assertThat(saved.getInetAddresses()).isEqualTo( - newHost.getInetAddresses()); + assertThat(saved.getFullyQualifiedHostName()).isEqualTo(newHost.getFullyQualifiedHostName()); + assertThat(saved.getInetAddresses()).isEqualTo(newHost.getInetAddresses()); assertThat(saved.getLastEppUpdateTime()).isEqualTo(newHost.getLastEppUpdateTime()); } @@ -159,21 +180,79 @@ public class RdeImportUtilsTest extends ShardableTestCase { public void testImportExistingHost() throws UnknownHostException { HostResource newHost = buildNewHost(); persistResource(newHost); - HostResource updatedHost = + final HostResource updatedHost = newHost .asBuilder() .setLastEppUpdateTime(newHost.getLastEppUpdateTime().plusSeconds(1)) .build(); - assertThat(rdeImportUtils.importHost(updatedHost)).isFalse(); + try { + ofy().transact(new VoidWork() { + @Override + public void vrun() { + rdeImportUtils.importHost(updatedHost); + }}); + fail("Expected ResourceExistsException"); + } catch (ResourceExistsException expected) { + // verify the contact was not updated + HostResource saved = getHost("FOO_ROID"); + assertThat(saved).isNotNull(); + assertThat(saved.getFullyQualifiedHostName()).isEqualTo(newHost.getFullyQualifiedHostName()); + assertThat(saved.getInetAddresses()).isEqualTo(newHost.getInetAddresses()); + assertThat(saved.getLastEppUpdateTime()).isEqualTo(newHost.getLastEppUpdateTime()); + } + } - // verify the new contact was saved - HostResource saved = getHost("FOO_ROID"); - assertThat(saved).isNotNull(); - assertThat(saved.getFullyQualifiedHostName()).isEqualTo( - newHost.getFullyQualifiedHostName()); - assertThat(saved.getInetAddresses()).isEqualTo( - newHost.getInetAddresses()); - assertThat(saved.getLastEppUpdateTime()).isEqualTo(newHost.getLastEppUpdateTime()); + @Test + public void testImportNewDomain() throws Exception { + final DomainResource newDomain = buildNewDomain(); + ofy().transact(new VoidWork() { + @Override + public void vrun() { + rdeImportUtils.importDomain(newDomain); + } + }); + + DomainResource saved = getDomain("Dexample1-TEST"); + assertThat(saved.getFullyQualifiedDomainName()) + .isEqualTo(newDomain.getFullyQualifiedDomainName()); + assertThat(saved.getStatusValues()).isEqualTo(newDomain.getStatusValues()); + assertThat(saved.getRegistrant()).isEqualTo(newDomain.getRegistrant()); + assertThat(saved.getContacts()).isEqualTo(newDomain.getContacts()); + assertThat(saved.getCurrentSponsorClientId()).isEqualTo(newDomain.getCurrentSponsorClientId()); + assertThat(saved.getCreationClientId()).isEqualTo(newDomain.getCreationClientId()); + assertThat(saved.getCreationTime()).isEqualTo(newDomain.getCreationTime()); + assertThat(saved.getRegistrationExpirationTime()) + .isEqualTo(newDomain.getRegistrationExpirationTime()); + } + + @Test + public void testImportExistingDomain() throws Exception { + DomainResource newDomain = buildNewDomain(); + persistResource(newDomain); + final DomainResource updatedDomain = newDomain.asBuilder() + .setFullyQualifiedDomainName("1" + newDomain.getFullyQualifiedDomainName()) + .build(); + try { + ofy().transact(new VoidWork() { + @Override + public void vrun() { + rdeImportUtils.importDomain(updatedDomain); + }}); + fail("Expected ResourceExistsException"); + } catch (ResourceExistsException expected) { + DomainResource saved = getDomain("Dexample1-TEST"); + assertThat(saved.getFullyQualifiedDomainName()) + .isEqualTo(newDomain.getFullyQualifiedDomainName()); + assertThat(saved.getStatusValues()).isEqualTo(newDomain.getStatusValues()); + assertThat(saved.getRegistrant()).isEqualTo(newDomain.getRegistrant()); + assertThat(saved.getContacts()).isEqualTo(newDomain.getContacts()); + assertThat(saved.getCurrentSponsorClientId()) + .isEqualTo(newDomain.getCurrentSponsorClientId()); + assertThat(saved.getCreationClientId()).isEqualTo(newDomain.getCreationClientId()); + assertThat(saved.getCreationTime()).isEqualTo(newDomain.getCreationTime()); + assertThat(saved.getRegistrationExpirationTime()) + .isEqualTo(newDomain.getRegistrationExpirationTime()); + } } private static ContactResource buildNewContact() { @@ -188,16 +267,34 @@ public class RdeImportUtilsTest extends ShardableTestCase { private static HostResource buildNewHost() throws UnknownHostException { return new HostResource.Builder() .setFullyQualifiedHostName("foo.bar.example") - .setInetAddresses(ImmutableSet.of( - InetAddress.getByName("192.0.2.2"), - InetAddress.getByName("192.0.2.29"), - InetAddress.getByName("1080:0:0:0:8:800:200C:417A") - )) + .setInetAddresses( + ImmutableSet.of( + InetAddress.getByName("192.0.2.2"), + InetAddress.getByName("192.0.2.29"), + InetAddress.getByName("1080:0:0:0:8:800:200C:417A"))) .setLastEppUpdateTime(DateTime.parse("2010-10-10T00:00:00.000Z")) .setRepoId("FOO_ROID") .build(); } + private DomainResource buildNewDomain() { + ContactResource registrant = persistActiveContact("jd1234"); + ContactResource admin = persistActiveContact("sh8013"); + return new DomainResource.Builder() + .setFullyQualifiedDomainName("example1.example") + .setRepoId("Dexample1-TEST") + .setStatusValues(ImmutableSet.of(StatusValue.OK)) + .setRegistrant(Key.create(registrant)) + .setContacts(ImmutableSet.of( + DesignatedContact.create(Type.ADMIN, Key.create(admin)), + DesignatedContact.create(Type.TECH, Key.create(admin)))) + .setCurrentSponsorClientId("RegistrarX") + .setCreationClientId("RegistrarX") + .setCreationTime(DateTime.parse("1999-04-03T22:00:00.0Z")) + .setRegistrationExpirationTime(DateTime.parse("2015-04-03T22:00:00.0Z")) + .build(); + } + /** Verifies that no errors are thrown when a valid escrow file is validated */ @Test @SuppressWarnings("CheckReturnValue") @@ -248,7 +345,7 @@ public class RdeImportUtilsTest extends ShardableTestCase { }}); } - /** Gets the contact with the specified ROID */ + /** Gets the host with the specified ROID */ private static HostResource getHost(String repoId) { final Key key = Key.create(HostResource.class, repoId); return ofy().transact(new Work() { @@ -259,6 +356,16 @@ public class RdeImportUtilsTest extends ShardableTestCase { }); } + /** Gets the domain with the specified ROID */ + private static DomainResource getDomain(String repoId) { + final Key key = Key.create(DomainResource.class, repoId); + return ofy().transact(new Work() { + @Override + public DomainResource run() { + return ofy().load().key(key).now(); + }}); + } + /** Confirms that a ForeignKeyIndex exists in the datastore for a given resource. */ private void assertForeignKeyIndexFor(final T resource) { assertThat(ForeignKeyIndex.load(resource.getClass(), resource.getForeignKey(), clock.nowUtc())) diff --git a/javatests/google/registry/rde/imports/XjcToContactResourceConverterTest.java b/javatests/google/registry/rde/imports/XjcToContactResourceConverterTest.java index cfc12d2e1..c88a238ed 100644 --- a/javatests/google/registry/rde/imports/XjcToContactResourceConverterTest.java +++ b/javatests/google/registry/rde/imports/XjcToContactResourceConverterTest.java @@ -15,27 +15,29 @@ package google.registry.rde.imports; import static com.google.common.truth.Truth.assertThat; +import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.rde.imports.RdeImportTestUtils.checkTrid; import static google.registry.testing.DatastoreHelper.createTld; +import static google.registry.testing.DatastoreHelper.getHistoryEntries; +import static java.util.Arrays.asList; +import com.google.common.base.Joiner; import com.google.common.io.ByteSource; +import com.googlecode.objectify.Work; import google.registry.model.contact.ContactResource; import google.registry.model.contact.PostalInfo; import google.registry.model.eppcommon.StatusValue; +import google.registry.model.reporting.HistoryEntry; import google.registry.model.transfer.TransferData; import google.registry.model.transfer.TransferStatus; import google.registry.testing.AppEngineRule; -import google.registry.xjc.XjcXmlTransformer; import google.registry.xjc.rdecontact.XjcRdeContact; import google.registry.xjc.rdecontact.XjcRdeContactElement; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.InputStream; -import javax.xml.stream.XMLInputFactory; -import javax.xml.stream.XMLStreamReader; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.stax.StAXSource; -import javax.xml.transform.stream.StreamResult; +import java.util.List; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Unmarshaller; import org.joda.time.DateTime; import org.junit.Before; import org.junit.Rule; @@ -46,6 +48,22 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class XjcToContactResourceConverterTest { + //List of packages to initialize JAXBContext + private static final String JAXB_CONTEXT_PACKAGES = Joiner.on(":").join(asList( + "google.registry.xjc.contact", + "google.registry.xjc.domain", + "google.registry.xjc.host", + "google.registry.xjc.mark", + "google.registry.xjc.rde", + "google.registry.xjc.rdecontact", + "google.registry.xjc.rdedomain", + "google.registry.xjc.rdeeppparams", + "google.registry.xjc.rdeheader", + "google.registry.xjc.rdeidn", + "google.registry.xjc.rdenndn", + "google.registry.xjc.rderegistrar", + "google.registry.xjc.smd")); + private static final ByteSource CONTACT_XML = RdeImportsTestData.get("contact_fragment.xml"); @Rule @@ -53,22 +71,24 @@ public class XjcToContactResourceConverterTest { .withDatastore() .build(); + private Unmarshaller unmarshaller; + @Before public void before() throws Exception { createTld("xn--q9jyb4c"); + unmarshaller = JAXBContext.newInstance(JAXB_CONTEXT_PACKAGES).createUnmarshaller(); } @Test public void testConvertContact() throws Exception { - XjcRdeContact contact = getContact(); - ContactResource resource = XjcToContactResourceConverter.convertContact(contact); + XjcRdeContact contact = loadContactFromRdeXml(); + ContactResource resource = convertContactInTransaction(contact); assertThat(resource.getContactId()).isEqualTo("love-id"); assertThat(resource.getRepoId()).isEqualTo("2-ROID"); // The imported XML also had LINKED status, but that should have been dropped on import. assertThat(resource.getStatusValues()) .containsExactly( - StatusValue.CLIENT_DELETE_PROHIBITED, - StatusValue.SERVER_UPDATE_PROHIBITED); + StatusValue.CLIENT_DELETE_PROHIBITED, StatusValue.SERVER_UPDATE_PROHIBITED); assertThat(resource.getInternationalizedPostalInfo()).isNotNull(); PostalInfo postalInfo = resource.getInternationalizedPostalInfo(); @@ -122,49 +142,66 @@ public class XjcToContactResourceConverterTest { @Test public void testConvertContact_absentVoiceAndFaxNumbers() throws Exception { - XjcRdeContact contact = getContact(); + XjcRdeContact contact = loadContactFromRdeXml(); contact.setVoice(null); contact.setFax(null); - ContactResource resource = XjcToContactResourceConverter.convertContact(contact); + ContactResource resource = convertContactInTransaction(contact); assertThat(resource.getVoiceNumber()).isNull(); assertThat(resource.getFaxNumber()).isNull(); } @Test public void testConvertContact_absentDisclose() throws Exception { - XjcRdeContact contact = getContact(); + XjcRdeContact contact = loadContactFromRdeXml(); contact.setDisclose(null); - ContactResource resource = XjcToContactResourceConverter.convertContact(contact); + ContactResource resource = convertContactInTransaction(contact); assertThat(resource.getDisclose()).isNull(); } @Test public void testConvertContact_absentTransferData() throws Exception { - XjcRdeContact contact = getContact(); + XjcRdeContact contact = loadContactFromRdeXml(); contact.setTrDate(null); contact.setTrnData(null); - ContactResource resource = XjcToContactResourceConverter.convertContact(contact); + ContactResource resource = convertContactInTransaction(contact); assertThat(resource.getLastTransferTime()).isNull(); assertThat(resource.getTransferData()).isSameAs(TransferData.EMPTY); } - private XjcRdeContact getContact() throws Exception { - InputStream in = null; - try { - in = CONTACT_XML.openBufferedStream(); - XMLInputFactory factory = XMLInputFactory.newInstance(); - XMLStreamReader reader = factory.createXMLStreamReader(in); - TransformerFactory tf = TransformerFactory.newInstance(); - Transformer t = tf.newTransformer(); - ByteArrayOutputStream bout = new ByteArrayOutputStream(); - t.transform(new StAXSource(reader), new StreamResult(bout)); - ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray()); - XjcRdeContactElement element = XjcXmlTransformer.unmarshal(XjcRdeContactElement.class, bin); - return element.getValue(); - } finally { - if (in != null) { - in.close(); + @Test + public void testConvertContactResourceHistoryEntry() throws Exception { + XjcRdeContact contact = loadContactFromRdeXml(); + ContactResource resource = convertContactInTransaction(contact); + List historyEntries = getHistoryEntries(resource); + assertThat(historyEntries).hasSize(1); + HistoryEntry entry = historyEntries.get(0); + assertThat(entry.getType()).isEqualTo(HistoryEntry.Type.RDE_IMPORT); + assertThat(entry.getClientId()).isEqualTo("TheRegistrar"); + assertThat(entry.getBySuperuser()).isTrue(); + assertThat(entry.getReason()).isEqualTo("RDE Import"); + assertThat(entry.getRequestedByRegistrar()).isFalse(); + checkTrid(entry.getTrid()); + // check xml against original domain xml + try (InputStream ins = new ByteArrayInputStream(entry.getXmlBytes())) { + XjcRdeContact unmarshalledXml = + ((XjcRdeContactElement) unmarshaller.unmarshal(ins)).getValue(); + assertThat(unmarshalledXml.getId()).isEqualTo("love-id"); + assertThat(unmarshalledXml.getRoid()).isEqualTo("2-ROID"); + } + } + + private static ContactResource convertContactInTransaction(final XjcRdeContact xjcContact) { + return ofy().transact(new Work() { + @Override + public ContactResource run() { + return XjcToContactResourceConverter.convertContact(xjcContact); } + }); + } + + private XjcRdeContact loadContactFromRdeXml() throws Exception { + try (InputStream ins = CONTACT_XML.openStream()) { + return ((XjcRdeContactElement) unmarshaller.unmarshal(ins)).getValue(); } } } diff --git a/javatests/google/registry/rde/imports/XjcToDomainResourceConverterTest.java b/javatests/google/registry/rde/imports/XjcToDomainResourceConverterTest.java index 7134d5ef0..e2cf8c17b 100644 --- a/javatests/google/registry/rde/imports/XjcToDomainResourceConverterTest.java +++ b/javatests/google/registry/rde/imports/XjcToDomainResourceConverterTest.java @@ -17,6 +17,7 @@ package google.registry.rde.imports; import static com.google.common.io.BaseEncoding.base16; import static com.google.common.truth.Truth.assertThat; import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.rde.imports.RdeImportTestUtils.checkTrid; import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.testing.DatastoreHelper.getHistoryEntries; import static google.registry.testing.DatastoreHelper.persistActiveContact; @@ -117,6 +118,8 @@ public class XjcToDomainResourceConverterTest { assertThat(domain.getGracePeriods()).isEmpty(); assertThat(domain.getLastEppUpdateClientId()).isNull(); assertThat(domain.getLastEppUpdateTime()).isNull(); + assertThat(domain.getAutorenewBillingEvent()).isNotNull(); + assertThat(domain.getAutorenewPollMessage()).isNotNull(); } @Test @@ -128,7 +131,7 @@ public class XjcToDomainResourceConverterTest { assertThat(domain.getGracePeriods()).hasSize(1); GracePeriod gracePeriod = domain.getGracePeriods().asList().get(0); assertThat(gracePeriod.getType()).isEqualTo(GracePeriodStatus.ADD); - assertThat(gracePeriod.getClientId()).isEqualTo(xjcDomain.getCrRr().getClient()); + assertThat(gracePeriod.getClientId()).isEqualTo("RegistrarX"); assertThat(gracePeriod.getExpirationTime()).isEqualTo(xjcDomain.getCrDate().plusDays(5)); } @@ -141,7 +144,7 @@ public class XjcToDomainResourceConverterTest { assertThat(domain.getGracePeriods()).hasSize(1); GracePeriod gracePeriod = domain.getGracePeriods().asList().get(0); assertThat(gracePeriod.getType()).isEqualTo(GracePeriodStatus.AUTO_RENEW); - assertThat(gracePeriod.getClientId()).isEqualTo(xjcDomain.getClID()); + assertThat(gracePeriod.getClientId()).isEqualTo("RegistrarX"); assertThat(gracePeriod.getExpirationTime()).isEqualTo(xjcDomain.getUpDate().plusDays(45)); } @@ -154,7 +157,7 @@ public class XjcToDomainResourceConverterTest { assertThat(domain.getGracePeriods()).hasSize(1); GracePeriod gracePeriod = domain.getGracePeriods().asList().get(0); assertThat(gracePeriod.getType()).isEqualTo(GracePeriodStatus.REDEMPTION); - assertThat(gracePeriod.getClientId()).isEqualTo(xjcDomain.getClID()); + assertThat(gracePeriod.getClientId()).isEqualTo("RegistrarX"); assertThat(gracePeriod.getExpirationTime()).isEqualTo(xjcDomain.getUpDate().plusDays(30)); } @@ -167,7 +170,7 @@ public class XjcToDomainResourceConverterTest { assertThat(domain.getGracePeriods()).hasSize(1); GracePeriod gracePeriod = domain.getGracePeriods().asList().get(0); assertThat(gracePeriod.getType()).isEqualTo(GracePeriodStatus.RENEW); - assertThat(gracePeriod.getClientId()).isEqualTo(xjcDomain.getClID()); + assertThat(gracePeriod.getClientId()).isEqualTo("RegistrarX"); assertThat(gracePeriod.getExpirationTime()).isEqualTo(xjcDomain.getUpDate().plusDays(5)); } @@ -180,7 +183,7 @@ public class XjcToDomainResourceConverterTest { assertThat(domain.getGracePeriods()).hasSize(1); GracePeriod gracePeriod = domain.getGracePeriods().asList().get(0); assertThat(gracePeriod.getType()).isEqualTo(GracePeriodStatus.PENDING_DELETE); - assertThat(gracePeriod.getClientId()).isEqualTo(xjcDomain.getClID()); + assertThat(gracePeriod.getClientId()).isEqualTo("RegistrarX"); assertThat(gracePeriod.getExpirationTime()).isEqualTo(xjcDomain.getUpDate().plusDays(5)); } @@ -203,7 +206,7 @@ public class XjcToDomainResourceConverterTest { assertThat(domain.getGracePeriods()).hasSize(1); GracePeriod gracePeriod = domain.getGracePeriods().asList().get(0); assertThat(gracePeriod.getType()).isEqualTo(GracePeriodStatus.TRANSFER); - assertThat(gracePeriod.getClientId()).isEqualTo(xjcDomain.getClID()); + assertThat(gracePeriod.getClientId()).isEqualTo("RegistrarX"); assertThat(gracePeriod.getExpirationTime()).isEqualTo(xjcDomain.getUpDate().plusDays(5)); } @@ -298,10 +301,11 @@ public class XjcToDomainResourceConverterTest { assertThat(historyEntries).hasSize(1); HistoryEntry entry = historyEntries.get(0); assertThat(entry.getType()).isEqualTo(HistoryEntry.Type.RDE_IMPORT); - assertThat(entry.getClientId()).isEqualTo(xjcDomain.getClID()); + assertThat(entry.getClientId()).isEqualTo("RegistrarX"); assertThat(entry.getBySuperuser()).isTrue(); assertThat(entry.getReason()).isEqualTo("RDE Import"); assertThat(entry.getRequestedByRegistrar()).isFalse(); + checkTrid(entry.getTrid()); // check xml against original domain xml try (InputStream ins = new ByteArrayInputStream(entry.getXmlBytes())) { XjcRdeDomain unmarshalledXml = ((XjcRdeDomainElement) unmarshaller.unmarshal(ins)).getValue(); @@ -318,17 +322,12 @@ public class XjcToDomainResourceConverterTest { // First import in a transaction, then verify in another transaction. // Ancestor queries don't work within the same transaction. DomainResource domain = persistResource(convertDomainInTransaction(xjcDomain)); - List historyEntries = getHistoryEntries(domain); - assertThat(historyEntries).hasSize(1); - HistoryEntry entry = historyEntries.get(0); - List billingEvents = - ofy().load().type(BillingEvent.Recurring.class).ancestor(entry).list(); - assertThat(billingEvents).hasSize(1); - BillingEvent.Recurring autoRenewEvent = billingEvents.get(0); + BillingEvent.Recurring autoRenewEvent = + ofy().load().key(domain.getAutorenewBillingEvent()).now(); assertThat(autoRenewEvent.getReason()).isEqualTo(Reason.RENEW); assertThat(autoRenewEvent.getFlags()).isEqualTo(ImmutableSet.of(Flag.AUTO_RENEW)); assertThat(autoRenewEvent.getTargetId()).isEqualTo(xjcDomain.getRoid()); - assertThat(autoRenewEvent.getClientId()).isEqualTo(xjcDomain.getClID()); + assertThat(autoRenewEvent.getClientId()).isEqualTo("RegistrarX"); assertThat(autoRenewEvent.getEventTime()).isEqualTo(xjcDomain.getExDate()); assertThat(autoRenewEvent.getRecurrenceEndTime()).isEqualTo(END_OF_TIME); } @@ -341,15 +340,10 @@ public class XjcToDomainResourceConverterTest { // First import in a transaction, then verify in another transaction. // Ancestor queries don't work within the same transaction. DomainResource domain = persistResource(convertDomainInTransaction(xjcDomain)); - List historyEntries = getHistoryEntries(domain); - assertThat(historyEntries).hasSize(1); - HistoryEntry entry = historyEntries.get(0); - List pollMessages = ofy().load().type(PollMessage.class).ancestor(entry).list(); - assertThat(pollMessages).hasSize(1); - PollMessage pollMessage = pollMessages.get(0); + PollMessage pollMessage = ofy().load().key(domain.getAutorenewPollMessage()).now(); assertThat(pollMessage).isInstanceOf(PollMessage.Autorenew.class); assertThat(((PollMessage.Autorenew) pollMessage).getTargetId()).isEqualTo(xjcDomain.getRoid()); - assertThat(pollMessage.getClientId()).isEqualTo(xjcDomain.getClID()); + assertThat(pollMessage.getClientId()).isEqualTo("RegistrarX"); assertThat(pollMessage.getEventTime()).isEqualTo(xjcDomain.getExDate()); assertThat(pollMessage.getMsg()).isEqualTo("Domain was auto-renewed."); } diff --git a/javatests/google/registry/rde/imports/XjcToHostResourceConverterTest.java b/javatests/google/registry/rde/imports/XjcToHostResourceConverterTest.java index 0da4e2762..76c63540b 100644 --- a/javatests/google/registry/rde/imports/XjcToHostResourceConverterTest.java +++ b/javatests/google/registry/rde/imports/XjcToHostResourceConverterTest.java @@ -15,19 +15,26 @@ package google.registry.rde.imports; import static com.google.common.truth.Truth.assertThat; +import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.rde.imports.RdeImportTestUtils.checkTrid; import static google.registry.testing.DatastoreHelper.createTld; +import static google.registry.testing.DatastoreHelper.getHistoryEntries; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.io.ByteSource; import com.google.common.net.InetAddresses; +import com.googlecode.objectify.Work; import google.registry.model.eppcommon.StatusValue; import google.registry.model.host.HostResource; +import google.registry.model.reporting.HistoryEntry; import google.registry.testing.AppEngineRule; import google.registry.testing.ShardableTestCase; import google.registry.xjc.rdehost.XjcRdeHost; import google.registry.xjc.rdehost.XjcRdeHostElement; +import java.io.ByteArrayInputStream; import java.io.InputStream; +import java.util.List; import javax.xml.bind.JAXBContext; import javax.xml.bind.Unmarshaller; import org.joda.time.DateTime; @@ -45,7 +52,7 @@ public class XjcToHostResourceConverterTest extends ShardableTestCase { private static final ByteSource HOST_XML = RdeImportsTestData.get("host_fragment.xml"); - //List of packages to initialize JAXBContext + // List of packages to initialize JAXBContext private static final String JAXB_CONTEXT_PACKAGES = Joiner.on(":") .join(ImmutableList.of( "google.registry.xjc.contact", @@ -72,22 +79,22 @@ public class XjcToHostResourceConverterTest extends ShardableTestCase { @Before public void before() throws Exception { createTld("example"); - unmarshaller = JAXBContext.newInstance(JAXB_CONTEXT_PACKAGES) - .createUnmarshaller(); + unmarshaller = JAXBContext.newInstance(JAXB_CONTEXT_PACKAGES).createUnmarshaller(); } @Test public void testConvertHostResource() throws Exception { - XjcRdeHost xjcHost = getHost(); - HostResource host = XjcToHostResourceConverter.convert(xjcHost); + XjcRdeHost xjcHost = loadHostFromRdeXml(); + HostResource host = convertHostInTransaction(xjcHost); assertThat(host.getFullyQualifiedHostName()).isEqualTo("ns1.example1.test"); assertThat(host.getRepoId()).isEqualTo("Hns1_example1_test-TEST"); // The imported XML also had LINKED status, but that should have been dropped on import. assertThat(host.getStatusValues()).containsExactly(StatusValue.OK); - assertThat(host.getInetAddresses()).containsExactly( - InetAddresses.forString("192.0.2.2"), - InetAddresses.forString("192.0.2.29"), - InetAddresses.forString("1080:0:0:0:8:800:200C:417A")); + assertThat(host.getInetAddresses()) + .containsExactly( + InetAddresses.forString("192.0.2.2"), + InetAddresses.forString("192.0.2.29"), + InetAddresses.forString("1080:0:0:0:8:800:200C:417A")); assertThat(host.getCurrentSponsorClientId()).isEqualTo("RegistrarX"); assertThat(host.getCreationClientId()).isEqualTo("RegistrarX"); assertThat(host.getCreationTime()).isEqualTo(DateTime.parse("1999-05-08T12:10:00.0Z")); @@ -96,7 +103,37 @@ public class XjcToHostResourceConverterTest extends ShardableTestCase { assertThat(host.getLastTransferTime()).isEqualTo(DateTime.parse("2008-10-03T09:34:00.0Z")); } - private XjcRdeHost getHost() throws Exception { + @Test + public void testConvertHostResourceHistoryEntry() throws Exception { + XjcRdeHost xjcHost = loadHostFromRdeXml(); + HostResource host = convertHostInTransaction(xjcHost); + List historyEntries = getHistoryEntries(host); + assertThat(historyEntries).hasSize(1); + HistoryEntry entry = historyEntries.get(0); + assertThat(entry.getType()).isEqualTo(HistoryEntry.Type.RDE_IMPORT); + assertThat(entry.getClientId()).isEqualTo("RegistrarX"); + assertThat(entry.getBySuperuser()).isTrue(); + assertThat(entry.getReason()).isEqualTo("RDE Import"); + assertThat(entry.getRequestedByRegistrar()).isFalse(); + checkTrid(entry.getTrid()); + // check xml against original domain xml + try (InputStream ins = new ByteArrayInputStream(entry.getXmlBytes())) { + XjcRdeHost unmarshalledXml = ((XjcRdeHostElement) unmarshaller.unmarshal(ins)).getValue(); + assertThat(unmarshalledXml.getName()).isEqualTo(xjcHost.getName()); + assertThat(unmarshalledXml.getRoid()).isEqualTo(xjcHost.getRoid()); + } + } + + private static HostResource convertHostInTransaction(final XjcRdeHost xjcHost) { + return ofy().transact(new Work() { + @Override + public HostResource run() { + return XjcToHostResourceConverter.convert(xjcHost); + } + }); + } + + private XjcRdeHost loadHostFromRdeXml() throws Exception { try (InputStream ins = HOST_XML.openStream()) { return ((XjcRdeHostElement) unmarshaller.unmarshal(ins)).getValue(); } diff --git a/javatests/google/registry/rde/imports/testdata/deposit_10_domain.xml b/javatests/google/registry/rde/imports/testdata/deposit_10_domain.xml new file mode 100644 index 000000000..9c1173efd --- /dev/null +++ b/javatests/google/registry/rde/imports/testdata/deposit_10_domain.xml @@ -0,0 +1,369 @@ + + + + 2010-10-17T00:00:00Z + + 1.0 + urn:ietf:params:xml:ns:rdeHeader-1.0 + urn:ietf:params:xml:ns:rdeContact-1.0 + urn:ietf:params:xml:ns:rdeHost-1.0 + urn:ietf:params:xml:ns:rdeDomain-1.0 + urn:ietf:params:xml:ns:rdeRegistrar-1.0 + urn:ietf:params:xml:ns:rdeIDN-1.0 + urn:ietf:params:xml:ns:rdeNNDN-1.0 + urn:ietf:params:xml:ns:rdeEppParams-1.0 + + + + + + + test + 2 + + 1 + + 1 + + 1 + + 1 + + 1 + + 1 + + + + + + example1.test + Dexample1-TEST + + jd1234 + sh8013 + sh8013 + RegistrarX + RegistrarX + 1999-04-03T22:00:00.0Z + 2015-04-03T22:00:00.0Z + + + + + example2.test + Dexample2-TEST + + + jd1234 + sh8013 + sh8013 + RegistrarX + RegistrarX + 1999-04-03T22:00:00.0Z + 2015-04-03T22:00:00.0Z + + + + + example3.test + Dexample3-TEST + + + jd1234 + sh8013 + sh8013 + RegistrarX + RegistrarX + 1999-04-03T22:00:00.0Z + 2015-04-03T22:00:00.0Z + + + + + example4.test + Dexample4-TEST + + + jd1234 + sh8013 + sh8013 + RegistrarX + RegistrarX + 1999-04-03T22:00:00.0Z + 2015-04-03T22:00:00.0Z + + + + + example5.test + Dexample5-TEST + + jd1234 + sh8013 + sh8013 + RegistrarX + RegistrarX + 1999-04-03T22:00:00.0Z + 2015-04-03T22:00:00.0Z + + + + + example6.test + Dexample6-TEST + + jd1234 + sh8013 + sh8013 + RegistrarX + RegistrarX + 1999-04-03T22:00:00.0Z + 2015-04-03T22:00:00.0Z + + + + + example7.test + Dexample7-TEST + + jd1234 + sh8013 + sh8013 + RegistrarX + RegistrarX + 1999-04-03T22:00:00.0Z + 2015-04-03T22:00:00.0Z + + + + + example8.test + Dexample8-TEST + + jd1234 + sh8013 + sh8013 + RegistrarX + RegistrarX + 1999-04-03T22:00:00.0Z + 2015-04-03T22:00:00.0Z + + + + + example9.test + Dexample9-TEST + + jd1234 + sh8013 + sh8013 + RegistrarX + RegistrarX + 1999-04-03T22:00:00.0Z + 2015-04-03T22:00:00.0Z + + + + + example10.test + Dexample10-TEST + + jd1234 + sh8013 + sh8013 + RegistrarX + RegistrarX + 1999-04-03T22:00:00.0Z + 2015-04-03T22:00:00.0Z + + + + + ns1.example.com + Hns1_example_com-TEST + + + 192.0.2.2 + 192.0.2.29 + 1080:0:0:0:8:800:200C:417A + + RegistrarX + RegistrarX + 1999-05-08T12:10:00.0Z + RegistrarX + 2009-10-03T09:34:00.0Z + + + + + ns1.example1.test + Hns1_example1_test-TEST + + + 192.0.2.2 + 192.0.2.29 + 1080:0:0:0:8:800:200C:417A + + RegistrarX + RegistrarX + 1999-05-08T12:10:00.0Z + RegistrarX + 2009-10-03T09:34:00.0Z + + + + + sh8013 + Csh8013-TEST + + + + John Doe + Example Inc. + + 123 Example Dr. + Suite 100 + Dulles + VA + 20166-6503 + US + + + +1.7035555555 + + +1.7035555556 + + jdoe@example.test + + RegistrarX + RegistrarX + + 2009-09-13T08:01:00.0Z + RegistrarX + + 2009-11-26T09:10:00.0Z + 2009-12-03T09:05:00.0Z + + + + + + + + + RegistrarX + Registrar X + 123 + ok + + + 123 Example Dr. + + Suite 100 + + Dulles + VA + 20166-6503 + US + + + +1.7035555555 + + +1.7035555556 + + jdoe@example.test + + http://www.example.test + + + whois.example.test + + http://whois.example.test + + + 2005-04-23T11:49:00.0Z + 2009-02-17T17:51:00.0Z + + + + + +http://www.iana.org/domains/idn-tables/tables/br_pt-br_1.0.html + + + http://registro.br/dominio/regras.html + + + + + + xn--exampl-gva.test + pt-BR + example1.test + withheld + 2005-04-23T11:49:00.0Z + + + + + 1.0 + en + + urn:ietf:params:xml:ns:domain-1.0 + + + urn:ietf:params:xml:ns:contact-1.0 + + + urn:ietf:params:xml:ns:host-1.0 + + + urn:ietf:params:xml:ns:rgp-1.0 + + urn:ietf:params:xml:ns:secDNS-1.1 + + + + + + + + + + + + + + + + + + + + + + diff --git a/javatests/google/registry/rde/imports/testdata/deposit_1_domain.xml b/javatests/google/registry/rde/imports/testdata/deposit_1_domain.xml new file mode 100644 index 000000000..4a3ccaa34 --- /dev/null +++ b/javatests/google/registry/rde/imports/testdata/deposit_1_domain.xml @@ -0,0 +1,240 @@ + + + + 2010-10-17T00:00:00Z + + 1.0 + urn:ietf:params:xml:ns:rdeHeader-1.0 + urn:ietf:params:xml:ns:rdeContact-1.0 + urn:ietf:params:xml:ns:rdeHost-1.0 + urn:ietf:params:xml:ns:rdeDomain-1.0 + urn:ietf:params:xml:ns:rdeRegistrar-1.0 + urn:ietf:params:xml:ns:rdeIDN-1.0 + urn:ietf:params:xml:ns:rdeNNDN-1.0 + urn:ietf:params:xml:ns:rdeEppParams-1.0 + + + + + + + test + 2 + + 1 + + 1 + + 1 + + 1 + + 1 + + 1 + + + + + + example1.test + Dexample1-TEST + + jd1234 + sh8013 + sh8013 + RegistrarX + RegistrarX + 1999-04-03T22:00:00.0Z + 2015-04-03T22:00:00.0Z + + + + + ns1.example.com + Hns1_example_com-TEST + + + 192.0.2.2 + 192.0.2.29 + 1080:0:0:0:8:800:200C:417A + + RegistrarX + RegistrarX + 1999-05-08T12:10:00.0Z + RegistrarX + 2009-10-03T09:34:00.0Z + + + + + ns1.example1.test + Hns1_example1_test-TEST + + + 192.0.2.2 + 192.0.2.29 + 1080:0:0:0:8:800:200C:417A + + RegistrarX + RegistrarX + 1999-05-08T12:10:00.0Z + RegistrarX + 2009-10-03T09:34:00.0Z + + + + + sh8013 + Csh8013-TEST + + + + John Doe + Example Inc. + + 123 Example Dr. + Suite 100 + Dulles + VA + 20166-6503 + US + + + +1.7035555555 + + +1.7035555556 + + jdoe@example.test + + RegistrarX + RegistrarX + + 2009-09-13T08:01:00.0Z + RegistrarX + + 2009-11-26T09:10:00.0Z + 2009-12-03T09:05:00.0Z + + + + + + + + + RegistrarX + Registrar X + 123 + ok + + + 123 Example Dr. + + Suite 100 + + Dulles + VA + 20166-6503 + US + + + +1.7035555555 + + +1.7035555556 + + jdoe@example.test + + http://www.example.test + + + whois.example.test + + http://whois.example.test + + + 2005-04-23T11:49:00.0Z + 2009-02-17T17:51:00.0Z + + + + + +http://www.iana.org/domains/idn-tables/tables/br_pt-br_1.0.html + + + http://registro.br/dominio/regras.html + + + + + + xn--exampl-gva.test + pt-BR + example1.test + withheld + 2005-04-23T11:49:00.0Z + + + + + 1.0 + en + + urn:ietf:params:xml:ns:domain-1.0 + + + urn:ietf:params:xml:ns:contact-1.0 + + + urn:ietf:params:xml:ns:host-1.0 + + + urn:ietf:params:xml:ns:rgp-1.0 + + urn:ietf:params:xml:ns:secDNS-1.1 + + + + + + + + + + + + + + + + + + + + + + diff --git a/javatests/google/registry/rde/imports/testdata/deposit_3_domain.xml b/javatests/google/registry/rde/imports/testdata/deposit_3_domain.xml new file mode 100644 index 000000000..dd75e9793 --- /dev/null +++ b/javatests/google/registry/rde/imports/testdata/deposit_3_domain.xml @@ -0,0 +1,270 @@ + + + + 2010-10-17T00:00:00Z + + 1.0 + urn:ietf:params:xml:ns:rdeHeader-1.0 + urn:ietf:params:xml:ns:rdeContact-1.0 + urn:ietf:params:xml:ns:rdeHost-1.0 + urn:ietf:params:xml:ns:rdeDomain-1.0 + urn:ietf:params:xml:ns:rdeRegistrar-1.0 + urn:ietf:params:xml:ns:rdeIDN-1.0 + urn:ietf:params:xml:ns:rdeNNDN-1.0 + urn:ietf:params:xml:ns:rdeEppParams-1.0 + + + + + + + test + 2 + + 1 + + 1 + + 1 + + 1 + + 1 + + 1 + + + + + + example1.test + Dexample1-TEST + + jd1234 + sh8013 + sh8013 + RegistrarX + RegistrarX + 1999-04-03T22:00:00.0Z + 2015-04-03T22:00:00.0Z + + + + + example2.test + Dexample2-TEST + + + jd1234 + sh8013 + sh8013 + RegistrarX + RegistrarX + 1999-04-03T22:00:00.0Z + 2015-04-03T22:00:00.0Z + + + + + example3.test + Dexample3-TEST + + + jd1234 + sh8013 + sh8013 + RegistrarX + RegistrarX + 1999-04-03T22:00:00.0Z + 2015-04-03T22:00:00.0Z + + + + + ns1.example.com + Hns1_example_com-TEST + + + 192.0.2.2 + 192.0.2.29 + 1080:0:0:0:8:800:200C:417A + + RegistrarX + RegistrarX + 1999-05-08T12:10:00.0Z + RegistrarX + 2009-10-03T09:34:00.0Z + + + + + ns1.example1.test + Hns1_example1_test-TEST + + + 192.0.2.2 + 192.0.2.29 + 1080:0:0:0:8:800:200C:417A + + RegistrarX + RegistrarX + 1999-05-08T12:10:00.0Z + RegistrarX + 2009-10-03T09:34:00.0Z + + + + + sh8013 + Csh8013-TEST + + + + John Doe + Example Inc. + + 123 Example Dr. + Suite 100 + Dulles + VA + 20166-6503 + US + + + +1.7035555555 + + +1.7035555556 + + jdoe@example.test + + RegistrarX + RegistrarX + + 2009-09-13T08:01:00.0Z + RegistrarX + + 2009-11-26T09:10:00.0Z + 2009-12-03T09:05:00.0Z + + + + + + + + + RegistrarX + Registrar X + 123 + ok + + + 123 Example Dr. + + Suite 100 + + Dulles + VA + 20166-6503 + US + + + +1.7035555555 + + +1.7035555556 + + jdoe@example.test + + http://www.example.test + + + whois.example.test + + http://whois.example.test + + + 2005-04-23T11:49:00.0Z + 2009-02-17T17:51:00.0Z + + + + + +http://www.iana.org/domains/idn-tables/tables/br_pt-br_1.0.html + + + http://registro.br/dominio/regras.html + + + + + + xn--exampl-gva.test + pt-BR + example1.test + withheld + 2005-04-23T11:49:00.0Z + + + + + 1.0 + en + + urn:ietf:params:xml:ns:domain-1.0 + + + urn:ietf:params:xml:ns:contact-1.0 + + + urn:ietf:params:xml:ns:host-1.0 + + + urn:ietf:params:xml:ns:rgp-1.0 + + urn:ietf:params:xml:ns:secDNS-1.1 + + + + + + + + + + + + + + + + + + + + + + diff --git a/javatests/google/registry/rde/imports/testdata/deposit_4_domain.xml b/javatests/google/registry/rde/imports/testdata/deposit_4_domain.xml new file mode 100644 index 000000000..1b0924154 --- /dev/null +++ b/javatests/google/registry/rde/imports/testdata/deposit_4_domain.xml @@ -0,0 +1,285 @@ + + + + 2010-10-17T00:00:00Z + + 1.0 + urn:ietf:params:xml:ns:rdeHeader-1.0 + urn:ietf:params:xml:ns:rdeContact-1.0 + urn:ietf:params:xml:ns:rdeHost-1.0 + urn:ietf:params:xml:ns:rdeDomain-1.0 + urn:ietf:params:xml:ns:rdeRegistrar-1.0 + urn:ietf:params:xml:ns:rdeIDN-1.0 + urn:ietf:params:xml:ns:rdeNNDN-1.0 + urn:ietf:params:xml:ns:rdeEppParams-1.0 + + + + + + + test + 2 + + 1 + + 1 + + 1 + + 1 + + 1 + + 1 + + + + + + example1.test + Dexample1-TEST + + jd1234 + sh8013 + sh8013 + RegistrarX + RegistrarX + 1999-04-03T22:00:00.0Z + 2015-04-03T22:00:00.0Z + + + + + example2.test + Dexample2-TEST + + + jd1234 + sh8013 + sh8013 + RegistrarX + RegistrarX + 1999-04-03T22:00:00.0Z + 2015-04-03T22:00:00.0Z + + + + + example3.test + Dexample3-TEST + + + jd1234 + sh8013 + sh8013 + RegistrarX + RegistrarX + 1999-04-03T22:00:00.0Z + 2015-04-03T22:00:00.0Z + + + + + example4.test + Dexample4-TEST + + + jd1234 + sh8013 + sh8013 + RegistrarX + RegistrarX + 1999-04-03T22:00:00.0Z + 2015-04-03T22:00:00.0Z + + + + + ns1.example.com + Hns1_example_com-TEST + + + 192.0.2.2 + 192.0.2.29 + 1080:0:0:0:8:800:200C:417A + + RegistrarX + RegistrarX + 1999-05-08T12:10:00.0Z + RegistrarX + 2009-10-03T09:34:00.0Z + + + + + ns1.example1.test + Hns1_example1_test-TEST + + + 192.0.2.2 + 192.0.2.29 + 1080:0:0:0:8:800:200C:417A + + RegistrarX + RegistrarX + 1999-05-08T12:10:00.0Z + RegistrarX + 2009-10-03T09:34:00.0Z + + + + + sh8013 + Csh8013-TEST + + + + John Doe + Example Inc. + + 123 Example Dr. + Suite 100 + Dulles + VA + 20166-6503 + US + + + +1.7035555555 + + +1.7035555556 + + jdoe@example.test + + RegistrarX + RegistrarX + + 2009-09-13T08:01:00.0Z + RegistrarX + + 2009-11-26T09:10:00.0Z + 2009-12-03T09:05:00.0Z + + + + + + + + + RegistrarX + Registrar X + 123 + ok + + + 123 Example Dr. + + Suite 100 + + Dulles + VA + 20166-6503 + US + + + +1.7035555555 + + +1.7035555556 + + jdoe@example.test + + http://www.example.test + + + whois.example.test + + http://whois.example.test + + + 2005-04-23T11:49:00.0Z + 2009-02-17T17:51:00.0Z + + + + + +http://www.iana.org/domains/idn-tables/tables/br_pt-br_1.0.html + + + http://registro.br/dominio/regras.html + + + + + + xn--exampl-gva.test + pt-BR + example1.test + withheld + 2005-04-23T11:49:00.0Z + + + + + 1.0 + en + + urn:ietf:params:xml:ns:domain-1.0 + + + urn:ietf:params:xml:ns:contact-1.0 + + + urn:ietf:params:xml:ns:host-1.0 + + + urn:ietf:params:xml:ns:rgp-1.0 + + urn:ietf:params:xml:ns:secDNS-1.1 + + + + + + + + + + + + + + + + + + + + + + diff --git a/javatests/google/registry/xjc/JaxbFragmentTest.java b/javatests/google/registry/xjc/JaxbFragmentTest.java new file mode 100644 index 000000000..f1c09bb60 --- /dev/null +++ b/javatests/google/registry/xjc/JaxbFragmentTest.java @@ -0,0 +1,58 @@ +// Copyright 2017 The Nomulus 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 google.registry.xjc; + +import static com.google.common.truth.Truth.assertThat; +import static google.registry.util.ResourceUtils.readResourceUtf8; + +import google.registry.xjc.rdehost.XjcRdeHostElement; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class JaxbFragmentTest { + + private static final String HOST_FRAGMENT = + readResourceUtf8(XjcObjectTest.class, "testdata/host_fragment.xml"); + + /** Verifies that a {@link JaxbFragment} can be serialized and deserialized successfully. */ + @SuppressWarnings("unchecked") + @Test + public void testJavaSerialization() throws Exception { + // Load rdeHost xml fragment into a jaxb object, wrap it, marshal, unmarshal, verify host. + // The resulting host name should be "ns1.example1.test", from the original xml fragment. + try (InputStream source = new ByteArrayInputStream(HOST_FRAGMENT.getBytes())) { + // Load xml + JaxbFragment hostFragment = + JaxbFragment.create(XjcXmlTransformer.unmarshal(XjcRdeHostElement.class, source)); + // Marshal + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + new ObjectOutputStream(bout).writeObject(hostFragment); + // Unmarshal + ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bout.toByteArray())); + JaxbFragment restoredHostFragment = + (JaxbFragment) in.readObject(); + // Verify host name + assertThat(restoredHostFragment.getInstance().getValue().getName()) + .isEqualTo("ns1.example1.test"); + } + } +} diff --git a/javatests/google/registry/xjc/testdata/host_fragment.xml b/javatests/google/registry/xjc/testdata/host_fragment.xml new file mode 100644 index 000000000..0a9856a7d --- /dev/null +++ b/javatests/google/registry/xjc/testdata/host_fragment.xml @@ -0,0 +1,15 @@ + + ns1.example1.test + Hns1_example1_test-TEST + + + 192.0.2.2 + 192.0.2.29 + 1080:0:0:0:8:800:200C:417A + RegistrarX + RegistrarX + 1999-05-08T12:10:00.0Z + RegistrarX + 2009-10-03T09:34:00.0Z + 2008-10-03T09:34:00.0Z +