mv com/google/domain/registry google/registry

This change renames directories in preparation for the great package
rename. The repository is now in a broken state because the code
itself hasn't been updated. However this should ensure that git
correctly preserves history for each file.
This commit is contained in:
Justine Tunney 2016-05-13 18:55:08 -04:00
parent a41677aea1
commit 5012893c1d
2396 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,39 @@
package(
default_visibility = ["//java/com/google/domain/registry:registry_project"],
)
java_library(
name = "server",
srcs = glob(["*.java"]),
deps = [
"//java/com/google/common/annotations",
"//java/com/google/common/base",
"//java/com/google/common/collect",
"//java/com/google/common/io",
"//java/com/google/common/net",
"//java/com/google/common/primitives",
"//java/com/google/common/util/concurrent",
"//java/com/google/domain/registry/config",
"//java/com/google/domain/registry/export",
"//java/com/google/domain/registry/flows",
"//java/com/google/domain/registry/gcs",
"//java/com/google/domain/registry/groups",
"//java/com/google/domain/registry/mapreduce",
"//java/com/google/domain/registry/mapreduce/inputs",
"//java/com/google/domain/registry/model",
"//java/com/google/domain/registry/request",
"//java/com/google/domain/registry/util",
"//third_party/java/appengine:appengine-api",
"//third_party/java/appengine_gcs_client",
"//third_party/java/appengine_mapreduce2:appengine_mapreduce",
"//third_party/java/dagger",
"//third_party/java/jcommander",
"//third_party/java/joda_money",
"//third_party/java/joda_time",
"//third_party/java/jsr305_annotations",
"//third_party/java/jsr330_inject",
"//third_party/java/objectify:objectify-v4_1",
"//third_party/java/servlet/servlet_api",
],
)

View file

@ -0,0 +1,133 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.tools.server;
import static com.google.domain.registry.export.SyncGroupMembersAction.getGroupEmailAddressForContactType;
import static com.google.domain.registry.request.Action.Method.POST;
import static java.util.Arrays.asList;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.domain.registry.config.ConfigModule.Config;
import com.google.domain.registry.groups.GroupsConnection;
import com.google.domain.registry.groups.GroupsConnection.Role;
import com.google.domain.registry.model.registrar.Registrar;
import com.google.domain.registry.model.registrar.RegistrarContact;
import com.google.domain.registry.model.registrar.RegistrarContact.Type;
import com.google.domain.registry.request.Action;
import com.google.domain.registry.request.HttpException.BadRequestException;
import com.google.domain.registry.request.HttpException.InternalServerErrorException;
import com.google.domain.registry.request.Parameter;
import com.google.domain.registry.request.Response;
import com.google.domain.registry.util.Concurrent;
import com.google.domain.registry.util.FormattingLogger;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
import javax.annotation.Nullable;
import javax.inject.Inject;
/** Action that creates Google Groups for a registrar's mailing lists. */
@Action(path = CreateGroupsAction.PATH, method = POST)
public class CreateGroupsAction implements Runnable {
public static final String PATH = "/_dr/admin/createGroups";
public static final String CLIENT_ID_PARAM = "clientId";
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
private static final int NUM_SIMULTANEOUS_CONNECTIONS = 5;
@Inject GroupsConnection groupsConnection;
@Inject Response response;
@Inject @Config("publicDomainName") String publicDomainName;
@Inject @Parameter("clientId") Optional<String> clientId;
@Inject CreateGroupsAction() {}
@Override
public void run() {
final Registrar registrar = initAndLoadRegistrar();
if (registrar == null) {
return;
}
List<RegistrarContact.Type> types = asList(RegistrarContact.Type.values());
// Concurrently create the groups for each RegistrarContact.Type, collecting the results from
// each call (which are either an Exception if it failed, or absent() if it succeeded).
List<Optional<Exception>> results = Concurrent.transform(
types,
NUM_SIMULTANEOUS_CONNECTIONS,
new Function<RegistrarContact.Type, Optional<Exception>>() {
@Override
public Optional<Exception> apply(Type type) {
try {
String groupKey = getGroupEmailAddressForContactType(
registrar.getClientIdentifier(), type, publicDomainName);
String parentGroup =
getGroupEmailAddressForContactType("registrar", type, publicDomainName);
// Creates the group, then adds it as a member to the global registrar group for
// that type.
groupsConnection.createGroup(groupKey);
groupsConnection.addMemberToGroup(parentGroup, groupKey, Role.MEMBER);
return Optional.<Exception> absent();
} catch (Exception e) {
return Optional.of(e);
}
}});
// Return the correct server response based on the results of the group creations.
if (Optional.presentInstances(results).iterator().hasNext()) {
StringWriter responseString = new StringWriter();
PrintWriter responseWriter = new PrintWriter(responseString);
for (int i = 0; i < results.size(); i++) {
Optional<Exception> e = results.get(i);
if (e.isPresent()) {
responseWriter.append(types.get(i).getDisplayName()).append(" => ");
e.get().printStackTrace(responseWriter);
logger.severefmt(
e.get(),
"Could not create Google Group for registrar %s for type %s",
registrar.getRegistrarName(),
types.get(i).toString());
} else {
responseWriter.printf("%s => Success%n", types.get(i).getDisplayName());
}
}
throw new InternalServerErrorException(responseString.toString());
} else {
response.setStatus(SC_OK);
response.setPayload("Success!");
logger.info("Successfully created groups for registrar: " + registrar.getRegistrarName());
}
}
@Nullable
private Registrar initAndLoadRegistrar() {
if (!clientId.isPresent()) {
respondToBadRequest("Error creating Google Groups, missing parameter: clientId");
}
final Registrar registrar = Registrar.loadByClientId(clientId.get());
if (registrar == null) {
respondToBadRequest(String.format(
"Error creating Google Groups; could not find registrar with id %s", clientId.get()));
}
return registrar;
}
private void respondToBadRequest(String message) {
logger.severe(message);
throw new BadRequestException(message);
}
}

View file

@ -0,0 +1,52 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.tools.server;
import com.google.common.collect.ImmutableMap;
import com.google.domain.registry.request.JsonResponse;
import com.google.domain.registry.request.Parameter;
import com.google.domain.registry.util.FormattingLogger;
import javax.inject.Inject;
/**
* Abstract base class for actions that update premium lists.
*/
public abstract class CreateOrUpdatePremiumListAction implements Runnable {
protected static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
public static final String NAME_PARAM = "name";
public static final String INPUT_PARAM = "inputData";
@Inject JsonResponse response;
@Inject @Parameter(NAME_PARAM) String name;
@Inject @Parameter(INPUT_PARAM) String inputData;
@Override
public void run() {
try {
savePremiumList();
} catch (RuntimeException e) {
logger.severe(e, e.getMessage());
response.setPayload(ImmutableMap.of(
"error", e.toString(),
"status", "error"));
}
}
/** Creates a new premium list or updates an existing one. */
protected abstract void savePremiumList();
}

View file

@ -0,0 +1,68 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.tools.server;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.domain.registry.model.registry.Registries.assertTldExists;
import static com.google.domain.registry.request.Action.Method.POST;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.google.domain.registry.model.registry.label.PremiumList;
import com.google.domain.registry.request.Action;
import com.google.domain.registry.request.Parameter;
import java.util.List;
import javax.inject.Inject;
/**
* An action that creates a premium list, for use by the registry_tool create_premium_list command.
*/
@Action(path = CreatePremiumListAction.PATH, method = POST)
public class CreatePremiumListAction extends CreateOrUpdatePremiumListAction {
public static final String OVERRIDE_PARAM = "override";
public static final String PATH = "/_dr/admin/createPremiumList";
@Inject @Parameter(OVERRIDE_PARAM) boolean override;
@Inject CreatePremiumListAction() {}
@Override
protected void savePremiumList() {
checkArgument(
!PremiumList.exists(name),
"A premium list of this name already exists: %s.", name);
if (!override) {
assertTldExists(name);
}
logger.infofmt("Saving premium list for TLD %s", name);
logger.infofmt("Got the following input data: %s", inputData);
List<String> inputDataPreProcessed =
Splitter.on('\n').omitEmptyStrings().splitToList(inputData);
PremiumList premiumList = new PremiumList.Builder()
.setName(name)
.setPremiumListMapFromLines(inputDataPreProcessed)
.build();
premiumList.saveAndUpdateEntries();
logger.infofmt("Saved premium list %s with entries %s",
premiumList.getName(),
premiumList.getPremiumListEntries());
response.setPayload(ImmutableMap.of("status", "success"));
}
}

View file

