1
0
mirror of https://github.com/google/nomulus synced 2026-07-08 09:06:58 +00:00

Compare commits

...

4 Commits

Author SHA1 Message Date
sarahcaseybot abc1a0ef3d Add java changes for createBillingCostTransitions (#2314)
* Add java changes for createBillingCostTransitions

* Add negative cost test

* Remove default value

* remove unused variable

* Add check that create cost and trnasitions map are the same

* inject clock, only use key set when checking for missing fields

* Add test for removing map
2024-02-09 17:08:51 +00:00
Weimin Yu 7b47ecb1f1 Add REGISTER_BSA allocation type (#2319)
* Add ALLOW_BSA allocation type

Add a new type to allow creation of domains blocked by BSA.
Except for the BSA semantics, the new type behaves exactly
like SINGLE_USE.

* Addressing reviews

* Addressing review
2024-02-08 21:45:13 +00:00
Ben McIlwain 469d62703a Fix the test class name for UpdateRecurrenceCommand (#2320)
It looks like the command was renamed at some point to be shorter but then the test class itself was forgotten.
2024-02-08 19:34:18 +00:00
Lai Jiang 009fda67b7 Do not retry transactions inside Beam (#2318) 2024-02-05 18:40:56 +00:00
30 changed files with 654 additions and 42 deletions
@@ -209,10 +209,18 @@ public final class RegistryJpaIO {
@ProcessElement
public void processElement(OutputReceiver<T> outputReceiver) {
tm().transact(
// Note the use of no-retry transaction here. The results from the query are streamed to the
// output receiver inside the transaction, which cannot be rolled back in case of a retry,
// which in turn results in duplicate elements. If we try to pass the results to the output
// receiver outside the transaction, they have to be materialized into a list containing all
// the elements (without resorting to manual pagination) and greatly decrease the
// parallelism of the pipeline.
tm().transactNoRetry(
() -> {
query.stream().map(resultMapper::apply).forEach(outputReceiver::output);
});
return null;
},
null);
}
}
}
@@ -26,6 +26,7 @@ import static google.registry.flows.domain.DomainFlowUtils.checkHasBillingAccoun
import static google.registry.flows.domain.DomainFlowUtils.getReservationTypes;
import static google.registry.flows.domain.DomainFlowUtils.handleFeeRequest;
import static google.registry.flows.domain.DomainFlowUtils.isAnchorTenant;
import static google.registry.flows.domain.DomainFlowUtils.isRegisterBsaCreate;
import static google.registry.flows.domain.DomainFlowUtils.isReserved;
import static google.registry.flows.domain.DomainFlowUtils.isValidReservedCreate;
import static google.registry.flows.domain.DomainFlowUtils.validateDomainName;
@@ -269,13 +270,13 @@ public final class DomainCheckFlow implements TransactionalFlow {
if (tokenResult.isPresent()) {
return tokenResult;
}
if (bsaBlockedDomains.contains(domainName)) {
// TODO(weiminyu): extract to a constant for here and CheckApiAction.
// Excerpt from BSA's custom message. Max len 32 chars by EPP XML schema.
return Optional.of("Blocked by a GlobalBlock service");
} else {
if (isRegisterBsaCreate(domainName, allocationToken)
|| !bsaBlockedDomains.contains(domainName)) {
return Optional.empty();
}
// TODO(weiminyu): extract to a constant for here and CheckApiAction.
// Excerpt from BSA's custom message. Max len 32 chars by EPP XML schema.
return Optional.of("Blocked by a GlobalBlock service");
}
/** Handle the fee check extension. */
@@ -330,7 +330,7 @@ public final class DomainCreateFlow implements MutatingFlow {
.verifySignedMarks(launchCreate.get().getSignedMarks(), domainLabel, now)
.getId();
}
verifyNotBlockedByBsa(domainLabel, tld, now);
verifyNotBlockedByBsa(domainName, tld, now, allocationToken);
flowCustomLogic.afterValidation(
DomainCreateFlowCustomLogic.AfterValidationParameters.newBuilder()
.setDomainName(domainName)
@@ -421,8 +421,7 @@ public final class DomainCreateFlow implements MutatingFlow {
createNameCollisionOneTimePollMessage(targetId, domainHistory, registrarId, now));
}
entitiesToSave.add(domain, domainHistory);
if (allocationToken.isPresent()
&& TokenType.SINGLE_USE.equals(allocationToken.get().getTokenType())) {
if (allocationToken.isPresent() && allocationToken.get().getTokenType().isOneTimeUse()) {
entitiesToSave.add(
allocationTokenFlowUtils.redeemToken(
allocationToken.get(), domainHistory.getHistoryEntryId()));
@@ -27,6 +27,7 @@ import static com.google.common.collect.Sets.intersection;
import static com.google.common.collect.Sets.union;
import static google.registry.bsa.persistence.BsaLabelUtils.isLabelBlocked;
import static google.registry.model.domain.Domain.MAX_REGISTRATION_YEARS;
import static google.registry.model.domain.token.AllocationToken.TokenType.REGISTER_BSA;
import static google.registry.model.tld.Tld.TldState.GENERAL_AVAILABILITY;
import static google.registry.model.tld.Tld.TldState.PREDELEGATION;
import static google.registry.model.tld.Tld.TldState.QUIET_PERIOD;
@@ -265,9 +266,14 @@ public class DomainFlowUtils {
* Verifies that the {@code domainLabel} is not blocked by any BSA block label for the given
* {@code tld} at the specified time.
*/
public static void verifyNotBlockedByBsa(String domainLabel, Tld tld, DateTime now)
public static void verifyNotBlockedByBsa(
InternetDomainName domainName,
Tld tld,
DateTime now,
Optional<AllocationToken> allocationToken)
throws DomainLabelBlockedByBsaException {
if (isBlockedByBsa(domainLabel, tld, now)) {
if (!isRegisterBsaCreate(domainName, allocationToken)
&& isBlockedByBsa(domainName.parts().get(0), tld, now)) {
throw new DomainLabelBlockedByBsaException();
}
}
@@ -311,6 +317,15 @@ public class DomainFlowUtils {
&& token.get().getDomainName().get().equals(domainName.toString());
}
/** Returns whether a given domain create request may bypass the BSA block check. */
public static boolean isRegisterBsaCreate(
InternetDomainName domainName, Optional<AllocationToken> token) {
return token.isPresent()
&& token.get().getTokenType().equals(REGISTER_BSA)
&& token.get().getDomainName().isPresent()
&& token.get().getDomainName().get().equals(domainName.toString());
}
/** Check if the registrar running the flow has access to the TLD in question. */
public static void checkAllowedAccessToTld(String registrarId, String tld) throws EppException {
if (!Registrar.loadByRegistrarIdCached(registrarId).get().getAllowedTlds().contains(tld)) {
@@ -73,7 +73,6 @@ import google.registry.model.domain.fee.FeeTransformResponseExtension;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.TokenType;
import google.registry.model.domain.token.AllocationTokenExtension;
import google.registry.model.eppcommon.AuthInfo;
import google.registry.model.eppcommon.StatusValue;
@@ -258,8 +257,7 @@ public final class DomainRenewFlow implements MutatingFlow {
ImmutableSet.Builder<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
entitiesToSave.add(
newDomain, domainHistory, explicitRenewEvent, newAutorenewEvent, newAutorenewPollMessage);
if (allocationToken.isPresent()
&& TokenType.SINGLE_USE.equals(allocationToken.get().getTokenType())) {
if (allocationToken.isPresent() && allocationToken.get().getTokenType().isOneTimeUse()) {
entitiesToSave.add(
allocationTokenFlowUtils.redeemToken(
allocationToken.get(), domainHistory.getHistoryEntryId()));
@@ -36,7 +36,6 @@ import google.registry.model.domain.fee.FeeQueryCommandExtensionItem.CommandName
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.TokenBehavior;
import google.registry.model.domain.token.AllocationToken.TokenStatus;
import google.registry.model.domain.token.AllocationToken.TokenType;
import google.registry.model.domain.token.AllocationTokenExtension;
import google.registry.model.reporting.HistoryEntry.HistoryEntryId;
import google.registry.model.tld.Tld;
@@ -105,8 +104,7 @@ public class AllocationTokenFlowUtils {
/** Redeems a SINGLE_USE {@link AllocationToken}, returning the redeemed copy. */
public AllocationToken redeemToken(AllocationToken token, HistoryEntryId redemptionHistoryId) {
checkArgument(
TokenType.SINGLE_USE.equals(token.getTokenType()),
"Only SINGLE_USE tokens can be marked as redeemed");
token.getTokenType().isOneTimeUse(), "Only SINGLE_USE tokens can be marked as redeemed");
return token.asBuilder().setRedemptionHistoryId(redemptionHistoryId).build();
}
@@ -22,6 +22,7 @@ import static google.registry.model.domain.token.AllocationToken.TokenStatus.CAN
import static google.registry.model.domain.token.AllocationToken.TokenStatus.ENDED;
import static google.registry.model.domain.token.AllocationToken.TokenStatus.NOT_STARTED;
import static google.registry.model.domain.token.AllocationToken.TokenStatus.VALID;
import static google.registry.model.domain.token.AllocationToken.TokenType.REGISTER_BSA;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.CollectionUtils.forceEmptyToNull;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
@@ -120,18 +121,37 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
/** Type of the token that indicates how and where it should be used. */
public enum TokenType {
/** Token used for bulk pricing */
BULK_PRICING,
BULK_PRICING(/* isOneTimeUse= */ false),
/** Token saved on a TLD to use if no other token is passed from the client */
DEFAULT_PROMO,
DEFAULT_PROMO(/* isOneTimeUse= */ false),
/** This is the old name for what is now BULK_PRICING. */
// TODO(sarahbot@): Remove this type once all tokens of this type have been scrubbed from the
// database
@Deprecated
PACKAGE,
PACKAGE(/* isOneTimeUse= */ false),
/** Invalid after use */
SINGLE_USE,
SINGLE_USE(/* isOneTimeUse= */ true),
/** Do not expire after use */
UNLIMITED_USE,
UNLIMITED_USE(/* isOneTimeUse= */ false),
/**
* Allows bypassing the BSA check during domain creation, otherwise has the same semantics as
* {@link #SINGLE_USE}.
*
* <p>This token applies to a single domain only. If the domain is not blocked by BSA at the
* redemption time this token is processed like {@code SINGLE_USE}, as mentioned above.
*/
REGISTER_BSA(/* isOneTimeUse= */ true);
private final boolean isOneTimeUse;
private TokenType(boolean isOneTimeUse) {
this.isOneTimeUse = isOneTimeUse;
}
/** Returns true if token should be invalidated after use. */
public boolean isOneTimeUse() {
return this.isOneTimeUse;
}
}
/**
@@ -361,12 +381,11 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
|| !getInstance().discountPremiums,
"Bulk tokens cannot discount premium names");
checkArgument(
getInstance().domainName == null || TokenType.SINGLE_USE.equals(getInstance().tokenType),
"Domain name can only be specified for SINGLE_USE tokens");
getInstance().domainName == null || getInstance().tokenType.isOneTimeUse(),
"Domain name can only be specified for SINGLE_USE or REGISTER_BSA tokens");
checkArgument(
getInstance().redemptionHistoryId == null
|| TokenType.SINGLE_USE.equals(getInstance().tokenType),
"Redemption history entry can only be specified for SINGLE_USE tokens");
getInstance().redemptionHistoryId == null || getInstance().tokenType.isOneTimeUse(),
"Redemption history entry can only be specified for SINGLE_USE or REGISTER_BSA tokens");
checkArgument(
getInstance().tokenType != TokenType.BULK_PRICING
|| (getInstance().allowedClientIds != null
@@ -378,6 +397,10 @@ public class AllocationToken extends UpdateAutoTimestampEntity implements Builda
checkArgument(
getInstance().discountFraction > 0 || getInstance().discountYears == 1,
"Discount years can only be specified along with a discount fraction");
if (getInstance().getTokenType().equals(REGISTER_BSA)) {
checkArgumentNotNull(
getInstance().domainName, "REGISTER_BSA tokens must be tied to a domain");
}
if (getInstance().registrationBehavior.equals(RegistrationBehavior.ANCHOR_TENANT)) {
checkArgumentNotNull(
getInstance().domainName, "ANCHOR_TENANT tokens must be tied to a domain");
@@ -74,6 +74,7 @@ import google.registry.tldconfig.idn.IdnTableEnum;
import google.registry.util.Idn;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
@@ -458,6 +459,8 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
@JsonDeserialize(using = CurrencyDeserializer.class)
CurrencyUnit currency = DEFAULT_CURRENCY;
// TODO(sarahbot@): Remove this field and make createBillingCostTransitions not-null once all TLDs
// are populated with a create cost transition map
/** The per-year billing cost for registering a new domain name. */
@Type(type = JodaMoneyType.TYPE_NAME)
@Columns(
@@ -467,6 +470,12 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
})
Money createBillingCost = DEFAULT_CREATE_BILLING_COST;
// TODO(sarahbot@): Make this field not null and add a default value once field is populated on
// all existing TLDs
/** A property that transitions to different create billing costs at different times. */
@JsonDeserialize(using = TimedTransitionPropertyMoneyDeserializer.class)
TimedTransitionProperty<Money> createBillingCostTransitions;
/** The one-time billing cost for restoring a domain name from the redemption grace period. */
@Type(type = JodaMoneyType.TYPE_NAME)
@Columns(
@@ -676,6 +685,13 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
return createBillingCost;
}
public ImmutableSortedMap<DateTime, Money> getCreateBillingCostTransitions() {
return Objects.requireNonNullElseGet(
createBillingCostTransitions,
() -> TimedTransitionProperty.withInitialValue(getCreateBillingCost()))
.toValueMap();
}
/**
* Returns the add-on cost of a domain restore (the flat tld-wide fee charged in addition to one
* year of renewal for that name).
@@ -959,6 +975,17 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
return this;
}
public Builder setCreateBillingCostTransitions(
ImmutableSortedMap<DateTime, Money> createCostsMap) {
checkArgumentNotNull(createCostsMap, "Create billing costs map cannot be null");
checkArgument(
createCostsMap.values().stream().allMatch(Money::isPositiveOrZero),
"Create billing cost cannot be negative");
getInstance().createBillingCostTransitions =
TimedTransitionProperty.fromValueMap(createCostsMap);
return this;
}
public Builder setReservedListsByName(Set<String> reservedListNames) {
// TODO(b/309175133): forbid if enrolled with BSA
checkArgument(reservedListNames != null, "reservedListNames must not be null");
@@ -1131,6 +1158,10 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl
// here to catch cases where we loaded an invalid TimedTransitionProperty from the database
// and cloned it into a new builder, to block re-building a Tld in an invalid state.
instance.tldStateTransitions.checkValidity();
// TODO(sarahbot@): Remove null check when createBillingCostTransitions field is made not-null
if (instance.createBillingCostTransitions != null) {
instance.createBillingCostTransitions.checkValidity();
}
instance.renewBillingCostTransitions.checkValidity();
instance.eapFeeSchedule.checkValidity();
// All costs must be in the expected currency.
@@ -203,6 +203,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
return transact(work, null);
}
@Override
public <T> T transactNoRetry(
Callable<T> work, @Nullable TransactionIsolationLevel isolationLevel) {
if (inTransaction()) {
@@ -63,6 +63,18 @@ public interface TransactionManager {
*/
<T> T transact(Callable<T> work, TransactionIsolationLevel isolationLevel);
/**
* Executes the work in a transaction at the given {@link TransactionIsolationLevel} and returns
* the result, without retrying upon retryable exceptions.
*
* <p>This method should only be used when the transaction contains side effects that are not
* rolled back by the transaction manager, for example in {@link
* google.registry.beam.common.RegistryJpaIO} where the results from a query are streamed to the
* next transformation inside a transaction, as the result stream has to materialize to a list
* outside a transaction and doing so would greatly affect the parallelism of the pipeline.
*/
<T> T transactNoRetry(Callable<T> work, TransactionIsolationLevel isolationLevel);
/**
* Executes the work in a (potentially wrapped) transaction and returns the result.
*
@@ -14,6 +14,7 @@
package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.model.tld.Tld.Builder.ROID_SUFFIX_PATTERN;
import static google.registry.model.tld.Tlds.getTlds;
import static google.registry.util.ListNamingUtils.convertFilePathToName;
@@ -28,10 +29,12 @@ import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import com.google.common.flogger.FluentLogger;
import google.registry.model.common.TimedTransitionProperty;
import google.registry.model.tld.Tld;
import google.registry.model.tld.label.PremiumList;
import google.registry.model.tld.label.PremiumListDao;
import google.registry.tools.params.PathParameter;
import google.registry.util.Clock;
import google.registry.util.Idn;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
@@ -40,9 +43,11 @@ import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.inject.Inject;
import javax.inject.Named;
import org.joda.money.CurrencyUnit;
@@ -90,6 +95,8 @@ public class ConfigureTldCommand extends MutatingCommand {
@Inject ObjectMapper mapper;
@Inject Clock clock;
@Inject
@Named("dnsWriterNames")
Set<String> validDnsWriterNames;
@@ -114,8 +121,11 @@ public class ConfigureTldCommand extends MutatingCommand {
String name = convertFilePathToName(inputFile);
Map<String, Object> tldData = new Yaml().load(Files.newBufferedReader(inputFile));
checkName(name, tldData);
checkForMissingFields(tldData);
Tld oldTld = getTlds().contains(name) ? Tld.get(name) : null;
checkForMissingFields(
Stream.concat(tldData.keySet().stream(), Stream.of("createBillingCostTransitions"))
.collect(toImmutableSet()));
Tld newTld = mapper.readValue(inputFile.toFile(), Tld.class);
if (oldTld != null) {
oldTldInBreakGlass = oldTld.getBreakglassMode();
@@ -146,6 +156,14 @@ public class ConfigureTldCommand extends MutatingCommand {
checkPremiumList(newTld);
checkDnsWriters(newTld);
checkCurrency(newTld);
// TODO(sarahbot@): Remove this once the createBillingCost field is removed
checkArgument(
Objects.equals(
TimedTransitionProperty.fromValueMap(newTld.getCreateBillingCostTransitions())
.getValueAtTime(clock.nowUtc()),
newTld.getCreateBillingCost()),
"The createBillingCostTransitions map must have the same current cost as the"
+ " createBillingCost field");
// bsaEnrollStartTime only exists in DB. Need to carry it over to the updated copy. See Tld.java
// for more information.
Optional<DateTime> bsaEnrollTime =
@@ -198,7 +216,7 @@ public class ConfigureTldCommand extends MutatingCommand {
ROID_SUFFIX_PATTERN.pattern());
}
private void checkForMissingFields(Map<String, Object> tldData) {
private void checkForMissingFields(ImmutableSet<String> keySet) {
Set<String> tldFields =
Arrays.stream(Tld.class.getDeclaredFields())
.filter(field -> !Modifier.isStatic(field.getModifiers()))
@@ -207,7 +225,7 @@ public class ConfigureTldCommand extends MutatingCommand {
.collect(Collectors.toSet());
Set<String> missingFields = new HashSet<>();
for (String field : tldFields) {
if (!tldData.containsKey(field)) {
if (!keySet.contains(field)) {
missingFields.add(field);
}
}
@@ -18,7 +18,6 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Iterables.partition;
import static com.google.common.collect.Streams.stream;
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.beust.jcommander.Parameter;
@@ -78,7 +77,7 @@ final class DeleteAllocationTokensCommand extends UpdateOrDeleteAllocationTokens
ImmutableSet<VKey<AllocationToken>> tokensToDelete =
tm().loadByKeys(batch).values().stream()
.filter(t -> withDomains || !t.getDomainName().isPresent())
.filter(t -> SINGLE_USE.equals(t.getTokenType()))
.filter(t -> t.getTokenType().isOneTimeUse())
.filter(t -> !t.isRedeemed())
.map(AllocationToken::createVKey)
.collect(toImmutableSet());
@@ -66,8 +66,6 @@ public class UploadBsaUnavailableDomainsActionTest {
private final GcsUtils gcsUtils = new GcsUtils(LocalStorageHelper.getOptions());
private final FakeResponse response = new FakeResponse();
@BeforeEach
@@ -19,6 +19,7 @@ import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.DEF
import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.NONPREMIUM;
import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.SPECIFIED;
import static google.registry.model.domain.token.AllocationToken.TokenType.DEFAULT_PROMO;
import static google.registry.model.domain.token.AllocationToken.TokenType.REGISTER_BSA;
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.model.eppoutput.CheckData.DomainCheck.create;
@@ -189,6 +190,40 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
create(true, "example3.tld", null));
}
@Test
void testSuccess_bsaBlocked_createAllowedWithToken() throws Exception {
persistBsaLabel("example1");
setEppInput("domain_check_allocationtoken.xml");
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(REGISTER_BSA)
.setDomainName("example1.tld")
.build());
doCheckTest(
create(true, "example1.tld", null),
create(false, "example2.tld", "Alloc token invalid for domain"),
create(false, "reserved.tld", "Reserved"),
create(false, "specificuse.tld", "Reserved; alloc. token required"));
}
@Test
void testSuccess_bsaBlocked_withIrrelevantTokenType() throws Exception {
persistBsaLabel("example1");
setEppInput("domain_check_allocationtoken.xml");
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setDomainName("example1.tld")
.build());
doCheckTest(
create(false, "example1.tld", "Blocked by a GlobalBlock service"),
create(false, "example2.tld", "Alloc token invalid for domain"),
create(false, "reserved.tld", "Reserved"),
create(false, "specificuse.tld", "Reserved; alloc. token required"));
}
@Test
void testSuccess_clTridNotSpecified() throws Exception {
setEppInput("domain_check_no_cltrid.xml");
@@ -29,6 +29,7 @@ import static google.registry.model.billing.BillingBase.RenewalPriceBehavior.SPE
import static google.registry.model.domain.fee.Fee.FEE_EXTENSION_URIS;
import static google.registry.model.domain.token.AllocationToken.TokenType.BULK_PRICING;
import static google.registry.model.domain.token.AllocationToken.TokenType.DEFAULT_PROMO;
import static google.registry.model.domain.token.AllocationToken.TokenType.REGISTER_BSA;
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.model.eppcommon.EppXmlTransformer.marshal;
@@ -255,6 +256,14 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
persistClaimsList(ImmutableMap.of("example-one", CLAIMS_KEY, "test-validate", CLAIMS_KEY));
}
private void enrollTldInBsa() {
persistResource(
Tld.get("tld")
.asBuilder()
.setBsaEnrollStartTime(Optional.of(clock.nowUtc().minusSeconds(1)))
.build());
}
/**
* Create host and contact entries for testing.
*
@@ -2593,12 +2602,92 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
}
@Test
void testFailure_blockedByBsa() throws Exception {
void testSuccess_blockedByBsa_hasRegisterBsaToken() throws Exception {
enrollTldInBsa();
allocationToken =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(REGISTER_BSA)
.setDomainName("example.tld")
.build());
persistBsaLabel("example");
persistContactsAndHosts();
setEppInput(
"domain_create_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
runFlow();
assertSuccessfulCreate("tld", ImmutableSet.of(), allocationToken);
}
@Test
void testSuccess_blockedByBsa_reservedDomain_viaAllocationTokenExtension() throws Exception {
enrollTldInBsa();
allocationToken =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(REGISTER_BSA)
.setDomainName("resdom.tld")
.build());
persistBsaLabel("resdom");
setEppInput(
"domain_create_allocationtoken.xml", ImmutableMap.of("DOMAIN", "resdom.tld", "YEARS", "2"));
persistContactsAndHosts();
runFlowAssertResponse(
loadFile("domain_create_response.xml", ImmutableMap.of("DOMAIN", "resdom.tld")));
assertSuccessfulCreate("tld", ImmutableSet.of(RESERVED), allocationToken);
assertNoLordn();
assertAllocationTokenWasRedeemed("abc123");
}
@Test
void testSuccess_blockedByBsa_quietPeriod_skipTldStateCheckWithToken() throws Exception {
enrollTldInBsa();
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(REGISTER_BSA)
.setRegistrationBehavior(RegistrationBehavior.BYPASS_TLD_STATE)
.setDomainName("example.tld")
.build());
persistContactsAndHosts();
persistBsaLabel("example");
setEppInput(
"domain_create_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
persistResource(
Tld.get("tld")
.asBuilder()
.setBsaEnrollStartTime(Optional.of(clock.nowUtc().minusSeconds(1)))
.setTldStateTransitions(ImmutableSortedMap.of(START_OF_TIME, QUIET_PERIOD))
.build());
runFlow();
assertSuccessfulCreate("tld", ImmutableSet.of(), token);
}
@Test
void testSuccess_blockedByBsa_anchorTenant() throws Exception {
enrollTldInBsa();
allocationToken =
persistResource(
new AllocationToken.Builder()
.setToken("abcDEF23456")
.setTokenType(REGISTER_BSA)
.setDomainName("anchor.tld")
.build());
setEppInput("domain_create_anchor_allocationtoken.xml");
persistContactsAndHosts();
persistBsaLabel("anchor");
runFlowAssertResponse(loadFile("domain_create_anchor_response.xml"));
assertSuccessfulCreate("tld", ImmutableSet.of(ANCHOR_TENANT), allocationToken);
assertNoLordn();
assertAllocationTokenWasRedeemed("abcDEF23456");
}
@Test
void testFailure_blockedByBsa() throws Exception {
enrollTldInBsa();
persistBsaLabel("example");
persistContactsAndHosts();
EppException thrown = assertThrows(DomainLabelBlockedByBsaException.class, this::runFlow);
@@ -2619,6 +2708,41 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
.isEqualTo(loadFile("domain_create_blocked_by_bsa.xml"));
}
@Test
void testFailure_blockedByBsa_hasWrongToken() throws Exception {
enrollTldInBsa();
allocationToken =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setRegistrationBehavior(RegistrationBehavior.BYPASS_TLD_STATE)
.setDomainName("example.tld")
.build());
persistBsaLabel("example");
persistContactsAndHosts();
setEppInput(
"domain_create_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
EppException thrown = assertThrows(DomainLabelBlockedByBsaException.class, this::runFlow);
assertAboutEppExceptions()
.that(thrown)
.marshalsToXml()
.and()
.hasMessage("Domain label is blocked by the Brand Safety Alliance");
byte[] responseXmlBytes =
marshal(
EppOutput.create(
new EppResponse.Builder()
.setTrid(Trid.create(null, "server-trid"))
.setResult(thrown.getResult())
.build()),
ValidationMode.STRICT);
assertThat(new String(responseXmlBytes, StandardCharsets.UTF_8))
.isEqualTo(loadFile("domain_create_blocked_by_bsa.xml"));
}
@Test
void testFailure_uppercase() {
doFailingDomainNameTest("Example.tld", BadDomainNameCharacterException.class);
@@ -21,6 +21,7 @@ import static google.registry.model.domain.token.AllocationToken.TokenStatus.END
import static google.registry.model.domain.token.AllocationToken.TokenStatus.NOT_STARTED;
import static google.registry.model.domain.token.AllocationToken.TokenStatus.VALID;
import static google.registry.model.domain.token.AllocationToken.TokenType.BULK_PRICING;
import static google.registry.model.domain.token.AllocationToken.TokenType.REGISTER_BSA;
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.testing.DatabaseHelper.createTld;
@@ -245,6 +246,18 @@ public class AllocationTokenTest extends EntityTestCase {
.isEqualTo("Bulk tokens may only be valid for CREATE actions");
}
@Test
void testBuild_registerBsa_missingDomain() {
createTld("tld");
// REGISTER_BSA requires a domain
AllocationToken.Builder token =
new AllocationToken.Builder().setToken("abc").setTokenType(REGISTER_BSA);
assertThat(assertThrows(IllegalArgumentException.class, () -> token.build()))
.hasMessageThat()
.isEqualTo("REGISTER_BSA tokens must be tied to a domain");
token.setDomainName("example.tld").build();
}
@Test
void testFail_bulkTokenNullEppActions() {
AllocationToken.Builder builder =
@@ -317,7 +330,7 @@ public class AllocationTokenTest extends EntityTestCase {
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, builder::build);
assertThat(thrown)
.hasMessageThat()
.isEqualTo("Domain name can only be specified for SINGLE_USE tokens");
.isEqualTo("Domain name can only be specified for SINGLE_USE or REGISTER_BSA tokens");
}
@Test
@@ -347,7 +360,8 @@ public class AllocationTokenTest extends EntityTestCase {
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, builder::build);
assertThat(thrown)
.hasMessageThat()
.isEqualTo("Redemption history entry can only be specified for SINGLE_USE tokens");
.isEqualTo(
"Redemption history entry can only be specified for SINGLE_USE or REGISTER_BSA tokens");
}
@Test
@@ -46,6 +46,7 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.dns.writer.VoidDnsWriter;
import google.registry.model.EntityTestCase;
import google.registry.model.common.TimedTransitionProperty;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.tld.Tld.TldNotFoundException;
import google.registry.model.tld.Tld.TldState;
@@ -149,6 +150,8 @@ public final class TldTest extends EntityTestCase {
.asBuilder()
.setDnsAPlusAaaaTtl(Duration.standardHours(1))
.setDnsWriters(ImmutableSet.of("baz", "bang"))
.setCreateBillingCostTransitions(
TimedTransitionProperty.withInitialValue(Money.of(USD, 13)).toValueMap())
.setEapFeeSchedule(
ImmutableSortedMap.of(
START_OF_TIME,
@@ -170,7 +173,12 @@ public final class TldTest extends EntityTestCase {
@Test
void testSuccess_tldYamlRoundtrip() throws Exception {
Tld testTld = createTld("test");
Tld testTld =
createTld("test")
.asBuilder()
.setCreateBillingCostTransitions(
TimedTransitionProperty.withInitialValue(Money.of(USD, 8)).toValueMap())
.build();
ObjectMapper mapper = createObjectMapper();
String yaml = mapper.writeValueAsString(testTld);
Tld constructedTld = mapper.readValue(yaml, Tld.class);
@@ -224,6 +232,46 @@ public final class TldTest extends EntityTestCase {
assertThat(registry.getRestoreBillingCost()).isEqualTo(Money.of(USD, 17));
}
@Test
void testSetCreateBillingCostTransitions() {
ImmutableSortedMap<DateTime, Money> createCostTransitions =
ImmutableSortedMap.of(
START_OF_TIME,
Money.of(USD, 8),
fakeClock.nowUtc(),
Money.of(USD, 1),
fakeClock.nowUtc().plusMonths(1),
Money.of(USD, 2),
fakeClock.nowUtc().plusMonths(2),
Money.of(USD, 3));
Tld registry =
Tld.get("tld").asBuilder().setCreateBillingCostTransitions(createCostTransitions).build();
assertThat(registry.getCreateBillingCostTransitions()).isEqualTo(createCostTransitions);
}
@Test
void testSetCreateBillingCostTransitionsNegativeCost() throws Exception {
ImmutableSortedMap<DateTime, Money> createCostTransitions =
ImmutableSortedMap.of(
START_OF_TIME,
Money.of(USD, 8),
fakeClock.nowUtc(),
Money.of(USD, 1),
fakeClock.nowUtc().plusMonths(1),
Money.of(USD, -2),
fakeClock.nowUtc().plusMonths(2),
Money.of(USD, 3));
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
Tld.get("tld")
.asBuilder()
.setCreateBillingCostTransitions(createCostTransitions)
.build());
assertThat(thrown.getMessage()).isEqualTo("Create billing cost cannot be negative");
}
@Test
void testSettingRestoreBillingCost() {
Tld registry = Tld.get("tld").asBuilder().setRestoreBillingCost(Money.of(USD, 42)).build();
@@ -25,6 +25,7 @@ import static google.registry.testing.TestDataHelper.loadFile;
import static google.registry.tldconfig.idn.IdnTableEnum.EXTENDED_LATIN;
import static google.registry.tldconfig.idn.IdnTableEnum.JA;
import static google.registry.tldconfig.idn.IdnTableEnum.UNCONFUSABLE_LATIN;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.logging.Level.INFO;
import static org.joda.money.CurrencyUnit.JPY;
@@ -39,6 +40,7 @@ import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.io.Files;
import com.google.common.testing.TestLogHandler;
import google.registry.model.domain.token.AllocationToken;
@@ -70,6 +72,7 @@ public class ConfigureTldCommandTest extends CommandTestCase<ConfigureTldCommand
command.mapper = objectMapper;
premiumList = persistPremiumList("test", USD, "silver,USD 50", "gold,USD 80");
command.validDnsWriterNames = ImmutableSet.of("VoidDnsWriter", "FooDnsWriter");
command.clock = fakeClock;
logger.addHandler(logHandler);
}
@@ -173,6 +176,57 @@ public class ConfigureTldCommandTest extends CommandTestCase<ConfigureTldCommand
assertThat(updatedTld.getCreateBillingCost()).isEqualTo(Money.of(USD, 25));
}
@Test
void testSuccess_fileMissingCreateBillingCostTransitions() throws Exception {
createTld("nocreatecostmap");
File tldFile = tmpDir.resolve("nocreatecostmap.yaml").toFile();
Files.asCharSink(tldFile, UTF_8).write(loadFile(getClass(), "nocreatecostmap.yaml"));
runCommandForced("--input=" + tldFile);
Tld updatedTld = Tld.get("nocreatecostmap");
assertThat(updatedTld.getCreateBillingCostTransitions())
.isEqualTo(ImmutableSortedMap.of(START_OF_TIME, Money.of(USD, 25)));
}
@Test
void testSuccess_fileMissingCreateBillingCostTransitionsRevertsToBasicConstructedMap()
throws Exception {
ImmutableSortedMap<DateTime, Money> createCostTransitions =
ImmutableSortedMap.of(
START_OF_TIME,
Money.of(USD, 8),
fakeClock.nowUtc(),
Money.of(USD, 1),
fakeClock.nowUtc().plusMonths(1),
Money.of(USD, 2),
fakeClock.nowUtc().plusMonths(2),
Money.of(USD, 3));
Tld tld =
createTld("nocreatecostmap")
.asBuilder()
.setCreateBillingCostTransitions(createCostTransitions)
.build();
assertThat(tld.getCreateBillingCostTransitions().size()).isEqualTo(4);
File tldFile = tmpDir.resolve("nocreatecostmap.yaml").toFile();
Files.asCharSink(tldFile, UTF_8).write(loadFile(getClass(), "nocreatecostmap.yaml"));
runCommandForced("--input=" + tldFile);
Tld updatedTld = Tld.get("nocreatecostmap");
assertThat(updatedTld.getCreateBillingCostTransitions())
.isEqualTo(ImmutableSortedMap.of(START_OF_TIME, Money.of(USD, 25)));
}
@Test
void testFailure_billingCostTransitionsDoesNotMatchCreateCost() throws Exception {
createTld("diffcostmap");
File tldFile = tmpDir.resolve("diffcostmap.yaml").toFile();
Files.asCharSink(tldFile, UTF_8).write(loadFile(getClass(), "diffcostmap.yaml"));
IllegalArgumentException thrown =
assertThrows(IllegalArgumentException.class, () -> runCommandForced("--input=" + tldFile));
assertThat(thrown.getMessage())
.isEqualTo(
"The createBillingCostTransitions map must have the same current cost as the"
+ " createBillingCost field");
}
@Test
void testFailure_fileMissingNullableFieldsOnCreate() throws Exception {
File tldFile = tmpDir.resolve("missingnullablefields.yaml").toFile();
@@ -185,6 +239,20 @@ public class ConfigureTldCommandTest extends CommandTestCase<ConfigureTldCommand
+ " premiumListName, currency, numDnsPublishLocks]");
}
@Test
void testSuccess_addCreateBillingCostTransitions() throws Exception {
createTld("costmap");
File tldFile = tmpDir.resolve("costmap.yaml").toFile();
Files.asCharSink(tldFile, UTF_8).write(loadFile(getClass(), "costmap.yaml"));
runCommandForced("--input=" + tldFile);
Tld updatedTld = Tld.get("costmap");
ImmutableSortedMap<DateTime, Money> costTransitions =
updatedTld.getCreateBillingCostTransitions();
assertThat(costTransitions.size()).isEqualTo(3);
assertThat(costTransitions.get(START_OF_TIME)).isEqualTo(Money.of(USD, 13));
assertThat(costTransitions.get(START_OF_TIME.plusYears(26))).isEqualTo(Money.of(USD, 14));
}
@Test
void testFailure_fileMissingNullableFieldOnUpdate() throws Exception {
Tld tld = createTld("missingnullablefields");
@@ -380,7 +380,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
.hasMessageThat()
.isEqualTo(
"Invalid value for -t parameter. Allowed values:[BULK_PRICING, DEFAULT_PROMO, PACKAGE,"
+ " SINGLE_USE, UNLIMITED_USE]");
+ " SINGLE_USE, UNLIMITED_USE, REGISTER_BSA]");
}
@Test
@@ -19,14 +19,17 @@ import static google.registry.testing.DatabaseHelper.createTlds;
import static google.registry.testing.DatabaseHelper.persistPremiumList;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.TestDataHelper.loadFile;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.money.CurrencyUnit.USD;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.beust.jcommander.ParameterException;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.model.EntityYamlUtils;
import google.registry.model.tld.Tld;
import google.registry.model.tld.label.PremiumList;
import org.joda.money.Money;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -48,6 +51,12 @@ class GetTldCommandTest extends CommandTestCase<GetTldCommand> {
.setDnsAPlusAaaaTtl(Duration.standardMinutes(15))
.setDriveFolderId("driveFolder")
.setCreateBillingCost(Money.of(USD, 25))
.setCreateBillingCostTransitions(
ImmutableSortedMap.of(
START_OF_TIME,
Money.of(USD, 8),
DateTime.parse("2020-01-01T00:00:00Z"),
Money.of(USD, 25)))
.setPremiumList(premiumList)
.build());
runCommand("tld");
@@ -42,7 +42,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** Tests for {@link UpdateRecurrenceCommand}. */
public class UpdateBillingRecurrenceCommandTest extends CommandTestCase<UpdateRecurrenceCommand> {
public class UpdateRecurrenceCommandTest extends CommandTestCase<UpdateRecurrenceCommand> {
@BeforeEach
void beforeEach() {
@@ -9,6 +9,10 @@ claimsPeriodEnd: "294247-01-10T04:00:54.775Z"
createBillingCost:
currency: "USD"
amount: 13.00
createBillingCostTransitions:
"1970-01-01T00:00:00.000Z":
currency: "USD"
amount: 13.00
creationTime: "1970-01-01T00:00:00.000Z"
currency: "USD"
defaultPromoTokens:
@@ -0,0 +1,75 @@
addGracePeriodLength: "PT432000S"
allowedFullyQualifiedHostNames:
- "foo"
allowedRegistrantContactIds: []
anchorTenantAddGracePeriodLength: "PT2592000S"
autoRenewGracePeriodLength: "PT3888000S"
automaticTransferLength: "PT432000S"
claimsPeriodEnd: "294247-01-10T04:00:54.775Z"
createBillingCost:
currency: "USD"
amount: 14.00
createBillingCostTransitions:
"1970-01-01T00:00:00.000Z":
currency: "USD"
amount: 13.00
"1996-01-01T00:00:00.000Z":
currency: "USD"
amount: 14.00
"2050-01-01T00:00:00.000Z":
currency: "USD"
amount: 15.00
creationTime: "1970-01-01T00:00:00.000Z"
currency: "USD"
defaultPromoTokens:
- "bbbbb"
dnsAPlusAaaaTtl: "PT3600S"
dnsDsTtl: null
dnsNsTtl: null
dnsPaused: false
dnsWriters:
- "VoidDnsWriter"
driveFolderId: null
eapFeeSchedule:
"1970-01-01T00:00:00.000Z":
currency: "USD"
amount: 0.00
"2000-06-01T00:00:00.000Z":
currency: "USD"
amount: 100.00
"2000-06-02T00:00:00.000Z":
currency: "USD"
amount: 0.00
escrowEnabled: false
idnTables:
- "EXTENDED_LATIN"
- "JA"
invoicingEnabled: false
lordnUsername: null
numDnsPublishLocks: 1
pendingDeleteLength: "PT432000S"
premiumListName: "test"
pricingEngineClassName: "google.registry.model.pricing.StaticPremiumListPricingEngine"
redemptionGracePeriodLength: "PT2592000S"
registryLockOrUnlockBillingCost:
currency: "USD"
amount: 0.00
renewBillingCostTransitions:
"1970-01-01T00:00:00.000Z":
currency: "USD"
amount: 11.00
renewGracePeriodLength: "PT432000S"
reservedListNames: []
restoreBillingCost:
currency: "USD"
amount: 17.00
roidSuffix: "COSTMAP"
serverStatusChangeBillingCost:
currency: "USD"
amount: 19.00
tldStateTransitions:
"1970-01-01T00:00:00.000Z": "GENERAL_AVAILABILITY"
tldStr: "costmap"
tldType: "REAL"
tldUnicode: "costmap"
transferGracePeriodLength: "PT432000S"
@@ -0,0 +1,59 @@
addGracePeriodLength: "PT432000S"
allowedFullyQualifiedHostNames: []
allowedRegistrantContactIds: []
anchorTenantAddGracePeriodLength: "PT2592000S"
autoRenewGracePeriodLength: "PT3888000S"
automaticTransferLength: "PT432000S"
claimsPeriodEnd: "294247-01-10T04:00:54.775Z"
createBillingCost:
currency: "USD"
amount: 25.00
createBillingCostTransitions:
"1970-01-01T00:00:00.000Z":
currency: "USD"
amount: 12.00
creationTime: "2022-09-01T00:00:00.000Z"
currency: "USD"
defaultPromoTokens: []
dnsAPlusAaaaTtl: "PT900S"
dnsDsTtl: null
dnsNsTtl: null
dnsPaused: false
dnsWriters:
- "VoidDnsWriter"
driveFolderId: "driveFolder"
eapFeeSchedule:
"1970-01-01T00:00:00.000Z":
currency: "USD"
amount: 0.00
escrowEnabled: false
idnTables: []
invoicingEnabled: false
lordnUsername: null
numDnsPublishLocks: 1
pendingDeleteLength: "PT432000S"
premiumListName: "test"
pricingEngineClassName: "google.registry.model.pricing.StaticPremiumListPricingEngine"
redemptionGracePeriodLength: "PT2592000S"
registryLockOrUnlockBillingCost:
currency: "USD"
amount: 0.00
renewBillingCostTransitions:
"1970-01-01T00:00:00.000Z":
currency: "USD"
amount: 11.00
renewGracePeriodLength: "PT432000S"
reservedListNames: []
restoreBillingCost:
currency: "USD"
amount: 17.00
roidSuffix: "DIFFMAP"
serverStatusChangeBillingCost:
currency: "USD"
amount: 19.00
tldStateTransitions:
"1970-01-01T00:00:00.000Z": "GENERAL_AVAILABILITY"
tldStr: "diffcostmap"
tldType: "REAL"
tldUnicode: "diffcostmap"
transferGracePeriodLength: "PT432000S"
@@ -12,6 +12,10 @@ claimsPeriodEnd: "294247-01-10T04:00:54.775Z"
createBillingCost:
currency: "USD"
amount: 13.00
createBillingCostTransitions:
"1970-01-01T00:00:00.000Z":
currency: "USD"
amount: 13.00
creationTime: "2022-09-01T00:00:00.000Z"
currency: "USD"
defaultPromoTokens: []
@@ -8,6 +8,10 @@ claimsPeriodEnd: "294247-01-10T04:00:54.775Z"
createBillingCost:
currency: "JPY"
amount: 250
createBillingCostTransitions:
"1970-01-01T00:00:00.000Z":
currency: "JPY"
amount: 250
creationTime: "2022-09-01T00:00:00.000Z"
currency: "JPY"
defaultPromoTokens: []
@@ -0,0 +1,55 @@
addGracePeriodLength: "PT432000S"
allowedFullyQualifiedHostNames: []
allowedRegistrantContactIds: []
anchorTenantAddGracePeriodLength: "PT2592000S"
autoRenewGracePeriodLength: "PT3888000S"
automaticTransferLength: "PT432000S"
claimsPeriodEnd: "294247-01-10T04:00:54.775Z"
createBillingCost:
currency: "USD"
amount: 25.00
creationTime: "2022-09-01T00:00:00.000Z"
currency: "USD"
defaultPromoTokens: []
dnsAPlusAaaaTtl: "PT900S"
dnsDsTtl: null
dnsNsTtl: null
dnsPaused: false
dnsWriters:
- "VoidDnsWriter"
driveFolderId: "driveFolder"
eapFeeSchedule:
"1970-01-01T00:00:00.000Z":
currency: "USD"
amount: 0.00
escrowEnabled: false
idnTables: []
invoicingEnabled: false
lordnUsername: null
numDnsPublishLocks: 1
pendingDeleteLength: "PT432000S"
premiumListName: "test"
pricingEngineClassName: "google.registry.model.pricing.StaticPremiumListPricingEngine"
redemptionGracePeriodLength: "PT2592000S"
registryLockOrUnlockBillingCost:
currency: "USD"
amount: 0.00
renewBillingCostTransitions:
"1970-01-01T00:00:00.000Z":
currency: "USD"
amount: 11.00
renewGracePeriodLength: "PT432000S"
reservedListNames: []
restoreBillingCost:
currency: "USD"
amount: 17.00
roidSuffix: "NOCREATE"
serverStatusChangeBillingCost:
currency: "USD"
amount: 19.00
tldStateTransitions:
"1970-01-01T00:00:00.000Z": "GENERAL_AVAILABILITY"
tldStr: "nocreatecostmap"
tldType: "REAL"
tldUnicode: "nocreatecostmap"
transferGracePeriodLength: "PT432000S"
@@ -8,6 +8,13 @@ claimsPeriodEnd: "294247-01-10T04:00:54.775Z"
createBillingCost:
currency: "USD"
amount: 25.00
createBillingCostTransitions:
"1970-01-01T00:00:00.000Z":
currency: "USD"
amount: 8.00
"2020-01-01T00:00:00.000Z":
currency: "USD"
amount: 25.00
creationTime: "2022-09-01T00:00:00.000Z"
currency: "USD"
defaultPromoTokens: []
@@ -8,6 +8,10 @@ claimsPeriodEnd: "294247-01-10T04:00:54.775Z"
createBillingCost:
currency: "USD"
amount: 25.00
createBillingCostTransitions:
"1970-01-01T00:00:00.000Z":
currency: "USD"
amount: 25.00
creationTime: "2022-09-01T00:00:00.000Z"
currency: "USD"
defaultPromoTokens: []
@@ -740,6 +740,7 @@
claims_period_end timestamptz not null,
create_billing_cost_amount numeric(19, 2),
create_billing_cost_currency text,
create_billing_cost_transitions hstore,
creation_time timestamptz not null,
currency text not null,
default_promo_tokens text[],