1
0
mirror of https://github.com/google/nomulus synced 2026-07-06 00:04:50 +00:00

Compare commits

...

7 Commits

Author SHA1 Message Date
gbrodman 1e650bd0a1 Convert to tm() some low-hanging ofy fruit (#1029)
* Convert to tm() some low-hanging ofy fruit
2021-03-25 20:01:53 -04:00
Ben McIlwain 2649a9362a Make TransactionManager.loadAllOf() smart w.r.t the cross-TLD entity group (#1040)
* Make TransactionManager.loadAllOf() smart w.r.t the cross-TLD entity group

The loadAllOf() method will now automatically append the cross-TLD entity group
ancestor query as necessary, iff the entity class being loaded is tagged with
the new @IsCrossTld annotation.

* Add tests
2021-03-25 18:55:18 -04:00
Weimin Yu 3c65ad0f8a Add SQL wipeout action in QA (#1035)
* Add SQL wipeout action in QA

Added the WipeOutSqlAction that deletes all data in Cloud SQL.

Wipe out is restricted to the QA environment, which will get production
data during migration testing.

Also added a cron job that invokes wipeout on every saturday morning.
This is part of the privacy requirments for using production data in QA.

Tested in QA.
2021-03-25 16:59:09 -04:00
Ben McIlwain 2bfd02f977 Add some load convenience methods to DatabaseHelper (#1038)
* Add some load convenience methods to DatabaseHelper

These can only be called by test code, and they automatically wrap the load
in a transaction if one isn't already specified (for convenience).

In production code we don't want to be able to use these, as we have to be
more thoughtful about transactions in production code (e.g. make sure that
we aren't loading and then saving a resource in separate transactions in a
way that makes it prone to contention errors).
2021-03-25 16:14:46 -04:00
Ben McIlwain 3af0f8c148 Include ReservedList in BigQuery exports (#1037)
* Include ReservedList in BigQuery exports
2021-03-25 13:14:29 -04:00
Weimin Yu 553b24e005 Add Gradle tasks to stage BEAM pipelines (#1031)
* Add Gradle tasks to stage BEAM pipelines

Add a Gracle task to stage flex-template based pipelines for alpha and
crash environments.

This is a follow up to go/r3pr/1028, which is also under review.
2021-03-24 18:47:14 -04:00
Ben McIlwain 3bf697c43c Make some minor improvements to TransactionManager API (#1032)
* Make some minor improvements to TransactionManager API
2021-03-24 10:40:13 -04:00
53 changed files with 733 additions and 167 deletions
+44 -1
View File
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import java.lang.reflect.Constructor
import com.google.common.base.CaseFormat
import java.util.Optional
plugins {
@@ -791,6 +791,49 @@ createUberJar(
'beam_pipeline_common',
'')
// Create beam staging task if environment is alpha or crash.
// All other environments use formally released pipelines through CloudBuild.
//
// User should install gcloud and login to GCP before invoking this tasks.
if (environment in ['alpha', 'crash']) {
def pipelines = [
[
mainClass: 'google.registry.beam.initsql.InitSqlPipeline',
metaData: 'google/registry/beam/init_sql_pipeline_metadata.json'
],
[
mainClass: 'google.registry.beam.datastore.BulkDeleteDatastorePipeline',
metaData: 'google/registry/beam/bulk_delete_datastore_pipeline_metadata.json'
],
]
project.tasks.create("stage_beam_pipelines") {
doLast {
pipelines.each {
def mainClass = it['mainClass']
def metaData = it['metaData']
def pipelineName = CaseFormat.UPPER_CAMEL.to(
CaseFormat.LOWER_UNDERSCORE,
mainClass.substring(mainClass.lastIndexOf('.') + 1))
def imageName = "gcr.io/${gcpProject}/beam/${pipelineName}"
def metaDataBaseName = metaData.substring(metaData.lastIndexOf('/') + 1)
def uberJarName = tasks.beam_pipeline_common.outputs.files.asPath
def command = "\
gcloud dataflow flex-template build \
gs://${gcpProject}-deploy/live/beam/${metaDataBaseName} \
--image-gcr-path ${imageName}:live \
--sdk-language JAVA \
--flex-template-base-image JAVA11 \
--metadata-file ${projectDir}/src/main/resources/${metaData} \
--jar ${uberJarName} \
--env FLEX_TEMPLATE_JAVA_MAIN_CLASS=${mainClass} \
--project ${gcpProject}".toString()
rootProject.ext.execInBash(command, '/tmp')
}
}
}.dependsOn(tasks.beam_pipeline_common)
}
// A jar with classes and resources from main sourceSet, excluding internal
// data. See comments on configurations.nomulus_test above for details.
// TODO(weiminyu): release process should build this using the public repo to eliminate the need
@@ -0,0 +1,98 @@
// Copyright 2021 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.batch;
import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryConfig.Config;
import google.registry.persistence.PersistenceModule.SchemaManagerConnection;
import google.registry.request.Action;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.util.Retrier;
import java.sql.Connection;
import java.sql.Statement;
import java.util.function.Supplier;
import javax.inject.Inject;
import org.flywaydb.core.api.FlywayException;
/**
* Wipes out all Cloud SQL data in a Nomulus GCP environment.
*
* <p>This class is created for the QA environment, where migration testing with production data
* will happen. A regularly scheduled wipeout is a prerequisite to using production data there.
*/
@Action(
service = Action.Service.BACKEND,
path = "/_dr/task/wipeOutCloudSql",
auth = Auth.AUTH_INTERNAL_OR_ADMIN)
public class WipeOutCloudSqlAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
// As a short-lived class, hardcode allowed projects here instead of using config files.
private static final ImmutableSet<String> ALLOWED_PROJECTS =
ImmutableSet.of("domain-registry-qa");
private final String projectId;
private final Supplier<Connection> connectionSupplier;
private final Response response;
private final Retrier retrier;
@Inject
WipeOutCloudSqlAction(
@Config("projectId") String projectId,
@SchemaManagerConnection Supplier<Connection> connectionSupplier,
Response response,
Retrier retrier) {
this.projectId = projectId;
this.connectionSupplier = connectionSupplier;
this.response = response;
this.retrier = retrier;
}
@Override
public void run() {
response.setContentType(PLAIN_TEXT_UTF_8);
if (!ALLOWED_PROJECTS.contains(projectId)) {
response.setStatus(SC_FORBIDDEN);
response.setPayload("Wipeout is not allowed in " + projectId);
return;
}
try {
retrier.callWithRetry(
() -> {
try (Connection conn = connectionSupplier.get();
Statement statement = conn.createStatement()) {
statement.execute("drop owned by schema_deployer;");
}
return null;
},
e -> !(e instanceof FlywayException));
response.setStatus(SC_OK);
response.setPayload("Wiped out Cloud SQL in " + projectId);
} catch (RuntimeException e) {
logger.atSevere().withCause(e).log("Failed to wipe out Cloud SQL data.");
response.setStatus(SC_INTERNAL_SERVER_ERROR);
response.setPayload("Failed to wipe out Cloud SQL in " + projectId);
}
}
}
@@ -379,6 +379,12 @@
<url-pattern>/_dr/task/relockDomain</url-pattern>
</servlet-mapping>
<!-- Action to wipeout Cloud SQL data -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/wipeOutCloudSql</url-pattern>
</servlet-mapping>
<!-- Security config -->
<security-constraint>
<web-resource-collection>
@@ -82,4 +82,13 @@
<target>backend</target>
</cron>
<cron>
<url><![CDATA[/_dr/task/wipeOutCloudSql]]></url>
<description>
This job runs an action that deletes all data in Cloud SQL.
</description>
<schedule>every saturday 03:07</schedule>
<target>backend</target>
</cron>
</cronentries>
@@ -21,12 +21,13 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Ordering;
import com.googlecode.objectify.Key;
import google.registry.model.EntityClasses;
import google.registry.model.annotations.InCrossTld;
import google.registry.model.annotations.NotBackedUp;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.annotations.VirtualEntity;
/** Constants related to export code. */
public final class ExportConstants {
public final class AnnotatedEntities {
/** Returns the names of kinds to include in Datastore backups. */
public static ImmutableSet<String> getBackupKinds() {
@@ -49,4 +50,13 @@ public final class ExportConstants {
.map(Key::getKind)
.collect(toImmutableSortedSet(Ordering.natural()));
}
/** Returns the names of kinds that are in the cross-TLD entity group. */
public static ImmutableSet<String> getCrossTldKinds() {
return EntityClasses.ALL_CLASSES.stream()
.filter(hasAnnotation(InCrossTld.class))
.filter(hasAnnotation(VirtualEntity.class).negate())
.map(Key::getKind)
.collect(toImmutableSortedSet(Ordering.natural()));
}
}
@@ -66,12 +66,13 @@ public class BackupDatastoreAction implements Runnable {
try {
Operation backup =
datastoreAdmin
.export(RegistryConfig.getDatastoreBackupsBucket(), ExportConstants.getBackupKinds())
.export(
RegistryConfig.getDatastoreBackupsBucket(), AnnotatedEntities.getBackupKinds())
.execute();
String backupName = backup.getName();
// Enqueue a poll task to monitor the backup and load REPORTING-related kinds into bigquery.
enqueuePollTask(backupName, ExportConstants.getReportingKinds());
enqueuePollTask(backupName, AnnotatedEntities.getReportingKinds());
String message =
String.format(
"Datastore backup started with name: %s\nSaving to %s",
@@ -0,0 +1,34 @@
// Copyright 2021 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.annotations;
import com.googlecode.objectify.annotation.Entity;
import google.registry.model.common.EntityGroupRoot;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation for an Objectify {@link Entity} to indicate that it is in the cross-TLD entity group.
*
* <p>This means that the entity's <code>@Parent</code> field has to have the value of {@link
* EntityGroupRoot#getCrossTldKey}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Inherited
public @interface InCrossTld {}
@@ -20,11 +20,13 @@ import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Parent;
import google.registry.model.ImmutableObject;
import google.registry.model.annotations.InCrossTld;
import javax.persistence.MappedSuperclass;
import javax.persistence.Transient;
/** A singleton entity in Datastore. */
@MappedSuperclass
@InCrossTld
public abstract class CrossTldSingleton extends ImmutableObject {
public static final long SINGLETON_ID = 1; // There is always exactly one of these.
@@ -29,6 +29,7 @@ import com.googlecode.objectify.annotation.OnLoad;
import com.googlecode.objectify.annotation.Parent;
import google.registry.model.ImmutableObject;
import google.registry.model.UpdateAutoTimestamp;
import google.registry.model.annotations.InCrossTld;
import google.registry.model.common.Cursor.CursorId;
import google.registry.model.registry.Registry;
import google.registry.persistence.VKey;
@@ -51,6 +52,7 @@ import org.joda.time.DateTime;
@Entity
@javax.persistence.Entity
@IdClass(CursorId.class)
@InCrossTld
public class Cursor extends ImmutableObject implements DatastoreAndSqlEntity {
/** The scope of a global cursor. A global cursor is a cursor that is not specific to one tld. */
@@ -32,6 +32,7 @@ import com.googlecode.objectify.annotation.Mapify;
import com.googlecode.objectify.annotation.Parent;
import google.registry.model.ImmutableObject;
import google.registry.model.UpdateAutoTimestamp;
import google.registry.model.annotations.InCrossTld;
import google.registry.model.common.TimedTransitionProperty.TimeMapper;
import google.registry.model.common.TimedTransitionProperty.TimedTransition;
import google.registry.model.registry.label.PremiumList;
@@ -46,6 +47,7 @@ import org.joda.time.DateTime;
@Entity
@Immutable
@InCrossTld
public class DatabaseTransitionSchedule extends ImmutableObject implements DatastoreOnlyEntity {
/**
@@ -17,6 +17,7 @@ package google.registry.model.ofy;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
@@ -29,6 +30,8 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Streams;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Result;
import com.googlecode.objectify.cmd.Query;
import google.registry.model.annotations.InCrossTld;
import google.registry.model.contact.ContactHistory;
import google.registry.model.domain.DomainHistory;
import google.registry.model.host.HostHistory;
@@ -251,7 +254,13 @@ public class DatastoreTransactionManager implements TransactionManager {
@Override
public <T> ImmutableList<T> loadAllOf(Class<T> clazz) {
return ImmutableList.copyOf(getOfy().load().type(clazz));
Query<T> query = getOfy().load().type(clazz);
// If the entity is in the cross-TLD entity group, then we can take advantage of an ancestor
// query to give us strong transactional consistency.
if (clazz.isAnnotationPresent(InCrossTld.class)) {
query = query.ancestor(getCrossTldKey());
}
return ImmutableList.copyOf(query);
}
@Override
@@ -298,6 +307,11 @@ public class DatastoreTransactionManager implements TransactionManager {
getOfy().clearSessionCache();
}
@Override
public boolean isOfy() {
return true;
}
/**
* Executes the given {@link Result} instance synchronously if not in a transaction.
*
@@ -72,6 +72,7 @@ import google.registry.model.ImmutableObject;
import google.registry.model.JsonMapBuilder;
import google.registry.model.Jsonifiable;
import google.registry.model.UpdateAutoTimestamp;
import google.registry.model.annotations.InCrossTld;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.common.EntityGroupRoot;
import google.registry.model.registrar.Registrar.BillingAccountEntry.CurrencyMapper;
@@ -112,6 +113,7 @@ import org.joda.time.DateTime;
columnList = "ianaIdentifier",
name = "registrar_iana_identifier_idx"),
})
@InCrossTld
public class Registrar extends ImmutableObject
implements Buildable, DatastoreAndSqlEntity, Jsonifiable {
@@ -985,9 +987,7 @@ public class Registrar extends ImmutableObject
/** Loads all registrar entities directly from Datastore. */
public static Iterable<Registrar> loadAll() {
return tm().isOfy()
? ImmutableList.copyOf(ofy().load().type(Registrar.class).ancestor(getCrossTldKey()))
: tm().transact(() -> tm().loadAllOf(Registrar.class));
return transactIfJpaTm(() -> tm().loadAllOf(Registrar.class));
}
/** Loads all registrar entities using an in-memory cache. */
@@ -44,6 +44,7 @@ import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.JsonMapBuilder;
import google.registry.model.Jsonifiable;
import google.registry.model.annotations.InCrossTld;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.registrar.RegistrarContact.RegistrarPocId;
import google.registry.persistence.VKey;
@@ -77,6 +78,7 @@ import javax.persistence.Transient;
@javax.persistence.Index(columnList = "gaeUserId", name = "registrarpoc_gae_user_id_idx")
})
@IdClass(RegistrarPocId.class)
@InCrossTld
public class RegistrarContact extends ImmutableObject
implements DatastoreAndSqlEntity, Jsonifiable {
@@ -52,6 +52,7 @@ import com.googlecode.objectify.annotation.Parent;
import google.registry.model.Buildable;
import google.registry.model.CreateAutoTimestamp;
import google.registry.model.ImmutableObject;
import google.registry.model.annotations.InCrossTld;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.common.EntityGroupRoot;
import google.registry.model.common.TimedTransitionProperty;
@@ -86,6 +87,7 @@ import org.joda.time.Duration;
@ReportedOn
@Entity
@javax.persistence.Entity(name = "Tld")
@InCrossTld
public class Registry extends ImmutableObject implements Buildable, DatastoreAndSqlEntity {
@Parent @Transient Key<EntityGroupRoot> parent = getCrossTldKey();
@@ -35,6 +35,7 @@ import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.Parent;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.annotations.InCrossTld;
import google.registry.model.common.EntityGroupRoot;
import google.registry.model.registry.Registry;
import google.registry.model.registry.label.ReservedList.ReservedListEntry;
@@ -59,6 +60,7 @@ import org.joda.time.DateTime;
* must subclass {@link DomainLabelEntry}.
*/
@MappedSuperclass
@InCrossTld
public abstract class BaseDomainLabelList<T extends Comparable<?>, R extends DomainLabelEntry<T, ?>>
extends ImmutableObject implements Buildable {
@@ -31,6 +31,7 @@ import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.Parent;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.annotations.InCrossTld;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.registry.Registry;
import google.registry.schema.replay.DatastoreOnlyEntity;
@@ -96,6 +97,7 @@ public final class PremiumList extends BaseDomainLabelList<Money, PremiumList.Pr
/** Virtual parent entity for premium list entry entities associated with a single revision. */
@ReportedOn
@Entity
@InCrossTld
public static class PremiumListRevision extends ImmutableObject implements DatastoreOnlyEntity {
@Parent Key<PremiumList> parent;
@@ -195,6 +197,7 @@ public final class PremiumList extends BaseDomainLabelList<Money, PremiumList.Pr
*/
@ReportedOn
@Entity
@InCrossTld
public static class PremiumListEntry extends DomainLabelEntry<Money, PremiumListEntry>
implements Buildable, DatastoreOnlyEntity {
@@ -35,6 +35,7 @@ import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Mapify;
import com.googlecode.objectify.mapper.Mapper;
import google.registry.model.Buildable;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.registry.Registry;
import google.registry.model.registry.label.DomainLabelMetrics.MetricsReservedListMatch;
import google.registry.schema.replay.NonReplicatedEntity;
@@ -62,6 +63,7 @@ import org.joda.time.DateTime;
* revisionId. This is fine though, because we only use the list with the highest revisionId.
*/
@Entity
@ReportedOn
@javax.persistence.Entity
@Table(indexes = {@Index(columnList = "name", name = "reservedlist_name_idx")})
public final class ReservedList
@@ -21,6 +21,7 @@ import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Parent;
import google.registry.model.ImmutableObject;
import google.registry.model.annotations.InCrossTld;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.common.EntityGroupRoot;
import google.registry.schema.replay.DatastoreOnlyEntity;
@@ -28,6 +29,7 @@ import google.registry.schema.replay.DatastoreOnlyEntity;
/** Pointer to the latest {@link KmsSecretRevision}. */
@Entity
@ReportedOn
@InCrossTld
public class KmsSecret extends ImmutableObject implements DatastoreOnlyEntity {
/** The unique name of this {@link KmsSecret}. */
@@ -26,6 +26,7 @@ import com.googlecode.objectify.annotation.Parent;
import google.registry.model.Buildable;
import google.registry.model.CreateAutoTimestamp;
import google.registry.model.ImmutableObject;
import google.registry.model.annotations.InCrossTld;
import google.registry.model.annotations.ReportedOn;
import google.registry.schema.replay.NonReplicatedEntity;
import javax.persistence.Column;
@@ -58,6 +59,7 @@ import javax.persistence.Transient;
@ReportedOn
@javax.persistence.Entity(name = "KmsSecret")
@Table(indexes = {@Index(columnList = "secretName")})
@InCrossTld
public class KmsSecretRevision extends ImmutableObject implements NonReplicatedEntity {
/**
@@ -30,6 +30,7 @@ import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.OnSave;
import com.googlecode.objectify.annotation.Parent;
import google.registry.model.ImmutableObject;
import google.registry.model.annotations.InCrossTld;
import google.registry.model.annotations.NotBackedUp;
import google.registry.model.annotations.NotBackedUp.Reason;
import google.registry.model.common.EntityGroupRoot;
@@ -67,6 +68,7 @@ import org.joda.time.DateTime;
@Entity
@javax.persistence.Entity
@NotBackedUp(reason = Reason.EXTERNALLY_SOURCED)
@InCrossTld
public class SignedMarkRevocationList extends ImmutableObject implements NonReplicatedEntity {
@VisibleForTesting static final int SHARD_SIZE = 10000;
@@ -35,6 +35,7 @@ import com.googlecode.objectify.annotation.OnSave;
import com.googlecode.objectify.annotation.Parent;
import google.registry.model.CreateAutoTimestamp;
import google.registry.model.ImmutableObject;
import google.registry.model.annotations.InCrossTld;
import google.registry.model.annotations.NotBackedUp;
import google.registry.model.annotations.NotBackedUp.Reason;
import google.registry.model.annotations.VirtualEntity;
@@ -89,6 +90,7 @@ import org.joda.time.DateTime;
@NotBackedUp(reason = Reason.EXTERNALLY_SOURCED)
@javax.persistence.Entity(name = "ClaimsList")
@Table
@InCrossTld
public class ClaimsListShard extends ImmutableObject implements NonReplicatedEntity {
/** The number of claims list entries to store per shard. */
@@ -36,6 +36,8 @@ import google.registry.keyring.api.KeyModule;
import google.registry.keyring.kms.KmsModule;
import google.registry.module.backend.BackendRequestComponent.BackendRequestComponentModule;
import google.registry.monitoring.whitebox.StackdriverModule;
import google.registry.persistence.PersistenceModule;
import google.registry.privileges.secretmanager.SecretManagerModule;
import google.registry.rde.JSchModule;
import google.registry.request.Modules.DatastoreServiceModule;
import google.registry.request.Modules.Jackson2Module;
@@ -71,6 +73,8 @@ import javax.inject.Singleton;
KeyringModule.class,
KmsModule.class,
NetHttpTransportModule.class,
PersistenceModule.class,
SecretManagerModule.class,
ServerTridProviderModule.class,
SheetsServiceModule.class,
StackdriverModule.class,
@@ -30,6 +30,7 @@ import google.registry.batch.RefreshDnsOnHostRenameAction;
import google.registry.batch.RelockDomainAction;
import google.registry.batch.ResaveAllEppResourcesAction;
import google.registry.batch.ResaveEntityAction;
import google.registry.batch.WipeOutCloudSqlAction;
import google.registry.cron.CommitLogFanoutAction;
import google.registry.cron.CronModule;
import google.registry.cron.TldFanoutAction;
@@ -205,6 +206,8 @@ interface BackendRequestComponent {
PublishInvoicesAction uploadInvoicesAction();
WipeOutCloudSqlAction wipeOutCloudSqlAction();
@Subcomponent.Builder
abstract class Builder implements RequestComponentBuilder<BackendRequestComponent> {
@@ -44,10 +44,14 @@ import google.registry.tools.AuthModule.CloudSqlClientCredential;
import google.registry.util.Clock;
import java.lang.annotation.Documented;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import javax.inject.Provider;
import javax.inject.Qualifier;
@@ -163,6 +167,37 @@ public abstract class PersistenceModule {
return ImmutableMap.copyOf(overrides);
}
/**
* Provides a {@link Supplier} of single-use JDBC {@link Connection connections} that can manage
* the database DDL schema.
*/
@Provides
@Singleton
@SchemaManagerConnection
static Supplier<Connection> provideSchemaManagerConnectionSupplier(
SqlCredentialStore credentialStore,
@PartialCloudSqlConfigs ImmutableMap<String, String> cloudSqlConfigs) {
SqlCredential credential =
credentialStore.getCredential(new RobotUser(RobotId.SCHEMA_DEPLOYER));
String user = credential.login();
String password = credential.password();
return () -> createJdbcConnection(user, password, cloudSqlConfigs);
}
private static Connection createJdbcConnection(
String user, String password, ImmutableMap<String, String> cloudSqlConfigs) {
Properties properties = new Properties();
properties.put("user", user);
properties.put("password", password);
properties.put("cloudSqlInstance", cloudSqlConfigs.get(HIKARI_DS_CLOUD_SQL_INSTANCE));
properties.put("socketFactory", cloudSqlConfigs.get(HIKARI_DS_SOCKET_FACTORY));
try {
return DriverManager.getConnection(cloudSqlConfigs.get(Environment.URL), properties);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Provides
@Singleton
@AppEngineJpaTm
@@ -378,6 +413,11 @@ public abstract class PersistenceModule {
}
}
/** Dagger qualifier for JDBC {@link Connection} with schema management privilege. */
@Qualifier
@Documented
public @interface SchemaManagerConnection {}
/** Dagger qualifier for {@link JpaTransactionManager} used for App Engine application. */
@Qualifier
@Documented
@@ -531,6 +531,11 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
// This is an intended no-op method as there is no session cache in Postgresql.
}
@Override
public boolean isOfy() {
return false;
}
@Override
public <T> void assertDelete(VKey<T> key) {
if (internalDelete(key) != 1) {
@@ -17,7 +17,7 @@ package google.registry.persistence.transaction;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import google.registry.model.ofy.DatastoreTransactionManager;
import google.registry.model.annotations.InCrossTld;
import google.registry.persistence.VKey;
import java.util.NoSuchElementException;
import java.util.Optional;
@@ -130,7 +130,7 @@ public interface TransactionManager {
void putAll(ImmutableCollection<?> entities);
/**
* Persists a new entity or update the existing entity in the database without writing any backup
* Persists a new entity or update the existing entity in the database without writing commit logs
* if the underlying database is Datastore.
*
* <p>This method is for the sake of keeping a single code path when replacing ofy() with tm() in
@@ -142,8 +142,8 @@ public interface TransactionManager {
void putWithoutBackup(Object entity);
/**
* Persists all new entities or update the existing entities in the database without writing any
* backup if the underlying database is Datastore.
* Persists all new entities or update the existing entities in the database without writing
* commit logs if the underlying database is Datastore.
*
* <p>This method is for the sake of keeping a single code path when replacing ofy() with tm() in
* the application code. When the method is invoked with Datastore, it won't write the commit log
@@ -160,7 +160,7 @@ public interface TransactionManager {
void updateAll(ImmutableCollection<?> entities);
/**
* Updates an entity in the database without writing any backup if the underlying database is
* Updates an entity in the database without writing commit logs if the underlying database is
* Datastore.
*
* <p>This method is for the sake of keeping a single code path when replacing ofy() with tm() in
@@ -240,7 +240,9 @@ public interface TransactionManager {
/**
* Returns a stream of all entities of the given type that exist in the database.
*
* <p>The resulting stream is empty if there are no entities of this type.
* <p>The resulting stream is empty if there are no entities of this type. In Datastore mode, if
* the class is a member of the cross-TLD entity group (i.e. if it has the {@link InCrossTld}
* annotation, then the correct ancestor query will automatically be applied.
*/
<T> ImmutableList<T> loadAllOf(Class<T> clazz);
@@ -254,19 +256,19 @@ public interface TransactionManager {
void delete(Object entity);
/**
* Deletes the entity by its id without writing any backup if the underlying database is
* Deletes the entity by its id without writing commit logs if the underlying database is
* Datastore.
*/
void deleteWithoutBackup(VKey<?> key);
/**
* Deletes the set of entities by their key id without writing any backup if the underlying
* Deletes the set of entities by their key id without writing commit logs if the underlying
* database is Datastore.
*/
void deleteWithoutBackup(Iterable<? extends VKey<?>> keys);
/**
* Deletes the given entity from the database without writing any backup if the underlying
* Deletes the given entity from the database without writing commit logs if the underlying
* database is Datastore.
*/
void deleteWithoutBackup(Object entity);
@@ -274,8 +276,6 @@ public interface TransactionManager {
/** Clears the session cache if the underlying database is Datastore, otherwise it is a no-op. */
void clearSessionCache();
/** Returns true if the transaction manager is DatastoreTransactionManager. */
default boolean isOfy() {
return this instanceof DatastoreTransactionManager;
}
/** Returns true if the transaction manager is DatastoreTransactionManager, false otherwise. */
boolean isOfy();
}
@@ -16,6 +16,7 @@ package google.registry.tools;
import static com.google.common.base.Preconditions.checkState;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.beust.jcommander.Parameter;
@@ -61,13 +62,7 @@ final class DeleteTldCommand extends ConfirmingCommand implements CommandWithRem
"Cannot delete TLD because registrar %s lists it as an allowed TLD",
registrar.getClientId());
}
int count = ofy().load()
.type(DomainBase.class)
.filter("tld", tld)
.limit(1)
.count();
checkState(count == 0, "Cannot delete TLD because a domain is defined on it");
checkState(!tldContainsDomains(tld), "Cannot delete TLD because a domain is defined on it");
}
@Override
@@ -77,8 +72,25 @@ final class DeleteTldCommand extends ConfirmingCommand implements CommandWithRem
@Override
protected String execute() {
tm().transactNew(() -> ofy().delete().entity(registry).now());
tm().transactNew(() -> tm().delete(registry));
registry.invalidateInCache();
return String.format("Deleted TLD '%s'.\n", tld);
}
private boolean tldContainsDomains(String tld) {
if (tm().isOfy()) {
return ofy().load().type(DomainBase.class).filter("tld", tld).limit(1).count() > 0;
} else {
return jpaTm()
.transact(
() ->
jpaTm()
.query("FROM Domain WHERE tld = :tld", DomainBase.class)
.setParameter("tld", tld)
.setMaxResults(1)
.getResultStream()
.findFirst()
.isPresent());
}
}
}
@@ -15,17 +15,19 @@
package google.registry.tools;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.base.Strings;
import com.googlecode.objectify.Key;
import com.google.common.collect.ImmutableMap;
import google.registry.model.common.Cursor;
import google.registry.model.common.Cursor.CursorType;
import google.registry.model.registry.Registries;
import google.registry.model.registry.Registry;
import google.registry.model.registry.Registry.TldType;
import google.registry.persistence.VKey;
import java.util.Map;
import java.util.Optional;
@@ -50,20 +52,18 @@ final class ListCursorsCommand implements CommandWithRemoteApi {
@Override
public void run() {
Map<Registry, Key<Cursor>> registries =
Registries.getTlds()
.stream()
Map<Registry, VKey<Cursor>> registries =
Registries.getTlds().stream()
.map(Registry::get)
.filter(r -> r.getTldType() == filterTldType)
.filter(r -> !filterEscrowEnabled || r.getEscrowEnabled())
.collect(toImmutableMap(r -> r, r -> Cursor.createKey(cursorType, r)));
Map<Key<Cursor>, Cursor> cursors = ofy().load().keys(registries.values());
.collect(toImmutableMap(r -> r, r -> Cursor.createVKey(cursorType, r.getTldStr())));
ImmutableMap<VKey<? extends Cursor>, Cursor> cursors =
transactIfJpaTm(() -> tm().loadByKeysIfPresent(registries.values()));
if (!registries.isEmpty()) {
String header = String.format(OUTPUT_FMT, "TLD", "Cursor Time", "Last Update Time");
System.out.printf("%s\n%s\n", header, Strings.repeat("-", header.length()));
registries
.entrySet()
.stream()
registries.entrySet().stream()
.map(
e ->
renderLine(
@@ -25,7 +25,7 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import google.registry.bigquery.BigqueryUtils.SourceFormat;
import google.registry.export.ExportConstants;
import google.registry.export.AnnotatedEntities;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -48,7 +48,7 @@ final class LoadSnapshotCommand extends BigqueryCommand {
@Parameter(
names = "--kinds",
description = "List of Datastore kinds for which to import snapshot data.")
private List<String> kindNames = new ArrayList<>(ExportConstants.getReportingKinds());
private List<String> kindNames = new ArrayList<>(AnnotatedEntities.getReportingKinds());
/** Runs the main snapshot import logic. */
@Override
@@ -15,13 +15,13 @@
package google.registry.tools.server;
import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.POST;
import static java.util.Comparator.comparing;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import google.registry.model.EppResourceUtils;
import google.registry.model.host.HostResource;
import google.registry.request.Action;
@@ -51,7 +51,7 @@ public final class ListHostsAction extends ListObjectsAction<HostResource> {
@Override
public ImmutableSet<HostResource> loadObjects() {
final DateTime now = clock.nowUtc();
return Streams.stream(ofy().load().type(HostResource.class))
return transactIfJpaTm(() -> tm().loadAllOf(HostResource.class)).stream()
.filter(host -> EppResourceUtils.isActive(host, now))
.collect(toImmutableSortedSet(comparing(HostResource::getHostName)));
}
@@ -14,16 +14,21 @@
package google.registry.tools.server;
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.POST;
import com.google.common.collect.ImmutableSet;
import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.PremiumListDualDao;
import google.registry.request.Action;
import google.registry.request.auth.Auth;
import java.util.Comparator;
import java.util.Optional;
import javax.inject.Inject;
import org.hibernate.Hibernate;
/**
* An action that lists premium lists, for use by the {@code nomulus list_premium_lists} command.
@@ -46,7 +51,14 @@ public final class ListPremiumListsAction extends ListObjectsAction<PremiumList>
@Override
public ImmutableSet<PremiumList> loadObjects() {
return ImmutableSet.copyOf(
ofy().load().type(PremiumList.class).ancestor(getCrossTldKey()).list());
return transactIfJpaTm(
() ->
tm().loadAllOf(PremiumList.class).stream()
.map(PremiumList::getName)
.map(PremiumListDualDao::getLatestRevision)
.filter(Optional::isPresent)
.map(Optional::get)
.peek(list -> Hibernate.initialize(list.getLabelsToPrices()))
.collect(toImmutableSortedSet(Comparator.comparing(PremiumList::getName))));
}
}
@@ -14,15 +14,19 @@
package google.registry.tools.server;
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.POST;
import com.google.common.collect.ImmutableSet;
import google.registry.model.registry.label.ReservedList;
import google.registry.model.registry.label.ReservedListDualDatabaseDao;
import google.registry.request.Action;
import google.registry.request.auth.Auth;
import java.util.Comparator;
import java.util.Optional;
import javax.inject.Inject;
/** A that lists reserved lists, for use by the {@code nomulus list_reserved_lists} command. */
@@ -44,7 +48,13 @@ public final class ListReservedListsAction extends ListObjectsAction<ReservedLis
@Override
public ImmutableSet<ReservedList> loadObjects() {
return ImmutableSet.copyOf(
ofy().load().type(ReservedList.class).ancestor(getCrossTldKey()).list());
return transactIfJpaTm(
() ->
tm().loadAllOf(ReservedList.class).stream()
.map(ReservedList::getName)
.map(ReservedListDualDatabaseDao::getLatestRevision)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(toImmutableSortedSet(Comparator.comparing(ReservedList::getName))));
}
}
@@ -0,0 +1,101 @@
// Copyright 2021 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.batch;
import static com.google.common.truth.Truth.assertThat;
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
import google.registry.testing.FakeSleeper;
import google.registry.util.Retrier;
import java.sql.Connection;
import java.sql.Statement;
import org.flywaydb.core.api.FlywayException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
/** Unit tests for {@link WipeOutCloudSqlAction}. */
@ExtendWith(MockitoExtension.class)
public class WipeOutCloudSqlActionTest {
@Mock private Statement stmt;
@Mock private Connection conn;
private FakeResponse response = new FakeResponse();
private Retrier retrier = new Retrier(new FakeSleeper(new FakeClock()), 2);
@BeforeEach
void beforeEach() throws Exception {
lenient().when(conn.createStatement()).thenReturn(stmt);
}
@Test
void run_projectAllowed() throws Exception {
WipeOutCloudSqlAction action =
new WipeOutCloudSqlAction("domain-registry-qa", () -> conn, response, retrier);
action.run();
assertThat(response.getStatus()).isEqualTo(SC_OK);
verify(stmt, times(1)).execute(anyString());
verify(stmt, times(1)).close();
verifyNoMoreInteractions(stmt);
}
@Test
void run_projectNotAllowed() {
WipeOutCloudSqlAction action =
new WipeOutCloudSqlAction("domain-registry", () -> conn, response, retrier);
action.run();
assertThat(response.getStatus()).isEqualTo(SC_FORBIDDEN);
verifyNoInteractions(stmt);
}
@Test
void run_nonRetrieableFailure() throws Exception {
doThrow(new FlywayException()).when(stmt).execute(anyString());
WipeOutCloudSqlAction action =
new WipeOutCloudSqlAction("domain-registry-qa", () -> conn, response, retrier);
action.run();
assertThat(response.getStatus()).isEqualTo(SC_INTERNAL_SERVER_ERROR);
verify(stmt, times(1)).execute(anyString());
verify(stmt, times(1)).close();
verifyNoMoreInteractions(stmt);
}
@Test
void run_retrieableFailure() throws Exception {
when(stmt.execute(anyString())).thenThrow(new RuntimeException()).thenReturn(true);
WipeOutCloudSqlAction action =
new WipeOutCloudSqlAction("domain-registry-qa", () -> conn, response, retrier);
action.run();
assertThat(response.getStatus()).isEqualTo(SC_OK);
verify(stmt, times(2)).execute(anyString());
verify(stmt, times(2)).close();
verifyNoMoreInteractions(stmt);
}
}
@@ -19,8 +19,9 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.io.Resources.getResource;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static google.registry.export.ExportConstants.getBackupKinds;
import static google.registry.export.ExportConstants.getReportingKinds;
import static google.registry.export.AnnotatedEntities.getBackupKinds;
import static google.registry.export.AnnotatedEntities.getCrossTldKinds;
import static google.registry.export.AnnotatedEntities.getReportingKinds;
import static google.registry.util.ResourceUtils.readResourceUtf8;
import com.google.common.base.Joiner;
@@ -31,13 +32,15 @@ import com.google.common.collect.Streams;
import com.google.re2j.Pattern;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link ExportConstants}. */
class ExportConstantsTest {
/** Unit tests for {@link AnnotatedEntities}. */
class AnnotatedEntitiesTest {
private static final String GOLDEN_BACKUP_KINDS_FILENAME = "backup_kinds.txt";
private static final String GOLDEN_REPORTING_KINDS_FILENAME = "reporting_kinds.txt";
private static final String GOLDEN_CROSSTLD_KINDS_FILENAME = "crosstld_kinds.txt";
private static final String UPDATE_INSTRUCTIONS_TEMPLATE = Joiner.on('\n').join(
"",
repeat("-", 80),
@@ -50,15 +53,20 @@ class ExportConstantsTest {
"");
@Test
void testBackupKinds_matchGoldenBackupKindsFile() {
void testBackupKinds_matchGoldenFile() {
checkKindsMatchGoldenFile("backed-up", GOLDEN_BACKUP_KINDS_FILENAME, getBackupKinds());
}
@Test
void testReportingKinds_matchGoldenReportingKindsFile() {
void testReportingKinds_matchGoldenFile() {
checkKindsMatchGoldenFile("reporting", GOLDEN_REPORTING_KINDS_FILENAME, getReportingKinds());
}
@Test
void testCrossTldKinds_matchGoldenFile() {
checkKindsMatchGoldenFile("crosstld", GOLDEN_CROSSTLD_KINDS_FILENAME, getCrossTldKinds());
}
@Test
void testReportingKinds_areSubsetOfBackupKinds() {
assertThat(getBackupKinds()).containsAtLeastElementsIn(getReportingKinds());
@@ -70,7 +78,7 @@ class ExportConstantsTest {
String.format(
UPDATE_INSTRUCTIONS_TEMPLATE,
kindsName,
getResource(ExportConstantsTest.class, goldenFilename).toString(),
getResource(AnnotatedEntitiesTest.class, goldenFilename).toString(),
Joiner.on('\n').join(actualKinds));
assertWithMessage(updateInstructions)
.that(actualKinds)
@@ -85,7 +93,7 @@ class ExportConstantsTest {
* @return ImmutableList<String>
*/
private static ImmutableList<String> extractListFromFile(String filename) {
String fileContents = readResourceUtf8(ExportConstantsTest.class, filename);
String fileContents = readResourceUtf8(AnnotatedEntitiesTest.class, filename);
final Pattern stripComments = Pattern.compile("\\s*#.*$");
return Streams.stream(Splitter.on('\n').split(fileContents.trim()))
.map(line -> stripComments.matcher(line).replaceFirst(""))
@@ -54,7 +54,7 @@ public class BackupDatastoreActionTest {
action.response = response;
when(datastoreAdmin.export(
"gs://registry-project-id-datastore-backups", ExportConstants.getBackupKinds()))
"gs://registry-project-id-datastore-backups", AnnotatedEntities.getBackupKinds()))
.thenReturn(exportRequest);
when(exportRequest.execute()).thenReturn(backupOperation);
when(backupOperation.getName())
@@ -73,7 +73,7 @@ public class BackupDatastoreActionTest {
.param(CHECK_BACKUP_NAME_PARAM, "projects/registry-project-id/operations/ASA1ODYwNjc")
.param(
CHECK_BACKUP_KINDS_TO_LOAD_PARAM,
Joiner.on(",").join(ExportConstants.getReportingKinds()))
Joiner.on(",").join(AnnotatedEntities.getReportingKinds()))
.method("POST"));
assertThat(response.getPayload())
.isEqualTo(
@@ -20,13 +20,12 @@ import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.collect.MoreCollectors.onlyElement;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.config.RegistryConfig.getContactAutomaticTransferLength;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.testing.ContactResourceSubject.assertAboutContacts;
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
import static google.registry.testing.DatabaseHelper.assertPollMessagesEqual;
import static google.registry.testing.DatabaseHelper.deleteResource;
import static google.registry.testing.DatabaseHelper.getPollMessages;
import static google.registry.testing.DatabaseHelper.loadByKeys;
import static google.registry.testing.DatabaseHelper.persistActiveContact;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
@@ -136,10 +135,7 @@ class ContactTransferRequestFlowTest
// poll messages, the approval notice ones for gaining and losing registrars.
assertPollMessagesEqual(
Iterables.filter(
transactIfJpaTm(
() -> tm().loadByKeys(contact.getTransferData().getServerApproveEntities()))
.values(),
PollMessage.class),
loadByKeys(contact.getTransferData().getServerApproveEntities()), PollMessage.class),
ImmutableList.of(gainingApproveMessage, losingApproveMessage));
}
@@ -35,6 +35,8 @@ import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.getOnlyHistoryEntryOfType;
import static google.registry.testing.DatabaseHelper.getOnlyPollMessage;
import static google.registry.testing.DatabaseHelper.getPollMessages;
import static google.registry.testing.DatabaseHelper.loadByKey;
import static google.registry.testing.DatabaseHelper.loadByKeys;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.persistActiveContact;
import static google.registry.testing.DatabaseHelper.persistResource;
@@ -300,15 +302,13 @@ class DomainTransferRequestFlowTest
// Assert that the domain's TransferData server-approve billing events match the above.
if (expectTransferBillingEvent) {
assertBillingEventsEqual(
transactIfJpaTm(
() -> tm().loadByKey(domain.getTransferData().getServerApproveBillingEvent())),
loadByKey(domain.getTransferData().getServerApproveBillingEvent()),
optionalTransferBillingEvent.get());
} else {
assertThat(domain.getTransferData().getServerApproveBillingEvent()).isNull();
}
assertBillingEventsEqual(
transactIfJpaTm(
() -> tm().loadByKey(domain.getTransferData().getServerApproveAutorenewEvent())),
loadByKey(domain.getTransferData().getServerApproveAutorenewEvent()),
gainingClientAutorenew);
// Assert that the full set of server-approve billing events is exactly the extra ones plus
// the transfer billing event (if present) and the gaining client autorenew.
@@ -318,14 +318,10 @@ class DomainTransferRequestFlowTest
.collect(toImmutableSet());
assertBillingEventsEqual(
Iterables.filter(
transactIfJpaTm(
() ->
tm().loadByKeys(domain.getTransferData().getServerApproveEntities()).values()),
BillingEvent.class),
loadByKeys(domain.getTransferData().getServerApproveEntities()), BillingEvent.class),
Sets.union(expectedServeApproveBillingEvents, extraBillingEvents));
// The domain's autorenew billing event should still point to the losing client's event.
BillingEvent.Recurring domainAutorenewEvent =
transactIfJpaTm(() -> tm().loadByKey(domain.getAutorenewBillingEvent()));
BillingEvent.Recurring domainAutorenewEvent = loadByKey(domain.getAutorenewBillingEvent());
assertThat(domainAutorenewEvent.getClientId()).isEqualTo("TheRegistrar");
assertThat(domainAutorenewEvent.getRecurrenceEndTime()).isEqualTo(implicitTransferTime);
// The original grace periods should remain untouched.
@@ -421,17 +417,13 @@ class DomainTransferRequestFlowTest
// Assert that the poll messages show up in the TransferData server approve entities.
assertPollMessagesEqual(
transactIfJpaTm(
() -> tm().loadByKey(domain.getTransferData().getServerApproveAutorenewPollMessage())),
loadByKey(domain.getTransferData().getServerApproveAutorenewPollMessage()),
autorenewPollMessage);
// Assert that the full set of server-approve poll messages is exactly the server approve
// OneTime messages to gaining and losing registrars plus the gaining client autorenew.
assertPollMessagesEqual(
Iterables.filter(
transactIfJpaTm(
() ->
tm().loadByKeys(domain.getTransferData().getServerApproveEntities()).values()),
PollMessage.class),
loadByKeys(domain.getTransferData().getServerApproveEntities()), PollMessage.class),
ImmutableList.of(
transferApprovedPollMessage, losingTransferApprovedPollMessage, autorenewPollMessage));
}
@@ -449,11 +441,7 @@ class DomainTransferRequestFlowTest
.hasLastEppUpdateTime(implicitTransferTime)
.and()
.hasLastEppUpdateClientId("NewRegistrar");
assertThat(
transactIfJpaTm(
() ->
tm().loadByKey(domainAfterAutomaticTransfer.getAutorenewBillingEvent())
.getEventTime()))
assertThat(loadByKey(domainAfterAutomaticTransfer.getAutorenewBillingEvent()).getEventTime())
.isEqualTo(expectedExpirationTime);
// And after the expected grace time, the grace period should be gone.
DomainBase afterGracePeriod =
@@ -19,10 +19,10 @@ import static com.google.common.truth.Truth.assertThat;
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_HOST_RENAME;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.getOnlyHistoryEntryOfType;
import static google.registry.testing.DatabaseHelper.loadByEntity;
import static google.registry.testing.DatabaseHelper.newDomainBase;
import static google.registry.testing.DatabaseHelper.newHostResource;
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
@@ -302,8 +302,7 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, HostResour
.hasPersistedCurrentSponsorClientId("TheRegistrar")
.and()
.hasLastTransferTime(oneDayAgo);
DomainBase reloadedDomain =
transactIfJpaTm(() -> tm().loadByEntity(domain)).cloneProjectedAtTime(now);
DomainBase reloadedDomain = loadByEntity(domain).cloneProjectedAtTime(now);
assertThat(reloadedDomain.getSubordinateHosts()).containsExactly("ns2.example.tld");
assertDnsTasksEnqueued("ns1.example.tld", "ns2.example.tld");
}
@@ -337,15 +336,8 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, HostResour
.hasPersistedCurrentSponsorClientId("TheRegistrar")
.and()
.hasLastTransferTime(null);
assertThat(
transactIfJpaTm(() -> tm().loadByEntity(foo))
.cloneProjectedAtTime(now)
.getSubordinateHosts())
.isEmpty();
assertThat(
transactIfJpaTm(() -> tm().loadByEntity(example))
.cloneProjectedAtTime(now)
.getSubordinateHosts())
assertThat(loadByEntity(foo).cloneProjectedAtTime(now).getSubordinateHosts()).isEmpty();
assertThat(loadByEntity(example).cloneProjectedAtTime(now).getSubordinateHosts())
.containsExactly("ns2.example.tld");
assertDnsTasksEnqueued("ns2.foo.tld", "ns2.example.tld");
}
@@ -380,11 +372,9 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, HostResour
.hasPersistedCurrentSponsorClientId("TheRegistrar")
.and()
.hasLastTransferTime(null);
DomainBase reloadedFooDomain =
transactIfJpaTm(() -> tm().loadByEntity(fooDomain)).cloneProjectedAtTime(now);
DomainBase reloadedFooDomain = loadByEntity(fooDomain).cloneProjectedAtTime(now);
assertThat(reloadedFooDomain.getSubordinateHosts()).isEmpty();
DomainBase reloadedTldDomain =
transactIfJpaTm(() -> tm().loadByEntity(tldDomain)).cloneProjectedAtTime(now);
DomainBase reloadedTldDomain = loadByEntity(tldDomain).cloneProjectedAtTime(now);
assertThat(reloadedTldDomain.getSubordinateHosts()).containsExactly("ns2.example.tld");
assertDnsTasksEnqueued("ns1.example.foo", "ns2.example.tld");
}
@@ -427,8 +417,7 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, HostResour
.and()
.hasLastSuperordinateChange(clock.nowUtc());
assertThat(renamedHost.getLastTransferTime()).isEqualTo(oneDayAgo);
DomainBase reloadedDomain =
transactIfJpaTm(() -> tm().loadByEntity(domain)).cloneProjectedAtTime(clock.nowUtc());
DomainBase reloadedDomain = loadByEntity(domain).cloneProjectedAtTime(clock.nowUtc());
assertThat(reloadedDomain.getSubordinateHosts()).isEmpty();
assertDnsTasksEnqueued("ns1.example.foo");
}
@@ -464,10 +453,7 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, HostResour
.hasPersistedCurrentSponsorClientId("TheRegistrar")
.and()
.hasLastTransferTime(null);
assertThat(
transactIfJpaTm(() -> tm().loadByEntity(domain))
.cloneProjectedAtTime(now)
.getSubordinateHosts())
assertThat(loadByEntity(domain).cloneProjectedAtTime(now).getSubordinateHosts())
.containsExactly("ns2.example.tld");
assertDnsTasksEnqueued("ns2.example.tld");
}
@@ -19,8 +19,9 @@ import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIM
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.ofyTmOrDoNothing;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadByEntity;
import static google.registry.testing.DatabaseHelper.loadByKey;
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
@@ -184,14 +185,11 @@ public class BillingEventTest extends EntityTestCase {
@TestOfyAndSql
void testPersistence() {
assertThat(transactIfJpaTm(() -> tm().loadByEntity(oneTime))).isEqualTo(oneTime);
assertThat(transactIfJpaTm(() -> tm().loadByEntity(oneTimeSynthetic)))
.isEqualTo(oneTimeSynthetic);
assertThat(transactIfJpaTm(() -> tm().loadByEntity(recurring))).isEqualTo(recurring);
assertThat(transactIfJpaTm(() -> tm().loadByEntity(cancellationOneTime)))
.isEqualTo(cancellationOneTime);
assertThat(transactIfJpaTm(() -> tm().loadByEntity(cancellationRecurring)))
.isEqualTo(cancellationRecurring);
assertThat(loadByEntity(oneTime)).isEqualTo(oneTime);
assertThat(loadByEntity(oneTimeSynthetic)).isEqualTo(oneTimeSynthetic);
assertThat(loadByEntity(recurring)).isEqualTo(recurring);
assertThat(loadByEntity(cancellationOneTime)).isEqualTo(cancellationOneTime);
assertThat(loadByEntity(cancellationRecurring)).isEqualTo(cancellationRecurring);
ofyTmOrDoNothing(() -> assertThat(tm().loadByEntity(modification)).isEqualTo(modification));
}
@@ -220,10 +218,8 @@ public class BillingEventTest extends EntityTestCase {
@TestOfyAndSql
void testCancellationMatching() {
VKey<?> recurringKey =
transactIfJpaTm(
() -> tm().loadByEntity(oneTimeSynthetic).getCancellationMatchingBillingEvent());
assertThat(transactIfJpaTm(() -> tm().loadByKey(recurringKey))).isEqualTo(recurring);
VKey<?> recurringKey = loadByEntity(oneTimeSynthetic).getCancellationMatchingBillingEvent();
assertThat(loadByKey(recurringKey)).isEqualTo(recurring);
}
@TestOfyOnly
@@ -22,9 +22,8 @@ import static google.registry.model.domain.token.AllocationToken.TokenStatus.NOT
import static google.registry.model.domain.token.AllocationToken.TokenStatus.VALID;
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadByEntity;
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
@@ -78,8 +77,7 @@ public class AllocationTokenTest extends EntityTestCase {
.put(DateTime.now(UTC).plusWeeks(8), TokenStatus.ENDED)
.build())
.build());
assertThat(transactIfJpaTm(() -> tm().loadByEntity(unlimitedUseToken)))
.isEqualTo(unlimitedUseToken);
assertThat(loadByEntity(unlimitedUseToken)).isEqualTo(unlimitedUseToken);
DomainBase domain = persistActiveDomain("example.foo");
Key<HistoryEntry> historyEntryKey = Key.create(Key.create(domain), HistoryEntry.class, 1);
@@ -92,7 +90,7 @@ public class AllocationTokenTest extends EntityTestCase {
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setTokenType(SINGLE_USE)
.build());
assertThat(transactIfJpaTm(() -> tm().loadByEntity(singleUseToken))).isEqualTo(singleUseToken);
assertThat(loadByEntity(singleUseToken)).isEqualTo(singleUseToken);
}
@TestOfyOnly
@@ -0,0 +1,72 @@
// Copyright 2021 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.ofy;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
import static google.registry.testing.DatabaseHelper.persistResources;
import com.google.common.collect.ImmutableList;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Parent;
import google.registry.model.ImmutableObject;
import google.registry.model.annotations.InCrossTld;
import google.registry.model.common.EntityGroupRoot;
import google.registry.schema.replay.EntityTest.EntityForTesting;
import google.registry.testing.AppEngineExtension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link DatastoreTransactionManager}. */
public class DatastoreTransactionManagerTest {
@RegisterExtension
public final AppEngineExtension appEngine =
AppEngineExtension.builder()
.withDatastore()
.withOfyTestEntities(InCrossTldTestEntity.class)
.build();
@Test
void test_loadAllOf_usesAncestorQuery() {
InCrossTldTestEntity foo = new InCrossTldTestEntity("foo");
InCrossTldTestEntity bar = new InCrossTldTestEntity("bar");
InCrossTldTestEntity baz = new InCrossTldTestEntity("baz");
baz.parent = null;
persistResources(ImmutableList.of(foo, bar, baz));
// baz is excluded by the cross-TLD ancestor query
assertThat(ofyTm().loadAllOf(InCrossTldTestEntity.class)).containsExactly(foo, bar);
}
@Entity
@EntityForTesting
@InCrossTld
private static class InCrossTldTestEntity extends ImmutableObject {
@Id String name;
@Parent Key<EntityGroupRoot> parent = getCrossTldKey();
private InCrossTldTestEntity(String name) {
this.name = name;
}
// Needs to exist to make Objectify happy.
@SuppressWarnings("unused")
private InCrossTldTestEntity() {}
}
}
@@ -54,6 +54,7 @@ import static org.junit.jupiter.api.Assertions.fail;
import com.google.common.base.Ascii;
import com.google.common.base.Splitter;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@@ -1244,5 +1245,45 @@ public class DatabaseHelper {
clientId);
}
/**
* Loads (i.e. reloads) the specified entity from the DB.
*
* <p>If the transaction manager is Cloud SQL, then this creates an inner wrapping transaction for
* convenience, so you don't need to wrap it in a transaction at the callsite.
*/
public static <T> T loadByEntity(T entity) {
return transactIfJpaTm(() -> tm().loadByEntity(entity));
}
/**
* Loads the specified entity by its key from the DB.
*
* <p>If the transaction manager is Cloud SQL, then this creates an inner wrapping transaction for
* convenience, so you don't need to wrap it in a transaction at the callsite.
*/
public static <T> T loadByKey(VKey<T> key) {
return transactIfJpaTm(() -> tm().loadByKey(key));
}
/**
* Loads the specified entities by their keys from the DB.
*
* <p>If the transaction manager is Cloud SQL, then this creates an inner wrapping transaction for
* convenience, so you don't need to wrap it in a transaction at the callsite.
*/
public static <T> ImmutableCollection<T> loadByKeys(Iterable<? extends VKey<? extends T>> keys) {
return transactIfJpaTm(() -> tm().loadByKeys(keys).values());
}
/**
* Loads all of the entities of the specified type from the DB.
*
* <p>If the transaction manager is Cloud SQL, then this creates an inner wrapping transaction for
* convenience, so you don't need to wrap it in a transaction at the callsite.
*/
public static <T> ImmutableList<T> loadAllOf(Class<T> clazz) {
return transactIfJpaTm(() -> tm().loadAllOf(clazz));
}
private DatabaseHelper() {}
}
@@ -19,6 +19,7 @@ import static google.registry.model.domain.token.AllocationToken.TokenType.SINGL
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.testing.DatabaseHelper.createTlds;
import static google.registry.testing.DatabaseHelper.loadAllOf;
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
import static google.registry.testing.DatabaseHelper.persistResource;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -134,10 +135,9 @@ class DeleteAllocationTokensCommandTest extends CommandTestCase<DeleteAllocation
for (int i = 0; i < 50; i++) {
persistToken(String.format("batch%2d", i), null, i % 2 == 0);
}
assertThat(transactIfJpaTm(() -> tm().loadAllOf(AllocationToken.class).size())).isEqualTo(56);
assertThat(loadAllOf(AllocationToken.class).size()).isEqualTo(56);
runCommandForced("--prefix", "batch");
assertThat(transactIfJpaTm(() -> tm().loadAllOf(AllocationToken.class).size()))
.isEqualTo(56 - 25);
assertThat(loadAllOf(AllocationToken.class).size()).isEqualTo(56 - 25);
}
@TestOfyAndSql
@@ -27,11 +27,13 @@ import com.google.common.collect.ImmutableSortedMap;
import google.registry.model.registry.Registry;
import google.registry.model.registry.Registry.RegistryNotFoundException;
import google.registry.model.registry.Registry.TldType;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link DeleteTldCommand}. */
@DualDatabaseTest
class DeleteTldCommandTest extends CommandTestCase<DeleteTldCommand> {
private static final String TLD_REAL = "tldreal";
@@ -53,7 +55,7 @@ class DeleteTldCommandTest extends CommandTestCase<DeleteTldCommand> {
TldType.TEST));
}
@Test
@TestOfyAndSql
void testSuccess_otherTldUnaffected() throws Exception {
runCommandForced("--tld=" + TLD_TEST);
@@ -61,24 +63,24 @@ class DeleteTldCommandTest extends CommandTestCase<DeleteTldCommand> {
assertThrows(RegistryNotFoundException.class, () -> Registry.get(TLD_TEST));
}
@Test
@TestOfyAndSql
void testFailure_whenTldDoesNotExist() {
assertThrows(RegistryNotFoundException.class, () -> runCommandForced("--tld=nonexistenttld"));
}
@Test
@TestOfyAndSql
void testFailure_whenTldIsReal() {
assertThrows(IllegalStateException.class, () -> runCommandForced("--tld=" + TLD_REAL));
}
@Test
@TestOfyAndSql
void testFailure_whenDomainsArePresent() {
persistDeletedDomain("domain." + TLD_TEST, DateTime.parse("2000-01-01TZ"));
assertThrows(IllegalStateException.class, () -> runCommandForced("--tld=" + TLD_TEST));
}
@Test
@TestOfyAndSql
void testFailure_whenRegistrarLinksToTld() {
allowRegistrarAccess("TheRegistrar", TLD_TEST);
@@ -17,10 +17,9 @@ package google.registry.tools;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.testing.DatabaseHelper.assertAllocationTokens;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadAllOf;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -134,7 +133,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
runCommand("--prefix", "ooo", "--number", "100", "--length", "16");
// The deterministic string generator makes it too much hassle to assert about each token, so
// just assert total number.
assertThat(transactIfJpaTm(() -> tm().loadAllOf(AllocationToken.class).size())).isEqualTo(100);
assertThat(loadAllOf(AllocationToken.class).size()).isEqualTo(100);
}
@TestOfyAndSql
@@ -199,7 +198,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
Collection<String> sampleTokens = command.stringGenerator.createStrings(13, 100);
runCommand("--tokens", Joiner.on(",").join(sampleTokens));
assertInStdout(Iterables.toArray(sampleTokens, String.class));
assertThat(transactIfJpaTm(() -> tm().loadAllOf(AllocationToken.class).size())).isEqualTo(100);
assertThat(loadAllOf(AllocationToken.class).size()).isEqualTo(100);
}
@TestOfyAndSql
@@ -24,14 +24,15 @@ import google.registry.model.common.Cursor;
import google.registry.model.common.Cursor.CursorType;
import google.registry.model.ofy.Ofy;
import google.registry.model.registry.Registry;
import google.registry.testing.FakeClock;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.InjectExtension;
import google.registry.testing.TestOfyAndSql;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link ListCursorsCommand}. */
@DualDatabaseTest
public class ListCursorsCommandTest extends CommandTestCase<ListCursorsCommand> {
private static final String HEADER_ONE =
@@ -44,17 +45,17 @@ public class ListCursorsCommandTest extends CommandTestCase<ListCursorsCommand>
@BeforeEach
void beforeEach() {
inject.setStaticField(
Ofy.class, "clock", new FakeClock(DateTime.parse("1984-12-21T06:07:08.789Z")));
fakeClock.setTo(DateTime.parse("1984-12-21T06:07:08.789Z"));
inject.setStaticField(Ofy.class, "clock", fakeClock);
}
@Test
@TestOfyAndSql
void testListCursors_noTlds_printsNothing() throws Exception {
runCommand("--type=BRDA");
assertThat(getStdoutAsString()).isEmpty();
}
@Test
@TestOfyAndSql
void testListCursors_twoTldsOneAbsent_printsAbsentAndTimestampSorted() throws Exception {
createTlds("foo", "bar");
persistResource(
@@ -69,19 +70,19 @@ public class ListCursorsCommandTest extends CommandTestCase<ListCursorsCommand>
.inOrder();
}
@Test
@TestOfyAndSql
void testListCursors_badCursor_throwsIae() {
ParameterException thrown =
assertThrows(ParameterException.class, () -> runCommand("--type=love"));
assertThat(thrown).hasMessageThat().contains("Invalid value for --type parameter.");
}
@Test
@TestOfyAndSql
void testListCursors_lowercaseCursor_isAllowed() throws Exception {
runCommand("--type=brda");
}
@Test
@TestOfyAndSql
void testListCursors_filterEscrowEnabled_doesWhatItSays() throws Exception {
createTlds("foo", "bar");
persistResource(Registry.get("bar").asBuilder().setEscrowEnabled(true).build());
@@ -17,13 +17,15 @@ package google.registry.tools.server;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.persistActiveHost;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.FakeClock;
import google.registry.testing.TestOfyAndSql;
import java.util.Optional;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link ListHostsAction}. */
@DualDatabaseTest
class ListHostsActionTest extends ListActionTestCase {
private ListHostsAction action;
@@ -35,7 +37,7 @@ class ListHostsActionTest extends ListActionTestCase {
action.clock = new FakeClock(DateTime.parse("2000-01-01TZ"));
}
@Test
@TestOfyAndSql
void testRun_noParameters() {
testRunSuccess(
action,
@@ -44,7 +46,7 @@ class ListHostsActionTest extends ListActionTestCase {
null);
}
@Test
@TestOfyAndSql
void testRun_twoLinesWithRepoId() {
persistActiveHost("example2.foo");
persistActiveHost("example1.foo");
@@ -59,7 +61,7 @@ class ListHostsActionTest extends ListActionTestCase {
"^example2.foo\\s+2-ROID\\s*$");
}
@Test
@TestOfyAndSql
void testRun_twoLinesWithWildcard() {
persistActiveHost("example2.foo");
persistActiveHost("example1.foo");
@@ -74,7 +76,7 @@ class ListHostsActionTest extends ListActionTestCase {
"^example2.foo\\s+.*1");
}
@Test
@TestOfyAndSql
void testRun_twoLinesWithWildcardAndAnotherField() {
persistActiveHost("example2.foo");
persistActiveHost("example1.foo");
@@ -89,7 +91,7 @@ class ListHostsActionTest extends ListActionTestCase {
"^example2.foo\\s+.*1");
}
@Test
@TestOfyAndSql
void testRun_withBadField_returnsError() {
persistActiveHost("example2.foo");
persistActiveHost("example1.foo");
@@ -16,11 +16,15 @@ package google.registry.tools.server;
import static google.registry.testing.DatabaseHelper.persistPremiumList;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import google.registry.testing.TestOfyOnly;
import google.registry.testing.TestSqlOnly;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link ListPremiumListsAction}. */
@DualDatabaseTest
class ListPremiumListsActionTest extends ListActionTestCase {
private ListPremiumListsAction action;
@@ -32,7 +36,7 @@ class ListPremiumListsActionTest extends ListActionTestCase {
action = new ListPremiumListsAction();
}
@Test
@TestOfyAndSql
void testRun_noParameters() {
testRunSuccess(
action,
@@ -43,7 +47,7 @@ class ListPremiumListsActionTest extends ListActionTestCase {
"^xn--q9jyb4c$");
}
@Test
@TestOfyOnly // only ofy has revisionKey
void testRun_withParameters() {
testRunSuccess(
action,
@@ -56,7 +60,20 @@ class ListPremiumListsActionTest extends ListActionTestCase {
"^xn--q9jyb4c\\s+.*PremiumList.*$");
}
@Test
@TestSqlOnly
void testRun_withLabelsToPrices() {
testRunSuccess(
action,
Optional.of("labelsToPrices"),
Optional.empty(),
Optional.empty(),
"^name\\s+labelsToPrices\\s*$",
"^-+\\s+-+\\s*$",
"^how\\s+\\{richer=5000\\.00\\}$",
"^xn--q9jyb4c\\s+\\{rich=100\\.00\\}\\s+$");
}
@TestOfyOnly
void testRun_withWildcard() {
testRunSuccess(
action,
@@ -69,7 +86,7 @@ class ListPremiumListsActionTest extends ListActionTestCase {
"^xn--q9jyb4c\\s+.*PremiumList");
}
@Test
@TestOfyAndSql
void testRun_withBadField_returnsError() {
testRunError(
action,
@@ -20,11 +20,13 @@ import static google.registry.testing.DatabaseHelper.persistResource;
import google.registry.model.registry.Registry;
import google.registry.model.registry.label.ReservedList;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link ListReservedListsAction}. */
@DualDatabaseTest
class ListReservedListsActionTest extends ListActionTestCase {
private ListReservedListsAction action;
@@ -38,7 +40,7 @@ class ListReservedListsActionTest extends ListActionTestCase {
action = new ListReservedListsAction();
}
@Test
@TestOfyAndSql
void testRun_noParameters() {
testRunSuccess(
action,
@@ -49,7 +51,7 @@ class ListReservedListsActionTest extends ListActionTestCase {
"^xn--q9jyb4c-published\\s*$");
}
@Test
@TestOfyAndSql
void testRun_withParameters() {
testRunSuccess(
action,
@@ -62,7 +64,7 @@ class ListReservedListsActionTest extends ListActionTestCase {
"^xn--q9jyb4c-published\\s+true\\s*$");
}
@Test
@TestOfyAndSql
void testRun_withWildcard() {
testRunSuccess(
action,
@@ -75,7 +77,7 @@ class ListReservedListsActionTest extends ListActionTestCase {
"^xn--q9jyb4c-published\\s+.*true");
}
@Test
@TestOfyAndSql
void testRun_withBadField_returnsError() {
testRunError(
action,
@@ -0,0 +1,16 @@
ClaimsListShard
ClaimsListSingleton
Cursor
DatabaseTransitionSchedule
KmsSecret
KmsSecretRevision
PremiumList
PremiumListEntry
PremiumListRevision
Registrar
RegistrarContact
Registry
ReservedList
ServerSecret
SignedMarkRevocationList
TmchCrl
@@ -20,3 +20,4 @@ Recurring
Registrar
RegistrarContact
Registry
ReservedList
@@ -43,3 +43,4 @@ PATH CLASS METHOD
/_dr/task/updateRegistrarRdapBaseUrls UpdateRegistrarRdapBaseUrlsAction GET y INTERNAL,API APP ADMIN
/_dr/task/updateSnapshotView UpdateSnapshotViewAction POST n INTERNAL,API APP ADMIN
/_dr/task/uploadDatastoreBackup UploadDatastoreBackupAction POST n INTERNAL,API APP ADMIN
/_dr/task/wipeOutCloudSql WipeOutCloudSqlAction GET n INTERNAL,API APP ADMIN
+13
View File
@@ -133,3 +133,16 @@ for more information.
[app-engine-sdk]: https://cloud.google.com/appengine/docs/java/download
[java-jdk11]: https://www.oracle.com/java/technologies/javase-downloads.html
## Deploy the BEAM Pipelines
Nomulus is in the middle of migrating all pipelines to use flex-template. For
pipelines already based on flex-template, deployment in the testing environments
(alpha and crash) can be done using the following command:
```shell
./nom_build :core:stage_beam_pipelines --environment=alpha
```
Pipeline deployment in other environments are through CloudBuild. Please refer
to the [release folder](http://github.com/google/nomulus/release) for details.