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,35 @@
package(
default_visibility = ["//java/com/google/domain/registry:registry_project"],
)
java_library(
name = "whitebox",
srcs = glob(["*.java"]),
deps = [
"//apiserving/discoverydata/bigquery:bigqueryv2",
"//java/com/google/api/client/util",
"//java/com/google/common/annotations",
"//java/com/google/common/base",
"//java/com/google/common/cache",
"//java/com/google/common/collect",
"//java/com/google/common/net",
"//java/com/google/domain/registry/bigquery",
"//java/com/google/domain/registry/config",
"//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/request:modules",
"//java/com/google/domain/registry/util",
"//third_party/java/appengine:appengine-api",
"//third_party/java/appengine_mapreduce2:appengine_mapreduce",
"//third_party/java/auto:auto_factory",
"//third_party/java/dagger",
"//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,42 @@
// 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.monitoring.whitebox;
import static com.google.domain.registry.bigquery.BigqueryUtils.FieldType.STRING;
import static com.google.domain.registry.bigquery.BigqueryUtils.FieldType.TIMESTAMP;
import com.google.api.services.bigquery.model.TableFieldSchema;
import com.google.common.collect.ImmutableList;
/** The Bigquery schema for the entity integrity alerts table. */
final class EntityIntegrityAlertsSchema {
static final String DATASET = "entity_integrity";
static final String TABLE_ID = "alerts";
static final String FIELD_SCANTIME = "scanTime";
static final String FIELD_SOURCE = "source";
static final String FIELD_TARGET = "target";
static final String FIELD_MESSAGE = "message";
static final ImmutableList<TableFieldSchema> ENTITY_INTEGRITY_ALERTS_SCHEMA_FIELDS =
ImmutableList.of(
new TableFieldSchema().setName(FIELD_SCANTIME).setType(TIMESTAMP.name()),
new TableFieldSchema().setName(FIELD_SOURCE).setType(STRING.name()),
new TableFieldSchema().setName(FIELD_TARGET).setType(STRING.name()),
new TableFieldSchema().setName(FIELD_MESSAGE).setType(STRING.name()));
private EntityIntegrityAlertsSchema() {}
}

View file

@ -0,0 +1,72 @@
// 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.monitoring.whitebox;
import com.google.api.services.bigquery.model.TableFieldSchema;
import com.google.common.collect.ImmutableList;
import com.google.domain.registry.bigquery.BigqueryUtils.FieldType;
import com.google.domain.registry.model.eppoutput.Result.Code;
/** The EPP Metrics collector. See {@link Metrics}. */
public class EppMetrics extends Metrics {
static final String EPPMETRICS_TABLE_ID = "eppMetrics";
static final ImmutableList<TableFieldSchema> EPPMETRICS_SCHEMA_FIELDS =
ImmutableList.of(
new TableFieldSchema().setName("requestId").setType(FieldType.STRING.name()),
new TableFieldSchema().setName("startTime").setType(FieldType.TIMESTAMP.name()),
new TableFieldSchema().setName("endTime").setType(FieldType.TIMESTAMP.name()),
new TableFieldSchema().setName("commandName").setType(FieldType.STRING.name()),
new TableFieldSchema().setName("clientId").setType(FieldType.STRING.name()),
new TableFieldSchema().setName("privilegeLevel").setType(FieldType.STRING.name()),
new TableFieldSchema().setName("eppTarget").setType(FieldType.STRING.name()),
new TableFieldSchema().setName("eppStatus").setType(FieldType.INTEGER.name()),
new TableFieldSchema().setName("attempts").setType(FieldType.INTEGER.name()));
public EppMetrics() {
setTableId(EPPMETRICS_TABLE_ID);
fields.put("attempts", 0);
}
public void setCommandName(String name) {
fields.put("commandName", name);
}
public void setClientId(String clientId) {
fields.put("clientId", clientId);
}
public void setPrivilegeLevel(String level) {
fields.put("privilegeLevel", level);
}
public void setEppTarget(String eppTarget) {
fields.put("eppTarget", eppTarget);
}
public void setRequestId(String requestId) {
fields.put("requestId", requestId);
}
public void setEppStatus(Code status) {
fields.put("eppStatus", String.valueOf(status.code));
}
public void incrementAttempts() {
int attempts = (int) fields.get("attempts");
fields.put("attempts", attempts + 1);
}
}

View file

@ -0,0 +1,83 @@
// 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.monitoring.whitebox;
import static com.google.appengine.api.taskqueue.QueueFactory.getQueue;
import static com.google.appengine.api.taskqueue.TaskOptions.Builder.withUrl;
import static com.google.domain.registry.bigquery.BigqueryUtils.toBigqueryTimestamp;
import com.google.appengine.api.modules.ModulesService;
import com.google.appengine.api.modules.ModulesServiceFactory;
import com.google.appengine.api.taskqueue.TaskOptions;
import com.google.appengine.api.taskqueue.TransientFailureException;
import com.google.common.base.Supplier;
import com.google.domain.registry.util.Clock;
import com.google.domain.registry.util.FormattingLogger;
import com.google.domain.registry.util.NonFinalForTesting;
import com.google.domain.registry.util.SystemClock;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/** A collector of metric information. */
public abstract class Metrics {
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
public static final String QUEUE = "bigquery-streaming-metrics";
@NonFinalForTesting
private static ModulesService modulesService = ModulesServiceFactory.getModulesService();
@NonFinalForTesting
private static Clock clock = new SystemClock();
@NonFinalForTesting
private static Supplier<String> idGenerator =
new Supplier<String>() {
@Override
public String get() {
return UUID.randomUUID().toString();
}};
protected final Map<String, Object> fields = new HashMap<>();
private final long startTimeMillis = clock.nowUtc().getMillis();
public void setTableId(String tableId) {
fields.put("tableId", tableId);
}
public void export() {
try {
String hostname = modulesService.getVersionHostname("backend", null);
TaskOptions opts = withUrl(MetricsExportAction.PATH)
.header("Host", hostname)
.param("insertId", idGenerator.get())
.param("startTime", toBigqueryTimestamp(startTimeMillis, TimeUnit.MILLISECONDS))
.param("endTime", toBigqueryTimestamp(clock.nowUtc().getMillis(), TimeUnit.MILLISECONDS));
for (Entry<String, Object> entry : fields.entrySet()) {
opts.param(entry.getKey(), String.valueOf(entry.getValue()));
}
getQueue(QUEUE).add(opts);
} catch (TransientFailureException e) {
// Log and swallow. We may drop some metrics here but this should be rare.
logger.info(e, e.getMessage());
}
}
}

View file

@ -0,0 +1,103 @@
// 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.monitoring.whitebox;
import static com.google.common.base.Predicates.in;
import static com.google.common.base.Predicates.not;
import static com.google.common.collect.Multimaps.filterKeys;
import static com.google.domain.registry.request.Action.Method.POST;
import static com.google.domain.registry.util.FormattingLogger.getLoggerForCallerClass;
import com.google.api.services.bigquery.Bigquery;
import com.google.api.services.bigquery.model.TableDataInsertAllRequest;
import com.google.api.services.bigquery.model.TableDataInsertAllResponse;
import com.google.api.services.bigquery.model.TableDataInsertAllResponse.InsertErrors;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.domain.registry.bigquery.BigqueryFactory;
import com.google.domain.registry.config.ConfigModule.Config;
import com.google.domain.registry.request.Action;
import com.google.domain.registry.request.Parameter;
import com.google.domain.registry.request.ParameterMap;
import com.google.domain.registry.util.FormattingLogger;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
/** Action for exporting metrics to BigQuery. */
@Action(path = MetricsExportAction.PATH, method = POST)
public class MetricsExportAction implements Runnable {
public static final String PATH = "/_dr/task/metrics";
private static final FormattingLogger logger = getLoggerForCallerClass();
private static final String DATASET_ID = "metrics";
private static final Set<String> SPECIAL_PARAMS = ImmutableSet.of("tableId", "insertId");
@Inject @Parameter("tableId") String tableId;
@Inject @Parameter("insertId") String insertId;
@Inject @Config("projectId") String projectId;
@Inject BigqueryFactory bigqueryFactory;
@Inject @ParameterMap ImmutableListMultimap<String, String> parameters;
@Inject MetricsExportAction() {}
/** Exports metrics to BigQuery. */
@Override
public void run() {
try {
Bigquery bigquery = bigqueryFactory.create(projectId, DATASET_ID, tableId);
// Filter out the special parameters that the Action is called with. Everything that's left
// is returned in a Map that is suitable to pass to Bigquery as row data.
Map<String, Object> jsonRows =
ImmutableMap.<String, Object>copyOf(
filterKeys(parameters, not(in(SPECIAL_PARAMS))).entries());
TableDataInsertAllResponse response = bigquery.tabledata()
.insertAll(
projectId,
DATASET_ID,
tableId,
new TableDataInsertAllRequest()
.setRows(
ImmutableList.of(new TableDataInsertAllRequest.Rows()
.setInsertId(insertId)
.setJson(jsonRows))))
.execute();
if (response.getInsertErrors() != null && !response.getInsertErrors().isEmpty()) {
throw new RuntimeException(FluentIterable
.from(response.getInsertErrors())
.transform(new Function<InsertErrors, String>() {
@Override
public String apply(InsertErrors error) {
try {
return error.toPrettyString();
} catch (IOException e) {
return error.toString();
}
}})
.join(Joiner.on('\n')));
}
} catch (Throwable e) {
logger.warningfmt("Caught Unknown Exception: %s", e);
}
}
}

View file

@ -0,0 +1,594 @@
// 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.monitoring.whitebox;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.domain.registry.model.EppResourceUtils.isActive;
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
import static com.google.domain.registry.util.DateTimeUtils.END_OF_TIME;
import static com.google.domain.registry.util.DateTimeUtils.START_OF_TIME;
import static com.google.domain.registry.util.DateTimeUtils.earliestOf;
import static com.google.domain.registry.util.DateTimeUtils.isBeforeOrAt;
import static com.google.domain.registry.util.FormattingLogger.getLoggerForCallerClass;
import static com.google.domain.registry.util.PipelineUtils.createJobPath;
import static com.googlecode.objectify.Key.getKind;
import static org.joda.time.DateTimeZone.UTC;
import com.google.appengine.tools.mapreduce.Input;
import com.google.appengine.tools.mapreduce.Mapper;
import com.google.appengine.tools.mapreduce.Reducer;
import com.google.appengine.tools.mapreduce.ReducerInput;
import com.google.appengine.tools.mapreduce.inputs.DatastoreKeyInput;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
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.ImmutableObject;
import com.google.domain.registry.model.contact.ContactResource;
import com.google.domain.registry.model.domain.DomainApplication;
import com.google.domain.registry.model.domain.DomainBase;
import com.google.domain.registry.model.domain.DomainResource;
import com.google.domain.registry.model.domain.GracePeriod;
import com.google.domain.registry.model.domain.ReferenceUnion;
import com.google.domain.registry.model.host.HostResource;
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.model.index.ForeignKeyIndex.ForeignKeyContactIndex;
import com.google.domain.registry.model.index.ForeignKeyIndex.ForeignKeyDomainIndex;
import com.google.domain.registry.model.index.ForeignKeyIndex.ForeignKeyHostIndex;
import com.google.domain.registry.model.transfer.TransferData.TransferServerApproveEntity;
import com.google.domain.registry.request.Action;
import com.google.domain.registry.request.Response;
import com.google.domain.registry.util.FormattingLogger;
import com.google.domain.registry.util.NonFinalForTesting;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Ref;
import org.joda.time.DateTime;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import javax.inject.Inject;
/**
* A mapreduce to verify integrity of entities in Datastore.
*
* <p>Specifically this validates all of the following system invariants that are expected to hold
* true for all {@link EppResource} entities and their related indexes:
* <ul>
* <li>All {@link Key} and {@link Ref} fields (including nested ones) point to entities that
* exist.
* <li>There is exactly one {@link EppResourceIndex} pointing to each {@link EppResource}.
* <li>All contacts, hosts, and domains, when grouped by foreign key, have at most one active
* resource, and exactly one {@link ForeignKeyIndex} of the appropriate type, which points to
* the active resource if one exists, or to the most recently deleted resource if not. The
* foreignKey and deletionTime fields on the index must also match the respective resource(s).
* <li>All domain applications, when grouped by foreign key, have exactly one
* {@link DomainApplicationIndex} that links to all of them, and has a matching
* fullyQualifiedDomainName.
* </ul>
*/
@Action(path = "/_dr/task/verifyEntityIntegrity")
public class VerifyEntityIntegrityAction implements Runnable {
private static final FormattingLogger logger = getLoggerForCallerClass();
private static final int NUM_SHARDS = 200;
@NonFinalForTesting
@VisibleForTesting
static WhiteboxComponent component = DaggerWhiteboxComponent.create();
private static final ImmutableSet<Class<?>> RESOURCE_CLASSES =
ImmutableSet.<Class<?>>of(
ForeignKeyDomainIndex.class,
DomainApplicationIndex.class,
ForeignKeyHostIndex.class,
ForeignKeyContactIndex.class,
DomainBase.class,
HostResource.class,
ContactResource.class);
static final String KIND_CONTACT_RESOURCE = getKind(ContactResource.class);
static final String KIND_CONTACT_INDEX = getKind(ForeignKeyContactIndex.class);
static final String KIND_DOMAIN_APPLICATION_INDEX = getKind(DomainApplicationIndex.class);
static final String KIND_DOMAIN_BASE_RESOURCE = getKind(DomainBase.class);
static final String KIND_DOMAIN_INDEX = getKind(ForeignKeyDomainIndex.class);
static final String KIND_EPPRESOURCE_INDEX = getKind(EppResourceIndex.class);
static final String KIND_HOST_RESOURCE = getKind(HostResource.class);
static final String KIND_HOST_INDEX = getKind(ForeignKeyHostIndex.class);
@Inject MapreduceRunner mrRunner;
@Inject Response response;
@Inject VerifyEntityIntegrityAction() {}
@Override
public void run() {
DateTime scanTime = DateTime.now(UTC);
response.sendJavaScriptRedirect(createJobPath(mrRunner
.setJobName("Verify entity integrity")
.setModuleName("backend")
.setDefaultReduceShards(NUM_SHARDS)
.runMapreduce(
new VerifyEntityIntegrityMapper(scanTime),
new VerifyEntityIntegrityReducer(scanTime),
getInputs())));
}
private static ImmutableSet<Input<? extends Object>> getInputs() {
ImmutableSet.Builder<Input<? extends Object>> builder =
new ImmutableSet.Builder<Input<? extends Object>>()
.add(EppResourceInputs.createIndexInput());
for (Class<?> clazz : RESOURCE_CLASSES) {
builder.add(new DatastoreKeyInput(getKind(clazz), NUM_SHARDS));
}
return builder.build();
}
/**
* The mapreduce key that the mapper outputs. Each {@link EppResource} has two different
* mapreduce keys that are output for it: one for its specific type (domain, application, host, or
* contact), which is used to check {@link ForeignKeyIndex} constraints, and one that is common
* for all EppResources, to check {@link EppResourceIndex} constraints.
*/
private static enum EntityKind {
DOMAIN,
APPLICATION,
CONTACT,
HOST,
/**
* Used to verify 1-to-1 constraints between all types of EPP resources and their indexes.
*/
EPP_RESOURCE
}
private static class MapperKey implements Serializable {
private static final long serialVersionUID = 3222302549441420932L;
/**
* The relevant id for this mapper key, which is either the foreign key of the EppResource (for
* verifying foreign key indexes) or its repoId (for verifying EppResourceIndexes).
*/
public String id;
public EntityKind kind;
public static MapperKey create(EntityKind kind, String id) {
MapperKey instance = new MapperKey();
instance.kind = kind;
instance.id = id;
return instance;
}
}
/**
* Mapper that checks validity of references on all resources and outputs key/value pairs used to
* check integrity of foreign key entities.
*/
public static class VerifyEntityIntegrityMapper
extends Mapper<Object, MapperKey, Key<? extends ImmutableObject>> {
private static final long serialVersionUID = -5413882340475018051L;
private final DateTime scanTime;
private transient VerifyEntityIntegrityStreamer integrityStreamer;
// The integrityStreamer field must be marked as transient so that instances of the Mapper class
// can be serialized by the MapReduce framework. Thus, every time is used, lazily construct it
// if it doesn't exist yet.
private VerifyEntityIntegrityStreamer integrity() {
if (integrityStreamer == null) {
integrityStreamer = component.verifyEntityIntegrityStreamerFactory().create(scanTime);
}
return integrityStreamer;
}
public VerifyEntityIntegrityMapper(DateTime scanTime) {
this.scanTime = scanTime;
}
@Override
public final void map(Object keyOrEntity) {
try {
// We use different inputs, some that return keys and some that return entities. Load any
// keys that we get so we're dealing only with entities.
if (keyOrEntity instanceof com.google.appengine.api.datastore.Key) {
Key<?> key = Key.create((com.google.appengine.api.datastore.Key) keyOrEntity);
keyOrEntity = ofy().load().key(key).now();
}
mapEntity(keyOrEntity);
} catch (Throwable e) {
// Log and swallow so that the mapreduce doesn't abort on first error.
logger.severefmt(e, "Exception while checking integrity of entity: %s", keyOrEntity);
}
}
private void mapEntity(Object entity) {
if (entity instanceof EppResource) {
mapEppResource((EppResource) entity);
} else if (entity instanceof ForeignKeyIndex<?>) {
mapForeignKeyIndex((ForeignKeyIndex<?>) entity);
} else if (entity instanceof DomainApplicationIndex) {
mapDomainApplicationIndex((DomainApplicationIndex) entity);
} else if (entity instanceof EppResourceIndex) {
mapEppResourceIndex((EppResourceIndex) entity);
} else {
throw new IllegalStateException(
String.format("Unknown entity in integrity mapper: %s", entity));
}
}
private void mapEppResource(EppResource resource) {
emit(MapperKey.create(EntityKind.EPP_RESOURCE, resource.getRepoId()), Key.create(resource));
if (resource instanceof DomainBase) {
DomainBase domainBase = (DomainBase) resource;
Key<?> key = Key.create(domainBase);
verifyExistence(key, bustUnions(domainBase.getReferencedContacts()));
verifyExistence(key, bustUnions(domainBase.getNameservers()));
verifyExistence(key, domainBase.getTransferData().getServerApproveAutorenewEvent());
verifyExistence(key, domainBase.getTransferData().getServerApproveAutorenewPollMessage());
verifyExistence(key, domainBase.getTransferData().getServerApproveBillingEvent());
verifyExistence(key, FluentIterable
.from(domainBase.getTransferData().getServerApproveEntities())
.transform(
new Function<Key<? extends TransferServerApproveEntity>,
Key<TransferServerApproveEntity>>() {
@SuppressWarnings("unchecked")
@Override
public Key<TransferServerApproveEntity> apply(
Key<? extends TransferServerApproveEntity> key) {
return (Key<TransferServerApproveEntity>) key;
}})
.toSet());
if (domainBase instanceof DomainApplication) {
getContext().incrementCounter("domain applications");
DomainApplication application = (DomainApplication) domainBase;
emit(
MapperKey.create(EntityKind.APPLICATION, application.getFullyQualifiedDomainName()),
Key.create(application));
} else if (domainBase instanceof DomainResource) {
getContext().incrementCounter("domain resources");
DomainResource domain = (DomainResource) domainBase;
verifyExistence(key, domain.getApplication());
verifyExistence(key, domain.getAutorenewBillingEvent());
verifyExistence(key, domain.getAutorenewPollMessage());
for (GracePeriod gracePeriod : domain.getGracePeriods()) {
verifyExistence(key, gracePeriod.getOneTimeBillingEvent());
verifyExistence(key, gracePeriod.getRecurringBillingEvent());
}
emit(
MapperKey.create(EntityKind.DOMAIN, domain.getFullyQualifiedDomainName()),
Key.create(domain));
}
} else if (resource instanceof ContactResource) {
getContext().incrementCounter("contact resources");
ContactResource contact = (ContactResource) resource;
emit(
MapperKey.create(EntityKind.CONTACT, contact.getContactId()),
Key.create(contact));
} else if (resource instanceof HostResource) {
getContext().incrementCounter("host resources");
HostResource host = (HostResource) resource;
verifyExistence(Key.create(host), host.getSuperordinateDomain());
emit(
MapperKey.create(EntityKind.HOST, host.getFullyQualifiedHostName()),
Key.create(host));
} else {
throw new IllegalStateException(
String.format("EppResource with unknown type in integrity mapper: %s", resource));
}
}
private void mapForeignKeyIndex(ForeignKeyIndex<?> fki) {
Key<ForeignKeyIndex<?>> fkiKey = Key.<ForeignKeyIndex<?>>create(fki);
@SuppressWarnings("cast")
EppResource resource = verifyExistence(fkiKey, fki.getReference());
if (resource != null) {
integrity().check(
fki.getForeignKey().equals(resource.getForeignKey()),
fkiKey,
Key.create(resource),
"Foreign key index points to EppResource with different foreign key");
}
if (fki instanceof ForeignKeyDomainIndex) {
getContext().incrementCounter("domain foreign key indexes");
emit(MapperKey.create(EntityKind.DOMAIN, fki.getForeignKey()), fkiKey);
} else if (fki instanceof ForeignKeyContactIndex) {
getContext().incrementCounter("contact foreign key indexes");
emit(MapperKey.create(EntityKind.CONTACT, fki.getForeignKey()), fkiKey);
} else if (fki instanceof ForeignKeyHostIndex) {
getContext().incrementCounter("host foreign key indexes");
emit(MapperKey.create(EntityKind.HOST, fki.getForeignKey()), fkiKey);
} else {
throw new IllegalStateException(
String.format("Foreign key index is of unknown type: %s", fki));
}
}
private void mapDomainApplicationIndex(DomainApplicationIndex dai) {
getContext().incrementCounter("domain application indexes");
Key<DomainApplicationIndex> daiKey = Key.create(dai);
for (Ref<DomainApplication> ref : dai.getReferences()) {
DomainApplication application = verifyExistence(daiKey, ref);
if (application != null) {
integrity().check(
dai.getFullyQualifiedDomainName().equals(application.getFullyQualifiedDomainName()),
daiKey,
Key.create(application),
"Domain application index points to application with different domain name");
}
emit(
MapperKey.create(EntityKind.APPLICATION, dai.getFullyQualifiedDomainName()),
daiKey);
}
}
private void mapEppResourceIndex(EppResourceIndex eri) {
Key<EppResourceIndex> eriKey = Key.create(eri);
String eriRepoId = Key.create(eri.getId()).getName();
integrity().check(
eriRepoId.equals(eri.getReference().getKey().getName()),
eriKey,
eri.getReference().getKey(),
"EPP resource index id does not match repoId of reference");
verifyExistence(eriKey, eri.getReference());
emit(MapperKey.create(EntityKind.EPP_RESOURCE, eriRepoId), eriKey);
getContext().incrementCounter("EPP resource indexes to " + eri.getKind());
}
private <E> void verifyExistence(Key<?> source, Set<Key<E>> targets) {
Set<Key<E>> missingEntityKeys =
Sets.difference(targets, ofy().load().<E>keys(targets).keySet());
integrity().checkOneToMany(
missingEntityKeys.isEmpty(),
source,
targets,
"Target entity does not exist");
}
@Nullable
private <E> E verifyExistence(Key<?> source, @Nullable Ref<E> target) {
if (target == null) {
return null;
}
return verifyExistence(source, target.getKey());
}
@Nullable
private <E> E verifyExistence(Key<?> source, @Nullable Key<E> target) {
if (target == null) {
return null;
}
E entity = ofy().load().key(target).now();
integrity().check(entity != null, source, target, "Target entity does not exist");
return entity;
}
private static <E extends EppResource> Set<Key<E>> bustUnions(
Iterable<ReferenceUnion<E>> unions) {
return FluentIterable
.from(unions)
.transform(new Function<ReferenceUnion<E>, Key<E>>() {
@Override
public Key<E> apply(ReferenceUnion<E> union) {
return union.getLinked().getKey();
}})
.toSet();
}
}
/** Reducer that checks integrity of foreign key entities. */
public static class VerifyEntityIntegrityReducer
extends Reducer<MapperKey, Key<? extends ImmutableObject>, Void> {
private static final long serialVersionUID = -151271247606894783L;
private final DateTime scanTime;
private transient VerifyEntityIntegrityStreamer integrityStreamer;
// The integrityStreamer field must be marked as transient so that instances of the Reducer
// class can be serialized by the MapReduce framework. Thus, every time is used, lazily
// construct it if it doesn't exist yet.
private VerifyEntityIntegrityStreamer integrity() {
if (integrityStreamer == null) {
integrityStreamer = component.verifyEntityIntegrityStreamerFactory().create(scanTime);
}
return integrityStreamer;
}
public VerifyEntityIntegrityReducer(DateTime scanTime) {
this.scanTime = scanTime;
}
@SuppressWarnings("unchecked")
@Override
public void reduce(MapperKey mapperKey, ReducerInput<Key<? extends ImmutableObject>> keys) {
try {
reduceKeys(mapperKey, keys);
} catch (Throwable e) {
// Log and swallow so that the mapreduce doesn't abort on first error.
logger.severefmt(
e, "Exception while checking foreign key integrity constraints for: %s", mapperKey);
}
}
private void reduceKeys(
MapperKey mapperKey, ReducerInput<Key<? extends ImmutableObject>> keys) {
getContext().incrementCounter("reduced resources " + mapperKey.kind);
switch (mapperKey.kind) {
case EPP_RESOURCE:
checkEppResourceIndexes(keys, mapperKey.id);
break;
case APPLICATION:
checkIndexes(
keys,
mapperKey.id,
KIND_DOMAIN_BASE_RESOURCE,
KIND_DOMAIN_APPLICATION_INDEX,
false);
break;
case CONTACT:
checkIndexes(keys, mapperKey.id, KIND_CONTACT_RESOURCE, KIND_CONTACT_INDEX, true);
break;
case DOMAIN:
checkIndexes(
keys, mapperKey.id, KIND_DOMAIN_BASE_RESOURCE, KIND_DOMAIN_INDEX, true);
break;
case HOST:
checkIndexes(keys, mapperKey.id, KIND_HOST_RESOURCE, KIND_HOST_INDEX, true);
break;
default:
throw new IllegalStateException(
String.format("Unknown type of foreign key %s", mapperKey.kind));
}
}
@SuppressWarnings("unchecked")
private void checkEppResourceIndexes(
Iterator<Key<? extends ImmutableObject>> keys, String repoId) {
List<Key<EppResource>> resources = new ArrayList<>();
List<Key<EppResourceIndex>> eppResourceIndexes = new ArrayList<>();
while (keys.hasNext()) {
Key<?> key = keys.next();
String kind = key.getKind();
if (kind.equals(KIND_EPPRESOURCE_INDEX)) {
eppResourceIndexes.add((Key<EppResourceIndex>) key);
} else if (kind.equals(KIND_DOMAIN_BASE_RESOURCE)
|| kind.equals(KIND_CONTACT_RESOURCE)
|| kind.equals(KIND_HOST_RESOURCE)) {
resources.add((Key<EppResource>) key);
} else {
throw new IllegalStateException(
String.format(
"While verifying EppResourceIndexes for repoId %s, found key of unknown type: %s",
repoId,
key));
}
}
// This is a checkState and not an integrity check because the Datastore schema ensures that
// there can't be multiple EppResources with the same repoId.
checkState(
resources.size() == 1,
String.format("Found more than one EppResource for repoId %s: %s", repoId, resources));
if (integrity().check(
!eppResourceIndexes.isEmpty(),
null,
getOnlyElement(resources),
"Missing EPP resource index for EPP resource")) {
integrity().checkManyToOne(
eppResourceIndexes.size() == 1,
eppResourceIndexes,
getOnlyElement(resources),
"Duplicate EPP resource indexes pointing to same resource");
}
}
@SuppressWarnings("unchecked")
private <R extends EppResource, I> void checkIndexes(
Iterator<Key<? extends ImmutableObject>> keys,
String foreignKey,
String resourceKind,
String foreignKeyIndexKind,
boolean thereCanBeOnlyOne) {
List<Key<R>> resources = new ArrayList<>();
List<Key<I>> foreignKeyIndexes = new ArrayList<>();
while (keys.hasNext()) {
Key<?> key = keys.next();
if (key.getKind().equals(resourceKind)) {
resources.add((Key<R>) key);
} else if (key.getKind().equals(foreignKeyIndexKind)) {
foreignKeyIndexes.add((Key<I>) key);
} else {
throw new IllegalStateException(
String.format(
"While processing links to foreign key %s of type %s, found unknown key: %s",
foreignKey,
resourceKind,
key));
}
}
// This is a checkState and not an integrity check because it should truly be impossible to
// have multiple foreign key indexes for the same foreign key because of the Datastore schema.
checkState(
foreignKeyIndexes.size() <= 1,
String.format(
"Found more than one foreign key index for %s: %s", foreignKey, foreignKeyIndexes));
integrity().check(
!foreignKeyIndexes.isEmpty(),
foreignKey,
resourceKind,
"Missing foreign key index for EppResource");
if (thereCanBeOnlyOne) {
verifyOnlyOneActiveResource(resources, getOnlyElement(foreignKeyIndexes));
}
}
private <R extends EppResource, I> void verifyOnlyOneActiveResource(
List<Key<R>> resources, Key<I> fkiKey) {
DateTime oldestActive = END_OF_TIME;
DateTime mostRecentInactive = START_OF_TIME;
Key<R> mostRecentInactiveKey = null;
List<Key<R>> activeResources = new ArrayList<>();
Map<Key<R>, R> allResources = ofy().load().keys(resources);
ForeignKeyIndex<?> fki = (ForeignKeyIndex<?>) ofy().load().key(fkiKey).now();
for (Map.Entry<Key<R>, R> entry : allResources.entrySet()) {
R resource = entry.getValue();
if (isActive(resource, scanTime)) {
activeResources.add(entry.getKey());
oldestActive = earliestOf(oldestActive, resource.getCreationTime());
} else {
if (resource.getDeletionTime().isAfter(mostRecentInactive)) {
mostRecentInactive = resource.getDeletionTime();
mostRecentInactiveKey = entry.getKey();
}
}
}
if (activeResources.isEmpty()) {
integrity().check(
fki.getDeletionTime().isEqual(mostRecentInactive),
fkiKey,
mostRecentInactiveKey,
"Foreign key index deletion time not equal to that of most recently deleted resource");
} else {
integrity().checkOneToMany(
activeResources.size() == 1,
fkiKey,
activeResources,
"Multiple active EppResources with same foreign key");
integrity().check(
fki.getDeletionTime().isEqual(END_OF_TIME),
fkiKey,
null,
"Foreign key index has deletion time but active resource exists");
integrity().check(
isBeforeOrAt(mostRecentInactive, oldestActive),
fkiKey,
mostRecentInactiveKey,
"Found inactive resource deleted more recently than when active resource was created");
}
}
}
}

View file

@ -0,0 +1,222 @@
// 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.monitoring.whitebox;
import static com.google.api.client.util.Data.NULL_STRING;
import static com.google.domain.registry.monitoring.whitebox.EntityIntegrityAlertsSchema.DATASET;
import static com.google.domain.registry.monitoring.whitebox.EntityIntegrityAlertsSchema.FIELD_MESSAGE;
import static com.google.domain.registry.monitoring.whitebox.EntityIntegrityAlertsSchema.FIELD_SCANTIME;
import static com.google.domain.registry.monitoring.whitebox.EntityIntegrityAlertsSchema.FIELD_SOURCE;
import static com.google.domain.registry.monitoring.whitebox.EntityIntegrityAlertsSchema.FIELD_TARGET;
import static com.google.domain.registry.monitoring.whitebox.EntityIntegrityAlertsSchema.TABLE_ID;
import com.google.api.services.bigquery.Bigquery;
import com.google.api.services.bigquery.Bigquery.Tabledata.InsertAll;
import com.google.api.services.bigquery.model.TableDataInsertAllRequest;
import com.google.api.services.bigquery.model.TableDataInsertAllRequest.Rows;
import com.google.api.services.bigquery.model.TableDataInsertAllResponse;
import com.google.api.services.bigquery.model.TableDataInsertAllResponse.InsertErrors;
import com.google.auto.factory.AutoFactory;
import com.google.auto.factory.Provided;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Supplier;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.domain.registry.bigquery.BigqueryFactory;
import com.google.domain.registry.config.RegistryEnvironment;
import com.google.domain.registry.util.Retrier;
import org.joda.time.DateTime;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import javax.annotation.Nullable;
/**
* An injected utility class used to check entity integrity and stream violations to BigQuery.
*/
@AutoFactory(allowSubclasses = true)
public class VerifyEntityIntegrityStreamer {
private final DateTime scanTime;
private Bigquery bigquery;
BigqueryFactory bigqueryFactory;
RegistryEnvironment environment;
Retrier retrier;
Supplier<String> idGenerator;
public VerifyEntityIntegrityStreamer(
@Provided BigqueryFactory bigqueryFactory,
@Provided RegistryEnvironment environment,
@Provided Retrier retrier,
@Provided Supplier<String> idGenerator,
DateTime scanTime) {
this.bigqueryFactory = bigqueryFactory;
this.environment = environment;
this.retrier = retrier;
this.idGenerator = idGenerator;
this.scanTime = scanTime;
}
// This is lazily loaded so we only construct the connection when needed, as in a healthy
// Datastore almost every single check should short-circuit return and won't output anything to
// BigQuery.
private Bigquery getBigquery() throws IOException {
if (bigquery == null) {
bigquery =
bigqueryFactory.create(
environment.config().getProjectId(), DATASET, TABLE_ID);
}
return bigquery;
}
/**
* Check that the given conditional holds, and if not, stream the supplied source, target, and
* message information to BigQuery.
*
* @return Whether the check succeeded.
*/
boolean check(
boolean conditional,
@Nullable Object source,
@Nullable Object target,
@Nullable String message) {
return checkOneToMany(
conditional, source, ImmutableList.of((target == null) ? NULL_STRING : target), message);
}
/**
* Check that the given conditional holds, and if not, stream a separate row to BigQuery for each
* supplied target (the source and message will be the same for each).
*
* @return Whether the check succeeded.
*/
<T> boolean checkOneToMany(
boolean conditional,
@Nullable Object source,
Iterable<T> targets,
@Nullable String message) {
return checkManyToMany(
conditional, ImmutableList.of((source == null) ? NULL_STRING : source), targets, message);
}
/**
* Check that the given conditional holds, and if not, stream a separate row to BigQuery for each
* supplied target (the source and message will be the same for each).
*
* @return Whether the check succeeded.
*/
<S> boolean checkManyToOne(
boolean conditional,
Iterable<S> sources,
@Nullable Object target,
@Nullable String message) {
return checkManyToMany(
conditional, sources, ImmutableList.of((target == null) ? NULL_STRING : target), message);
}
/**
* Check that the given conditional holds, and if not, stream a separate row to BigQuery for the
* cross product of every supplied target and source (the message will be the same for each).
* This is used in preference to records (repeated fields) in BigQuery because they are
* significantly harder to work with.
*
* @return Whether the check succeeded.
*/
private <S, T> boolean checkManyToMany(
boolean conditional,
Iterable<S> sources,
Iterable<T> targets,
@Nullable String message) {
if (conditional) {
return true;
}
ImmutableList.Builder<Rows> rows = new ImmutableList.Builder<>();
for (S source : sources) {
for (T target : targets) {
Map<String, Object> rowData =
new ImmutableMap.Builder<String, Object>()
.put(
FIELD_SCANTIME,
new com.google.api.client.util.DateTime(scanTime.toDate()))
.put(
FIELD_SOURCE,
source.toString())
.put(
FIELD_TARGET,
target.toString())
.put(
FIELD_MESSAGE,
(message == null) ? NULL_STRING : message)
.build();
rows.add(
new TableDataInsertAllRequest.Rows().setJson(rowData).setInsertId(idGenerator.get()));
}
}
streamToBigqueryWithRetry(rows.build());
return false;
}
private void streamToBigqueryWithRetry(List<Rows> rows) {
try {
final InsertAll request =
getBigquery()
.tabledata()
.insertAll(
environment.config().getProjectId(),
DATASET,
TABLE_ID,
new TableDataInsertAllRequest().setRows(rows));
Callable<Void> callable =
new Callable<Void>() {
@Override
public Void call() throws Exception {
TableDataInsertAllResponse response = request.execute();
// Turn errors on the response object into RuntimeExceptions that the retrier will
// retry.
if (response.getInsertErrors() != null && !response.getInsertErrors().isEmpty()) {
throw new RuntimeException(
FluentIterable.from(response.getInsertErrors())
.transform(
new Function<InsertErrors, String>() {
@Override
public String apply(InsertErrors error) {
try {
return error.toPrettyString();
} catch (IOException e) {
return error.toString();
}
}
})
.join(Joiner.on('\n')));
}
return null;
}
};
retrier.callWithRetry(callable, RuntimeException.class);
} catch (IOException e) {
throw new RuntimeException("Error sending integrity error to BigQuery", e);
}
}
}

View file

@ -0,0 +1,38 @@
// 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.monitoring.whitebox;
import com.google.domain.registry.bigquery.BigqueryModule;
import com.google.domain.registry.config.ConfigModule;
import com.google.domain.registry.request.Modules.DatastoreServiceModule;
import com.google.domain.registry.util.SystemSleeper.SystemSleeperModule;
import dagger.Component;
import javax.inject.Singleton;
/** Dagger component with instance lifetime for Whitebox package. */
@Singleton
@Component(
modules = {
BigqueryModule.class,
ConfigModule.class,
DatastoreServiceModule.class,
SystemSleeperModule.class,
WhiteboxModule.class
})
interface WhiteboxComponent {
VerifyEntityIntegrityStreamerFactory verifyEntityIntegrityStreamerFactory();
}

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.monitoring.whitebox;
import static com.google.domain.registry.monitoring.whitebox.EntityIntegrityAlertsSchema.ENTITY_INTEGRITY_ALERTS_SCHEMA_FIELDS;
import static com.google.domain.registry.monitoring.whitebox.EntityIntegrityAlertsSchema.TABLE_ID;
import static com.google.domain.registry.monitoring.whitebox.EppMetrics.EPPMETRICS_SCHEMA_FIELDS;
import static com.google.domain.registry.monitoring.whitebox.EppMetrics.EPPMETRICS_TABLE_ID;
import static com.google.domain.registry.request.RequestParameters.extractRequiredParameter;
import static dagger.Provides.Type.MAP;
import com.google.api.services.bigquery.model.TableFieldSchema;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.domain.registry.request.Parameter;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.StringKey;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
/**
* Dagger module for injecting common settings for Whitebox tasks.
*/
@Module
public class WhiteboxModule {
@Provides(type = MAP)
@StringKey(EPPMETRICS_TABLE_ID)
static ImmutableList<TableFieldSchema> provideEppMetricsSchema() {
return EPPMETRICS_SCHEMA_FIELDS;
}
@Provides(type = MAP)
@StringKey(TABLE_ID)
static ImmutableList<TableFieldSchema> provideEntityIntegrityAlertsSchema() {
return ENTITY_INTEGRITY_ALERTS_SCHEMA_FIELDS;
}
@Provides
@Parameter("tableId")
static String provideTableId(HttpServletRequest req) {
return extractRequiredParameter(req, "tableId");
}
@Provides
@Parameter("insertId")
static String provideInsertId(HttpServletRequest req) {
return extractRequiredParameter(req, "insertId");
}
@Provides
static Supplier<String> provideIdGenerator() {
return new Supplier<String>() {
@Override
public String get() {
return UUID.randomUUID().toString();
}
};
}
}

View file

@ -0,0 +1,16 @@
// 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.
@javax.annotation.ParametersAreNonnullByDefault
package com.google.domain.registry.monitoring.whitebox;