@ -0,0 +1,118 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.tools.server;
import static com.google.appengine.api.datastore.DatastoreServiceFactory.getDatastoreService;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.googlecode.objectify.Key.create;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.common.base.Optional;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.domain.registry.request.Action;
import com.google.domain.registry.request.HttpException.BadRequestException;
import com.google.domain.registry.request.Parameter;
import com.google.domain.registry.request.Response;
import com.google.domain.registry.util.FormattingLogger;
import com.googlecode.objectify.VoidWork;
import com.googlecode.objectify.impl.EntityMetadata;
import javax.inject.Inject;
/**
* An action to delete entities in Datastore specified by raw key ids, which can be found in
* Datastore Viewer in the AppEngine console - it's the really long alphanumeric key that is
* labeled "Entity key" on the page for an individual entity.
*
* <p>rawKeys is the only required parameter. It is a comma-delimited list of Strings.
*
* <p><b>WARNING:</b> This servlet can be dangerous if used incorrectly as it can bypass checks on
* deletion (including whether the entity is referenced by other entities) and it does not write
* commit log entries for non-registered types. It should mainly be used for deleting testing or
* malformed data that cannot be properly deleted using existing tools. Generally, if there already
* exists an entity-specific deletion command, then use that one instead.
*/
@Action(path = DeleteEntityAction.PATH)
public class DeleteEntityAction implements Runnable {
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
public static final String PATH = "/_dr/admin/deleteEntity";
public static final String PARAM_RAW_KEYS = "rawKeys";
@Inject @Parameter(PARAM_RAW_KEYS) String rawKeys;
@Inject Response response;
@Inject DeleteEntityAction() {}
@Override
public void run() {
// Get raw key strings from request
ImmutableList.Builder<Object> ofyDeletionsBuilder = new ImmutableList.Builder<>();
ImmutableList.Builder<Key> rawDeletionsBuilder = new ImmutableList.Builder<>();
for (String rawKeyString : Splitter.on(',').split(rawKeys)) {
// Convert raw keys string to real keys. Try to load it from Objectify if it's a registered
// type, and fall back to DatastoreService if its not registered.
Key rawKey = KeyFactory.stringToKey(rawKeyString);
Optional<Object> ofyEntity = loadOfyEntity(rawKey);
if (ofyEntity.isPresent()) {
ofyDeletionsBuilder.add(ofyEntity.get());
continue;
}
Optional<Entity> rawEntity = loadRawEntity(rawKey);
if (rawEntity.isPresent()) {
rawDeletionsBuilder.add(rawEntity.get().getKey());
continue;
}
// The entity could not be found by either Objectify or the datastore service
throw new BadRequestException("Could not find entity with key " + rawKeyString);
}
// Delete raw entities.
ImmutableList<Key> rawDeletions = rawDeletionsBuilder.build();
getDatastoreService().delete(rawDeletions);
// Delete ofy entities.
final ImmutableList<Object> ofyDeletions = ofyDeletionsBuilder.build();
ofy().transactNew(new VoidWork() {
@Override
public void vrun() {
ofy().delete().entities(ofyDeletions).now();
}});
String message = String.format(
"Deleted %d raw entities and %d registered entities",
rawDeletions.size(),
ofyDeletions.size());
logger.info(message);
response.setPayload(message);
}
private Optional<Object> loadOfyEntity(Key rawKey) {
EntityMetadata<Object> metadata = ofy().factory().getMetadata(rawKey.getKind());
return Optional.fromNullable(metadata == null ? null : ofy().load().key(create(rawKey)).now());
}
private Optional<Entity> loadRawEntity(Key rawKey) {
try {
return Optional.fromNullable(getDatastoreService().get(rawKey));
} catch (EntityNotFoundException e) {
logger.warningfmt(e, "Could not load entity from datastore service with key %s",
rawKey.toString());
return Optional.absent();
}
}
}

View file

@ -0,0 +1,171 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.tools.server;
import static com.google.common.base.Verify.verifyNotNull;
import static com.google.domain.registry.mapreduce.MapreduceRunner.PARAM_DRY_RUN;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.model.registry.Registries.getTldsOfType;
import com.google.appengine.tools.mapreduce.Mapper;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.domain.registry.mapreduce.MapreduceRunner;
import com.google.domain.registry.mapreduce.inputs.EppResourceInputs;
import com.google.domain.registry.model.domain.DomainApplication;
import com.google.domain.registry.model.domain.DomainBase;
import com.google.domain.registry.model.index.EppResourceIndex;
import com.google.domain.registry.model.index.ForeignKeyIndex;
import com.google.domain.registry.model.index.ForeignKeyIndex.ForeignKeyDomainIndex;
import com.google.domain.registry.model.registry.Registry;
import com.google.domain.registry.model.registry.Registry.TldType;
import com.google.domain.registry.request.Action;
import com.google.domain.registry.request.Parameter;
import com.google.domain.registry.request.Response;
import com.google.domain.registry.util.FormattingLogger;
import com.google.domain.registry.util.PipelineUtils;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Work;
import java.util.List;
import javax.inject.Inject;
/**
* Deletes all prober DomainResources and their subordinate history entries, poll messages, and
* billing events, along with their ForeignKeyDomainIndex and EppResourceIndex entities.
*
* <p>See: https://www.youtube.com/watch?v=xuuv0syoHnM
*/
@Action(path = "/_dr/task/deleteProberData")
public class DeleteProberDataAction implements Runnable {
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
@Inject @Parameter(PARAM_DRY_RUN) boolean isDryRun;
@Inject MapreduceRunner mrRunner;
@Inject Response response;
@Inject DeleteProberDataAction() {}
@Override
public void run() {
response.sendJavaScriptRedirect(PipelineUtils.createJobPath(mrRunner
.setJobName("Delete prober data")
// TODO(b/27309488): maybe move this to the backend module.
.setModuleName("tools")
.runMapOnly(
new DeleteProberDataMapper(getProberRoidSuffixes(), isDryRun),
ImmutableList.of(EppResourceInputs.createKeyInput(DomainBase.class)))));
}
private static ImmutableSet<String> getProberRoidSuffixes() {
return FluentIterable.from(getTldsOfType(TldType.TEST))
.filter(new Predicate<String>() {
@Override
public boolean apply(String tld) {
// Extra sanity check to prevent us from nuking prod data if a real TLD accidentally
// gets set to type TEST.
return tld.endsWith(".test");
}})
.transform(
new Function<String, String>() {
@Override
public String apply(String tld) {
return Registry.get(tld).getRoidSuffix();
}})
.toSet();
}
/** Provides the map method that runs for each existing DomainBase entity. */
public static class DeleteProberDataMapper extends Mapper<Key<DomainBase>, Void, Void> {
private static final long serialVersionUID = 1737761271804180412L;
private final ImmutableSet<String> proberRoidSuffixes;
private final Boolean isDryRun;
public DeleteProberDataMapper(ImmutableSet<String> proberRoidSuffixes, Boolean isDryRun) {
this.proberRoidSuffixes = proberRoidSuffixes;
this.isDryRun = isDryRun;
}
@Override
public final void map(Key<DomainBase> key) {
try {
String roidSuffix = Iterables.getLast(Splitter.on('-').split(key.getName()));
if (proberRoidSuffixes.contains(roidSuffix)) {
deleteDomain(key);
} else {
getContext().incrementCounter(String.format("skipped, non-prober data"));
}
} catch (Throwable t) {
logger.severefmt(t, "Error while deleting prober data for key %s", key);
getContext().incrementCounter(String.format("error, kind %s", key.getKind()));
}
}
private void deleteDomain(final Key<DomainBase> domainKey) {
final DomainBase domain = ofy().load().key(domainKey).now();
if (domain == null) {
// Depending on how stale Datastore indexes are, we can get keys to resources that are
// already deleted (e.g. by a recent previous invocation of this mapreduce). So ignore them.
getContext().incrementCounter("already deleted");
return;
}
if (domain instanceof DomainApplication) {
// Cover the case where we somehow have a domain application with a prober ROID suffix.
getContext().incrementCounter("skipped, domain application");
return;
}
if (domain.getFullyQualifiedDomainName().equals("nic." + domain.getTld())) {
getContext().incrementCounter("skipped, NIC domain");
return;
}
int dependentsDeleted = ofy().transact(new Work<Integer>() {
@Override
public Integer run() {
EppResourceIndex eppIndex = ofy().load().entity(EppResourceIndex.create(domainKey)).now();
verifyNotNull(eppIndex, "Missing EppResourceIndex for domain %s", domain);
ForeignKeyIndex<?> fki = ofy().load().key(ForeignKeyDomainIndex.createKey(domain)).now();
verifyNotNull(fki, "Missing ForeignKeyDomainIndex for domain %s", domain);
// This ancestor query selects all descendant HistoryEntries, BillingEvents, and
// PollMessages, as well as the domain itself.
List<Key<Object>> domainAndDependentKeys = ofy().load().ancestor(domainKey).keys().list();
if (isDryRun) {
logger.infofmt(
"Would delete the following entities: %s",
new ImmutableList.Builder<Object>()
.add(fki)
.add(eppIndex)
.addAll(domainAndDependentKeys)
.build());
} else {
ofy().deleteWithoutBackup().keys(domainAndDependentKeys);
ofy().deleteWithoutBackup().entities(eppIndex, fki);
}
return domainAndDependentKeys.size() - 1;
}
});
getContext().incrementCounter(String.format("deleted, kind %s", domainKey.getKind()));
getContext().incrementCounter("deleted, dependent keys", dependentsDeleted);
}
}
}

View file

