Stop writing PremiumList to Datastore (#1160)

* Stop writing PremiumList to Datastore

* Fix formatting

* Format fix

* Rename the DAO

* Fix merge conflicts and add comment
This commit is contained in:
sarahcaseybot 2021-05-14 16:13:05 -04:00 committed by GitHub
parent b9027047c4
commit 8293e1e807
34 changed files with 142 additions and 1295 deletions

View file

@ -32,12 +32,12 @@ import com.google.common.net.MediaType;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.registry.Registry;
import google.registry.model.registry.label.PremiumList.PremiumListEntry;
import google.registry.model.registry.label.PremiumListDualDao;
import google.registry.request.Action;
import google.registry.request.Parameter;
import google.registry.request.RequestParameters;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.schema.tld.PremiumListDao;
import google.registry.storage.drive.DriveConnection;
import java.io.IOException;
import java.util.Optional;
@ -139,9 +139,10 @@ public class ExportPremiumTermsAction implements Runnable {
private String getFormattedPremiumTerms(Registry registry) {
String premiumListName = registry.getPremiumList().getName();
checkState(
PremiumListDualDao.exists(premiumListName), "Could not load premium list for " + tld);
PremiumListDao.getLatestRevision(premiumListName).isPresent(),
"Could not load premium list for " + tld);
SortedSet<String> premiumTerms =
Streams.stream(PremiumListDualDao.loadAllPremiumListEntries(premiumListName))
Streams.stream(PremiumListDao.loadAllPremiumListEntries(premiumListName))
.map(PremiumListEntry::toString)
.collect(ImmutableSortedSet.toImmutableSortedSet(String::compareTo));

View file

@ -39,8 +39,8 @@ import google.registry.model.registrar.RegistrarContact;
import google.registry.model.registry.Registry;
import google.registry.model.registry.Registry.TldState;
import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.PremiumListDualDao;
import google.registry.persistence.VKey;
import google.registry.schema.tld.PremiumListDao;
import google.registry.util.CidrAddressBlock;
import java.util.Collection;
import java.util.Optional;
@ -295,7 +295,7 @@ public final class OteAccountBuilder {
boolean isEarlyAccess,
int roidSuffix) {
String tldNameAlphaNumerical = tldName.replaceAll("[^a-z0-9]", "");
Optional<PremiumList> premiumList = PremiumListDualDao.getLatestRevision(DEFAULT_PREMIUM_LIST);
Optional<PremiumList> premiumList = PremiumListDao.getLatestRevision(DEFAULT_PREMIUM_LIST);
checkState(premiumList.isPresent(), "Couldn't find premium list %s.", DEFAULT_PREMIUM_LIST);
Registry.Builder builder =
new Registry.Builder()

View file

@ -19,7 +19,7 @@ import static google.registry.util.DomainNameUtils.getTldFromDomainName;
import com.google.common.net.InternetDomainName;
import google.registry.model.registry.Registry;
import google.registry.model.registry.label.PremiumListDualDao;
import google.registry.schema.tld.PremiumListDao;
import java.util.Optional;
import javax.inject.Inject;
import org.joda.money.Money;
@ -38,7 +38,8 @@ public final class StaticPremiumListPricingEngine implements PremiumPricingEngin
String tld = getTldFromDomainName(fullyQualifiedDomainName);
String label = InternetDomainName.from(fullyQualifiedDomainName).parts().get(0);
Registry registry = Registry.get(checkNotNull(tld, "tld"));
Optional<Money> premiumPrice = PremiumListDualDao.getPremiumPrice(label, registry);
Optional<Money> premiumPrice =
PremiumListDao.getPremiumPrice(registry.getPremiumList().getName(), label);
return DomainPrices.create(
premiumPrice.isPresent(),
premiumPrice.orElse(registry.getStandardCreateCost()),

View file

@ -36,7 +36,7 @@ import google.registry.model.annotations.ReportedOn;
import google.registry.model.registry.Registry;
import google.registry.schema.replay.DatastoreOnlyEntity;
import google.registry.schema.replay.NonReplicatedEntity;
import google.registry.schema.tld.PremiumListSqlDao;
import google.registry.schema.tld.PremiumListDao;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
@ -173,7 +173,7 @@ public final class PremiumList extends BaseDomainLabelList<Money, PremiumList.Pr
* <p>Note that this is lazily loaded and thus will throw a {@link LazyInitializationException} if
* used outside the transaction in which the given entity was loaded. You generally should not be
* using this anyway as it's inefficient to load all of the PremiumEntry rows if you don't need
* them. To check prices, use {@link PremiumListSqlDao#getPremiumPrice} instead.
* them. To check prices, use {@link PremiumListDao#getPremiumPrice} instead.
*/
@Nullable
public ImmutableMap<String, BigDecimal> getLabelsToPrices() {

View file

@ -1,375 +0,0 @@
// 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.registry.label;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Iterables.partition;
import static google.registry.config.RegistryConfig.getDomainLabelListCacheDuration;
import static google.registry.config.RegistryConfig.getSingletonCachePersistDuration;
import static google.registry.config.RegistryConfig.getStaticPremiumListMaxCachedEntries;
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
import static google.registry.model.registry.label.DomainLabelMetrics.PremiumListCheckOutcome.BLOOM_FILTER_NEGATIVE;
import static google.registry.model.registry.label.DomainLabelMetrics.PremiumListCheckOutcome.CACHED_NEGATIVE;
import static google.registry.model.registry.label.DomainLabelMetrics.PremiumListCheckOutcome.CACHED_POSITIVE;
import static google.registry.model.registry.label.DomainLabelMetrics.PremiumListCheckOutcome.UNCACHED_NEGATIVE;
import static google.registry.model.registry.label.DomainLabelMetrics.PremiumListCheckOutcome.UNCACHED_POSITIVE;
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static org.joda.time.DateTimeZone.UTC;
import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.CacheLoader.InvalidCacheLoadException;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import com.googlecode.objectify.Key;
import google.registry.model.registry.label.DomainLabelMetrics.PremiumListCheckOutcome;
import google.registry.model.registry.label.PremiumList.PremiumListEntry;
import google.registry.model.registry.label.PremiumList.PremiumListRevision;
import google.registry.persistence.VKey;
import google.registry.util.NonFinalForTesting;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import org.joda.money.Money;
import org.joda.time.DateTime;
import org.joda.time.Duration;
/**
* DAO for {@link PremiumList} objects stored in Datastore.
*
* <p>This class handles both the mapping from string to Datastore-level PremiumList objects as well
* as the mapping from PremiumList objects to the contents of those premium lists in the Datastore
* world. Specifically, this deals with retrieving the most recent revision for a given list and
* retrieving (or writing/deleting) all entries associated with that particular revision. The {@link
* PremiumList} object itself, in the Datastore world, does not store the premium pricing data.
*/
public class PremiumListDatastoreDao {
/** The number of premium list entry entities that are created and deleted per batch. */
private static final int TRANSACTION_BATCH_SIZE = 200;
/**
* In-memory cache for premium lists.
*
* <p>This is cached for a shorter duration because we need to periodically reload this entity to
* check if a new revision has been published, and if so, then use that.
*
* <p>We also cache the absence of premium lists with a given name to avoid unnecessary pointless
* lookups. Note that this cache is only applicable to PremiumList objects stored in Datastore.
*/
@NonFinalForTesting
static LoadingCache<String, Optional<PremiumList>> premiumListCache =
createPremiumListCache(getDomainLabelListCacheDuration());
@VisibleForTesting
public static void setPremiumListCacheForTest(Optional<Duration> expiry) {
Duration effectiveExpiry = expiry.orElse(getSingletonCachePersistDuration());
premiumListCache = createPremiumListCache(effectiveExpiry);
}
@VisibleForTesting
public static LoadingCache<String, Optional<PremiumList>> createPremiumListCache(
Duration cachePersistDuration) {
return CacheBuilder.newBuilder()
.expireAfterWrite(java.time.Duration.ofMillis(cachePersistDuration.getMillis()))
.build(
new CacheLoader<String, Optional<PremiumList>>() {
@Override
public Optional<PremiumList> load(final String name) {
return tm().doTransactionless(() -> getLatestRevisionUncached(name));
}
});
}
/**
* In-memory cache for {@link PremiumListRevision}s, used for retrieving Bloom filters quickly.
*
* <p>This is cached for a long duration (essentially indefinitely) because a given {@link
* PremiumListRevision} is immutable and cannot ever be changed once created, so its cache need
* not ever expire.
*/
static final LoadingCache<Key<PremiumListRevision>, PremiumListRevision>
premiumListRevisionsCache =
CacheBuilder.newBuilder()
.expireAfterWrite(
java.time.Duration.ofMillis(getSingletonCachePersistDuration().getMillis()))
.build(
new CacheLoader<Key<PremiumListRevision>, PremiumListRevision>() {
@Override
public PremiumListRevision load(final Key<PremiumListRevision> revisionKey) {
return ofyTm()
.doTransactionless(() -> auditedOfy().load().key(revisionKey).now());
}
});
/**
* In-memory cache for {@link PremiumListEntry}s for a given label and {@link PremiumListRevision}
*
* <p>Because the PremiumList itself makes up part of the PremiumListRevision's key, this is
* specific to a given premium list. Premium list entries might not be present, as indicated by
* the Optional wrapper, and we want to cache that as well.
*
* <p>This is cached for a long duration (essentially indefinitely) because a given {@link
* PremiumListRevision} and its child {@link PremiumListEntry}s are immutable and cannot ever be
* changed once created, so the cache need not ever expire.
*
* <p>A maximum size is set here on the cache because it can potentially grow too big to fit in
* memory if there are a large number of distinct premium list entries being queried (both those
* that exist, as well as those that might exist according to the Bloom filter, must be cached).
* The entries judged least likely to be accessed again will be evicted first.
*/
@NonFinalForTesting
static LoadingCache<Key<PremiumListEntry>, Optional<PremiumListEntry>> premiumListEntriesCache =
createPremiumListEntriesCache(getSingletonCachePersistDuration());
@VisibleForTesting
public static void setPremiumListEntriesCacheForTest(Optional<Duration> expiry) {
Duration effectiveExpiry = expiry.orElse(getSingletonCachePersistDuration());
premiumListEntriesCache = createPremiumListEntriesCache(effectiveExpiry);
}
@VisibleForTesting
static LoadingCache<Key<PremiumListEntry>, Optional<PremiumListEntry>>
createPremiumListEntriesCache(Duration cachePersistDuration) {
return CacheBuilder.newBuilder()
.expireAfterWrite(java.time.Duration.ofMillis(cachePersistDuration.getMillis()))
.maximumSize(getStaticPremiumListMaxCachedEntries())
.build(
new CacheLoader<Key<PremiumListEntry>, Optional<PremiumListEntry>>() {
@Override
public Optional<PremiumListEntry> load(final Key<PremiumListEntry> entryKey) {
return ofyTm()
.doTransactionless(
() -> Optional.ofNullable(auditedOfy().load().key(entryKey).now()));
}
});
}
public static Optional<PremiumList> getLatestRevision(String name) {
return premiumListCache.getUnchecked(name);
}
/**
* Returns the premium price for the specified list, label, and TLD, or absent if the label is not
* premium.
*/
public static Optional<Money> getPremiumPrice(String premiumListName, String label, String tld) {
DateTime startTime = DateTime.now(UTC);
Optional<PremiumList> maybePremumList = getLatestRevision(premiumListName);
if (!maybePremumList.isPresent()) {
return Optional.empty();
}
PremiumList premiumList = maybePremumList.get();
// If we're dealing with a list from SQL, reload from Datastore if necessary
if (premiumList.getRevisionKey() == null) {
Optional<PremiumList> fromDatastore = getLatestRevision(premiumList.getName());
if (fromDatastore.isPresent()) {
premiumList = fromDatastore.get();
} else {
return Optional.empty();
}
}
PremiumListRevision revision;
try {
revision = premiumListRevisionsCache.get(premiumList.getRevisionKey());
} catch (InvalidCacheLoadException | ExecutionException e) {
throw new RuntimeException(
"Could not load premium list revision " + premiumList.getRevisionKey(), e);
}
checkState(
revision.getProbablePremiumLabels() != null,
"Probable premium labels Bloom filter is null on revision '%s'",
premiumList.getRevisionKey());
CheckResults checkResults = checkStatus(revision, label);
DomainLabelMetrics.recordPremiumListCheckOutcome(
tld,
premiumList.getName(),
checkResults.checkOutcome(),
DateTime.now(UTC).getMillis() - startTime.getMillis());
return checkResults.premiumPrice();
}
/**
* Persists a new or updated PremiumList object and its descendant entities to Datastore.
*
* <p>The flow here is: save the new premium list entries parented on that revision entity,
* save/update the PremiumList, and then delete the old premium list entries associated with the
* old revision.
*
* <p>This is the only valid way to save these kinds of entities!
*/
public static PremiumList save(String name, List<String> inputData) {
PremiumList premiumList = new PremiumList.Builder().setName(name).build();
ImmutableMap<String, PremiumListEntry> premiumListEntries = premiumList.parse(inputData);
final Optional<PremiumList> oldPremiumList = getLatestRevisionUncached(premiumList.getName());
// Create the new revision (with its Bloom filter) and parent the entries on it.
final PremiumListRevision newRevision =
PremiumListRevision.create(premiumList, premiumListEntries.keySet());
final Key<PremiumListRevision> newRevisionKey = Key.create(newRevision);
ImmutableSet<PremiumListEntry> parentedEntries =
parentPremiumListEntriesOnRevision(premiumListEntries.values(), newRevisionKey);
// Save the new child entities in a series of transactions.
for (final List<PremiumListEntry> batch : partition(parentedEntries, TRANSACTION_BATCH_SIZE)) {
ofyTm().transactNew(() -> auditedOfy().save().entities(batch));
}
// Save the new PremiumList and revision itself.
return ofyTm()
.transactNew(
() -> {
DateTime now = ofyTm().getTransactionTime();
// Assert that the premium list hasn't been changed since we started this process.
Key<PremiumList> key =
Key.create(getCrossTldKey(), PremiumList.class, premiumList.getName());
Optional<PremiumList> existing =
ofyTm().loadByKeyIfPresent(VKey.createOfy(PremiumList.class, key));
checkOfyFieldsEqual(existing, oldPremiumList);
PremiumList newList =
premiumList
.asBuilder()
.setLastUpdateTime(now)
.setCreationTime(
oldPremiumList.isPresent() ? oldPremiumList.get().creationTime : now)
.setRevision(newRevisionKey)
.build();
auditedOfy().save().entities(newList, newRevision);
premiumListCache.invalidate(premiumList.getName());
return newList;
});
}
public static void delete(PremiumList premiumList) {
ofyTm().transactNew(() -> auditedOfy().delete().entity(premiumList));
if (premiumList.getRevisionKey() == null) {
return;
}
for (final List<Key<PremiumListEntry>> batch :
partition(
auditedOfy()
.load()
.type(PremiumListEntry.class)
.ancestor(premiumList.revisionKey)
.keys(),
TRANSACTION_BATCH_SIZE)) {
ofyTm().transactNew(() -> auditedOfy().delete().keys(batch));
batch.forEach(premiumListEntriesCache::invalidate);
}
ofyTm().transactNew(() -> auditedOfy().delete().key(premiumList.getRevisionKey()));
premiumListCache.invalidate(premiumList.getName());
premiumListRevisionsCache.invalidate(premiumList.getRevisionKey());
}
/** Re-parents the given {@link PremiumListEntry}s on the given {@link PremiumListRevision}. */
@VisibleForTesting
public static ImmutableSet<PremiumListEntry> parentPremiumListEntriesOnRevision(
Iterable<PremiumListEntry> entries, final Key<PremiumListRevision> revisionKey) {
return Streams.stream(entries)
.map((PremiumListEntry entry) -> entry.asBuilder().setParent(revisionKey).build())
.collect(toImmutableSet());
}
/**
* Returns all {@link PremiumListEntry PremiumListEntries} in the given {@code premiumList}.
*
* <p>This is an expensive operation and should only be used when the entire list is required.
*/
public static Iterable<PremiumListEntry> loadPremiumListEntriesUncached(PremiumList premiumList) {
return auditedOfy()
.load()
.type(PremiumListEntry.class)
.ancestor(premiumList.revisionKey)
.iterable();
}
private static Optional<PremiumList> getLatestRevisionUncached(String name) {
return Optional.ofNullable(
auditedOfy().load().key(Key.create(getCrossTldKey(), PremiumList.class, name)).now());
}
private static void checkOfyFieldsEqual(
Optional<PremiumList> oneOptional, Optional<PremiumList> twoOptional) {
if (!oneOptional.isPresent()) {
checkState(!twoOptional.isPresent(), "Premium list concurrently deleted");
return;
} else {
checkState(twoOptional.isPresent(), "Premium list concurrently deleted");
}
PremiumList one = oneOptional.get();
PremiumList two = twoOptional.get();
checkState(
Objects.equals(one.revisionKey, two.revisionKey),
"Premium list revision key concurrently edited");
checkState(Objects.equals(one.name, two.name), "Premium list name concurrently edited");
checkState(Objects.equals(one.parent, two.parent), "Premium list parent concurrently edited");
checkState(
Objects.equals(one.creationTime, two.creationTime),
"Premium list creation time concurrently edited");
}
private static CheckResults checkStatus(PremiumListRevision premiumListRevision, String label) {
if (!premiumListRevision.getProbablePremiumLabels().mightContain(label)) {
return CheckResults.create(BLOOM_FILTER_NEGATIVE, Optional.empty());
}
Key<PremiumListEntry> entryKey =
Key.create(Key.create(premiumListRevision), PremiumListEntry.class, label);
try {
// getIfPresent() returns null if the key is not in the cache
Optional<PremiumListEntry> entry = premiumListEntriesCache.getIfPresent(entryKey);
if (entry != null) {
if (entry.isPresent()) {
return CheckResults.create(CACHED_POSITIVE, Optional.of(entry.get().getValue()));
} else {
return CheckResults.create(CACHED_NEGATIVE, Optional.empty());
}
}
entry = premiumListEntriesCache.get(entryKey);
if (entry.isPresent()) {
return CheckResults.create(UNCACHED_POSITIVE, Optional.of(entry.get().getValue()));
} else {
return CheckResults.create(UNCACHED_NEGATIVE, Optional.empty());
}
} catch (InvalidCacheLoadException | ExecutionException e) {
throw new RuntimeException("Could not load premium list entry " + entryKey, e);
}
}
/** Value type class used by {@link #checkStatus} to return the results of a premiumness check. */
@AutoValue
abstract static class CheckResults {
static CheckResults create(PremiumListCheckOutcome checkOutcome, Optional<Money> premiumPrice) {
return new AutoValue_PremiumListDatastoreDao_CheckResults(checkOutcome, premiumPrice);
}
abstract PremiumListCheckOutcome checkOutcome();
abstract Optional<Money> premiumPrice();
}
private PremiumListDatastoreDao() {}
}

View file

@ -1,140 +0,0 @@
// 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.registry.label;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.model.DatabaseMigrationUtils.suppressExceptionUnlessInTest;
import com.google.common.collect.Streams;
import google.registry.model.registry.Registry;
import google.registry.model.registry.label.PremiumList.PremiumListEntry;
import google.registry.schema.tld.PremiumListSqlDao;
import java.util.List;
import java.util.Optional;
import org.joda.money.BigMoney;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
/**
* DAO for {@link PremiumList} objects that handles the branching paths for SQL and Datastore.
*
* <p>For write actions, this class will perform the action against Cloud SQL then, after that
* success or failure, against Datastore. If Datastore fails, an error is logged (but not thrown).
*
* <p>For read actions, when retrieving a price, we will log if the primary and secondary databases
* have different values (or if the retrieval from Datastore fails).
*/
public class PremiumListDualDao {
/**
* Retrieves from Cloud SQL and returns the most recent premium list with the given name, or
* absent if no such list exists.
*/
public static Optional<PremiumList> getLatestRevision(String premiumListName) {
return PremiumListSqlDao.getLatestRevision(premiumListName);
}
/**
* Returns the premium price for the specified label and registry.
*
* <p>Returns absent if the label is not premium or there is no premium list for this registry.
*
* <p>Retrieves the price from both primary and secondary databases, and logs in the event of a
* failure in Datastore (but does not throw an exception).
*/
public static Optional<Money> getPremiumPrice(String label, Registry registry) {
if (registry.getPremiumList() == null) {
return Optional.empty();
}
String premiumListName = registry.getPremiumList().getName();
Optional<Money> primaryResult;
primaryResult = PremiumListSqlDao.getPremiumPrice(premiumListName, label);
// Also load the value from Datastore, compare the two results, and log if different.
suppressExceptionUnlessInTest(
() -> {
Optional<Money> secondaryResult =
PremiumListDatastoreDao.getPremiumPrice(premiumListName, label, registry.getTldStr());
checkState(
primaryResult.equals(secondaryResult),
"Unequal prices for domain %s.%s from primary SQL DB (%s) and secondary Datastore db"
+ " (%s).",
label,
registry.getTldStr(),
primaryResult,
secondaryResult);
},
String.format(
"Error loading price of domain %s.%s from Datastore.", label, registry.getTldStr()));
return primaryResult;
}
/**
* Saves the given list data to both primary and secondary databases.
*
* <p>Logs but doesn't throw an exception in the event of a failure when writing to Datastore.
*/
public static PremiumList save(String name, List<String> inputData) {
PremiumList result = PremiumListSqlDao.save(name, inputData);
suppressExceptionUnlessInTest(
() -> PremiumListDatastoreDao.save(name, inputData),
"Error when saving premium list to Datastore.");
return result;
}
/**
* Deletes the premium list.
*
* <p>Logs but doesn't throw an exception in the event of a failure when deleting from Datastore.
*/
public static void delete(PremiumList premiumList) {
PremiumListSqlDao.delete(premiumList);
suppressExceptionUnlessInTest(
() -> PremiumListDatastoreDao.delete(premiumList),
"Error when deleting premium list from Datastore.");
}
/** Returns whether or not there exists a premium list with the given name. */
public static boolean exists(String premiumListName) {
// It may seem like overkill, but loading the list has ways been the way we check existence and
// given that we usually load the list around the time we check existence, we'll hit the cache
return PremiumListSqlDao.getLatestRevision(premiumListName).isPresent();
}
/**
* Returns all {@link PremiumListEntry PremiumListEntries} in the list with the given name.
*
* <p>This is an expensive operation and should only be used when the entire list is required.
*/
public static Iterable<PremiumListEntry> loadAllPremiumListEntries(String premiumListName) {
PremiumList premiumList =
getLatestRevision(premiumListName)
.orElseThrow(
() ->
new IllegalArgumentException(
String.format("No premium list with name %s.", premiumListName)));
CurrencyUnit currencyUnit = premiumList.getCurrency();
return Streams.stream(PremiumListSqlDao.loadPremiumListEntriesUncached(premiumList))
.map(
premiumEntry ->
new PremiumListEntry.Builder()
.setPrice(BigMoney.of(currencyUnit, premiumEntry.getPrice()).toMoney())
.setLabel(premiumEntry.getDomainLabel())
.build())
.collect(toImmutableList());
}
private PremiumListDualDao() {}
}

View file

@ -14,6 +14,7 @@
package google.registry.schema.tld;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.config.RegistryConfig.getDomainLabelListCacheDuration;
import static google.registry.config.RegistryConfig.getSingletonCachePersistDuration;
import static google.registry.config.RegistryConfig.getStaticPremiumListMaxCachedEntries;
@ -25,6 +26,7 @@ import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.CacheLoader.InvalidCacheLoadException;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Streams;
import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.PremiumList.PremiumListEntry;
import google.registry.util.NonFinalForTesting;
@ -32,6 +34,8 @@ import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import org.joda.money.BigMoney;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import org.joda.time.Duration;
@ -43,7 +47,7 @@ import org.joda.time.Duration;
* {@link PremiumList} object in SQL, and caching these entries so that future lookups can be
* quicker.
*/
public class PremiumListSqlDao {
public class PremiumListDao {
/**
* In-memory cache for premium lists.
@ -188,7 +192,7 @@ public class PremiumListSqlDao {
*
* <p>This is an expensive operation and should only be used when the entire list is required.
*/
public static Iterable<PremiumEntry> loadPremiumListEntriesUncached(PremiumList premiumList) {
public static Iterable<PremiumEntry> loadPremiumListEntries(PremiumList premiumList) {
return jpaTm()
.transact(
() ->
@ -220,6 +224,29 @@ public class PremiumListSqlDao {
.findFirst());
}
/**
* Returns all {@link PremiumListEntry PremiumListEntries} in the list with the given name.
*
* <p>This is an expensive operation and should only be used when the entire list is required.
*/
public static Iterable<PremiumListEntry> loadAllPremiumListEntries(String premiumListName) {
PremiumList premiumList =
getLatestRevision(premiumListName)
.orElseThrow(
() ->
new IllegalArgumentException(
String.format("No premium list with name %s.", premiumListName)));
CurrencyUnit currencyUnit = premiumList.getCurrency();
return Streams.stream(loadPremiumListEntries(premiumList))
.map(
premiumEntry ->
new PremiumListEntry.Builder()
.setPrice(BigMoney.of(currencyUnit, premiumEntry.getPrice()).toMoney())
.setLabel(premiumEntry.getDomainLabel())
.build())
.collect(toImmutableList());
}
@AutoValue
abstract static class RevisionIdAndLabel {
abstract long revisionId();
@ -227,9 +254,9 @@ public class PremiumListSqlDao {
abstract String label();
static RevisionIdAndLabel create(long revisionId, String label) {
return new AutoValue_PremiumListSqlDao_RevisionIdAndLabel(revisionId, label);
return new AutoValue_PremiumListDao_RevisionIdAndLabel(revisionId, label);
}
}
private PremiumListSqlDao() {}
private PremiumListDao() {}
}

View file

@ -1,105 +0,0 @@
// 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.tools;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.PremiumList.PremiumListEntry;
import google.registry.model.registry.label.PremiumListDatastoreDao;
import google.registry.schema.tld.PremiumEntry;
import google.registry.schema.tld.PremiumListSqlDao;
import java.util.Optional;
import org.joda.money.BigMoney;
/** Command to compare all PremiumLists in Datastore to all PremiumLists in Cloud SQL. */
@Parameters(
separators = " =",
commandDescription = "Compare all the PremiumLists in Datastore to those in Cloud SQL.")
final class ComparePremiumListsCommand implements CommandWithRemoteApi {
@Override
public void run() {
ImmutableSet<String> datastoreLists =
ofyTm().loadAllOf(PremiumList.class).stream()
.map(PremiumList::getName)
.collect(toImmutableSet());
ImmutableSet<String> sqlLists =
jpaTm()
.transact(
() ->
jpaTm().loadAllOf(PremiumList.class).stream()
.map(PremiumList::getName)
.collect(toImmutableSet()));
int listsWithDiffs = 0;
for (String listName : Sets.difference(datastoreLists, sqlLists)) {
listsWithDiffs++;
System.out.printf(
"PremiumList '%s' is present in Datastore, but not in Cloud SQL.%n", listName);
}
for (String listName : Sets.difference(sqlLists, datastoreLists)) {
listsWithDiffs++;
System.out.printf(
"PremiumList '%s' is present in Cloud SQL, but not in Datastore.%n", listName);
}
for (String listName : Sets.intersection(datastoreLists, sqlLists)) {
Optional<PremiumList> sqlList = PremiumListSqlDao.getLatestRevision(listName);
// Datastore and Cloud SQL use different objects to represent premium list entries
// so the best way to compare them is to compare their string representations.
ImmutableSet<String> datastoreListStrings =
Streams.stream(
PremiumListDatastoreDao.loadPremiumListEntriesUncached(
PremiumListDatastoreDao.getLatestRevision(listName).get()))
.map(PremiumListEntry::toString)
.collect(toImmutableSet());
Iterable<PremiumEntry> sqlListEntries =
jpaTm().transact(() -> PremiumListSqlDao.loadPremiumListEntriesUncached(sqlList.get()));
ImmutableSet<String> sqlListStrings =
Streams.stream(sqlListEntries)
.map(
premiumEntry ->
new PremiumListEntry.Builder()
.setPrice(
BigMoney.of(sqlList.get().getCurrency(), premiumEntry.getPrice())
.toMoney())
.setLabel(premiumEntry.getDomainLabel())
.build()
.toString())
.collect(toImmutableSet());
// This will only print out the name of the unequal list. GetPremiumListCommand
// should be used to determine what the actual differences are.
if (!datastoreListStrings.equals(sqlListStrings)) {
listsWithDiffs++;
System.out.printf("PremiumList '%s' has different entries in each database.%n", listName);
}
}
System.out.printf("Found %d unequal list(s).%n", listsWithDiffs);
}
}

View file

@ -16,7 +16,7 @@ package google.registry.tools;
import com.beust.jcommander.Parameter;
import com.google.common.flogger.FluentLogger;
import google.registry.schema.tld.PremiumListSqlDao;
import google.registry.schema.tld.PremiumListDao;
import google.registry.tools.params.PathParameter;
import java.nio.file.Path;
import java.util.List;
@ -51,7 +51,7 @@ abstract class CreateOrUpdatePremiumListCommand extends MutatingCommand {
String message = String.format("Saved premium list %s with %d entries", name, inputData.size());
try {
logger.atInfo().log("Saving premium list for TLD %s", name);
PremiumListSqlDao.save(name, inputData);
PremiumListDao.save(name, inputData);
logger.atInfo().log(message);
} catch (Throwable e) {
message = "Unexpected error saving premium list from nomulus tool command";

View file

@ -31,7 +31,7 @@ import google.registry.model.registry.Registry;
import google.registry.model.registry.Registry.TldState;
import google.registry.model.registry.Registry.TldType;
import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.PremiumListDualDao;
import google.registry.schema.tld.PremiumListDao;
import google.registry.tools.params.OptionalStringParameter;
import google.registry.tools.params.TransitionListParameter.BillingCostTransitions;
import google.registry.tools.params.TransitionListParameter.TldStateTransitions;
@ -344,7 +344,7 @@ abstract class CreateOrUpdateTldCommand extends MutatingCommand {
if (premiumListName != null) {
if (premiumListName.isPresent()) {
Optional<PremiumList> premiumList =
PremiumListDualDao.getLatestRevision(premiumListName.get());
PremiumListDao.getLatestRevision(premiumListName.get());
checkArgument(
premiumList.isPresent(),
String.format("The premium list '%s' doesn't exist", premiumListName.get()));

View file

@ -25,7 +25,7 @@ import com.google.common.base.Strings;
import com.googlecode.objectify.Key;
import google.registry.model.registry.label.PremiumList;
import google.registry.persistence.VKey;
import google.registry.schema.tld.PremiumListSqlDao;
import google.registry.schema.tld.PremiumListDao;
import google.registry.schema.tld.PremiumListUtils;
import java.nio.file.Files;
@ -43,7 +43,7 @@ public class CreatePremiumListCommand extends CreateOrUpdatePremiumListCommand {
protected void init() throws Exception {
name = Strings.isNullOrEmpty(name) ? convertFilePathToName(inputFile) : name;
checkArgument(
!PremiumListSqlDao.getLatestRevision(name).isPresent(),
!PremiumListDao.getLatestRevision(name).isPresent(),
"A premium list already exists by this name");
if (!override) {
// refer to CreatePremiumListAction.java

View file

@ -21,7 +21,7 @@ import com.beust.jcommander.Parameters;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.PremiumListDualDao;
import google.registry.schema.tld.PremiumListDao;
import javax.annotation.Nullable;
/**
@ -42,10 +42,10 @@ final class DeletePremiumListCommand extends ConfirmingCommand implements Comman
@Override
protected void init() {
checkArgument(
PremiumListDualDao.exists(name),
PremiumListDao.getLatestRevision(name).isPresent(),
"Cannot delete the premium list %s because it doesn't exist.",
name);
premiumList = PremiumListDualDao.getLatestRevision(name).get();
premiumList = PremiumListDao.getLatestRevision(name).get();
ImmutableSet<String> tldsUsedOn = premiumList.getReferencingTlds();
checkArgument(
tldsUsedOn.isEmpty(),
@ -60,7 +60,7 @@ final class DeletePremiumListCommand extends ConfirmingCommand implements Comman
@Override
protected String execute() {
PremiumListDualDao.delete(premiumList);
PremiumListDao.delete(premiumList);
return String.format("Deleted premium list '%s'.\n", premiumList.getName());
}
}

View file

@ -18,7 +18,7 @@ import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.collect.Streams;
import google.registry.model.registry.label.PremiumList.PremiumListEntry;
import google.registry.model.registry.label.PremiumListDualDao;
import google.registry.schema.tld.PremiumListDao;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
@ -33,11 +33,11 @@ public class GetPremiumListCommand implements CommandWithRemoteApi {
@Override
public void run() {
for (String premiumListName : mainParameters) {
if (PremiumListDualDao.exists(premiumListName)) {
if (PremiumListDao.getLatestRevision(premiumListName).isPresent()) {
System.out.printf(
"%s:\n%s\n",
premiumListName,
Streams.stream(PremiumListDualDao.loadAllPremiumListEntries(premiumListName))
Streams.stream(PremiumListDao.loadAllPremiumListEntries(premiumListName))
.sorted(Comparator.comparing(PremiumListEntry::getLabel))
.map(PremiumListEntry::toString)
.collect(Collectors.joining("\n")));

View file

@ -38,7 +38,6 @@ public final class RegistryTool {
.put("canonicalize_labels", CanonicalizeLabelsCommand.class)
.put("check_domain", CheckDomainCommand.class)
.put("check_domain_claims", CheckDomainClaimsCommand.class)
.put("compare_premium_lists", ComparePremiumListsCommand.class)
.put("compare_reserved_lists", CompareReservedListsCommand.class)
.put("convert_idn", ConvertIdnCommand.class)
.put("count_domains", CountDomainsCommand.class)

View file

@ -29,7 +29,7 @@ import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.PremiumList.PremiumListEntry;
import google.registry.persistence.VKey;
import google.registry.schema.tld.PremiumEntry;
import google.registry.schema.tld.PremiumListSqlDao;
import google.registry.schema.tld.PremiumListDao;
import google.registry.schema.tld.PremiumListUtils;
import java.nio.file.Files;
import java.util.List;
@ -79,12 +79,12 @@ class UpdatePremiumListCommand extends CreateOrUpdatePremiumListCommand {
assertThat(persistedList.size()).isEqualTo(1);
*/
protected ImmutableSet<String> getExistingPremiumListEntry(String name) {
Optional<PremiumList> list = PremiumListSqlDao.getLatestRevision(name);
Optional<PremiumList> list = PremiumListDao.getLatestRevision(name);
checkArgument(
list.isPresent(),
String.format("Could not update premium list %s because it doesn't exist.", name));
Iterable<PremiumEntry> sqlListEntries =
jpaTm().transact(() -> PremiumListSqlDao.loadPremiumListEntriesUncached(list.get()));
jpaTm().transact(() -> PremiumListDao.loadPremiumListEntries(list.get()));
return Streams.stream(sqlListEntries)
.map(
premiumEntry ->

View file

@ -21,10 +21,10 @@ import static google.registry.request.Action.Method.POST;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
import google.registry.model.registry.label.PremiumListDualDao;
import google.registry.request.Action;
import google.registry.request.Parameter;
import google.registry.request.auth.Auth;
import google.registry.schema.tld.PremiumListDao;
import java.util.List;
import javax.inject.Inject;
@ -51,7 +51,9 @@ public class CreatePremiumListAction extends CreateOrUpdatePremiumListAction {
@Override
protected void save() {
checkArgument(
!PremiumListDualDao.exists(name), "A premium list of this name already exists: %s", name);
!PremiumListDao.getLatestRevision(name).isPresent(),
"A premium list of this name already exists: %s",
name);
if (!override) {
assertTldExists(
name,
@ -62,7 +64,7 @@ public class CreatePremiumListAction extends CreateOrUpdatePremiumListAction {
logInputData();
List<String> inputDataPreProcessed =
Splitter.on('\n').omitEmptyStrings().splitToList(inputData);
PremiumListDualDao.save(name, inputDataPreProcessed);
PremiumListDao.save(name, inputDataPreProcessed);
String message =
String.format("Saved premium list %s with %d entries", name, inputDataPreProcessed.size());
logger.atInfo().log(message);

View file

@ -21,9 +21,9 @@ 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 google.registry.schema.tld.PremiumListDao;
import java.util.Comparator;
import java.util.Optional;
import javax.inject.Inject;
@ -55,7 +55,7 @@ public final class ListPremiumListsAction extends ListObjectsAction<PremiumList>
() ->
jpaTm().loadAllOf(PremiumList.class).stream()
.map(PremiumList::getName)
.map(PremiumListDualDao::getLatestRevision)
.map(PremiumListDao::getLatestRevision)
.filter(Optional::isPresent)
.map(Optional::get)
.peek(list -> Hibernate.initialize(list.getLabelsToPrices()))

View file

@ -21,9 +21,9 @@ import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
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 google.registry.schema.tld.PremiumListDao;
import java.util.List;
import javax.inject.Inject;
@ -47,7 +47,7 @@ public class UpdatePremiumListAction extends CreateOrUpdatePremiumListAction {
@Override
protected void save() {
checkArgument(
PremiumListDualDao.exists(name),
PremiumListDao.getLatestRevision(name).isPresent(),
"Could not update premium list %s because it doesn't exist.",
name);
@ -55,7 +55,7 @@ public class UpdatePremiumListAction extends CreateOrUpdatePremiumListAction {
logInputData();
List<String> inputDataPreProcessed =
Splitter.on('\n').omitEmptyStrings().splitToList(inputData);
PremiumList newPremiumList = PremiumListDualDao.save(name, inputDataPreProcessed);
PremiumList newPremiumList = PremiumListDao.save(name, inputDataPreProcessed);
String message =
String.format(

View file

@ -37,8 +37,8 @@ import com.google.common.collect.ImmutableList;
import com.google.common.net.MediaType;
import google.registry.model.registry.Registry;
import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.PremiumListDualDao;
import google.registry.request.Response;
import google.registry.schema.tld.PremiumListDao;
import google.registry.storage.drive.DriveConnection;
import google.registry.testing.AppEngineExtension;
import java.io.IOException;
@ -75,7 +75,7 @@ public class ExportPremiumTermsActionTest {
@BeforeEach
void beforeEach() throws Exception {
createTld("tld");
PremiumList pl = PremiumListDualDao.save("pl-name", PREMIUM_NAMES);
PremiumList pl = PremiumListDao.save("pl-name", PREMIUM_NAMES);
persistResource(
Registry.get("tld").asBuilder().setPremiumList(pl).setDriveFolderId("folder_id").build());
when(driveConnection.createOrUpdateFile(
@ -143,7 +143,7 @@ public class ExportPremiumTermsActionTest {
@Test
void test_exportPremiumTerms_failure_noPremiumList() {
PremiumListDualDao.delete(new PremiumList.Builder().setName("pl-name").build());
PremiumListDao.delete(new PremiumList.Builder().setName("pl-name").build());
assertThrows(RuntimeException.class, () -> runAction("tld"));
verifyNoInteractions(driveConnection);

View file

@ -42,8 +42,8 @@ import google.registry.model.EntityTestCase;
import google.registry.model.registry.Registry.RegistryNotFoundException;
import google.registry.model.registry.Registry.TldState;
import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.PremiumListDualDao;
import google.registry.model.registry.label.ReservedList;
import google.registry.schema.tld.PremiumListDao;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
@ -251,7 +251,7 @@ public final class RegistryTest extends EntityTestCase {
Registry registry = Registry.get("tld").asBuilder().setPremiumList(pl2).build();
Key<PremiumList> plKey = registry.getPremiumList();
assertThat(plKey).isNotNull();
PremiumList stored = PremiumListDualDao.getLatestRevision(plKey.getName()).get();
PremiumList stored = PremiumListDao.getLatestRevision(plKey.getName()).get();
assertThat(stored.getName()).isEqualTo("tld2");
}

View file

@ -1,326 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.registry.label;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static com.google.monitoring.metrics.contrib.DistributionMetricSubject.assertThat;
import static com.google.monitoring.metrics.contrib.LongMetricSubject.assertThat;
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
import static google.registry.model.registry.label.DomainLabelMetrics.PremiumListCheckOutcome.BLOOM_FILTER_NEGATIVE;
import static google.registry.model.registry.label.DomainLabelMetrics.PremiumListCheckOutcome.CACHED_NEGATIVE;
import static google.registry.model.registry.label.DomainLabelMetrics.PremiumListCheckOutcome.CACHED_POSITIVE;
import static google.registry.model.registry.label.DomainLabelMetrics.PremiumListCheckOutcome.UNCACHED_NEGATIVE;
import static google.registry.model.registry.label.DomainLabelMetrics.PremiumListCheckOutcome.UNCACHED_POSITIVE;
import static google.registry.model.registry.label.DomainLabelMetrics.premiumListChecks;
import static google.registry.model.registry.label.DomainLabelMetrics.premiumListProcessingTime;
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.loadPremiumListEntries;
import static google.registry.testing.DatabaseHelper.persistPremiumList;
import static google.registry.testing.DatabaseHelper.persistResource;
import static org.joda.time.Duration.standardDays;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Streams;
import com.googlecode.objectify.Key;
import google.registry.model.registry.Registry;
import google.registry.model.registry.label.PremiumList.PremiumListEntry;
import google.registry.model.registry.label.PremiumList.PremiumListRevision;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.TestCacheExtension;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import org.joda.money.Money;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link PremiumListDatastoreDao}. */
public class PremiumListDatastoreDaoTest {
@RegisterExtension
public final AppEngineExtension appEngine =
AppEngineExtension.builder().withDatastoreAndCloudSql().build();
// Set long persist times on caches so they can be tested (cache times default to 0 in tests).
@RegisterExtension
public final TestCacheExtension testCacheExtension =
new TestCacheExtension.Builder()
.withPremiumListsCache(standardDays(1))
.withPremiumListEntriesCache(standardDays(1))
.build();
private PremiumList pl;
@BeforeEach
void before() {
// createTld() overwrites the premium list, so call it before persisting pl.
createTld("tld");
pl =
persistPremiumList(
"tld",
"lol,USD 999 # yup",
"rich,USD 1999 #tada",
"icann,JPY 100",
"johnny-be-goode,USD 20.50");
persistResource(Registry.get("tld").asBuilder().setPremiumList(pl).build());
premiumListChecks.reset();
premiumListProcessingTime.reset();
}
void assertMetricOutcomeCount(
int checkCount, DomainLabelMetrics.PremiumListCheckOutcome outcome) {
assertThat(premiumListChecks)
.hasValueForLabels(checkCount, "tld", "tld", outcome.toString())
.and()
.hasNoOtherValues();
assertThat(premiumListProcessingTime)
.hasAnyValueForLabels("tld", "tld", outcome.toString())
.and()
.hasNoOtherValues();
}
@Test
void testSave_largeNumberOfEntries_succeeds() {
PremiumList premiumList = persistHumongousPremiumList("tld", 2500);
assertThat(loadPremiumListEntries(premiumList)).hasSize(2500);
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld", "7", "tld"))
.hasValue(Money.parse("USD 100"));
assertMetricOutcomeCount(1, UNCACHED_POSITIVE);
}
@Test
void testSave_updateTime_isUpdatedOnEverySave() {
PremiumList pl = PremiumListDatastoreDao.save("tld3", ImmutableList.of("slime,USD 10"));
PremiumList newPl = PremiumListDatastoreDao.save("tld3", ImmutableList.of("mutants,USD 20"));
assertThat(newPl.getLastUpdateTime()).isGreaterThan(pl.getLastUpdateTime());
}
@Test
void testSave_creationTime_onlyUpdatedOnFirstCreation() {
PremiumList pl = persistPremiumList("tld3", "sludge,JPY 1000");
PremiumList newPl =
PremiumListDatastoreDao.save("tld3", ImmutableList.of("sleighbells,CHF 2000"));
assertThat(newPl.creationTime).isEqualTo(pl.creationTime);
}
@Test
void testExists() {
assertThat(PremiumListDatastoreDao.getLatestRevision("tld")).isPresent();
assertThat(PremiumListDatastoreDao.getLatestRevision("nonExistentPremiumList")).isEmpty();
}
@Test
void testGetPremiumPrice_comesFromBloomFilter() throws Exception {
PremiumList pl = PremiumListDatastoreDao.getLatestRevision("tld").get();
PremiumListEntry entry =
persistResource(
new PremiumListEntry.Builder()
.setParent(pl.getRevisionKey())
.setLabel("missingno")
.setPrice(Money.parse("USD 1000"))
.build());
// "missingno" shouldn't be in the Bloom filter, thus it should return not premium without
// attempting to load the entity that is actually present.
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld", "missingno", "tld")).isEmpty();
// However, if we manually query the cache to force an entity load, it should be found.
assertThat(
PremiumListDatastoreDao.premiumListEntriesCache.get(
Key.create(pl.getRevisionKey(), PremiumListEntry.class, "missingno")))
.hasValue(entry);
assertMetricOutcomeCount(1, BLOOM_FILTER_NEGATIVE);
}
@Test
void testGetPremiumPrice_cachedSecondTime() {
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld", "rich", "tld"))
.hasValue(Money.parse("USD 1999"));
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld", "rich", "tld"))
.hasValue(Money.parse("USD 1999"));
assertThat(premiumListChecks)
.hasValueForLabels(1, "tld", "tld", UNCACHED_POSITIVE.toString())
.and()
.hasValueForLabels(1, "tld", "tld", CACHED_POSITIVE.toString())
.and()
.hasNoOtherValues();
assertThat(premiumListProcessingTime)
.hasAnyValueForLabels("tld", "tld", UNCACHED_POSITIVE.toString())
.and()
.hasAnyValueForLabels("tld", "tld", CACHED_POSITIVE.toString())
.and()
.hasNoOtherValues();
}
@Test
void testGetPremiumPrice_bloomFilterFalsePositive() {
// Remove one of the premium list entries from behind the Bloom filter's back.
tm().transactNew(
() ->
auditedOfy()
.delete()
.keys(Key.create(pl.getRevisionKey(), PremiumListEntry.class, "rich")));
auditedOfy().clearSessionCache();
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld", "rich", "tld")).isEmpty();
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld", "rich", "tld")).isEmpty();
assertThat(premiumListChecks)
.hasValueForLabels(1, "tld", "tld", UNCACHED_NEGATIVE.toString())
.and()
.hasValueForLabels(1, "tld", "tld", CACHED_NEGATIVE.toString())
.and()
.hasNoOtherValues();
assertThat(premiumListProcessingTime)
.hasAnyValueForLabels("tld", "tld", UNCACHED_NEGATIVE.toString())
.and()
.hasAnyValueForLabels("tld", "tld", CACHED_NEGATIVE.toString())
.and()
.hasNoOtherValues();
}
@Test
void testSave_removedPremiumListEntries_areNoLongerInDatastore() {
PremiumList pl = persistPremiumList("tld", "genius,USD 10", "dolt,JPY 1000");
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld", "genius", "tld"))
.hasValue(Money.parse("USD 10"));
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld", "dolt", "tld"))
.hasValue(Money.parse("JPY 1000"));
assertThat(
auditedOfy()
.load()
.type(PremiumListEntry.class)
.parent(pl.getRevisionKey())
.id("dolt")
.now()
.price)
.isEqualTo(Money.parse("JPY 1000"));
PremiumListDatastoreDao.save("tld", ImmutableList.of("genius,USD 10", "savant,USD 90"));
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld", "genius", "tld"))
.hasValue(Money.parse("USD 10"));
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld", "savant", "tld"))
.hasValue(Money.parse("USD 90"));
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld", "dolt", "tld")).isEmpty();
// TODO(b/79888775): Assert that the old premium list is enqueued for later deletion.
assertThat(premiumListChecks)
.hasValueForLabels(4, "tld", "tld", UNCACHED_POSITIVE.toString())
.and()
.hasValueForLabels(1, "tld", "tld", BLOOM_FILTER_NEGATIVE.toString())
.and()
.hasNoOtherValues();
assertThat(premiumListProcessingTime)
.hasAnyValueForLabels("tld", "tld", UNCACHED_POSITIVE.toString())
.and()
.hasAnyValueForLabels("tld", "tld", BLOOM_FILTER_NEGATIVE.toString())
.and()
.hasNoOtherValues();
}
@Test
void testGetPremiumPrice_allLabelsAreNonPremium_whenNotInList() {
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld", "blah", "tld")).isEmpty();
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld", "slinge", "tld")).isEmpty();
assertMetricOutcomeCount(2, BLOOM_FILTER_NEGATIVE);
}
@Test
void testSave_simple() {
PremiumList pl =
PremiumListDatastoreDao.save("tld2", ImmutableList.of("lol , USD 999 # yupper rooni "));
persistResource(Registry.get("tld").asBuilder().setPremiumList(pl).build());
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld2", "lol", "tld"))
.hasValue(Money.parse("USD 999"));
assertThat(PremiumListDatastoreDao.getPremiumPrice("tld2", "lol ", "tld")).isEmpty();
ImmutableMap<String, PremiumListEntry> entries =
Streams.stream(PremiumListDatastoreDao.loadPremiumListEntriesUncached(pl))
.collect(toImmutableMap(PremiumListEntry::getLabel, Function.identity()));
assertThat(entries.keySet()).containsExactly("lol");
assertThat(entries).doesNotContainKey("lol ");
PremiumListEntry entry = entries.get("lol");
assertThat(entry.comment).isEqualTo("yupper rooni");
assertThat(entry.price).isEqualTo(Money.parse("USD 999"));
assertThat(entry.label).isEqualTo("lol");
assertThat(premiumListChecks)
.hasValueForLabels(1, "tld", "tld2", UNCACHED_POSITIVE.toString())
.and()
.hasValueForLabels(1, "tld", "tld2", BLOOM_FILTER_NEGATIVE.toString())
.and()
.hasNoOtherValues();
assertThat(premiumListProcessingTime)
.hasAnyValueForLabels("tld", "tld2", UNCACHED_POSITIVE.toString())
.and()
.hasAnyValueForLabels("tld", "tld2", BLOOM_FILTER_NEGATIVE.toString())
.and()
.hasNoOtherValues();
}
@Test
void test_saveAndUpdateEntriesTwice() {
PremiumList firstSave = PremiumListDatastoreDao.save("tld", ImmutableList.of("test,USD 1"));
ImmutableMap<String, PremiumListEntry> entries =
Streams.stream(PremiumListDatastoreDao.loadPremiumListEntriesUncached(firstSave))
.collect(toImmutableMap(PremiumListEntry::getLabel, Function.identity()));
assertThat(entries.keySet()).containsExactly("test");
// Save again with no changes, and clear the cache to force a re-load from Datastore.
PremiumList resaved = PremiumListDatastoreDao.save("tld", ImmutableList.of("test,USD 1"));
auditedOfy().clearSessionCache();
Map<String, PremiumListEntry> entriesReloaded =
Streams.stream(PremiumListDatastoreDao.loadPremiumListEntriesUncached(resaved))
.collect(toImmutableMap(PremiumListEntry::getLabel, Function.identity()));
assertThat(entriesReloaded).hasSize(1);
assertThat(entriesReloaded).containsKey("test");
assertThat(entriesReloaded.get("test").parent).isEqualTo(resaved.getRevisionKey());
}
@Test
void testDelete() {
persistPremiumList("gtld1", "trombone,USD 10");
Optional<PremiumList> gtld1 = PremiumListDatastoreDao.getLatestRevision("gtld1");
assertThat(gtld1).isPresent();
Key<PremiumListRevision> parent = gtld1.get().getRevisionKey();
PremiumListDatastoreDao.delete(gtld1.get());
assertThat(PremiumListDatastoreDao.getLatestRevision("gtld1")).isEmpty();
assertThat(auditedOfy().load().type(PremiumListEntry.class).ancestor(parent).list()).isEmpty();
}
@Test
void testDelete_largeNumberOfEntries_succeeds() {
PremiumList large = persistHumongousPremiumList("ginormous", 2500);
PremiumListDatastoreDao.delete(large);
assertThat(PremiumListDatastoreDao.getLatestRevision("ginormous")).isEmpty();
}
@Test
void test_savePremiumList_clearsCache() {
assertThat(PremiumListDatastoreDao.premiumListCache.getIfPresent("tld")).isNull();
PremiumList pl = PremiumListDatastoreDao.getLatestRevision("tld").get();
assertThat(PremiumListDatastoreDao.premiumListCache.getIfPresent("tld").get()).isEqualTo(pl);
transactIfJpaTm(() -> PremiumListDatastoreDao.save("tld", ImmutableList.of("test,USD 1")));
assertThat(PremiumListDatastoreDao.premiumListCache.getIfPresent("tld")).isNull();
}
/** Persists a premium list with a specified number of nonsense entries. */
private PremiumList persistHumongousPremiumList(String name, int size) {
String[] entries = new String[size];
for (int i = 0; i < size; i++) {
entries[i] = String.format("%d,USD 100 # blahz", i);
}
return persistPremiumList(name, entries);
}
}

View file

@ -1,111 +0,0 @@
// 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.registry.label;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.newRegistry;
import static google.registry.testing.DatabaseHelper.persistResource;
import static org.joda.time.Duration.standardDays;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.truth.Truth8;
import google.registry.dns.writer.VoidDnsWriter;
import google.registry.model.EntityTestCase;
import google.registry.model.pricing.StaticPremiumListPricingEngine;
import google.registry.model.registry.Registry;
import google.registry.testing.TestCacheExtension;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link PremiumListDualDao}. */
public class PremiumListDualDaoTest extends EntityTestCase {
// Set long persist times on caches so they can be tested (cache times default to 0 in tests).
@RegisterExtension
public final TestCacheExtension testCacheExtension =
new TestCacheExtension.Builder()
.withPremiumListsCache(standardDays(1))
.withPremiumListEntriesCache(standardDays(1))
.build();
@BeforeEach
void before() {
createTld("tld");
fakeClock.setAutoIncrementByOneMilli();
}
@AfterEach
void after() {
fakeClock.disableAutoIncrement();
}
@Test
void testGetPremiumPrice_secondaryLoadMissingOfy() {
PremiumList premiumList = PremiumListDatastoreDao.getLatestRevision("tld").get();
PremiumListDatastoreDao.delete(premiumList);
assertThat(
assertThrows(
IllegalStateException.class,
() -> PremiumListDualDao.getPremiumPrice("brass", Registry.get("tld"))))
.hasMessageThat()
.isEqualTo(
"Unequal prices for domain brass.tld from primary SQL DB (Optional[USD 20.00]) "
+ "and secondary Datastore db (Optional.empty).");
}
@Test
void testGetPremiumPrice_secondaryDifferentOfy() {
PremiumListDatastoreDao.save("tld", ImmutableList.of("brass,USD 50"));
assertThat(
assertThrows(
IllegalStateException.class,
() -> PremiumListDualDao.getPremiumPrice("brass", Registry.get("tld"))))
.hasMessageThat()
.isEqualTo(
"Unequal prices for domain brass.tld from primary SQL DB "
+ "(Optional[USD 20.00]) and secondary Datastore db (Optional[USD 50.00]).");
}
@Test
void testGetPremiumPrice_returnsNoPriceWhenNoPremiumListConfigured() {
createTld("ghost");
persistResource(
new Registry.Builder()
.setTldStr("ghost")
.setPremiumPricingEngine(StaticPremiumListPricingEngine.NAME)
.setDnsWriters(ImmutableSet.of(VoidDnsWriter.NAME))
.build());
assertThat(Registry.get("ghost").getPremiumList()).isNull();
Truth8.assertThat(PremiumListDualDao.getPremiumPrice("blah", Registry.get("ghost"))).isEmpty();
}
@Test
void testGetPremiumPrice_emptyWhenPremiumListDeleted() {
PremiumList toDelete = PremiumListDualDao.getLatestRevision("tld").get();
PremiumListDualDao.delete(toDelete);
Truth8.assertThat(PremiumListDualDao.getPremiumPrice("blah", Registry.get("tld"))).isEmpty();
}
@Test
void getPremiumPrice_returnsNoneWhenNoPremiumListConfigured() {
persistResource(newRegistry("foobar", "FOOBAR").asBuilder().setPremiumList(null).build());
Truth8.assertThat(PremiumListDualDao.getPremiumPrice("rich", Registry.get("foobar"))).isEmpty();
}
}

View file

@ -26,6 +26,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.hash.BloomFilter;
import google.registry.model.registry.Registry;
import google.registry.model.registry.label.PremiumList.PremiumListEntry;
import google.registry.schema.tld.PremiumListDao;
import google.registry.testing.AppEngineExtension;
import org.joda.money.Money;
import org.junit.jupiter.api.BeforeEach;
@ -68,7 +69,7 @@ public class PremiumListTest {
@Test
void testBloomFilter() {
PremiumList pl = PremiumListDualDao.getLatestRevision("tld").get();
PremiumList pl = PremiumListDao.getLatestRevision("tld").get();
BloomFilter<String> bloomFilter = pl.getBloomFilter();
assertThat(bloomFilter.mightContain("notpremium")).isFalse();
for (String label : ImmutableList.of("rich", "lol", "johnny-be-goode", "icann")) {
@ -84,7 +85,7 @@ public class PremiumListTest {
assertThrows(
IllegalStateException.class,
() ->
PremiumListDualDao.getLatestRevision("tld")
PremiumListDao.getLatestRevision("tld")
.get()
.parse(
ImmutableList.of(

View file

@ -42,7 +42,7 @@ import google.registry.schema.integration.SqlIntegrationTestSuite.AfterSuiteTest
import google.registry.schema.integration.SqlIntegrationTestSuite.BeforeSuiteTest;
import google.registry.schema.registrar.RegistrarDaoTest;
import google.registry.schema.replay.SqlReplayCheckpointTest;
import google.registry.schema.tld.PremiumListSqlDaoTest;
import google.registry.schema.tld.PremiumListDaoTest;
import google.registry.testing.AppEngineExtension;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
@ -92,7 +92,7 @@ import org.junit.runner.RunWith;
KmsSecretRevisionSqlDaoTest.class,
LockTest.class,
PollMessageTest.class,
PremiumListSqlDaoTest.class,
PremiumListDaoTest.class,
RdeRevisionTest.class,
RegistrarDaoTest.class,
RegistryTest.class,

View file

@ -40,8 +40,8 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link PremiumListSqlDao}. */
public class PremiumListSqlDaoTest {
/** Unit tests for {@link PremiumListDao}. */
public class PremiumListDaoTest {
private final FakeClock fakeClock = new FakeClock();
@ -58,7 +58,6 @@ public class PremiumListSqlDaoTest {
public final TestCacheExtension testCacheExtension =
new TestCacheExtension.Builder()
.withPremiumListsCache(standardDays(1))
.withPremiumListEntriesCache(standardDays(1))
.build();
private ImmutableMap<String, BigDecimal> testPrices;
@ -86,12 +85,11 @@ public class PremiumListSqlDaoTest {
@Test
void saveNew_worksSuccessfully() {
PremiumListSqlDao.save(testList);
PremiumListDao.save(testList);
jpaTm()
.transact(
() -> {
Optional<PremiumList> persistedListOpt =
PremiumListSqlDao.getLatestRevision("testname");
Optional<PremiumList> persistedListOpt = PremiumListDao.getLatestRevision("testname");
assertThat(persistedListOpt).isPresent();
PremiumList persistedList = persistedListOpt.get();
assertThat(persistedList.getLabelsToPrices()).containsExactlyEntriesIn(testPrices);
@ -101,11 +99,11 @@ public class PremiumListSqlDaoTest {
@Test
void update_worksSuccessfully() {
PremiumListSqlDao.save(testList);
Optional<PremiumList> persistedList = PremiumListSqlDao.getLatestRevision("testname");
PremiumListDao.save(testList);
Optional<PremiumList> persistedList = PremiumListDao.getLatestRevision("testname");
assertThat(persistedList).isPresent();
long firstRevisionId = persistedList.get().getRevisionId();
PremiumListSqlDao.save(
PremiumListDao.save(
new PremiumList.Builder()
.setName("testname")
.setCurrency(USD)
@ -122,7 +120,7 @@ public class PremiumListSqlDaoTest {
jpaTm()
.transact(
() -> {
Optional<PremiumList> savedListOpt = PremiumListSqlDao.getLatestRevision("testname");
Optional<PremiumList> savedListOpt = PremiumListDao.getLatestRevision("testname");
assertThat(savedListOpt).isPresent();
PremiumList savedList = savedListOpt.get();
assertThat(savedList.getLabelsToPrices())
@ -142,26 +140,26 @@ public class PremiumListSqlDaoTest {
@Test
void checkExists_worksSuccessfully() {
assertThat(PremiumListSqlDao.getLatestRevision("testname")).isEmpty();
PremiumListSqlDao.save(testList);
assertThat(PremiumListSqlDao.getLatestRevision("testname")).isPresent();
assertThat(PremiumListDao.getLatestRevision("testname")).isEmpty();
PremiumListDao.save(testList);
assertThat(PremiumListDao.getLatestRevision("testname")).isPresent();
}
@Test
void getLatestRevision_returnsEmptyForNonexistentList() {
assertThat(PremiumListSqlDao.getLatestRevision("nonexistentlist")).isEmpty();
assertThat(PremiumListDao.getLatestRevision("nonexistentlist")).isEmpty();
}
@Test
void getLatestRevision_worksSuccessfully() {
PremiumListSqlDao.save(
PremiumListDao.save(
new PremiumList.Builder()
.setName("list1")
.setCurrency(JPY)
.setLabelsToPrices(ImmutableMap.of("wrong", BigDecimal.valueOf(1000.50)))
.setCreationTime(fakeClock.nowUtc())
.build());
PremiumListSqlDao.save(
PremiumListDao.save(
new PremiumList.Builder()
.setName("list1")
.setCurrency(JPY)
@ -171,7 +169,7 @@ public class PremiumListSqlDaoTest {
jpaTm()
.transact(
() -> {
Optional<PremiumList> persistedList = PremiumListSqlDao.getLatestRevision("list1");
Optional<PremiumList> persistedList = PremiumListDao.getLatestRevision("list1");
assertThat(persistedList).isPresent();
assertThat(persistedList.get().getName()).isEqualTo("list1");
assertThat(persistedList.get().getCurrency()).isEqualTo(JPY);
@ -191,18 +189,16 @@ public class PremiumListSqlDaoTest {
google.registry.model.registry.label.PremiumList.class,
"premlist"))
.build());
PremiumListSqlDao.save(
PremiumListDao.save(
new PremiumList.Builder()
.setName("premlist")
.setCurrency(USD)
.setLabelsToPrices(testPrices)
.setCreationTime(fakeClock.nowUtc())
.build());
assertThat(PremiumListSqlDao.getPremiumPrice("premlist", "silver"))
.hasValue(Money.of(USD, 10.23));
assertThat(PremiumListSqlDao.getPremiumPrice("premlist", "gold"))
.hasValue(Money.of(USD, 1305.47));
assertThat(PremiumListSqlDao.getPremiumPrice("premlist", "zirconium")).isEmpty();
assertThat(PremiumListDao.getPremiumPrice("premlist", "silver")).hasValue(Money.of(USD, 10.23));
assertThat(PremiumListDao.getPremiumPrice("premlist", "gold")).hasValue(Money.of(USD, 1305.47));
assertThat(PremiumListDao.getPremiumPrice("premlist", "zirconium")).isEmpty();
}
@Test
@ -216,7 +212,7 @@ public class PremiumListSqlDaoTest {
google.registry.model.registry.label.PremiumList.class,
"premlist"))
.build());
PremiumListSqlDao.save(
PremiumListDao.save(
new PremiumList.Builder()
.setName("premlist")
.setCurrency(JPY)
@ -230,20 +226,20 @@ public class PremiumListSqlDaoTest {
BigDecimal.valueOf(15000)))
.setCreationTime(fakeClock.nowUtc())
.build());
assertThat(PremiumListSqlDao.getPremiumPrice("premlist", "silver")).hasValue(moneyOf(JPY, 10));
assertThat(PremiumListSqlDao.getPremiumPrice("premlist", "gold")).hasValue(moneyOf(JPY, 1000));
assertThat(PremiumListSqlDao.getPremiumPrice("premlist", "palladium"))
assertThat(PremiumListDao.getPremiumPrice("premlist", "silver")).hasValue(moneyOf(JPY, 10));
assertThat(PremiumListDao.getPremiumPrice("premlist", "gold")).hasValue(moneyOf(JPY, 1000));
assertThat(PremiumListDao.getPremiumPrice("premlist", "palladium"))
.hasValue(moneyOf(JPY, 15000));
}
@Test
void test_savePremiumList_clearsCache() {
assertThat(PremiumListSqlDao.premiumListCache.getIfPresent("testname")).isNull();
PremiumListSqlDao.save(testList);
PremiumList pl = PremiumListSqlDao.getLatestRevision("testname").get();
assertThat(PremiumListSqlDao.premiumListCache.getIfPresent("testname").get()).isEqualTo(pl);
transactIfJpaTm(() -> PremiumListSqlDao.save("testname", ImmutableList.of("test,USD 1")));
assertThat(PremiumListSqlDao.premiumListCache.getIfPresent("testname")).isNull();
assertThat(PremiumListDao.premiumListCache.getIfPresent("testname")).isNull();
PremiumListDao.save(testList);
PremiumList pl = PremiumListDao.getLatestRevision("testname").get();
assertThat(PremiumListDao.premiumListCache.getIfPresent("testname").get()).isEqualTo(pl);
transactIfJpaTm(() -> PremiumListDao.save("testname", ImmutableList.of("test,USD 1")));
assertThat(PremiumListDao.premiumListCache.getIfPresent("testname")).isNull();
}
private static Money moneyOf(CurrencyUnit unit, double amount) {

View file

@ -30,11 +30,10 @@ import static google.registry.model.EppResourceUtils.createRepoId;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.model.ImmutableObjectSubject.immutableObjectCorrespondence;
import static google.registry.model.ResourceTransferUtils.createTransferResponse;
import static google.registry.model.ofy.ObjectifyService.allocateId;
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
import static google.registry.model.registry.Registry.TldState.GENERAL_AVAILABILITY;
import static google.registry.model.registry.label.PremiumListDatastoreDao.parentPremiumListEntriesOnRevision;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.ofyTmOrDoNothing;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
@ -99,8 +98,6 @@ import google.registry.model.registry.Registry.TldState;
import google.registry.model.registry.Registry.TldType;
import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.PremiumList.PremiumListEntry;
import google.registry.model.registry.label.PremiumList.PremiumListRevision;
import google.registry.model.registry.label.PremiumListDualDao;
import google.registry.model.registry.label.ReservedList;
import google.registry.model.registry.label.ReservedListDualDatabaseDao;
import google.registry.model.reporting.HistoryEntry;
@ -110,6 +107,7 @@ import google.registry.model.transfer.DomainTransferData;
import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.TransferStatus;
import google.registry.persistence.VKey;
import google.registry.schema.tld.PremiumListDao;
import google.registry.tmch.LordnTaskUtils;
import java.lang.reflect.Field;
import java.util.List;
@ -391,33 +389,14 @@ public class DatabaseHelper {
toImmutableMap(
Map.Entry::getKey, entry -> entry.getValue().getValue().getAmount())))
.build();
PremiumListRevision revision = PremiumListRevision.create(premiumList, entries.keySet());
ImmutableList<Object> premiumListOfyObjects =
ImmutableList.of(
premiumList.asBuilder().setRevision(Key.create(revision)).build(), revision);
ImmutableSet<PremiumListEntry> entriesOnRevision =
parentPremiumListEntriesOnRevision(entries.values(), Key.create(revision));
if (alwaysSaveWithBackup) {
ofyTm()
.transact(
() -> {
ofyTm().putAll(premiumListOfyObjects);
ofyTm().putAll(entriesOnRevision);
});
} else {
ofyTm().putAllWithoutBackup(premiumListOfyObjects);
ofyTm().putAllWithoutBackup(entriesOnRevision);
}
// Since we used to persist a PremiumList to Datastore here, it is necessary to allocate an ID
// here to prevent breaking some of the hard-coded flow tests. IDs in tests are allocated in a
// strictly increasing sequence, if we don't pad out the ID here, we would have to renumber
// hundreds of unit tests.
allocateId();
jpaTm().transact(() -> jpaTm().insert(premiumList));
maybeAdvanceClock();
// The above premiumList is in the session cache and it is different from the corresponding
// entity stored in Datastore because it has some @Ignore fields set dedicated for SQL. This
// breaks the assumption we have in our application code, see
// PremiumListUtils.savePremiumListAndEntries(). Clearing the session cache can help make sure
// we always get the same list.
tm().clearSessionCache();
return transactIfJpaTm(() -> tm().loadByEntity(premiumList));
return premiumList;
}
/** Creates and persists a tld. */
@ -1243,7 +1222,7 @@ public class DatabaseHelper {
/** Returns the entire map of {@link PremiumListEntry}s for the given {@link PremiumList}. */
public static ImmutableMap<String, PremiumListEntry> loadPremiumListEntries(
PremiumList premiumList) {
return Streams.stream(PremiumListDualDao.loadAllPremiumListEntries(premiumList.getName()))
return Streams.stream(PremiumListDao.loadAllPremiumListEntries(premiumList.getName()))
.collect(toImmutableMap(PremiumListEntry::getLabel, Function.identity()));
}

View file

@ -18,8 +18,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import google.registry.model.EppResource;
import google.registry.model.index.ForeignKeyIndex;
import google.registry.model.registry.label.PremiumListDatastoreDao;
import google.registry.schema.tld.PremiumListSqlDao;
import google.registry.schema.tld.PremiumListDao;
import java.util.Map;
import java.util.Optional;
import org.joda.time.Duration;
@ -72,17 +71,7 @@ public class TestCacheExtension implements BeforeEachCallback, AfterEachCallback
public Builder withPremiumListsCache(Duration expiry) {
cacheHandlerMap.put(
"PremiumListSqlDao.premiumListCache",
new TestCacheHandler(PremiumListSqlDao::setPremiumListCacheForTest, expiry));
cacheHandlerMap.put(
"PremiumListDatastoreDao.premiumListCache",
new TestCacheHandler(PremiumListDatastoreDao::setPremiumListCacheForTest, expiry));
return this;
}
public Builder withPremiumListEntriesCache(Duration expiry) {
cacheHandlerMap.put(
"PremiumList.cachePremiumListEntries",
new TestCacheHandler(PremiumListDatastoreDao::setPremiumListEntriesCacheForTest, expiry));
new TestCacheHandler(PremiumListDao::setPremiumListCacheForTest, expiry));
return this;
}

View file

@ -1,86 +0,0 @@
// 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.tools;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
import static google.registry.testing.DatabaseHelper.persistPremiumList;
import static org.joda.money.CurrencyUnit.USD;
import com.google.common.collect.ImmutableMap;
import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.PremiumListDatastoreDao;
import google.registry.schema.tld.PremiumListSqlDao;
import java.math.BigDecimal;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class ComparePremiumListsCommandTest extends CommandTestCase<ComparePremiumListsCommand> {
@BeforeEach
void beforeEach() {
persistPremiumList("xn--q9jyb4c", "rich,USD 100");
persistPremiumList("how", "richer,JPY 10000");
}
@Test
void test_success() throws Exception {
runCommand();
assertThat(getStdoutAsString()).isEqualTo("Found 0 unequal list(s).\n");
}
@Test
void test_listMissingFromCloudSql() throws Exception {
jpaTm()
.transact(
() -> {
PremiumList premiumList = PremiumListSqlDao.getLatestRevision("how").get();
PremiumListSqlDao.delete(premiumList);
});
runCommand();
assertThat(getStdoutAsString())
.isEqualTo(
"PremiumList 'how' is present in Datastore, but not in Cloud SQL.\n"
+ "Found 1 unequal list(s).\n");
}
@Test
void test_listMissingFromDatastore() throws Exception {
PremiumList premiumList = PremiumListDatastoreDao.getLatestRevision("how").get();
ofyTm().transact(() -> ofyTm().delete(premiumList));
runCommand();
assertThat(getStdoutAsString())
.isEqualTo(
"PremiumList 'how' is present in Cloud SQL, but not in Datastore.\n"
+ "Found 1 unequal list(s).\n");
}
@Test
void test_listsDiffer() throws Exception {
PremiumListSqlDao.save(
new PremiumList.Builder()
.setName("how")
.setCurrency(USD)
.setLabelsToPrices(ImmutableMap.of("silver", BigDecimal.valueOf(30.03)))
.setCreationTime(fakeClock.nowUtc())
.build());
runCommand();
assertThat(getStdoutAsString())
.isEqualTo(
"PremiumList 'how' has different entries in each database.\n"
+ "Found 1 unequal list(s).\n");
}
}

View file

@ -22,7 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import com.beust.jcommander.ParameterException;
import google.registry.model.registry.Registry;
import google.registry.model.registry.label.PremiumListDualDao;
import google.registry.schema.tld.PremiumListDao;
import google.registry.testing.DeterministicStringGenerator;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -101,7 +101,7 @@ class CreateDomainCommandTest extends EppToolCommandTestCase<CreateDomainCommand
persistResource(
Registry.get("baar")
.asBuilder()
.setPremiumList(PremiumListDualDao.getLatestRevision("baar").get())
.setPremiumList(PremiumListDao.getLatestRevision("baar").get())
.build());
runCommandForced(
"--client=NewRegistrar",

View file

@ -20,7 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.io.Files;
import google.registry.model.registry.Registry;
import google.registry.schema.tld.PremiumListSqlDao;
import google.registry.schema.tld.PremiumListDao;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.junit.jupiter.api.BeforeEach;
@ -40,14 +40,14 @@ class CreatePremiumListCommandTest<C extends CreatePremiumListCommand>
void verify_registryIsSetUpCorrectly() {
// ensure that no premium list is created before running the command
// this check also implicitly verifies the TLD is successfully created;
assertThat(PremiumListSqlDao.getLatestRevision(TLD_TEST).isPresent()).isFalse();
assertThat(PremiumListDao.getLatestRevision(TLD_TEST).isPresent()).isFalse();
}
@Test
void commandRun_successCreateList() throws Exception {
runCommandForced("--name=" + TLD_TEST, "--input=" + premiumTermsPath);
assertThat(registry.getTld().toString()).isEqualTo(TLD_TEST);
assertThat(PremiumListSqlDao.getLatestRevision(TLD_TEST).isPresent()).isTrue();
assertThat(PremiumListDao.getLatestRevision(TLD_TEST).isPresent()).isTrue();
}
@Test

View file

@ -24,7 +24,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import google.registry.model.registry.Registry;
import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.PremiumListDualDao;
import google.registry.schema.tld.PremiumListDao;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link DeletePremiumListCommand}. */
@ -35,7 +35,7 @@ class DeletePremiumListCommandTest extends CommandTestCase<DeletePremiumListComm
PremiumList premiumList = persistPremiumList("xn--q9jyb4c", "blah,USD 100");
assertThat(loadPremiumListEntries(premiumList)).hasSize(1);
runCommand("--force", "--name=xn--q9jyb4c");
assertThat(PremiumListDualDao.getLatestRevision("xn--q9jyb4c")).isEmpty();
assertThat(PremiumListDao.getLatestRevision("xn--q9jyb4c")).isEmpty();
}
@Test
@ -56,7 +56,7 @@ class DeletePremiumListCommandTest extends CommandTestCase<DeletePremiumListComm
assertThrows(
IllegalArgumentException.class,
() -> runCommandForced("--name=" + premiumList.getName()));
assertThat(PremiumListDualDao.getLatestRevision(premiumList.getName())).isPresent();
assertThat(PremiumListDao.getLatestRevision(premiumList.getName())).isPresent();
assertThat(thrown)
.hasMessageThat()
.isEqualTo("Cannot delete premium list because it is used on these tld(s): xn--q9jyb4c");

View file

@ -21,7 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.Files;
import google.registry.model.registry.Registry;
import google.registry.schema.tld.PremiumListSqlDao;
import google.registry.schema.tld.PremiumListDao;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
@ -41,7 +41,7 @@ class UpdatePremiumListCommandTest<C extends UpdatePremiumListCommand>
@Test
void verify_registryIsSetUpCorrectly() {
// ensure that no premium list is created before running the command
assertThat(PremiumListSqlDao.getLatestRevision(TLD_TEST).isPresent()).isTrue();
assertThat(PremiumListDao.getLatestRevision(TLD_TEST).isPresent()).isTrue();
// ensure that there's value in existing premium list;
UpdatePremiumListCommand command = new UpdatePremiumListCommand();
ImmutableSet<String> entries = command.getExistingPremiumListEntry(TLD_TEST);

View file

@ -20,9 +20,8 @@ import static google.registry.testing.DatabaseHelper.createTlds;
import static google.registry.testing.DatabaseHelper.loadPremiumListEntries;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import google.registry.model.registry.Registry;
import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.PremiumListDualDao;
import google.registry.schema.tld.PremiumListDao;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.FakeJsonResponse;
import org.joda.money.Money;
@ -45,7 +44,7 @@ public class CreatePremiumListActionTest {
@BeforeEach
void beforeEach() {
createTlds("foo", "xn--q9jyb4c", "how");
PremiumListDualDao.delete(PremiumListDualDao.getLatestRevision("foo").get());
PremiumListDao.delete(PremiumListDao.getLatestRevision("foo").get());
action = new CreatePremiumListAction();
response = new FakeJsonResponse();
action.response = response;
@ -89,7 +88,7 @@ public class CreatePremiumListActionTest {
action.override = true;
action.run();
assertThat(response.getStatus()).isEqualTo(SC_OK);
assertThat(loadPremiumListEntries(PremiumListDualDao.getLatestRevision("zanzibar").get()))
assertThat(loadPremiumListEntries(PremiumListDao.getLatestRevision("zanzibar").get()))
.hasSize(1);
}
@ -99,10 +98,10 @@ public class CreatePremiumListActionTest {
action.inputData = "rich,USD 25\nricher,USD 1000\n";
action.run();
assertThat(response.getStatus()).isEqualTo(SC_OK);
PremiumList premiumList = PremiumListDualDao.getLatestRevision("foo").get();
PremiumList premiumList = PremiumListDao.getLatestRevision("foo").get();
assertThat(loadPremiumListEntries(premiumList)).hasSize(2);
assertThat(PremiumListDualDao.getPremiumPrice("rich", Registry.get("foo")))
assertThat(PremiumListDao.getPremiumPrice(premiumList.getName(), "rich"))
.hasValue(Money.parse("USD 25"));
assertThat(PremiumListDualDao.getPremiumPrice("diamond", Registry.get("foo"))).isEmpty();
assertThat(PremiumListDao.getPremiumPrice(premiumList.getName(), "diamond")).isEmpty();
}
}

View file

@ -23,10 +23,8 @@ import static javax.servlet.http.HttpServletResponse.SC_OK;
import com.google.common.base.Splitter;
import com.google.common.truth.Truth8;
import google.registry.model.registry.Registry;
import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.PremiumListDualDao;
import google.registry.schema.tld.PremiumListSqlDao;
import google.registry.schema.tld.PremiumListDao;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.FakeJsonResponse;
@ -81,26 +79,24 @@ class UpdatePremiumListActionTest {
.omitEmptyStrings()
.splitToList(
readResourceUtf8(DatabaseHelper.class, "default_premium_list_testdata.csv"));
PremiumListDualDao.save("foo", inputLines);
PremiumListDao.save("foo", inputLines);
action.name = "foo";
action.inputData = "rich,USD 75\nricher,USD 5000\npoor, USD 0.99";
action.run();
assertThat(response.getStatus()).isEqualTo(SC_OK);
Registry registry = Registry.get("foo");
assertThat(loadPremiumListEntries(PremiumListDualDao.getLatestRevision("foo").get()))
.hasSize(3);
Truth8.assertThat(PremiumListDualDao.getPremiumPrice("rich", registry))
assertThat(loadPremiumListEntries(PremiumListDao.getLatestRevision("foo").get())).hasSize(3);
Truth8.assertThat(PremiumListDao.getPremiumPrice("foo", "rich"))
.hasValue(Money.parse("USD 75"));
Truth8.assertThat(PremiumListDualDao.getPremiumPrice("richer", registry))
Truth8.assertThat(PremiumListDao.getPremiumPrice("foo", "richer"))
.hasValue(Money.parse("USD 5000"));
Truth8.assertThat(PremiumListDualDao.getPremiumPrice("poor", registry))
Truth8.assertThat(PremiumListDao.getPremiumPrice("foo", "poor"))
.hasValue(Money.parse("USD 0.99"));
Truth8.assertThat(PremiumListDualDao.getPremiumPrice("diamond", registry)).isEmpty();
Truth8.assertThat(PremiumListDao.getPremiumPrice("foo", "diamond")).isEmpty();
jpaTm()
.transact(
() -> {
PremiumList persistedList = PremiumListSqlDao.getLatestRevision("foo").get();
PremiumList persistedList = PremiumListDao.getLatestRevision("foo").get();
assertThat(persistedList.getLabelsToPrices())
.containsEntry("rich", new BigDecimal("75.00"));
assertThat(persistedList.getLabelsToPrices())