From 62c7a3935a3cf5985ac07ba3e36c96cf9b6dba30 Mon Sep 17 00:00:00 2001 From: mountford Date: Fri, 31 Mar 2017 13:51:28 -0700 Subject: [PATCH] Add nomulus tool command to delete a TLD ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151863632 --- .../registry/model/registry/Registry.java | 35 ++++--- .../registry/tools/DeleteTldCommand.java | 89 ++++++++++++++++++ java/google/registry/tools/RegistryTool.java | 1 + .../registry/testing/DatastoreHelper.java | 19 +++- .../registry/tools/DeleteTldCommandTest.java | 91 +++++++++++++++++++ 5 files changed, 219 insertions(+), 16 deletions(-) create mode 100644 java/google/registry/tools/DeleteTldCommand.java create mode 100644 javatests/google/registry/tools/DeleteTldCommandTest.java diff --git a/java/google/registry/model/registry/Registry.java b/java/google/registry/model/registry/Registry.java index 7588d8049..8a2e78d2f 100644 --- a/java/google/registry/model/registry/Registry.java +++ b/java/google/registry/model/registry/Registry.java @@ -217,6 +217,26 @@ public class Registry extends ImmutableObject implements Buildable { } } + /** Returns the registry for a given TLD, throwing if none exists. */ + public static Registry get(String tld) { + Registry registry = CACHE.getUnchecked(tld).orNull(); + if (registry == null) { + throw new RegistryNotFoundException(tld); + } + return registry; + } + + /** + * Invalidates the cache entry. + * + *

This is called automatically when the registry is saved. One should also call it when a + * registry is deleted. + */ + @OnSave + public void invalidateInCache() { + CACHE.invalidate(tldStr); + } + /** A cache that loads the {@link Registry} for a given tld. */ private static final LoadingCache> CACHE = CacheBuilder.newBuilder() @@ -236,21 +256,6 @@ public class Registry extends ImmutableObject implements Buildable { }})); }}); - /** Returns the registry for a given TLD, throwing if none exists. */ - public static Registry get(String tld) { - Registry registry = CACHE.getUnchecked(tld).orNull(); - if (registry == null) { - throw new RegistryNotFoundException(tld); - } - return registry; - } - - /** Whenever a registry is saved, invalidate the cache entry. */ - @OnSave - void updateCache() { - CACHE.invalidate(tldStr); - } - /** * The name of the pricing engine that this TLD uses. * diff --git a/java/google/registry/tools/DeleteTldCommand.java b/java/google/registry/tools/DeleteTldCommand.java new file mode 100644 index 000000000..257afb71a --- /dev/null +++ b/java/google/registry/tools/DeleteTldCommand.java @@ -0,0 +1,89 @@ +// 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.tools; + +import static com.google.common.base.Preconditions.checkState; +import static google.registry.model.ofy.ObjectifyService.ofy; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.googlecode.objectify.VoidWork; +import google.registry.model.domain.DomainResource; +import google.registry.model.registrar.Registrar; +import google.registry.model.registry.Registry; +import google.registry.model.registry.Registry.TldType; +import google.registry.tools.Command.RemoteApiCommand; + +/** + * Command to delete the {@link Registry} associated with the specified TLD in Datastore. + * + *

This command will fail if any domains are currently registered on the TLD. + */ +@Parameters(separators = " =", commandDescription = "Delete a TLD from Datastore.") +final class DeleteTldCommand extends ConfirmingCommand implements RemoteApiCommand { + + private Registry registry; + + @Parameter( + names = {"-t", "--tld"}, + description = "The TLD to delete.", + required = true) + private String tld; + + /** + * Perform the command by deleting the TLD. + * + *

Note that this uses an eventually consistent query, so theoretically, if you create a TLD, + * create domains on it, then delete the TLD quickly enough, the code won't notice the domains, + * and will let you delete the TLD. Since this command is only intended to be used in cleanup + * tasks, that should be ok, and the check should always provide the desired safety against + * accidental deletion of established TLDs with domains on them. + */ + @Override + protected void init() throws Exception { + registry = Registry.get(tld); + checkState(registry.getTldType().equals(TldType.TEST), "Cannot delete a real TLD"); + + for (Registrar registrar : Registrar.loadAll()) { + checkState( + !registrar.getAllowedTlds().contains(tld), + "Cannot delete TLD because registrar %s lists it as an allowed TLD", + registrar.getClientId()); + } + + int count = ofy().load() + .type(DomainResource.class) + .filter("tld", tld) + .limit(1) + .count(); + checkState(count == 0, "Cannot delete TLD because a domain is defined on it"); + } + + @Override + protected String prompt() { + return "You are about to delete TLD: " + tld; + } + + @Override + protected String execute() throws Exception { + ofy().transactNew(new VoidWork() { + @Override + public void vrun() { + ofy().delete().entity(registry).now(); + }}); + registry.invalidateInCache(); + return String.format("Deleted TLD '%s'.\n", tld); + } +} diff --git a/java/google/registry/tools/RegistryTool.java b/java/google/registry/tools/RegistryTool.java index 1e9289de5..4b13d4e86 100644 --- a/java/google/registry/tools/RegistryTool.java +++ b/java/google/registry/tools/RegistryTool.java @@ -52,6 +52,7 @@ public final class RegistryTool { .put("delete_entity", DeleteEntityCommand.class) .put("delete_premium_list", DeletePremiumListCommand.class) .put("delete_reserved_list", DeleteReservedListCommand.class) + .put("delete_tld", DeleteTldCommand.class) .put("domain_application_info", DomainApplicationInfoCommand.class) .put("domain_check", DomainCheckCommand.class) .put("domain_check_claims", DomainCheckClaimsCommand.class) diff --git a/javatests/google/registry/testing/DatastoreHelper.java b/javatests/google/registry/testing/DatastoreHelper.java index 89e2ca988..37e5369ec 100644 --- a/javatests/google/registry/testing/DatastoreHelper.java +++ b/javatests/google/registry/testing/DatastoreHelper.java @@ -85,6 +85,7 @@ import google.registry.model.pricing.StaticPremiumListPricingEngine; import google.registry.model.registrar.Registrar; import google.registry.model.registry.Registry; import google.registry.model.registry.Registry.TldState; +import google.registry.model.registry.Registry.TldType; import google.registry.model.registry.label.PremiumList; import google.registry.model.registry.label.PremiumList.PremiumListEntry; import google.registry.model.registry.label.PremiumList.PremiumListRevision; @@ -237,7 +238,23 @@ public class DatastoreHelper { public static Registry newRegistry( String tld, String roidSuffix, ImmutableSortedMap tldStates) { - return new Registry.Builder() + return setupRegistry(new Registry.Builder(), tld, roidSuffix, tldStates); + } + + public static Registry newRegistry( + String tld, + String roidSuffix, + ImmutableSortedMap tldStates, + TldType tldType) { + return setupRegistry(new Registry.Builder().setTldType(tldType), tld, roidSuffix, tldStates); + } + + private static Registry setupRegistry( + Registry.Builder registryBuilder, + String tld, + String roidSuffix, + ImmutableSortedMap tldStates) { + return registryBuilder .setTldStr(tld) .setRoidSuffix(roidSuffix) .setTldStateTransitions(tldStates) diff --git a/javatests/google/registry/tools/DeleteTldCommandTest.java b/javatests/google/registry/tools/DeleteTldCommandTest.java new file mode 100644 index 000000000..51063050f --- /dev/null +++ b/javatests/google/registry/tools/DeleteTldCommandTest.java @@ -0,0 +1,91 @@ +// 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.tools; + +import static google.registry.testing.DatastoreHelper.allowRegistrarAccess; +import static google.registry.testing.DatastoreHelper.newRegistry; +import static google.registry.testing.DatastoreHelper.persistDeletedDomain; +import static google.registry.testing.DatastoreHelper.persistResource; +import static google.registry.util.DateTimeUtils.START_OF_TIME; + +import com.google.common.base.Ascii; +import com.google.common.collect.ImmutableSortedMap; +import google.registry.model.registry.Registry; +import google.registry.model.registry.Registry.RegistryNotFoundException; +import google.registry.model.registry.Registry.TldState; +import google.registry.model.registry.Registry.TldType; +import org.joda.time.DateTime; +import org.junit.Before; +import org.junit.Test; + +/** Unit tests for {@link DeleteTldCommand}. */ +public class DeleteTldCommandTest extends CommandTestCase { + + private static final String TLD_REAL = "tldreal"; + private static final String TLD_TEST = "tldtest"; + + @Before + public void setUp() { + persistResource( + newRegistry( + TLD_REAL, + Ascii.toUpperCase(TLD_REAL), + ImmutableSortedMap.of(START_OF_TIME, TldState.GENERAL_AVAILABILITY), + TldType.REAL)); + persistResource( + newRegistry( + TLD_TEST, + Ascii.toUpperCase(TLD_TEST), + ImmutableSortedMap.of(START_OF_TIME, TldState.GENERAL_AVAILABILITY), + TldType.TEST)); + } + + @Test + public void testSuccess_otherTldUnaffected() throws Exception { + runCommandForced("--tld=" + TLD_TEST); + + Registry.get(TLD_REAL); + thrown.expect(RegistryNotFoundException.class); + Registry.get(TLD_TEST); + } + + @Test + public void testFailure_whenTldDoesNotExist() throws Exception { + thrown.expect(RegistryNotFoundException.class); + runCommandForced("--tld=nonexistenttld"); + } + + @Test + public void testFailure_whenTldIsReal() throws Exception { + thrown.expect(IllegalStateException.class); + runCommandForced("--tld=" + TLD_REAL); + } + + @Test + public void testFailure_whenDomainsArePresent() throws Exception { + persistDeletedDomain("domain." + TLD_TEST, DateTime.parse("2000-01-01TZ")); + + thrown.expect(IllegalStateException.class); + runCommandForced("--tld=" + TLD_TEST); + } + + @Test + public void testFailure_whenRegistrarLinksToTld() throws Exception { + allowRegistrarAccess("TheRegistrar", TLD_TEST); + + thrown.expect(IllegalStateException.class); + runCommandForced("--tld=" + TLD_TEST); + } +}