@ -0,0 +1,333 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.tools.server;
import static com.google.appengine.tools.cloudstorage.GcsServiceFactory.createGcsService;
import static com.google.common.base.Predicates.notNull;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Iterators.filter;
import static com.google.common.io.BaseEncoding.base16;
import static com.google.domain.registry.mapreduce.inputs.EppResourceInputs.createEntityInput;
import static com.google.domain.registry.model.EppResourceUtils.loadAtPointInTime;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.request.Action.Method.POST;
import static com.google.domain.registry.util.PipelineUtils.createJobPath;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.joda.time.DateTimeZone.UTC;
import com.google.appengine.tools.cloudstorage.GcsFilename;
import com.google.appengine.tools.cloudstorage.RetryParams;
import com.google.appengine.tools.mapreduce.Mapper;
import com.google.appengine.tools.mapreduce.Reducer;
import com.google.appengine.tools.mapreduce.ReducerInput;
import com.google.common.base.Function;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.domain.registry.config.ConfigModule.Config;
import com.google.domain.registry.gcs.GcsUtils;
import com.google.domain.registry.mapreduce.MapreduceRunner;
import com.google.domain.registry.mapreduce.inputs.NullInput;
import com.google.domain.registry.model.EppResource;
import com.google.domain.registry.model.domain.DomainResource;
import com.google.domain.registry.model.domain.ReferenceUnion;
import com.google.domain.registry.model.domain.secdns.DelegationSignerData;
import com.google.domain.registry.model.host.HostResource;
import com.google.domain.registry.request.Action;
import com.google.domain.registry.request.HttpException.BadRequestException;
import com.google.domain.registry.request.JsonActionRunner;
import com.google.domain.registry.util.Clock;
import com.googlecode.objectify.Ref;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
/**
* MapReduce that requests generation of BIND zone files for a set of TLDs at a given time.
*
* <p>Zone files for each requested TLD are written to GCS. TLDs without entries produce zone files
* with only a header. The export time must be at least two minutes in the past and no more than
* 29 days in the past, and must be at midnight UTC.
*/
@Action(
path = GenerateZoneFilesAction.PATH,
method = POST,
xsrfProtection = true,
xsrfScope = "admin")
public class GenerateZoneFilesAction implements Runnable, JsonActionRunner.JsonAction {
public static final String PATH = "/_dr/task/generateZoneFiles";
/** Format for the zone file name. */
private static final String FILENAME_FORMAT = "%s-%s.zone";
/** Format for the GCS path to a file. */
private static final String GCS_PATH_FORMAT = "gs://%s/%s";
/** Format for the zone file header. */
private static final String HEADER_FORMAT = "$ORIGIN\t%s.\n\n";
/** Format for NS records. */
private static final String NS_FORMAT = "%s\t%d\tIN\tNS\t%s.\n";
/** Format for DS records. */
private static final String DS_FORMAT = "%s\t%d\tIN\tDS\t%d %d %d %s\n";
/** Format for A and AAAA records. */
private static final String A_FORMAT = "%s\t%d\tIN\t%s\t%s\n";
// TODO(b/20454352): Overhaul TTL configuration mechanism.
/** The time to live for exported NS record, in seconds. */
private static final int TTL_NS = 180;
/** The time to live for exported DS record, in seconds. */
private static final int TTL_DS = 86400;
/** The time to live for exported A/AAAA record, in seconds. */
private static final int TTL_A = 3600;
@Inject MapreduceRunner mrRunner;
@Inject JsonActionRunner jsonActionRunner;
@Inject @Config("zoneFilesBucket") String bucket;
@Inject @Config("gcsBufferSize") int gcsBufferSize;
@Inject @Config("commitLogDatastoreRetention") Duration datastoreRetention;
@Inject Clock clock;
@Inject GenerateZoneFilesAction() {}
@Override
public void run() {
jsonActionRunner.run(this);
}
@Override
public Map<String, Object> handleJsonRequest(Map<String, ?> json) {
@SuppressWarnings("unchecked")
ImmutableSet<String> tlds = ImmutableSet.copyOf((List<String>) json.get("tlds"));
final DateTime exportTime = DateTime.parse(json.get("exportTime").toString());
// We disallow exporting within the past 2 minutes because there might be outstanding writes.
// We can only reliably call loadAtPointInTime at times that are UTC midnight and >
// datastoreRetention ago in the past.
DateTime now = clock.nowUtc();
if (exportTime.isAfter(now.minusMinutes(2))) {
throw new BadRequestException("Invalid export time: must be > 2 minutes ago");
}
if (exportTime.isBefore(now.minus(datastoreRetention))) {
throw new BadRequestException(String.format(
"Invalid export time: must be < %d days ago",
datastoreRetention.getStandardDays()));
}
if (!exportTime.equals(exportTime.toDateTime(UTC).withTimeAtStartOfDay())) {
throw new BadRequestException("Invalid export time: must be midnight UTC");
}
String jobId = mrRunner
.setJobName("Generate bind file stanzas")
.setModuleName("tools")
.setDefaultReduceShards(tlds.size())
.runMapreduce(
new GenerateBindFileMapper(tlds, exportTime),
new GenerateBindFileReducer(bucket, exportTime, gcsBufferSize),
ImmutableList.of(
new NullInput<EppResource>(),
createEntityInput(DomainResource.class, HostResource.class)));
ImmutableList<String> filenames = FluentIterable.from(tlds)
.transform(
new Function<String, String>() {
@Override
public String apply(String tld) {
return String.format(
GCS_PATH_FORMAT,
bucket,
String.format(FILENAME_FORMAT, tld, exportTime));
}})
.toList();
return ImmutableMap.<String, Object>of(
"jobPath", createJobPath(jobId),
"filenames", filenames);
}
/** Mapper to find domains and hosts that were active at a given time. */
static class GenerateBindFileMapper extends Mapper<EppResource, String, String> {
private static final long serialVersionUID = 4647941823789859913L;
private final ImmutableSet<String> tlds;
private final DateTime exportTime;
GenerateBindFileMapper(ImmutableSet<String> tlds, DateTime exportTime) {
this.tlds = tlds;
this.exportTime = exportTime;
}
@Override
public void map(EppResource resource) {
if (resource == null) { // Force the reducer to always generate a bind header for each tld.
for (String tld : tlds) {
emit(tld, null);
}
} else if (resource instanceof DomainResource) {
mapDomain((DomainResource) resource);
} else {
mapHost((HostResource) resource);
}
}
private void mapDomain(DomainResource domain) {
// Domains never change their tld, so we can check if it's from the wrong tld right away.
if (tlds.contains(domain.getTld())) {
domain = loadAtPointInTime(domain, exportTime).now();
if (domain != null) { // A null means the domain was deleted (or not created) at this time.
String stanza = domainStanza(domain, exportTime);
if (!stanza.isEmpty()) {
emit(domain.getTld(), stanza);
getContext().incrementCounter(domain.getTld() + " domains");
}
}
}
}
private void mapHost(HostResource host) {
host = loadAtPointInTime(host, exportTime).now();
if (host != null) { // A null means the host was deleted (or not created) at this time.
// Find a matching tld. Hosts might change their tld, so check after the point-in-time load.
String fullyQualifiedHostName = host.getFullyQualifiedHostName();
for (String tld : tlds) {
if (fullyQualifiedHostName.endsWith("." + tld)) {
String stanza = hostStanza(host);
if (!stanza.isEmpty()) {
emit(tld, stanza);
getContext().incrementCounter(tld + " hosts");
}
return;
}
}
}
}
}
/** Reducer to write zone files to GCS. */
static class GenerateBindFileReducer extends Reducer<String, String, Void> {
private static final long serialVersionUID = -8489050680083119352L;
private final String bucket;
private final DateTime exportTime;
private final int gcsBufferSize;
GenerateBindFileReducer(String bucket, DateTime exportTime, int gcsBufferSize) {
this.bucket = bucket;
this.exportTime = exportTime;
this.gcsBufferSize = gcsBufferSize;
}
@Override
public void reduce(String tld, ReducerInput<String> stanzas) {
String stanzaCounter = tld + " stanzas";
GcsFilename filename =
new GcsFilename(bucket, String.format(FILENAME_FORMAT, tld, exportTime));
GcsUtils cloudStorage =
new GcsUtils(createGcsService(RetryParams.getDefaultInstance()), gcsBufferSize);
try (OutputStream gcsOutput = cloudStorage.openOutputStream(filename);
Writer osWriter = new OutputStreamWriter(gcsOutput, UTF_8);
PrintWriter writer = new PrintWriter(osWriter)) {
writer.printf(HEADER_FORMAT, tld);
for (Iterator<String> stanzaIter = filter(stanzas, notNull()); stanzaIter.hasNext(); ) {
writer.println(stanzaIter.next());
getContext().incrementCounter(stanzaCounter);
}
writer.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/**
* Generates DNS records for a domain (NS and DS).
*
* These look like this:
* {@code
* foo.tld 180 IN NS ns.example.com.
* foo.tld 86400 IN DS 1 2 3 000102
* }
*/
private static String domainStanza(DomainResource domain, DateTime exportTime) {
StringBuilder result = new StringBuilder();
for (HostResource nameserver : ofy().load().refs(
transform(
domain.getNameservers(),
new Function<ReferenceUnion<HostResource>, Ref<HostResource>>() {
@Override
public Ref<HostResource> apply(ReferenceUnion<HostResource> referenceUnion) {
return referenceUnion.getLinked();
}})).values()) {
result.append(String.format(
NS_FORMAT,
domain.getFullyQualifiedDomainName(),
TTL_NS,
// Load the nameservers at the export time in case they've been renamed or deleted.
loadAtPointInTime(nameserver, exportTime).now().getFullyQualifiedHostName()));
}
for (DelegationSignerData dsData : domain.getDsData()) {
result.append(String.format(
DS_FORMAT,
domain.getFullyQualifiedDomainName(),
TTL_DS,
dsData.getKeyTag(),
dsData.getAlgorithm(),
dsData.getDigestType(),
base16().encode((dsData.getDigest()))));
}
return result.toString();
}
/**
* Generates DNS records for a domain (A and AAAA).
*
* <p>These look like this:
* {@code
* ns.foo.tld 3600 IN A 127.0.0.1
* ns.foo.tld 3600 IN AAAA 0:0:0:0:0:0:0:1
* }
*/
private static String hostStanza(HostResource host) {
StringBuilder result = new StringBuilder();
for (InetAddress addr : host.getInetAddresses()) {
// must be either IPv4 or IPv6
String rrSetClass = (addr instanceof Inet4Address) ? "A" : "AAAA";
result.append(String.format(
A_FORMAT,
host.getFullyQualifiedHostName(),
TTL_A,
rrSetClass,
addr.getHostAddress()));
}
return result.toString();
}
}

View file

@ -0,0 +1,100 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.tools.server;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Lists.partition;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.request.Action.Method.POST;
import static com.google.domain.registry.util.PipelineUtils.createJobPath;
import com.google.appengine.tools.mapreduce.Mapper;
import com.google.appengine.tools.mapreduce.inputs.InMemoryInput;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.domain.registry.config.RegistryEnvironment;
import com.google.domain.registry.mapreduce.MapreduceAction;
import com.google.domain.registry.mapreduce.MapreduceRunner;
import com.google.domain.registry.model.ofy.CommitLogBucket;
import com.google.domain.registry.model.ofy.CommitLogCheckpointRoot;
import com.google.domain.registry.request.Action;
import com.google.domain.registry.request.Response;
import com.googlecode.objectify.Key;
import java.util.Arrays;
import javax.inject.Inject;
/** Deletes all commit logs in datastore. */
@Action(path = "/_dr/task/killAllCommitLogs", method = POST)
public class KillAllCommitLogsAction implements MapreduceAction {
@Inject MapreduceRunner mrRunner;
@Inject Response response;
@Inject KillAllCommitLogsAction() {}
@Override
public final void run() {
checkArgument( // safety
RegistryEnvironment.get() == RegistryEnvironment.CRASH
|| RegistryEnvironment.get() == RegistryEnvironment.UNITTEST,
"DO NOT RUN ANYWHERE ELSE EXCEPT CRASH OR TESTS.");
response.sendJavaScriptRedirect(createJobPath(mrRunner
.setJobName("Delete all commit logs and checkpoints")
.setModuleName("tools")
.runMapreduce(
new KillAllCommitLogsMapper(),
new KillAllEntitiesReducer(),
// Create a in-memory input, assigning each bucket to its own shard for maximum
// parallelization, with one extra shard for the CommitLogCheckpointRoot.
ImmutableList.of(
new InMemoryInput<>(
partition(
FluentIterable
.from(Arrays.<Key<?>>asList(CommitLogCheckpointRoot.getKey()))
.append(CommitLogBucket.getAllBucketKeys())
.toList(),
1))))));
}
/**
* Mapper to delete a {@link CommitLogBucket} or {@link CommitLogCheckpointRoot} and any commit
* logs or checkpoints that descend from it.
*
* <p>This will delete:
* <ul>
* <li>{@link CommitLogBucket}
* <li>{@code CommitLogCheckpoint}
* <li>{@link CommitLogCheckpointRoot}
* <li>{@code CommitLogManifest}
* <li>{@code CommitLogMutation}
* </ul>
*/
static class KillAllCommitLogsMapper extends Mapper<Key<?>, Key<?>, Key<?>> {
private static final long serialVersionUID = 1504266335352952033L;
@Override
public void map(Key<?> bucketOrRoot) {
for (Key<Object> key : ofy().load().ancestor(bucketOrRoot).keys()) {
emit(bucketOrRoot, key);
getContext().incrementCounter("entities emitted");
getContext().incrementCounter(String.format("%s emitted", key.getKind()));
}
}
}
}

View file

@ -0,0 +1,105 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.tools.server;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.domain.registry.model.common.EntityGroupRoot.getCrossTldKey;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.request.Action.Method.POST;
import static com.google.domain.registry.util.PipelineUtils.createJobPath;
import com.google.appengine.tools.mapreduce.Mapper;
import com.google.common.collect.ImmutableList;
import com.google.domain.registry.config.RegistryEnvironment;
import com.google.domain.registry.mapreduce.MapreduceAction;
import com.google.domain.registry.mapreduce.MapreduceRunner;
import com.google.domain.registry.mapreduce.inputs.NullInput;
import com.google.domain.registry.model.common.EntityGroupRoot;
import com.google.domain.registry.request.Action;
import com.google.domain.registry.request.Response;
import com.googlecode.objectify.Key;
import javax.inject.Inject;
/**
* Deletes all cross tld entities in datastore.
*
* <p>This doesn't really need to be a mapreduce, but doing so makes it consistent with the other
* kill-all actions, and gives us retries, a dashboard and counters for free.
*/
@Action(path = "/_dr/task/killAllCrossTld", method = POST)
public class KillAllCrossTldEntitiesAction implements MapreduceAction {
@Inject MapreduceRunner mrRunner;
@Inject Response response;
@Inject KillAllCrossTldEntitiesAction() {}
@Override
public final void run() {
checkArgument( // safety
RegistryEnvironment.get() == RegistryEnvironment.CRASH
|| RegistryEnvironment.get() == RegistryEnvironment.UNITTEST,
"DO NOT RUN ANYWHERE ELSE EXCEPT CRASH OR TESTS.");
response.sendJavaScriptRedirect(createJobPath(mrRunner
.setJobName("Delete all cross-tld entities")
.setModuleName("tools")
.runMapreduce(
new KillAllCrossTldEntitiesMapper(),
new KillAllEntitiesReducer(),
ImmutableList.of(new NullInput<Object>()))));
}
/**
* Mapper to delete all descendants of {@link EntityGroupRoot#getCrossTldKey()}.
*
* <p>This will delete:
* <ul>
* <i>{@code ClaimsListShard}
* <i>{@code ClaimsListSingleton}
* <i>{@link EntityGroupRoot}
* <i>{@code LogsExportCursor}
* <i>{@code PremiumList}
* <i>{@code PremiumListEntry}
* <i>{@code Registrar}
* <i>{@code RegistrarBillingEntry}
* <i>{@code RegistrarContact}
* <i>{@code RegistrarCredit}
* <i>{@code RegistrarCreditBalance}
* <i>{@code Registry}
* <i>{@code RegistryCursor}
* <i>{@code ReservedList}
* <i>{@code ServerSecret}
* <i>{@code SignedMarkRevocationList}
* <i>{@code TmchCrl}
* </ul>
*/
static class KillAllCrossTldEntitiesMapper extends Mapper<Object, Key<?>, Key<?>> {
private static final long serialVersionUID = 8343696167876596542L;
@Override
public void map(Object ignored) {
// There will be exactly one input to the mapper, and we ignore it.
Key<EntityGroupRoot> crossTldKey = getCrossTldKey();
for (Key<Object> key : ofy().load().ancestor(crossTldKey).keys()) {
emit(crossTldKey, key);
getContext().incrementCounter("entities emitted");
getContext().incrementCounter(String.format("%s emitted", key.getKind()));
}
}
}
}

View file

@ -0,0 +1,53 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.tools.server;
import static com.google.common.collect.Iterators.partition;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import com.google.appengine.tools.mapreduce.Reducer;
import com.google.appengine.tools.mapreduce.ReducerInput;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.VoidWork;
import java.util.Iterator;
import java.util.List;
/** Reducer that deletes a group of keys, identified by a shared ancestor key. */
public class KillAllEntitiesReducer extends Reducer<Key<?>, Key<?>, Void> {
private static final long serialVersionUID = 7939357855356876000L;
private static final int BATCH_SIZE = 100;
@Override
public void reduce(Key<?> ancestor, final ReducerInput<Key<?>> keysToDelete) {
Iterator<List<Key<?>>> batches = partition(keysToDelete, BATCH_SIZE);
while (batches.hasNext()) {
final List<Key<?>> batch = batches.next();
// Use a transaction to get retrying for free.
ofy().transact(new VoidWork() {
@Override
public void vrun() {
ofy().deleteWithoutBackup().keys(batch);
}});
getContext().incrementCounter("entities deleted", batch.size());
for (Key<?> key : batch) {
getContext().incrementCounter(String.format("%s deleted", key.getKind()));
}
}
}
}

View file

@ -0,0 +1,113 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.tools.server;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.request.Action.Method.POST;
import static com.google.domain.registry.util.PipelineUtils.createJobPath;
import com.google.appengine.tools.mapreduce.Mapper;
import com.google.common.collect.ImmutableList;
import com.google.domain.registry.config.RegistryEnvironment;
import com.google.domain.registry.mapreduce.MapreduceAction;
import com.google.domain.registry.mapreduce.MapreduceRunner;
import com.google.domain.registry.mapreduce.inputs.EppResourceInputs;
import com.google.domain.registry.model.EppResource;
import com.google.domain.registry.model.domain.DomainApplication;
import com.google.domain.registry.model.index.DomainApplicationIndex;
import com.google.domain.registry.model.index.EppResourceIndex;
import com.google.domain.registry.model.index.ForeignKeyIndex;
import com.google.domain.registry.request.Action;
import com.google.domain.registry.request.Response;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Work;
import javax.inject.Inject;
/** Deletes all {@link EppResource} objects in datastore, including indices and descendants. */
@Action(path = "/_dr/task/killAllEppResources", method = POST)
public class KillAllEppResourcesAction implements MapreduceAction {
@Inject MapreduceRunner mrRunner;
@Inject Response response;
@Inject KillAllEppResourcesAction() {}
@Override
public final void run() {
checkArgument( // safety
RegistryEnvironment.get() == RegistryEnvironment.CRASH
|| RegistryEnvironment.get() == RegistryEnvironment.UNITTEST,
"DO NOT RUN ANYWHERE ELSE EXCEPT CRASH OR TESTS.");
response.sendJavaScriptRedirect(createJobPath(mrRunner
.setJobName("Delete all EppResources, children, and indices")
.setModuleName("tools")
.runMapreduce(
new KillAllEppResourcesMapper(),
new KillAllEntitiesReducer(),
ImmutableList.of(EppResourceInputs.createIndexInput()))));
}
/**
* Mapper to delete an {@link EppResourceIndex}, its referent, all descendants of each referent,
* and the {@link ForeignKeyIndex} or {@link DomainApplicationIndex} of the referent, as
* appropriate.
*
* <p>This will delete:
* <ul>
* <li>All {@link ForeignKeyIndex} types
* <li>{@link DomainApplicationIndex}
* <li>{@link EppResourceIndex}
* <li>All {@link EppResource} types
* <li>{@code HistoryEntry}
* <li>All {@code BillingEvent} types
* <li>All {@code PollMessage} types
* </ul>
*/
static class KillAllEppResourcesMapper extends Mapper<EppResourceIndex, Key<?>, Key<?>> {
private static final long serialVersionUID = 8205309000002507407L;
@Override
public void map(final EppResourceIndex eri) {
Key<EppResourceIndex> eriKey = Key.create(eri);
emitAndIncrementCounter(eriKey, eriKey);
Key<?> resourceKey = eri.getReference().getKey();
for (Key<Object> key : ofy().load().ancestor(resourceKey).keys()) {
emitAndIncrementCounter(resourceKey, key);
}
// Load in a transaction to make sure we don't get stale data (in case of host renames).
// TODO(b/27424173): A transaction is overkill. When we have memcache-skipping, use that.
EppResource resource = ofy().transactNewReadOnly(
new Work<EppResource>() {
@Override
public EppResource run() {
return eri.getReference().get();
}});
// TODO(b/28247733): What about FKI's for renamed hosts?
Key<?> indexKey = resource instanceof DomainApplication
? DomainApplicationIndex.createKey((DomainApplication) resource)
: ForeignKeyIndex.createKey(resource);
emitAndIncrementCounter(indexKey, indexKey);
}
private void emitAndIncrementCounter(Key<?> ancestor, Key<?> child) {
emit(ancestor, child);
getContext().incrementCounter("entities emitted");
getContext().incrementCounter(String.format("%s emitted", child.getKind()));
}
}
}

View file

@ -0,0 +1,59 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.tools.server;
import static com.google.domain.registry.model.EppResourceUtils.queryNotDeleted;
import static com.google.domain.registry.model.registry.Registries.assertTldExists;
import static com.google.domain.registry.request.Action.Method.GET;
import static com.google.domain.registry.request.Action.Method.POST;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableSet;
import com.google.domain.registry.model.domain.DomainResource;
import com.google.domain.registry.request.Action;
import com.google.domain.registry.request.Parameter;
import com.google.domain.registry.util.Clock;
import java.util.Comparator;
import javax.inject.Inject;
/** An action that lists domains, for use by the registry_tool list_domains command. */
@Action(path = ListDomainsAction.PATH, method = {GET, POST})
public final class ListDomainsAction extends ListObjectsAction<DomainResource> {
public static final String PATH = "/_dr/admin/list/domains";
public static final String TLD_PARAM = "tld";
@Inject @Parameter("tld") String tld;
@Inject Clock clock;
@Inject ListDomainsAction() {}
@Override
public ImmutableSet<String> getPrimaryKeyFields() {
return ImmutableSet.of("fullyQualifiedDomainName");
}
@Override
public ImmutableSet<DomainResource> loadObjects() {
return FluentIterable
.from(queryNotDeleted(DomainResource.class, clock.nowUtc(), "tld", assertTldExists(tld)))
.toSortedSet(new Comparator<DomainResource>() {
@Override
public int compare(DomainResource a, DomainResource b) {
return a.getFullyQualifiedDomainName().compareTo(b.getFullyQualifiedDomainName());
}});
}
}

View file

@ -0,0 +1,69 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.tools.server;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.request.Action.Method.GET;
import static com.google.domain.registry.request.Action.Method.POST;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableSet;
import com.google.domain.registry.model.EppResourceUtils;
import com.google.domain.registry.model.host.HostResource;
import com.google.domain.registry.request.Action;
import com.google.domain.registry.util.Clock;
import org.joda.time.DateTime;
import java.util.Comparator;
import javax.inject.Inject;
/** An action that lists hosts, for use by the registry_tool list_hosts command. */
@Action(path = ListHostsAction.PATH, method = {GET, POST})
public final class ListHostsAction extends ListObjectsAction<HostResource> {
public static final String PATH = "/_dr/admin/list/hosts";
private static final Comparator<HostResource> comparator =
new Comparator<HostResource>() {
@Override
public int compare(HostResource host1, HostResource host2) {
return host1.getFullyQualifiedHostName()
.compareTo(host2.getFullyQualifiedHostName());
}};
@Inject Clock clock;
@Inject ListHostsAction() {}
@Override
public ImmutableSet<String> getPrimaryKeyFields() {
return ImmutableSet.of("fullyQualifiedHostName");
}
@Override
public ImmutableSet<HostResource> loadObjects() {
final DateTime now = clock.nowUtc();
return FluentIterable
.from(ofy().load().type(HostResource.class))
.filter(new Predicate<HostResource>() {
@Override
public boolean apply(HostResource host) {
return EppResourceUtils.isActive(host, now);
}})
.toSortedSet(comparator);
}
}

View file

@ -0,0 +1,286 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.tools.server;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.google.domain.registry.model.ImmutableObject;
import com.google.domain.registry.request.JsonResponse;
import com.google.domain.registry.request.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.inject.Inject;
/**
* Abstract base class for actions that list ImmutableObjects.
*
* <p>Returns formatted text to be displayed on the screen.
*
* @param <T> type of object
*/
public abstract class ListObjectsAction<T extends ImmutableObject> implements Runnable {
public static final String FIELDS_PARAM = "fields";
public static final String PRINT_HEADER_ROW_PARAM = "printHeaderRow";
public static final String FULL_FIELD_NAMES_PARAM = "fullFieldNames";
@Inject JsonResponse response;
@Inject @Parameter("fields") Optional<String> fields;
@Inject @Parameter("printHeaderRow") Optional<Boolean> printHeaderRow;
@Inject @Parameter("fullFieldNames") Optional<Boolean> fullFieldNames;
/** Returns the set of objects to list, in the desired listing order. */
abstract ImmutableSet<T> loadObjects();
/**
* Returns a set of fields to always include in the output as the leftmost columns. Subclasses
* can use this to specify the equivalent of a "primary key" for each object listed.
*/
ImmutableSet<String> getPrimaryKeyFields() {
return ImmutableSet.of();
}
/**
* Returns an {@link ImmutableBiMap} that maps any field name aliases to the actual field names.
* <p>
* Users can select aliased fields for display using either the original name or the alias. By
* default, aliased fields will use the alias name as the header instead of the original name.
*/
ImmutableBiMap<String, String> getFieldAliases() {
return ImmutableBiMap.of();
}
/**
* Returns for a given {@link ImmutableObject} a mapping from field names to field values that
* will override, for any overlapping field names, the default behavior of getting the field
* value by looking up that field name in the map returned by
* {@link ImmutableObject#toDiffableFieldMap}.
* <p>
* This can be used to specify customized printing of certain fields (e.g. to print out a boolean
* field as "active" or "-" instead of "true" or "false"). It can also be used to add fields to
* the data, e.g. for computed fields that can be accessed from the object directly but aren't
* stored as simple fields.
*/
ImmutableMap<String, String> getFieldOverrides(@SuppressWarnings("unused") T object) {
return ImmutableMap.of();
}
@Override
public void run() {
try {
// Get the object data first, so we can figure out the list of all available fields using the
// data if necessary.
ImmutableSet<T> objects = loadObjects();
// Get the list of fields we should return.
ImmutableSet<String> fieldsToUse = getFieldsToUse(objects);
// Convert the data into a table.
ImmutableTable<T, String, String> data = extractData(fieldsToUse, objects);
// Now that we have the data table, compute the column widths.
ImmutableMap<String, Integer> columnWidths =
computeColumnWidths(data, isHeaderRowInUse(data));
// Finally, convert the table to an array of lines of text.
List<String> lines = generateFormattedData(data, columnWidths);
// Return the results.
response.setPayload(ImmutableMap.of(
"lines", lines,
"status", "success"));
} catch (Exception e) {
String message = e.getMessage();
if (message == null) {
message = e.getClass().getName();
}
response.setPayload(ImmutableMap.of(
"error", message,
"status", "error"));
}
}
/**
* Returns the set of fields to return, aliased or not according to --full_field_names, and
* with duplicates eliminated but the ordering otherwise preserved.
*/
private ImmutableSet<String> getFieldsToUse(ImmutableSet<T> objects) {
// Get the list of fields from the received parameter.
List<String> fieldsToUse;
if ((fields == null) || !fields.isPresent()) {
fieldsToUse = new ArrayList<>();
} else {
fieldsToUse = Splitter.on(',').splitToList(fields.get());
// Check whether any field name is the wildcard; if so, use all fields.
if (fieldsToUse.contains("*")) {
fieldsToUse = getAllAvailableFields(objects);
}
}
// Handle aliases according to the state of the fullFieldNames parameter.
final ImmutableMap<String, String> nameMapping =
((fullFieldNames != null) && fullFieldNames.isPresent() && fullFieldNames.get())
? getFieldAliases() : getFieldAliases().inverse();
return ImmutableSet.copyOf(Iterables.transform(
Iterables.concat(getPrimaryKeyFields(), fieldsToUse),
new Function<String, String>() {
@Override
public String apply(String field) {
// Rename fields that are in the map according to the map, and leave the others as is.
return nameMapping.containsKey(field) ? nameMapping.get(field) : field;
}}));
}
/**
* Constructs a list of all available fields for use by the wildcard field specification.
* Don't include aliases, since then we'd wind up returning the same field twice.
*/
private ImmutableList<String> getAllAvailableFields(ImmutableSet<T> objects) {
ImmutableList.Builder<String> fields = new ImmutableList.Builder<>();
for (T object : objects) {
// Base case of the mapping is to use ImmutableObject's toDiffableFieldMap().
fields.addAll(object.toDiffableFieldMap().keySet());
// Next, overlay any field-level overrides specified by the subclass.
fields.addAll(getFieldOverrides(object).keySet());
}
return fields.build();
}
/**
* Returns a table of data for the given sets of fields and objects. The table is row-keyed by
* object and column-keyed by field, in the same iteration order as the provided sets.
*/
private ImmutableTable<T, String, String>
extractData(ImmutableSet<String> fields, ImmutableSet<T> objects) {
ImmutableTable.Builder<T, String, String> builder = ImmutableTable.builder();
for (T object : objects) {
Map<String, Object> fieldMap = new HashMap<>();
// Base case of the mapping is to use ImmutableObject's toDiffableFieldMap().
fieldMap.putAll(object.toDiffableFieldMap());
// Next, overlay any field-level overrides specified by the subclass.
fieldMap.putAll(getFieldOverrides(object));
// Next, add to the mapping all the aliases, with their values defined as whatever was in the
// map under the aliased field's original name.
fieldMap.putAll(
Maps.transformValues(getFieldAliases(), Functions.forMap(new HashMap<>(fieldMap))));
Set<String> expectedFields = ImmutableSortedSet.copyOf(fieldMap.keySet());
for (String field : fields) {
checkArgument(fieldMap.containsKey(field),
"Field '%s' not found - recognized fields are:\n%s", field, expectedFields);
builder.put(object, field, Objects.toString(fieldMap.get(field), ""));
}
}
return builder.build();
}
/**
* Computes the column widths of the given table of strings column-keyed by strings and returns
* them as a map from column key name to integer width. The column width is defined as the max
* length of any string in that column, including the name of the column.
*/
private static ImmutableMap<String, Integer> computeColumnWidths(
ImmutableTable<?, String, String> data, final boolean includingHeader) {
return ImmutableMap.copyOf(Maps.transformEntries(
data.columnMap(),
new Maps.EntryTransformer<String, Map<?, String>, Integer>() {
@Override
public Integer transformEntry(String columnName, Map<?, String> columnValues) {
// Return the length of the longest string in this column (including the column name).
return Ordering.natural().max(Iterables.transform(
Iterables.concat(
ImmutableList.of(includingHeader ? columnName : ""),
columnValues.values()),
new Function<String, Integer>() {
@Override
public Integer apply(String value) {
return value.length();
}}));
}}));
}
/**
* Check whether to display headers. If the parameter is not set, print headers only if there
* is more than one column.
*/
private boolean isHeaderRowInUse(final ImmutableTable<?, String, String> data) {
return ((printHeaderRow != null) && printHeaderRow.isPresent())
? printHeaderRow.get() : (data.columnKeySet().size() > 1);
}
/** Converts the provided table of data to text, formatted using the provided column widths. */
private List<String> generateFormattedData(
ImmutableTable<T, String, String> data,
ImmutableMap<String, Integer> columnWidths) {
Function<Map<String, String>, String> rowFormatter = makeRowFormatter(columnWidths);
List<String> lines = new ArrayList<>();
if (isHeaderRowInUse(data)) {
// Add a row of headers (column names mapping to themselves).
Map<String, String> headerRow =
Maps.asMap(data.columnKeySet(), Functions.<String>identity());
lines.add(rowFormatter.apply(headerRow));
// Add a row of separator lines (column names mapping to '-' * column width).
Map<String, String> separatorRow = Maps.transformValues(columnWidths,
new Function<Integer, String>() {
@Override
public String apply(Integer width) {
return Strings.repeat("-", width);
}});
lines.add(rowFormatter.apply(separatorRow));
}
// Add the actual data rows.
for (Map<String, String> row : data.rowMap().values()) {
lines.add(rowFormatter.apply(row));
}
return lines;
}
/**
* Returns for the given column widths map a row formatting function that converts a row map (of
* column keys to cell values) into a single string with each column right-padded to that width.
* <p>
* The resulting strings separate padded fields with two spaces and each end in a newline.
*/
private static Function<Map<String, String>, String> makeRowFormatter(
final Map<String, Integer> columnWidths) {
return new Function<Map<String, String>, String>() {
@Override
public String apply(Map<String, String> rowByColumns) {
List<String> paddedFields = new ArrayList<>();
for (Map.Entry<String, String> cell : rowByColumns.entrySet()) {
paddedFields.add(Strings.padEnd(cell.getValue(), columnWidths.get(cell.getKey()), ' '));
}
return Joiner.on(" ").join(paddedFields);
}};
}
}

View file

@ -0,0 +1,46 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.tools.server;
import static com.google.domain.registry.model.common.EntityGroupRoot.getCrossTldKey;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.request.Action.Method.GET;
import static com.google.domain.registry.request.Action.Method.POST;
import com.google.common.collect.ImmutableSet;
import com.google.domain.registry.model.registry.label.PremiumList;
import com.google.domain.registry.request.Action;
import javax.inject.Inject;
/** An action that lists premium lists, for use by the registry_tool list_premium_lists command. */
@Action(path = ListPremiumListsAction.PATH, method = {GET, POST})
public final class ListPremiumListsAction extends ListObjectsAction<PremiumList> {
public static final String PATH = "/_dr/admin/list/premiumLists";
@Inject ListPremiumListsAction() {}
@Override
public ImmutableSet<String> getPrimaryKeyFields() {
return ImmutableSet.of("name");
}
@Override
public ImmutableSet<PremiumList> loadObjects() {
return ImmutableSet.copyOf(
ofy().load().type(PremiumList.class).ancestor(getCrossTldKey()).list());
}
}

View file

@ -0,0 +1,58 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.tools.server;
import static com.google.domain.registry.request.Action.Method.GET;
import static com.google.domain.registry.request.Action.Method.POST;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.domain.registry.model.registrar.Registrar;
import com.google.domain.registry.request.Action;
import javax.inject.Inject;
/** An action that lists registrars, for use by the registry_tool list_registrars command. */
@Action(path = ListRegistrarsAction.PATH, method = {GET, POST})
public final class ListRegistrarsAction extends ListObjectsAction<Registrar> {
public static final String PATH = "/_dr/admin/list/registrars";
@Inject ListRegistrarsAction() {}
@Override
public ImmutableSet<String> getPrimaryKeyFields() {
return ImmutableSet.of("clientIdentifier");
}
@Override
public ImmutableSet<Registrar> loadObjects() {
return ImmutableSet.copyOf(Registrar.loadAll());
}
@Override
public ImmutableBiMap<String, String> getFieldAliases() {
return ImmutableBiMap.of(
"clientId", "clientIdentifier",
"premiumNames", "blockPremiumNames");
}
@Override
public ImmutableMap<String, String> getFieldOverrides(Registrar registrar) {
return ImmutableMap.of(
"blockPremiumNames", registrar.getBlockPremiumNames() ? "blocked" : "-");
}
}

View file

@ -0,0 +1,46 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.tools.server;
import static com.google.domain.registry.model.common.EntityGroupRoot.getCrossTldKey;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.request.Action.Method.GET;
import static com.google.domain.registry.request.Action.Method.POST;
import com.google.common.collect.ImmutableSet;
import com.google.domain.registry.model.registry.label.ReservedList;
import com.google.domain.registry.request.Action;
import javax.inject.Inject;
/** A that lists reserved lists, for use by the registry_tool list_reserved_lists command. */
@Action(path = ListReservedListsAction.PATH, method = {GET, POST})
public final class ListReservedListsAction extends ListObjectsAction<ReservedList> {
public static final String PATH = "/_dr/admin/list/reservedLists";
@Inject ListReservedListsAction() {}
@Override
public ImmutableSet<String> getPrimaryKeyFields() {
return ImmutableSet.of("name");
}
@Override
public ImmutableSet<ReservedList> loadObjects() {
return ImmutableSet.copyOf(
ofy().load().type(ReservedList.class).ancestor(getCrossTldKey()).list());
}
}

View file

@ -0,0 +1,82 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.tools.server;
import static com.google.domain.registry.model.registry.Registries.getTlds;
import static com.google.domain.registry.request.Action.Method.GET;
import static com.google.domain.registry.request.Action.Method.POST;
import com.google.common.base.Function;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.domain.registry.model.registry.Registry;
import com.google.domain.registry.request.Action;
import com.google.domain.registry.util.Clock;
import org.joda.time.DateTime;
import javax.inject.Inject;
/** An action that lists top-level domains, for use by the registry_tool list_tlds command. */
@Action(path = ListTldsAction.PATH, method = {GET, POST})
public final class ListTldsAction extends ListObjectsAction<Registry> {
public static final String PATH = "/_dr/admin/list/tlds";
@Inject Clock clock;
@Inject ListTldsAction() {}
@Override
public ImmutableSet<String> getPrimaryKeyFields() {
return ImmutableSet.of("tldStr");
}
@Override
public ImmutableSet<Registry> loadObjects() {
return FluentIterable.from(getTlds())
.transform(new Function<String, Registry>() {
@Override
public Registry apply(String tldString) {
return Registry.get(tldString);
}})
.toSet();
}
@Override
public ImmutableBiMap<String, String> getFieldAliases() {
return ImmutableBiMap.of(
"TLD", "tldStr",
"dns", "dnsPaused",
"escrow", "escrowEnabled",
"premiumPricing", "premiumPriceAckRequired");
}
@Override
public ImmutableMap<String, String> getFieldOverrides(Registry registry) {
final DateTime now = clock.nowUtc();
return new ImmutableMap.Builder<String, String>()
.put("dnsPaused", registry.getDnsPaused() ? "paused" : "-")
.put("escrowEnabled", registry.getEscrowEnabled() ? "enabled" : "-")
.put("premiumPriceAckRequired", registry.getPremiumPriceAckRequired() ? "ack req'd" : "-")
.put("tldState", registry.isPdt(now) ? "PDT" : registry.getTldState(now).toString())
.put("tldStateTransitions", registry.getTldStateTransitions().toString())
.put("renewBillingCost", registry.getStandardRenewCost(now).toString())
.put("renewBillingCostTransitions", registry.getRenewBillingCostTransitions().toString())
.build();
}
}

View file

@ -0,0 +1,76 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.tools.server;
import static com.google.domain.registry.mapreduce.inputs.EppResourceInputs.createEntityInput;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.util.PipelineUtils.createJobPath;
import com.google.appengine.tools.mapreduce.Mapper;
import com.google.common.collect.ImmutableList;
import com.google.domain.registry.mapreduce.MapreduceAction;
import com.google.domain.registry.mapreduce.MapreduceRunner;
import com.google.domain.registry.model.EppResource;
import com.google.domain.registry.request.Action;
import com.google.domain.registry.request.Response;
import com.googlecode.objectify.VoidWork;
import javax.inject.Inject;
/**
* A mapreduce that re-saves all EppResources without otherwise modifying them.
*
* <p>This is useful for completing data migrations on EppResource fields that are accomplished
* with @OnSave or @OnLoad annotations, and also guarantees that all EppResources will get fresh
* commit logs (for backup purposes).
*/
@Action(path = "/_dr/task/resaveAllEppResources")
public class ResaveAllEppResourcesAction implements MapreduceAction {
@Inject MapreduceRunner mrRunner;
@Inject Response response;
@Inject ResaveAllEppResourcesAction() {}
@SuppressWarnings("unchecked")
@Override
public void run() {
response.sendJavaScriptRedirect(createJobPath(mrRunner
.setJobName("Re-save all EPP resources")
.setModuleName("backend")
.runMapOnly(
new ResaveAllEppResourcesActionMapper(),
ImmutableList.of(createEntityInput(EppResource.class)))));
}
/** Mapper to re-save all EPP resources. */
public static class ResaveAllEppResourcesActionMapper extends Mapper<EppResource, Void, Void> {
private static final long serialVersionUID = -7721628665138087001L;
public ResaveAllEppResourcesActionMapper() {}
@Override
public final void map(final EppResource resource) {
ofy().transact(new VoidWork() {
@Override
public void vrun() {
ofy().save().entity(resource).now();
}});
getContext().incrementCounter(
String.format("%s entities re-saved", resource.getClass().getSimpleName()));
}
}
}

View file

@ -0,0 +1,91 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.tools.server;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.domain.registry.request.RequestParameters.extractBooleanParameter;
import static com.google.domain.registry.request.RequestParameters.extractOptionalParameter;
import static com.google.domain.registry.request.RequestParameters.extractRequiredParameter;
import com.google.common.base.Optional;
import com.google.domain.registry.request.Parameter;
import dagger.Module;
import dagger.Provides;
import javax.servlet.http.HttpServletRequest;
/**
* Dagger module for the tools package.
*/
@Module
public class ToolsServerModule {
@Provides
@Parameter("clientId")
static Optional<String> provideClientId(HttpServletRequest req) {
return Optional.fromNullable(emptyToNull(req.getParameter(CreateGroupsAction.CLIENT_ID_PARAM)));
}
@Provides
@Parameter("fields")
static Optional<String> provideFields(HttpServletRequest req) {
return extractOptionalParameter(req, ListObjectsAction.FIELDS_PARAM);
}
@Provides
@Parameter("fullFieldNames")
static Optional<Boolean> provideFullFieldNames(HttpServletRequest req) {
String s = emptyToNull(req.getParameter(ListObjectsAction.FULL_FIELD_NAMES_PARAM));
return (s == null) ? Optional.<Boolean>absent() : Optional.of(Boolean.parseBoolean(s));
}
@Provides
@Parameter("inputData")
static String provideInput(HttpServletRequest req) {
return extractRequiredParameter(req, CreatePremiumListAction.INPUT_PARAM);
}
@Provides
@Parameter("name")
static String provideName(HttpServletRequest req) {
return extractRequiredParameter(req, CreatePremiumListAction.NAME_PARAM);
}
@Provides
@Parameter("override")
static boolean provideOverride(HttpServletRequest req) {
return extractBooleanParameter(req, CreatePremiumListAction.OVERRIDE_PARAM);
}
@Provides
@Parameter("printHeaderRow")
static Optional<Boolean> providePrintHeaderRow(HttpServletRequest req) {
String s = emptyToNull(req.getParameter(ListObjectsAction.PRINT_HEADER_ROW_PARAM));
return (s == null) ? Optional.<Boolean>absent() : Optional.of(Boolean.parseBoolean(s));
}
@Provides
@Parameter("tld")
static String provideTld(HttpServletRequest req) {
return extractRequiredParameter(req, "tld");
}
@Provides
@Parameter("rawKeys")
static String provideRawKeys(HttpServletRequest req) {
return extractRequiredParameter(req, "rawKeys");
}
}

View file

@ -0,0 +1,69 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.tools.server;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.domain.registry.request.Action.Method.POST;
import com.google.common.base.Optional;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.google.domain.registry.model.registry.label.PremiumList;
import com.google.domain.registry.request.Action;
import java.util.List;
import javax.inject.Inject;
/**
* An action that creates a premium list, for use by the registry_tool create_premium_list command.
*/
@Action(path = UpdatePremiumListAction.PATH, method = POST)
public class UpdatePremiumListAction extends CreateOrUpdatePremiumListAction {
public static final String PATH = "/_dr/admin/updatePremiumList";
@Inject UpdatePremiumListAction() {}
@Override
protected void savePremiumList() {
Optional<PremiumList> existingName = PremiumList.get(name);
checkArgument(
existingName.isPresent(),
"Could not update premium list %s because it doesn't exist.",
name);
logger.infofmt("Updating premium list for TLD %s", name);
logger.infofmt("Got the following input data: %s", inputData);
List<String> inputDataPreProcessed =
Splitter.on('\n').omitEmptyStrings().splitToList(inputData);
PremiumList premiumList = existingName.get().asBuilder()
.setPremiumListMapFromLines(inputDataPreProcessed)
.build();
premiumList.saveAndUpdateEntries();
logger.infofmt("Updated premium list %s with entries %s",
premiumList.getName(),
premiumList.getPremiumListEntries());
String message = String.format(
"Saved premium list %s with %d entries.\n",
premiumList.getName(),
premiumList.getPremiumListEntries().size());
response.setPayload(ImmutableMap.of(
"status", "success",
"message", message));
}
}

View file

@ -0,0 +1,348 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.tools.server;
import static com.google.common.base.Predicates.not;
import static com.google.common.collect.Maps.toMap;
import static com.google.domain.registry.flows.EppXmlTransformer.unmarshal;
import static com.google.domain.registry.flows.FlowRegistry.getFlowClass;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.util.CollectionUtils.isNullOrEmpty;
import static com.google.domain.registry.util.DomainNameUtils.ACE_PREFIX;
import static java.util.Arrays.asList;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multiset;
import com.google.domain.registry.flows.EppException;
import com.google.domain.registry.flows.Flow;
import com.google.domain.registry.flows.contact.ContactCreateFlow;
import com.google.domain.registry.flows.contact.ContactDeleteFlow;
import com.google.domain.registry.flows.contact.ContactTransferApproveFlow;
import com.google.domain.registry.flows.contact.ContactTransferCancelFlow;
import com.google.domain.registry.flows.contact.ContactTransferRejectFlow;
import com.google.domain.registry.flows.contact.ContactTransferRequestFlow;
import com.google.domain.registry.flows.contact.ContactUpdateFlow;
import com.google.domain.registry.flows.domain.DomainApplicationCreateFlow;
import com.google.domain.registry.flows.domain.DomainApplicationDeleteFlow;
import com.google.domain.registry.flows.domain.DomainApplicationUpdateFlow;
import com.google.domain.registry.flows.domain.DomainCreateFlow;
import com.google.domain.registry.flows.domain.DomainDeleteFlow;
import com.google.domain.registry.flows.domain.DomainRenewFlow;
import com.google.domain.registry.flows.domain.DomainRestoreRequestFlow;
import com.google.domain.registry.flows.domain.DomainTransferApproveFlow;
import com.google.domain.registry.flows.domain.DomainTransferCancelFlow;
import com.google.domain.registry.flows.domain.DomainTransferRejectFlow;
import com.google.domain.registry.flows.domain.DomainTransferRequestFlow;
import com.google.domain.registry.flows.domain.DomainUpdateFlow;
import com.google.domain.registry.flows.host.HostCreateFlow;
import com.google.domain.registry.flows.host.HostDeleteFlow;
import com.google.domain.registry.flows.host.HostUpdateFlow;
import com.google.domain.registry.model.domain.DomainCommand;
import com.google.domain.registry.model.domain.fee.FeeCreateExtension;
import com.google.domain.registry.model.domain.launch.LaunchCreateExtension;
import com.google.domain.registry.model.domain.secdns.SecDnsCreateExtension;
import com.google.domain.registry.model.domain.secdns.SecDnsUpdateExtension;
import com.google.domain.registry.model.eppinput.EppInput;
import com.google.domain.registry.model.eppinput.EppInput.ResourceCommandWrapper;
import com.google.domain.registry.model.host.HostCommand;
import com.google.domain.registry.model.reporting.HistoryEntry;
import com.google.domain.registry.request.Action;
import com.google.domain.registry.request.JsonActionRunner;
import com.google.domain.registry.request.JsonActionRunner.JsonAction;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
import javax.inject.Inject;
/**
* A servlet that verifies a registrar's OTE status. Note that this is eventually consistent, so
* OT&amp;E commands that have been run just previously to verification may not be picked up yet.
*/
@Action(
path = VerifyOteAction.PATH,
method = Action.Method.POST,
xsrfProtection = true,
xsrfScope = "admin")
public class VerifyOteAction implements Runnable, JsonAction {
public static final String PATH = "/_dr/admin/verifyOte";
@Inject JsonActionRunner jsonActionRunner;
@Inject VerifyOteAction() {}
@Override
public void run() {
jsonActionRunner.run(this);
}
@Override
@SuppressWarnings("unchecked")
public Map<String, Object> handleJsonRequest(Map<String, ?> json) {
final boolean summarize = Boolean.parseBoolean((String) json.get("summarize"));
return toMap(
(List<String>) json.get("registrars"),
new Function<String, Object>() {
@Nonnull
@Override
public Object apply(@Nonnull String registrar) {
return checkRegistrar(registrar, summarize);
}});
}
/** Checks whether the provided registrar has passed OT&amp;E and returns relevant information. */
private String checkRegistrar(String registrarName, boolean summarize) {
HistoryEntryStats historyEntryStats =
new HistoryEntryStats().recordRegistrarHistory(registrarName);
List<String> failureMessages = historyEntryStats.findFailures();
String passedFraction = String.format(
"%2d/%2d", StatType.NUM_REQUIREMENTS - failureMessages.size(), StatType.NUM_REQUIREMENTS);
String status = failureMessages.isEmpty() ? "PASS" : "FAIL";
return summarize
? String.format(
"Num actions: %4d - Reqs passed: %s - Overall: %s",
historyEntryStats.statCounts.size(),
passedFraction,
status)
: String.format(
"%s\n%s\nRequirements passed: %s\nOverall OT&E status: %s\n",
historyEntryStats,
Joiner.on('\n').join(failureMessages),
passedFraction,
status);
}
private static final Predicate<EppInput> HAS_CLAIMS_NOTICE = new Predicate<EppInput>() {
@Override
public boolean apply(@Nonnull EppInput eppInput) {
LaunchCreateExtension launchCreate =
eppInput.getSingleExtension(LaunchCreateExtension.class);
return launchCreate != null && launchCreate.getNotice() != null;
}};
private static final Predicate<EppInput> HAS_FEE = new Predicate<EppInput>() {
@Override
public boolean apply(@Nonnull EppInput eppInput) {
return eppInput.getSingleExtension(FeeCreateExtension.class) != null;
}};
private static final Predicate<EppInput> HAS_SEC_DNS = new Predicate<EppInput>() {
@Override
public boolean apply(@Nonnull EppInput eppInput) {
return (eppInput.getSingleExtension(SecDnsCreateExtension.class) != null)
|| (eppInput.getSingleExtension(SecDnsUpdateExtension.class) != null);
}};
private static final Predicate<EppInput> IS_SUNRISE = new Predicate<EppInput>() {
@Override
public boolean apply(@Nonnull EppInput eppInput) {
LaunchCreateExtension launchCreate =
eppInput.getSingleExtension(LaunchCreateExtension.class);
return launchCreate != null && !isNullOrEmpty(launchCreate.getSignedMarks());
}};
private static final Predicate<EppInput> IS_IDN = new Predicate<EppInput>() {
@Override
public boolean apply(@Nonnull EppInput eppInput) {
return ((DomainCommand.Create) ((ResourceCommandWrapper)
eppInput.getCommandWrapper().getCommand()).getResourceCommand())
.getFullyQualifiedDomainName().startsWith(ACE_PREFIX);
}};
private static final Predicate<EppInput> IS_SUBORDINATE = new Predicate<EppInput>() {
@Override
public boolean apply(@Nonnull EppInput eppInput) {
return !isNullOrEmpty(((HostCommand.Create) ((ResourceCommandWrapper)
eppInput.getCommandWrapper().getCommand()).getResourceCommand())
.getInetAddresses());
}};
private static Predicate<EppInput> isFlow(final Class<? extends Flow> flowClass) {
return new Predicate<EppInput>() {
@Override
public boolean apply(@Nonnull EppInput eppInput) {
try {
return flowClass.equals(getFlowClass(eppInput));
} catch (EppException e) {
throw new RuntimeException(e);
}
}};
}
/** Enum defining the distinct statistics (types of registrar actions) to record. */
public enum StatType {
CONTACT_CREATES(0, isFlow(ContactCreateFlow.class)),
CONTACT_DELETES(0, isFlow(ContactDeleteFlow.class)),
CONTACT_TRANSFER_APPROVES(0, isFlow(ContactTransferApproveFlow.class)),
CONTACT_TRANSFER_CANCELS(0, isFlow(ContactTransferCancelFlow.class)),
CONTACT_TRANSFER_REJECTS(0, isFlow(ContactTransferRejectFlow.class)),
CONTACT_TRANSFER_REQUESTS(0, isFlow(ContactTransferRequestFlow.class)),
CONTACT_UPDATES(0, isFlow(ContactUpdateFlow.class)),
DOMAIN_APPLICATION_CREATES(0, isFlow(DomainApplicationCreateFlow.class)),
DOMAIN_APPLICATION_CREATES_LANDRUSH(
1, isFlow(DomainApplicationCreateFlow.class), not(IS_SUNRISE)),
DOMAIN_APPLICATION_CREATES_SUNRISE(1, isFlow(DomainApplicationCreateFlow.class), IS_SUNRISE),
DOMAIN_APPLICATION_DELETES(2, isFlow(DomainApplicationDeleteFlow.class)),
DOMAIN_APPLICATION_UPDATES(2, isFlow(DomainApplicationUpdateFlow.class)),
DOMAIN_CREATES(0, isFlow(DomainCreateFlow.class)),
DOMAIN_CREATES_ASCII(1, isFlow(DomainCreateFlow.class), not(IS_IDN)),
DOMAIN_CREATES_IDN(1, isFlow(DomainCreateFlow.class), IS_IDN),
DOMAIN_CREATES_WITH_CLAIMS_NOTICE(1, isFlow(DomainCreateFlow.class), HAS_CLAIMS_NOTICE),
DOMAIN_CREATES_WITH_FEE(1, isFlow(DomainCreateFlow.class), HAS_FEE),
DOMAIN_CREATES_WITH_SEC_DNS(1, isFlow(DomainCreateFlow.class), HAS_SEC_DNS),
DOMAIN_CREATES_WITHOUT_SEC_DNS(0, isFlow(DomainCreateFlow.class), not(HAS_SEC_DNS)),
DOMAIN_DELETES(2, isFlow(DomainDeleteFlow.class)),
DOMAIN_RENEWS(0, isFlow(DomainRenewFlow.class)),
DOMAIN_RESTORES(1, isFlow(DomainRestoreRequestFlow.class)),
DOMAIN_TRANSFER_APPROVES(1, isFlow(DomainTransferApproveFlow.class)),
DOMAIN_TRANSFER_CANCELS(1, isFlow(DomainTransferCancelFlow.class)),
DOMAIN_TRANSFER_REJECTS(1, isFlow(DomainTransferRejectFlow.class)),
DOMAIN_TRANSFER_REQUESTS(1, isFlow(DomainTransferRequestFlow.class)),
DOMAIN_UPDATES(0, isFlow(DomainUpdateFlow.class)),
DOMAIN_UPDATES_WITH_SEC_DNS(1, isFlow(DomainUpdateFlow.class), HAS_SEC_DNS),
DOMAIN_UPDATES_WITHOUT_SEC_DNS(0, isFlow(DomainUpdateFlow.class), not(HAS_SEC_DNS)),
HOST_CREATES(0, isFlow(HostCreateFlow.class)),
HOST_CREATES_EXTERNAL(0, isFlow(HostCreateFlow.class), not(IS_SUBORDINATE)),
HOST_CREATES_SUBORDINATE(1, isFlow(HostCreateFlow.class), IS_SUBORDINATE),
HOST_DELETES(1, isFlow(HostDeleteFlow.class)),
HOST_UPDATES(1, isFlow(HostUpdateFlow.class)),
UNCLASSIFIED_FLOWS(0, Predicates.<EppInput>alwaysFalse());
/** The number of StatTypes with a non-zero requirement. */
private static final int NUM_REQUIREMENTS = FluentIterable.from(asList(values()))
.filter(new Predicate<StatType>() {
@Override
public boolean apply(@Nonnull StatType statType) {
return statType.requirement > 0;
}})
.size();
/** Required number of times registrars must complete this action. */
final int requirement;
/** Filters to determine if this action was performed by an EppInput. */
private Predicate<EppInput>[] filters;
@SafeVarargs
StatType(int requirement, Predicate<EppInput>... filters) {
this.requirement = requirement;
this.filters = filters;
}
/** Returns a more human-readable translation of the enum constant. */
String description() {
return this.name().replace('_', ' ').toLowerCase();
}
/** An {@link EppInput} might match multiple actions, so check if this action matches. */
boolean matches(EppInput eppInput) {
return Predicates.and(filters).apply(eppInput);
}
}
/** Class to represent stats derived from HistoryEntry objects on actions taken by registrars. */
static class HistoryEntryStats {
/** Stores counts of how many times each action type was performed. */
Multiset<StatType> statCounts = HashMultiset.create();
/**
* Records data in the passed historyEntryStats object on what actions have been performed by
* the four numbered OT&amp;E variants of the registrar name.
*/
HistoryEntryStats recordRegistrarHistory(String registrarName) {
ImmutableList.Builder<String> clientIds = new ImmutableList.Builder<>();
for (int i = 1; i <= 4; i++) {
clientIds.add(String.format("%s-%d", registrarName, i));
}
for (HistoryEntry historyEntry :
ofy().load().type(HistoryEntry.class).filter("clientId in", clientIds.build()).list()) {
try {
record(historyEntry);
} catch (EppException e) {
throw new RuntimeException(e);
}
}
return this;
}
/** Interprets the data in the provided HistoryEntry and increments counters. */
void record(HistoryEntry historyEntry) throws EppException {
byte[] xmlBytes = historyEntry.getXmlBytes();
// xmlBytes can be null on contact create and update for safe-harbor compliance.
//
// TODO(b/26161587): inspect the history entry itself to handle this properly.
if (xmlBytes == null) {
return;
}
final EppInput eppInput = unmarshal(xmlBytes);
if (!statCounts.addAll(
FluentIterable.from(EnumSet.allOf(StatType.class))
.filter(
new Predicate<StatType>() {
@Override
public boolean apply(@Nonnull StatType statType) {
return statType.matches(eppInput);
}
})
.toList())) {
statCounts.add(StatType.UNCLASSIFIED_FLOWS);
}
}
/**
* Returns a list of failure messages describing any cases where the passed stats fail to
* meet the required thresholds, or the empty list if all requirements are met.
*/
List<String> findFailures() {
List<String> messages = new ArrayList<>();
for (StatType statType : StatType.values()) {
if (statCounts.count(statType) < statType.requirement) {
messages.add(String.format(
"Failure: %s %s found.",
(statType.requirement == 1 ? "No" : "Not enough"),
statType.description()));
}
}
return messages;
}
/** Returns a string showing all possible actions and how many times each was performed. */
@Override
public String toString() {
return FluentIterable.from(EnumSet.allOf(StatType.class))
.transform(
new Function<StatType, String>() {
@Nonnull
@Override
public String apply(@Nonnull StatType statType) {
return String.format(
"%s: %d", statType.description(), statCounts.count(statType));
}
})
.append(String.format("TOTAL: %d", statCounts.size()))
.join(Joiner.on("\n"));
}
}
}