1
0
mirror of https://github.com/google/nomulus synced 2026-06-09 16:33:02 +00:00

Compare commits

...

12 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
sarahcaseybot e492936cec Add check for build_environment flag in updateReservedListCommand and updatePremiumListCommand (#2317)
* Add check for build_environment flag in updateReservedListCommand

* Do the same for premium list
2024-02-02 16:43:45 -05:00
Weimin Yu d1d59c1afd Add a reminder for BEAM at Java version declaration (#2316) 2024-02-02 12:05:10 -05:00
Weimin Yu 7b786eaf1f Update dataflow java runtime to 17 (#2315) 2024-02-01 15:37:21 -05:00
Pavlo Tkach 45c5d12743 Add angular signals and computed to the console (#2308) 2024-02-01 14:15:05 -05:00
sarahcaseybot 73ab95bd9d Add Cloud Build sync job for reserved and premium lists (#2302)
* Change tld-update to db-object-updater

* rename sync_tlds.sh to sync_db_objects.sh

* Change to configured command name

* Change environment to sandbox explicitly for testing on alpha

* Add remaining object steps and change cloudbuild-tld-sync to cloudbuild-sync-db-objects

* Add build_environment flag

* Change order of command and directory

* Uncomment out reserved list part
2024-01-31 14:50:54 -05:00
Weimin Yu f85cf57e36 Reduce query batch size for BSA unavailables (#2313)
Query size is borderline too-large for the replica.

At 50000, about 2 out of 30 took more than 30 seconds and were retried.
Lower to 40000 and we will keep monitoring the executions.
2024-01-30 13:18:41 -05:00
Ben McIlwain 5e36cf30c3 Don't override existing registrar email address when setting referral email (#2300)
The fallback should only apply on creates, not on updates, otherwise it can
override an existing value for the email address when only the referral email
should be what's updated.

This fixes a bug introduced back in commit in 0ead4f8d9d.

BUG= http://b/322026165
2024-01-30 18:31:54 +01:00
sarahcaseybot 829be0777b Add a createBillingCostTransitions column to TLD (#2312) 2024-01-29 18:06:02 -05:00
63 changed files with 5939 additions and 18152 deletions
+3
View File
@@ -382,6 +382,9 @@ subprojects {
apply from: "${rootDir.path}/java_common.gradle"
// When changing Java version here, be sure to update BEAM Java runtime:
// in core/build.gradle, search for `flex-template-base-image` and update
// the parameter value.
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
+2310 -15168
View File
File diff suppressed because it is too large Load Diff
+8 -6
View File
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { AfterViewInit, Component, ViewChild } from '@angular/core';
import { AfterViewInit, Component, ViewChild, effect } from '@angular/core';
import { RegistrarService } from './registrar/registrar.service';
import { UserDataService } from './shared/services/userData.service';
import { GlobalLoaderService } from './shared/services/globalLoader.service';
@@ -36,11 +36,13 @@ export class AppComponent implements AfterViewInit {
protected globalLoader: GlobalLoaderService,
protected router: Router
) {
registrarService.activeRegistrarIdChange.subscribe(() => {
this.renderRouter = false;
setTimeout(() => {
this.renderRouter = true;
}, 400);
effect(() => {
if (registrarService.registrarId()) {
this.renderRouter = false;
setTimeout(() => {
this.renderRouter = true;
}, 400);
}
});
}
@@ -52,7 +52,7 @@ export class DomainListService {
) {
return this.backendService
.getDomains(
this.registrarService.activeRegistrarId,
this.registrarService.registrarId(),
this.checkpointTime,
pageNumber,
resultsPerPage,
@@ -23,8 +23,10 @@ export class BillingWidgetComponent {
constructor(public registrarService: RegistrarService) {}
public get driveFolderUrl(): string {
if (this.registrarService?.registrar.driveFolderId) {
return `https://drive.google.com/drive/folders/${this.registrarService?.registrar.driveFolderId}`;
if (this.registrarService.registrar()?.driveFolderId) {
return `https://drive.google.com/drive/folders/${
this.registrarService.registrar()?.driveFolderId
}`;
}
return '';
}
@@ -12,9 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component } from '@angular/core';
import { Component, effect } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { RegistrarService } from './registrar.service';
@@ -24,22 +23,15 @@ import { RegistrarService } from './registrar.service';
styleUrls: ['./emptyRegistrar.component.scss'],
})
export class EmptyRegistrar {
private registrarIdChangeSubscription?: Subscription;
constructor(
private route: ActivatedRoute,
protected registrarService: RegistrarService,
private router: Router
) {
this.registrarIdChangeSubscription =
registrarService.activeRegistrarIdChange.subscribe((newRegistrarId) => {
if (newRegistrarId) {
this.router.navigate([this.route.snapshot.paramMap.get('nextUrl')]);
}
});
}
ngOnDestroy() {
this.registrarIdChangeSubscription?.unsubscribe();
effect(() => {
if (registrarService.registrarId()) {
this.router.navigate([this.route.snapshot.paramMap.get('nextUrl')]);
}
});
}
}
@@ -34,7 +34,7 @@ export class RegistrarGuard {
_: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Promise<boolean> | boolean {
if (this.registrarService.activeRegistrarId) {
if (this.registrarService.registrarId()) {
return true;
}
return this.router.navigate([
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Observable, Subject, tap } from 'rxjs';
import { Injectable, computed, signal } from '@angular/core';
import { Observable, tap } from 'rxjs';
import { BackendService } from '../shared/services/backend.service';
import {
@@ -52,9 +52,11 @@ export interface Registrar {
providedIn: 'root',
})
export class RegistrarService implements GlobalLoader {
activeRegistrarId: string = '';
registrars: Registrar[] = [];
activeRegistrarIdChange: Subject<string> = new Subject<string>();
registrarId = signal<string>('');
registrars = signal<Registrar[]>([]);
registrar = computed<Registrar | undefined>(() =>
this.registrars().find((r) => r.registrarId === this.registrarId())
);
constructor(
private backend: BackendService,
@@ -67,22 +69,15 @@ export class RegistrarService implements GlobalLoader {
this.globalLoader.startGlobalLoader(this);
}
public get registrar(): Registrar {
return this.registrars.filter(
(r) => r.registrarId === this.activeRegistrarId
)[0];
}
public updateSelectedRegistrar(registrarId: string) {
this.activeRegistrarId = registrarId;
this.activeRegistrarIdChange.next(registrarId);
this.registrarId.set(registrarId);
}
public loadRegistrars(): Observable<Registrar[]> {
return this.backend.getRegistrars().pipe(
tap((registrars) => {
if (registrars) {
this.registrars = registrars;
this.registrars.set(registrars);
}
})
);
@@ -5,20 +5,20 @@
routerLinkActive="active"
*ngIf="isMobile; else desktop"
>
{{ registrarService.activeRegistrarId || "Select registrar" }}
{{ registrarService.registrarId() || "Select registrar" }}
<mat-icon>open_in_new</mat-icon>
</button>
<ng-template #desktop>
<mat-form-field class="mat-form-field-density-5" appearance="fill">
<mat-label>Registrar</mat-label>
<mat-select
[ngModel]="registrarService.activeRegistrarId"
[ngModel]="registrarService.registrarId()"
(selectionChange)="
registrarService.updateSelectedRegistrar($event.value)
"
>
<mat-option
*ngFor="let registrar of registrarService.registrars"
*ngFor="let registrar of registrarService.registrars()"
[value]="registrar.registrarId"
>
{{ registrar.registrarId }}
@@ -87,7 +87,7 @@ export class RegistrarComponent {
constructor(protected registrarService: RegistrarService) {
this.dataSource = new MatTableDataSource<Registrar>(
registrarService.registrars
registrarService.registrars()
);
}
@@ -1,41 +1,41 @@
<div *ngIf="loading" class="contact__loading">
@if (loading) {
<div class="contact__loading">
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</div>
<div *ngIf="!loading">
<div
class="contact__empty-contacts"
*ngIf="contactService.contacts.length === 0"
>
} @else {
<div>
@if (contactService.contacts().length === 0) {
<div class="contact__empty-contacts">
<mat-icon class="contact__empty-contacts-icon secondary-text"
>apps_outage</mat-icon
>
<h1>No contacts found</h1>
</div>
<div *ngFor="let group of groupedData">
<div class="contact__cards-wrapper" *ngIf="group.contacts.length">
<h3>{{ group.label }}s</h3>
<mat-divider></mat-divider>
<div class="contact__cards">
<mat-card class="contact__card" *ngFor="let contact of group.contacts">
<mat-card-title>{{ contact.name }}</mat-card-title>
<p *ngIf="contact.phoneNumber">{{ contact.phoneNumber }}</p>
<p *ngIf="contact.emailAddress">{{ contact.emailAddress }}</p>
<mat-card-actions class="contact__card-actions">
<button
mat-button
color="primary"
(click)="openDetails($event, contact)"
>
<mat-icon>edit</mat-icon>Edit
</button>
<button mat-button color="accent" (click)="deleteContact(contact)">
<mat-icon>delete</mat-icon>Delete
</button>
</mat-card-actions>
</mat-card>
</div>
} @else { @for (group of groupedContacts(); track group.emailAddress) {
<div class="contact__cards-wrapper">
<h3>{{ group.label }}s</h3>
<mat-divider></mat-divider>
<div class="contact__cards">
<mat-card class="contact__card" *ngFor="let contact of group.contacts">
<mat-card-title>{{ contact.name }}</mat-card-title>
<p *ngIf="contact.phoneNumber">{{ contact.phoneNumber }}</p>
<p *ngIf="contact.emailAddress">{{ contact.emailAddress }}</p>
<mat-card-actions class="contact__card-actions">
<button
mat-button
color="primary"
(click)="openDetails($event, contact)"
>
<mat-icon>edit</mat-icon>Edit
</button>
<button mat-button color="accent" (click)="deleteContact(contact)">
<mat-icon>delete</mat-icon>Delete
</button>
</mat-card-actions>
</mat-card>
</div>
</div>
} }
<div class="contact__actions">
<button mat-raised-button color="primary" (click)="openCreateNew($event)">
<mat-icon>add</mat-icon>Create a Contact
@@ -45,3 +45,4 @@
#contactDetailsWrapper
></app-dialog-bottom-sheet-wrapper>
</div>
}
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, ViewChild } from '@angular/core';
import { Component, ViewChild, computed } from '@angular/core';
import { Contact, ContactService } from './contact.service';
import { HttpErrorResponse } from '@angular/common/http';
import { MatSnackBar } from '@angular/material/snack-bar';
@@ -70,9 +70,9 @@ export class ContactDetailsDialogComponent implements DialogBottomSheetContent {
init(params: ContactDetailsParams) {
this.params = params;
this.contactIndex = this.contactService.contacts.findIndex(
(c) => c === params.data.contact
);
this.contactIndex = this.contactService
.contacts()
.findIndex((c) => c === params.data.contact);
this.contact = JSON.parse(JSON.stringify(params.data.contact));
}
@@ -115,6 +115,16 @@ export class ContactDetailsDialogComponent implements DialogBottomSheetContent {
})
export default class ContactComponent {
public static PATH = 'contact';
public groupedContacts = computed(() => {
return this.contactService.contacts().reduce((acc, contact) => {
contact.types.forEach((contactType) => {
acc
.find((group: GroupedContacts) => group.value === contactType)
?.contacts.push(contact);
});
return acc;
}, JSON.parse(JSON.stringify(contactTypes)));
});
@ViewChild('contactDetailsWrapper')
detailsComponentWrapper!: DialogBottomSheetWrapper;
@@ -131,17 +141,6 @@ export default class ContactComponent {
});
}
public get groupedData() {
return this.contactService.contacts?.reduce((acc, contact) => {
contact.types.forEach((type) => {
acc
.find((group: GroupedContacts) => group.value === type)
?.contacts.push(contact);
});
return acc;
}, JSON.parse(JSON.stringify(contactTypes)));
}
deleteContact(contact: Contact) {
if (confirm(`Please confirm contact ${contact.name} delete`)) {
this.contactService.deleteContact(contact).subscribe({
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, signal } from '@angular/core';
import { Observable, tap } from 'rxjs';
import { RegistrarService } from 'src/app/registrar/registrar.service';
import { BackendService } from 'src/app/shared/services/backend.service';
@@ -33,7 +33,7 @@ export interface Contact {
providedIn: 'root',
})
export class ContactService {
contacts: Contact[] = [];
contacts = signal<Contact[]>([]);
constructor(
private backend: BackendService,
@@ -42,39 +42,37 @@ export class ContactService {
// TODO: Come up with a better handling for registrarId
fetchContacts(): Observable<Contact[]> {
return this.backend
.getContacts(this.registrarService.activeRegistrarId)
.pipe(
tap((contacts = []) => {
this.contacts = contacts;
})
);
return this.backend.getContacts(this.registrarService.registrarId()).pipe(
tap((contacts = []) => {
this.contacts.set(contacts);
})
);
}
saveContacts(contacts: Contact[]): Observable<Contact[]> {
return this.backend
.postContacts(this.registrarService.activeRegistrarId, contacts)
.postContacts(this.registrarService.registrarId(), contacts)
.pipe(
tap((_) => {
this.contacts = contacts;
this.contacts.set(contacts);
})
);
}
updateContact(index: number, contact: Contact) {
const newContacts = this.contacts.map((c, i) =>
const newContacts = this.contacts().map((c, i) =>
i === index ? contact : c
);
return this.saveContacts(newContacts);
}
addContact(contact: Contact) {
const newContacts = this.contacts.concat([contact]);
const newContacts = this.contacts().concat([contact]);
return this.saveContacts(newContacts);
}
deleteContact(contact: Contact) {
const newContacts = this.contacts.filter((c) => c !== contact);
const newContacts = this.contacts().filter((c) => c !== contact);
return this.saveContacts(newContacts);
}
}
@@ -40,7 +40,7 @@ export default class SecurityComponent {
private _snackBar: MatSnackBar,
public registrarService: RegistrarService
) {
this.dataSource = apiToUiConverter(this.registrarService.registrar);
this.dataSource = apiToUiConverter(this.registrarService.registrar());
}
enableEdit() {
@@ -76,6 +76,6 @@ export default class SecurityComponent {
}
resetDataSource() {
this.dataSource = apiToUiConverter(this.registrarService.registrar);
this.dataSource = apiToUiConverter(this.registrarService.registrar());
}
}
@@ -64,7 +64,7 @@ export class SecurityService {
saveChanges(newSecuritySettings: SecuritySettings) {
return this.backend
.postSecuritySettings(
this.registrarService.activeRegistrarId,
this.registrarService.registrarId(),
uiToApiConverter(newSecuritySettings)
)
.pipe(
+1 -1
View File
@@ -776,7 +776,7 @@ if (environment == 'alpha') {
gs://${gcpProject}-deploy/live/beam/${metaDataBaseName} \
--image-gcr-path ${imageName}:live \
--sdk-language JAVA \
--flex-template-base-image JAVA11 \
--flex-template-base-image JAVA17 \
--metadata-file ${projectDir}/src/main/resources/${metaData} \
--jar ${uberJarName} \
--env FLEX_TEMPLATE_JAVA_MAIN_CLASS=${mainClass} \
@@ -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);
}
}
}
@@ -80,7 +80,7 @@ public class UploadBsaUnavailableDomainsAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final int BATCH_SIZE = 50000;
private static final int BATCH_SIZE = 40000;
Clock clock;
@@ -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);
}
}
@@ -292,8 +292,8 @@ abstract class CreateOrUpdateRegistrarCommand extends MutatingCommand {
}
if (!isNullOrEmpty(email)) {
builder.setEmailAddress(email);
} else if (!isNullOrEmpty(
icannReferralEmail)) { // fall back to ICANN referral email if present
} else if (!isNullOrEmpty(icannReferralEmail) && oldRegistrar == null) {
// On creates, fall back to ICANN referral email (if present).
builder.setEmailAddress(icannReferralEmail);
}
if (url != null) {
@@ -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());
@@ -48,11 +48,9 @@ class UpdatePremiumListCommand extends CreateOrUpdatePremiumListCommand {
@Override
protected String prompt() throws Exception {
// TODO(sarahbot): uncomment once go/r3pr/2292 is deployed
// checkArgument(
// !RegistryToolEnvironment.get().equals(RegistryToolEnvironment.PRODUCTION) || buildEnv,
// "The --build_environment flag must be used when running update_premium_list in
// production");
checkArgument(
!RegistryToolEnvironment.get().equals(RegistryToolEnvironment.PRODUCTION) || buildEnv,
"The --build_environment flag must be used when running update_premium_list in production");
name = Strings.isNullOrEmpty(name) ? convertFilePathToName(inputFile) : name;
PremiumList existingList =
PremiumListDao.getLatestRevision(name)
@@ -14,6 +14,7 @@
package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.util.DiffUtils.prettyPrintEntityDeepDiff;
import static google.registry.util.ListNamingUtils.convertFilePathToName;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -47,11 +48,10 @@ final class UpdateReservedListCommand extends CreateOrUpdateReservedListCommand
@Override
protected String prompt() throws Exception {
// TODO(sarahbot): uncomment once go/r3pr/2292 is deployed
// checkArgument(
// !RegistryToolEnvironment.get().equals(RegistryToolEnvironment.PRODUCTION) || buildEnv,
// "The --build_environment flag must be used when running update_reserved_list in"
// + " production");
checkArgument(
!RegistryToolEnvironment.get().equals(RegistryToolEnvironment.PRODUCTION) || buildEnv,
"The --build_environment flag must be used when running update_reserved_list in"
+ " production");
name = Strings.isNullOrEmpty(name) ? convertFilePathToName(input) : name;
ReservedList existingReservedList =
ReservedList.get(name)
@@ -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");
@@ -206,39 +206,38 @@ class UpdatePremiumListCommandTest<C extends UpdatePremiumListCommand>
.containsExactly(PremiumEntry.create(0L, new BigDecimal("9090.00"), "doge"));
}
// TODO(sarahbot): uncomment once go/r3pr/2292 is deployed
// @Test
// void testFailure_runCommandOnProduction_noFlag() throws Exception {
// File tmpFile = tmpDir.resolve(String.format("%s.txt", TLD_TEST)).toFile();
// String newPremiumListData = "eth,USD 9999";
// Files.asCharSink(tmpFile, UTF_8).write(newPremiumListData);
// IllegalArgumentException thrown =
// assertThrows(
// IllegalArgumentException.class,
// () ->
// runCommandInEnvironment(
// RegistryToolEnvironment.PRODUCTION,
// "--name=" + TLD_TEST,
// "--input=" + Paths.get(tmpFile.getPath())));
// assertThat(thrown.getMessage())
// .isEqualTo(
// "The --build_environment flag must be used when running update_premium_list in"
// + " production");
// }
//
// @Test
// void testSuccess_runCommandOnProduction_buildEnvFlag() throws Exception {
// File tmpFile = tmpDir.resolve(String.format("%s.txt", TLD_TEST)).toFile();
// String newPremiumListData = "eth,USD 9999";
// Files.asCharSink(tmpFile, UTF_8).write(newPremiumListData);
// runCommandInEnvironment(
// RegistryToolEnvironment.PRODUCTION,
// "--name=" + TLD_TEST,
// "--input=" + Paths.get(tmpFile.getPath()),
// "--build_environment",
// "-f");
// assertThat(PremiumListDao.loadAllPremiumEntries(TLD_TEST))
// .comparingElementsUsing(immutableObjectCorrespondence("revisionId"))
// .containsExactly(PremiumEntry.create(0L, new BigDecimal("9999.00"), "eth"));
// }
@Test
void testFailure_runCommandOnProduction_noFlag() throws Exception {
File tmpFile = tmpDir.resolve(String.format("%s.txt", TLD_TEST)).toFile();
String newPremiumListData = "eth,USD 9999";
Files.asCharSink(tmpFile, UTF_8).write(newPremiumListData);
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
runCommandInEnvironment(
RegistryToolEnvironment.PRODUCTION,
"--name=" + TLD_TEST,
"--input=" + Paths.get(tmpFile.getPath())));
assertThat(thrown.getMessage())
.isEqualTo(
"The --build_environment flag must be used when running update_premium_list in"
+ " production");
}
@Test
void testSuccess_runCommandOnProduction_buildEnvFlag() throws Exception {
File tmpFile = tmpDir.resolve(String.format("%s.txt", TLD_TEST)).toFile();
String newPremiumListData = "eth,USD 9999";
Files.asCharSink(tmpFile, UTF_8).write(newPremiumListData);
runCommandInEnvironment(
RegistryToolEnvironment.PRODUCTION,
"--name=" + TLD_TEST,
"--input=" + Paths.get(tmpFile.getPath()),
"--build_environment",
"-f");
assertThat(PremiumListDao.loadAllPremiumEntries(TLD_TEST))
.comparingElementsUsing(immutableObjectCorrespondence("revisionId"))
.containsExactly(PremiumEntry.create(0L, new BigDecimal("9999.00"), "eth"));
}
}
@@ -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() {
@@ -637,10 +637,12 @@ class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarCommand>
@Test
void testSuccess_setIcannEmail() throws Exception {
runCommand("--icann_referral_email=foo@bar.test", "--force", "TheRegistrar");
Registrar registrar = loadRegistrar("TheRegistrar");
assertThat(registrar.getIcannReferralEmail()).isEqualTo("foo@bar.test");
assertThat(registrar.getEmailAddress()).isEqualTo("foo@bar.test");
assertThat(registrar.getEmailAddress()).isEqualTo("the.registrar@example.com");
runCommand("--icann_referral_email=foo@bar.test", "--force", "TheRegistrar");
Registrar updatedRegistrar = loadRegistrar("TheRegistrar");
assertThat(updatedRegistrar.getIcannReferralEmail()).isEqualTo("foo@bar.test");
assertThat(updatedRegistrar.getEmailAddress()).isEqualTo("the.registrar@example.com");
}
@Test
@@ -153,40 +153,39 @@ class UpdateReservedListCommandTest
assertThat(reservedList.getReservationInList("helicopter")).hasValue(FULLY_BLOCKED);
}
// TODO(sarahbot): uncomment once go/r3pr/2292 is deployed
// @Test
// void testFailure_runCommandOnProduction_noFlag() throws Exception {
// IllegalArgumentException thrown =
// assertThrows(
// IllegalArgumentException.class,
// () ->
// runCommandInEnvironment(
// RegistryToolEnvironment.PRODUCTION,
// "--name=xn--q9jyb4c_common-reserved",
// "--input=" + reservedTermsPath));
// assertThat(thrown.getMessage())
// .isEqualTo(
// "The --build_environment flag must be used when running update_reserved_list in"
// + " production");
// }
//
// @Test
// void testSuccess_runCommandOnProduction_buildEnvFlag() throws Exception {
// runCommandInEnvironment(
// RegistryToolEnvironment.PRODUCTION,
// "--name=xn--q9jyb4c_common-reserved",
// "--input=" + reservedTermsPath,
// "--build_environment",
// "-f");
// assertThat(ReservedList.get("xn--q9jyb4c_common-reserved")).isPresent();
// ReservedList reservedList = ReservedList.get("xn--q9jyb4c_common-reserved").get();
// assertThat(reservedList.getReservedListEntries()).hasSize(2);
// assertThat(reservedList.getReservationInList("baddies")).hasValue(FULLY_BLOCKED);
// assertThat(reservedList.getReservationInList("ford")).hasValue(FULLY_BLOCKED);
// assertThat(reservedList.getReservationInList("helicopter")).isEmpty();
// assertInStdout("Update reserved list for xn--q9jyb4c_common-reserved?");
// assertInStdout("helicopter: helicopter,FULLY_BLOCKED -> null");
// assertInStdout("baddies: null -> baddies,FULLY_BLOCKED");
// assertInStdout("ford: null -> ford,FULLY_BLOCKED # random comment");
// }
@Test
void testFailure_runCommandOnProduction_noFlag() throws Exception {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
runCommandInEnvironment(
RegistryToolEnvironment.PRODUCTION,
"--name=xn--q9jyb4c_common-reserved",
"--input=" + reservedTermsPath));
assertThat(thrown.getMessage())
.isEqualTo(
"The --build_environment flag must be used when running update_reserved_list in"
+ " production");
}
@Test
void testSuccess_runCommandOnProduction_buildEnvFlag() throws Exception {
runCommandInEnvironment(
RegistryToolEnvironment.PRODUCTION,
"--name=xn--q9jyb4c_common-reserved",
"--input=" + reservedTermsPath,
"--build_environment",
"-f");
assertThat(ReservedList.get("xn--q9jyb4c_common-reserved")).isPresent();
ReservedList reservedList = ReservedList.get("xn--q9jyb4c_common-reserved").get();
assertThat(reservedList.getReservedListEntries()).hasSize(2);
assertThat(reservedList.getReservationInList("baddies")).hasValue(FULLY_BLOCKED);
assertThat(reservedList.getReservationInList("ford")).hasValue(FULLY_BLOCKED);
assertThat(reservedList.getReservationInList("helicopter")).isEmpty();
assertInStdout("Update reserved list for xn--q9jyb4c_common-reserved?");
assertInStdout("helicopter: helicopter,FULLY_BLOCKED -> null");
assertInStdout("baddies: null -> baddies,FULLY_BLOCKED");
assertInStdout("ford: null -> ford,FULLY_BLOCKED # random comment");
}
}
@@ -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: []
@@ -261,11 +261,11 @@ td.section {
</tr>
<tr>
<td class="property_name">generated on</td>
<td class="property_value">2024-01-23 16:30:08.582924</td>
<td class="property_value">2024-01-29 21:20:38.361551592</td>
</tr>
<tr>
<td class="property_name">last flyway file</td>
<td id="lastFlywayFile" class="property_value">V153__drop_bsa_domain_in_use_table.sql</td>
<td id="lastFlywayFile" class="property_value">V154__add_create_billing_cost_transitions_to_tld.sql</td>
</tr>
</tbody>
</table>
@@ -277,11 +277,11 @@ td.section {
SchemaCrawler_Diagram
</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-3205 4025,-3205 4025,4 -4,4" />
<text text-anchor="start" x="3752.5" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">generated by</text>
<text text-anchor="start" x="3835.5" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">SchemaCrawler 16.10.1</text>
<text text-anchor="start" x="3751.5" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">generated on</text>
<text text-anchor="start" x="3835.5" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">2024-01-23 16:30:08.582924</text>
<polygon fill="none" stroke="#888888" points="3748,-4 3748,-44 4013,-44 4013,-4 3748,-4" /> <!-- allocationtoken_a08ccbef -->
<text text-anchor="start" x="3730.5" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">generated by</text>
<text text-anchor="start" x="3813.5" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">SchemaCrawler 16.10.1</text>
<text text-anchor="start" x="3729.5" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">generated on</text>
<text text-anchor="start" x="3813.5" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">2024-01-29 21:20:38.361551592</text>
<polygon fill="none" stroke="#888888" points="3726,-4 3726,-44 4013,-44 4013,-4 3726,-4" /> <!-- allocationtoken_a08ccbef -->
<g id="node1" class="node">
<title>
allocationtoken_a08ccbef
File diff suppressed because it is too large Load Diff
+1
View File
@@ -151,3 +151,4 @@ V150__add_tld_bsa_enroll_date.sql
V151__add_bsa_unblockable_domain_table.sql
V152__add_bsa_domain_refresh_table.sql
V153__drop_bsa_domain_in_use_table.sql
V154__add_create_billing_cost_transitions_to_tld.sql
@@ -0,0 +1,15 @@
-- Copyright 2024 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.
ALTER TABLE "Tld" ADD COLUMN create_billing_cost_transitions hstore;
@@ -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[],
@@ -1178,7 +1178,8 @@ CREATE TABLE public."Tld" (
dns_ns_ttl interval,
idn_tables text[],
breakglass_mode boolean DEFAULT false NOT NULL,
bsa_enroll_start_time timestamp with time zone
bsa_enroll_start_time timestamp with time zone,
create_billing_cost_transitions public.hstore
);
+12 -12
View File
@@ -91,7 +91,7 @@ steps:
--format="get(digest)" --filter="tags = ${TAG_NAME}")
sed -i s/'prober_cert_updater:latest'/prober_cert_updater@$digest/g \
release/cloudbuild-renew-prober-certs-*.yaml
# Build the tld_updater image and upload it to GCR. This image extends
# Build the db_object_updater image and upload it to GCR. This image extends
# from the `builder` and the nomulus.jar built earlier.
- name: 'gcr.io/cloud-builders/docker'
entrypoint: /bin/bash
@@ -101,14 +101,14 @@ steps:
set -e
# The nomulus jar is not under the working dir. Must be copied over.
cp ../../output/nomulus.jar .
docker build -t gcr.io/${PROJECT_ID}/tld_updater:${TAG_NAME} \
docker build -t gcr.io/${PROJECT_ID}/db_object_updater:${TAG_NAME} \
--build-arg TAG_NAME=${TAG_NAME} --build-arg PROJECT_ID=${PROJECT_ID} .
docker tag gcr.io/${PROJECT_ID}/tld_updater:${TAG_NAME} \
gcr.io/${PROJECT_ID}/tld_updater:latest
docker push gcr.io/${PROJECT_ID}/tld_updater:latest
docker push gcr.io/${PROJECT_ID}/tld_updater:${TAG_NAME}
dir: 'release/tld-updater/'
# Update the tld_updater image digest in relevant GCB files.
docker tag gcr.io/${PROJECT_ID}/db_object_updater:${TAG_NAME} \
gcr.io/${PROJECT_ID}/db_object_updater:latest
docker push gcr.io/${PROJECT_ID}/db_object_updater:latest
docker push gcr.io/${PROJECT_ID}/db_object_updater:${TAG_NAME}
dir: 'release/db-object-updater/'
# Update the db_object_updater image digest in relevant GCB files.
- name: 'gcr.io/${PROJECT_ID}/builder:latest'
entrypoint: /bin/bash
args:
@@ -116,10 +116,10 @@ steps:
- |
set -e
digest=$(gcloud container images list-tags \
gcr.io/${PROJECT_ID}/tld_updater \
gcr.io/${PROJECT_ID}/db_object_updater \
--format="get(digest)" --filter="tags = ${TAG_NAME}")
sed -i s/'tld_updater:latest'/tld_updater@$digest/g \
release/cloudbuild-tld-sync-*.yaml
sed -i s/'db_object_updater:latest'/db_object_updater@$digest/g \
release/cloudbuild-sync-db-objects-*.yaml
# Build and stage Dataflow Flex templates.
- name: 'gcr.io/${PROJECT_ID}/builder:latest'
entrypoint: /bin/bash
@@ -190,7 +190,7 @@ artifacts:
- 'release/cloudbuild-renew-prober-certs-*.yaml'
- 'release/cloudbuild-schema-deploy-*.yaml'
- 'release/cloudbuild-schema-verify-*.yaml'
- 'release/cloudbuild-tld-sync-*.yaml'
- 'release/cloudbuild-sync-db-objects-*.yaml'
timeout: 7200s
options:
+5 -5
View File
@@ -139,9 +139,9 @@ steps:
gcloud container images list-tags \
gcr.io/${PROJECT_ID}/prober_cert_updater \
--format='get(digest)' --filter='tags = ${TAG_NAME}')
tld_updater_digest=$( \
db_object_updater_digest=$( \
gcloud container images list-tags \
gcr.io/${PROJECT_ID}/tld_updater \
gcr.io/${PROJECT_ID}/db_object_updater \
--format='get(digest)' --filter='tags = ${TAG_NAME}')
sed -i s/builder:latest/builder@$builder_digest/g \
release/cloudbuild-schema-deploy.yaml
@@ -150,7 +150,7 @@ steps:
sed -i s/builder:latest/builder@$builder_digest/g \
release/cloudbuild-renew-prober-certs.yaml
sed -i s/builder:latest/builder@$builder_digest/g \
release/cloudbuild-tld-sync.yaml
release/cloudbuild-sync-db-objects.yaml
sed -i s/schema_deployer:latest/schema_deployer@$schema_deployer_digest/g \
release/cloudbuild-schema-deploy.yaml
sed -i s/schema_verifier:latest/schema_verifier@$schema_verifier_digest/g \
@@ -163,8 +163,8 @@ steps:
> release/cloudbuild-schema-verify-${environment}.yaml
sed s/'$${_ENV}'/${environment}/g release/cloudbuild-renew-prober-certs.yaml \
> release/cloudbuild-renew-prober-certs-${environment}.yaml
sed s/'$${_ENV}'/${environment}/g release/cloudbuild-tld-sync.yaml \
> release/cloudbuild-tld-sync-${environment}.yaml
sed s/'$${_ENV}'/${environment}/g release/cloudbuild-sync-db-objects.yaml \
> release/cloudbuild-sync-db-objects-${environment}.yaml
done
# Upload the gradle binary to GCS if it does not exist and point URL in gradle wrapper to it.
- name: 'gcr.io/cloud-builders/gsutil'
@@ -1,7 +1,8 @@
# This will sync the Tld configurations in the internal repo with the Tld objects in the database.
# This will sync the configuration files in the internal repo with their
# corresponding objects in the database.
#
# To manually trigger a build on GCB, run:
# gcloud builds submit --config cloudbuild-tld-sync.yaml --substitutions \
# gcloud builds submit --config cloudbuild-sync-db-objects.yaml --substitutions \
# _INTERNAL_REPO_URL=[URL] ..
#
# To trigger a build automatically, follow the instructions below and add a trigger:
@@ -34,11 +35,26 @@ steps:
--secret nomulus-tool-cloudbuild-credential \
> nomulus_tool_credential.json
# Configure the TLDs using the stored configuration files in the internal repo
- name: 'gcr.io/$PROJECT_ID/tld_updater:latest'
- name: 'gcr.io/$PROJECT_ID/db_object_updater:latest'
args:
- ${_ENV}
- ./nomulus_tool_credential.json
- configure_tld
- nomulus-internal/core/src/main/java/google/registry/config/files/tld/
# Configure the premium lists using the stored configuration files in the internal repo
- name: 'gcr.io/$PROJECT_ID/db_object_updater:latest'
args:
- ${_ENV}
- ./nomulus_tool_credential.json
- update_premium_list
- nomulus-internal/core/src/main/java/google/registry/config/files/premium/
# Configure the reserved lists using the stored configuration files in the internal repo
- name: 'gcr.io/$PROJECT_ID/db_object_updater:latest'
args:
- ${_ENV}
- ./nomulus_tool_credential.json
- update_reserved_list
- nomulus-internal/core/src/main/java/google/registry/config/files/reserved/
timeout: 7200s
options:
@@ -17,6 +17,6 @@ ARG TAG_NAME
FROM gcr.io/${PROJECT_ID}/builder:${TAG_NAME}
COPY nomulus.jar /
COPY sync_tlds.sh /usr/local/bin
COPY sync_db_objects.sh /usr/local/bin
ENTRYPOINT [ "bash", "sync_tlds.sh" ]
ENTRYPOINT [ "bash", "sync_db_objects.sh" ]
@@ -13,23 +13,25 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# Sync the TLD configuration files from the internal repo with the Tld object
# in the database. Loops through the Tld configuration files and runs the configure_tld command
# with the file.
# Sync the configuration files in the internal repo with the objects in the
# database. Loops through the configuration files in the inputted directory and
# runs the passed in nomulus update command with the file.
# - env: The Nomulus environment, production, sandbox, etc.
# - tools_credential: The credential (.json) needed to run the nomulus command.
# - nomulus_command: The nomulus command to run.
# - config_file_directory: The internal directory storing the TLD config files.
set -e
if [ "$#" -ne 3 ]; then
echo "Expecting three parameters in order: env tools_credential config_file_directory"
if [ "$#" -ne 4 ]; then
echo "Expecting four parameters in order: env tools_credential nomulus_command config_file_directory"
exit 1
fi
nomulus_env="${1}"
tools_credential="${2}"
config_file_directory="${3}"
nomulus_command="${3}"
config_file_directory="${4}"
echo ${config_file_directory}
@@ -37,5 +39,5 @@ for FILE in ${config_file_directory}/${nomulus_env}/*; do
echo $FILE
java -jar /nomulus.jar -e "${nomulus_env}" \
--credential "${tools_credential}" \
configure_tld -i $FILE --force --build_environment
"${nomulus_command}" -i $FILE --force --build_environment
done