mirror of
https://github.com/google/nomulus
synced 2026-07-19 14:32:44 +00:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| abc1a0ef3d | |||
| 7b47ecb1f1 | |||
| 469d62703a | |||
| 009fda67b7 | |||
| e492936cec | |||
| d1d59c1afd | |||
| 7b786eaf1f | |||
| 45c5d12743 | |||
| 73ab95bd9d | |||
| f85cf57e36 | |||
| 5e36cf30c3 | |||
| 829be0777b | |||
| c0ac9bdba4 | |||
| 58ec0f826d | |||
| f9e0908022 | |||
| b21e1a1935 | |||
| 0112b3ae06 | |||
| a4903c27b9 | |||
| 2166c28d6d | |||
| 891e7c0174 | |||
| 64f5971275 | |||
| 818944317f | |||
| ea96ed300f | |||
| 8415c8bbe4 | |||
| dc48c257b5 | |||
| 2bf3867532 | |||
| 44f44be643 | |||
| f61579b350 |
@@ -33,7 +33,7 @@ jobs:
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '11'
|
||||
java-version: '17'
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
+38
-15
@@ -211,6 +211,30 @@ allprojects {
|
||||
options.fork = true
|
||||
options.forkOptions.executable =
|
||||
file("${System.env.JAVA_HOME}/bin/javac")
|
||||
options.compilerArgs = ["--add-exports",
|
||||
"jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED",
|
||||
"--add-exports",
|
||||
"jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
|
||||
"--add-exports",
|
||||
"jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
|
||||
"--add-exports",
|
||||
"jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
|
||||
"--add-exports",
|
||||
"jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
|
||||
"--add-exports",
|
||||
"jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED",
|
||||
"--add-exports",
|
||||
"jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED",
|
||||
"--add-exports",
|
||||
"jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED"]
|
||||
options.forkOptions.jvmArgs = ["-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED",
|
||||
"-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
|
||||
"-J--add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
|
||||
"-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
|
||||
"-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
|
||||
"-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED",
|
||||
"-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED",
|
||||
"-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -358,16 +382,11 @@ subprojects {
|
||||
|
||||
apply from: "${rootDir.path}/java_common.gradle"
|
||||
|
||||
if (!rootProject.enableCrossReferencing.toBoolean()) {
|
||||
compileJava {
|
||||
// TODO: Remove this once we migrate off of AppEngine.
|
||||
options.release = 8
|
||||
}
|
||||
compileTestJava {
|
||||
// TODO: Remove this once we migrate off of AppEngine.
|
||||
options.release = 8
|
||||
}
|
||||
}
|
||||
// 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
|
||||
|
||||
project.tasks.test.dependsOn runPresubmits
|
||||
|
||||
@@ -479,10 +498,14 @@ def createGetBuildSrcDirectDepsTask(outputFileName) {
|
||||
|
||||
rootProject.ext {
|
||||
invokeJavaDiffFormatScript = { action ->
|
||||
println("JAVA_HOME=${System.env.JAVA_HOME}")
|
||||
println("PATH=${System.env.PATH}")
|
||||
println(ext.execInBash("type java", "${rootDir}"))
|
||||
println(ext.execInBash("java -version", "${rootDir}"))
|
||||
def javaHome = project.findProperty('org.gradle.java.home')
|
||||
def javaBin
|
||||
if (javaHome != null) {
|
||||
javaBin = "$javaHome/bin/java"
|
||||
} else {
|
||||
javaBin = ext.execInBash("which java", rootDir)
|
||||
}
|
||||
println("Running the formatting tool with $javaBin")
|
||||
def scriptDir = rootDir.path.endsWith('buildSrc')
|
||||
? "${rootDir}/../java-format"
|
||||
: "${rootDir}/java-format"
|
||||
@@ -493,7 +516,7 @@ rootProject.ext {
|
||||
def pythonExe = getPythonExecutable()
|
||||
|
||||
return ext.execInBash(
|
||||
"PYTHON=${pythonExe} ${formatDiffScript} ${action}", "${workingDir}")
|
||||
"JAVA=${javaBin} PYTHON=${pythonExe} ${formatDiffScript} ${action}", "${workingDir}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ PRESUBMITS = {
|
||||
PresubmitCheck(
|
||||
r".*Copyright 20\d{2} The Nomulus Authors\. All Rights Reserved\.",
|
||||
("java", "js", "soy", "sql", "py", "sh", "gradle", "ts"), {
|
||||
".git", "/build/", "/generated/", "/generated_tests/",
|
||||
".git", "/build/", "/bin/generated-sources/", "/bin/generated-test-sources/",
|
||||
"node_modules/", "LoggerConfig.java", "registrar_bin.",
|
||||
"registrar_dbg.", "google-java-format-diff.py",
|
||||
"nomulus.golden.sql", "soyutils_usegoog.js", "javascript/checks.js"
|
||||
|
||||
Generated
+2310
-15168
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -1,3 +1,3 @@
|
||||
FROM gcr.io/distroless/java:debug
|
||||
FROM gcr.io/distroless/java17-debian11:debug
|
||||
ADD build/libs/nomulus.jar /nomulus.jar
|
||||
ENTRYPOINT ["/usr/bin/java", "-jar", "/nomulus.jar"]
|
||||
|
||||
+1
-1
@@ -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} \
|
||||
|
||||
@@ -163,7 +163,12 @@ public class InvoicingPipeline implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
/** Saves the billing events to a single overall invoice CSV file. */
|
||||
/**
|
||||
* Saves the billing events to a single overall invoice CSV file. TextIO always produces the file
|
||||
* of type text/plain, which we then update to desired text/csv before sending an email to billing
|
||||
* team {@link google.registry.reporting.billing.BillingEmailUtils#emailOverallInvoice()
|
||||
* emailOverallInvoice}
|
||||
*/
|
||||
static void saveInvoiceCsv(
|
||||
PCollection<google.registry.beam.billing.BillingEvent> billingEvents,
|
||||
InvoicingPipelineOptions options) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public class BsaRefreshAction implements Runnable {
|
||||
private final GcsClient gcsClient;
|
||||
private final BsaReportSender bsaReportSender;
|
||||
private final int transactionBatchSize;
|
||||
private final Duration domainTxnMaxDuration;
|
||||
private final Duration domainCreateTxnCommitTimeLag;
|
||||
private final BsaLock bsaLock;
|
||||
private final Clock clock;
|
||||
private final Response response;
|
||||
@@ -65,7 +65,7 @@ public class BsaRefreshAction implements Runnable {
|
||||
GcsClient gcsClient,
|
||||
BsaReportSender bsaReportSender,
|
||||
@Config("bsaTxnBatchSize") int transactionBatchSize,
|
||||
@Config("domainCreateTxnCommitTimeLag") Duration domainTxnMaxDuration,
|
||||
@Config("domainCreateTxnCommitTimeLag") Duration domainCreateTxnCommitTimeLag,
|
||||
BsaLock bsaLock,
|
||||
Clock clock,
|
||||
Response response) {
|
||||
@@ -73,7 +73,7 @@ public class BsaRefreshAction implements Runnable {
|
||||
this.gcsClient = gcsClient;
|
||||
this.bsaReportSender = bsaReportSender;
|
||||
this.transactionBatchSize = transactionBatchSize;
|
||||
this.domainTxnMaxDuration = domainTxnMaxDuration;
|
||||
this.domainCreateTxnCommitTimeLag = domainCreateTxnCommitTimeLag;
|
||||
this.bsaLock = bsaLock;
|
||||
this.clock = clock;
|
||||
this.response = response;
|
||||
@@ -109,7 +109,10 @@ public class BsaRefreshAction implements Runnable {
|
||||
RefreshSchedule schedule = maybeSchedule.get();
|
||||
DomainsRefresher refresher =
|
||||
new DomainsRefresher(
|
||||
schedule.prevRefreshTime(), clock.nowUtc(), domainTxnMaxDuration, transactionBatchSize);
|
||||
schedule.prevRefreshTime(),
|
||||
clock.nowUtc(),
|
||||
domainCreateTxnCommitTimeLag,
|
||||
transactionBatchSize);
|
||||
switch (schedule.stage()) {
|
||||
case CHECK_FOR_CHANGES:
|
||||
ImmutableList<UnblockableDomainChange> blockabilityChanges =
|
||||
@@ -132,10 +135,12 @@ public class BsaRefreshAction implements Runnable {
|
||||
case UPLOAD_REMOVALS:
|
||||
try (Stream<UnblockableDomainChange> changes =
|
||||
gcsClient.readRefreshChanges(schedule.jobName())) {
|
||||
// Unblockables with changes in REASON are removed then added back. That is why they are
|
||||
// included in this stage.
|
||||
Optional<String> report =
|
||||
JsonSerializations.toUnblockableDomainsRemovalReport(
|
||||
changes
|
||||
.filter(UnblockableDomainChange::isDelete)
|
||||
.filter(UnblockableDomainChange::isChangeOrDelete)
|
||||
.map(UnblockableDomainChange::domainName));
|
||||
if (report.isPresent()) {
|
||||
gcsClient.logRemovedUnblockableDomainsReport(
|
||||
@@ -153,12 +158,12 @@ public class BsaRefreshAction implements Runnable {
|
||||
Optional<String> report =
|
||||
JsonSerializations.toUnblockableDomainsReport(
|
||||
changes
|
||||
.filter(UnblockableDomainChange::isAddOrChange)
|
||||
.filter(UnblockableDomainChange::isNewOrChange)
|
||||
.map(UnblockableDomainChange::newValue));
|
||||
if (report.isPresent()) {
|
||||
gcsClient.logRemovedUnblockableDomainsReport(
|
||||
gcsClient.logAddedUnblockableDomainsReport(
|
||||
schedule.jobName(), LINE_SPLITTER.splitToStream(report.get()));
|
||||
bsaReportSender.removeUnblockableDomainsUpdates(report.get());
|
||||
bsaReportSender.addUnblockableDomainsUpdates(report.get());
|
||||
} else {
|
||||
logger.atInfo().log("No new Unblockable domains to add.");
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ public class BsaStringUtils {
|
||||
public static final Splitter LINE_SPLITTER = Splitter.on('\n');
|
||||
|
||||
public static String getLabelInDomain(String domainName) {
|
||||
List<String> parts = DOMAIN_SPLITTER.limit(1).splitToList(domainName);
|
||||
List<String> parts = DOMAIN_SPLITTER.splitToList(domainName);
|
||||
checkArgument(!parts.isEmpty(), "Not a valid domain: [%s]", domainName);
|
||||
return parts.get(0);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
import google.registry.persistence.transaction.TransactionManager.ThrowingRunnable;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
@@ -39,10 +40,16 @@ public final class BsaTransactions {
|
||||
return tm().transact(work, TRANSACTION_REPEATABLE_READ);
|
||||
}
|
||||
|
||||
public static void bsaTransact(ThrowingRunnable work) {
|
||||
verify(!isInTransaction(), "May only be used for top-level transactions.");
|
||||
tm().transact(work, TRANSACTION_REPEATABLE_READ);
|
||||
}
|
||||
|
||||
@CanIgnoreReturnValue
|
||||
public static <T> T bsaQuery(Callable<T> work) {
|
||||
verify(!isInTransaction(), "May only be used for top-level transactions.");
|
||||
return replicaTm().transact(work, TRANSACTION_REPEATABLE_READ);
|
||||
// TRANSACTION_REPEATABLE_READ is default on replica.
|
||||
return replicaTm().transact(work);
|
||||
}
|
||||
|
||||
private BsaTransactions() {}
|
||||
|
||||
@@ -161,7 +161,7 @@ public class GcsClient {
|
||||
}
|
||||
|
||||
Stream<UnblockableDomainChange> readRefreshChanges(String jobName) {
|
||||
BlobId blobId = getBlobId(jobName, UNBLOCKABLE_DOMAINS_FILE);
|
||||
BlobId blobId = getBlobId(jobName, REFRESHED_UNBLOCKABLE_DOMAINS_FILE);
|
||||
return readStream(blobId).map(UnblockableDomainChange::deserialize);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -28,9 +28,9 @@ import java.util.List;
|
||||
// TODO(1/15/2024): rename to UnblockableDomain.
|
||||
@AutoValue
|
||||
public abstract class UnblockableDomain {
|
||||
abstract String domainName();
|
||||
public abstract String domainName();
|
||||
|
||||
abstract Reason reason();
|
||||
public abstract Reason reason();
|
||||
|
||||
/** Reasons why a valid domain name cannot be blocked. */
|
||||
public enum Reason {
|
||||
|
||||
@@ -52,12 +52,16 @@ public abstract class UnblockableDomainChange {
|
||||
return UnblockableDomain.of(unblockable().domainName(), newReason().get());
|
||||
}
|
||||
|
||||
public boolean isAddOrChange() {
|
||||
public boolean isNewOrChange() {
|
||||
return newReason().isPresent();
|
||||
}
|
||||
|
||||
public boolean isChangeOrDelete() {
|
||||
return !isNew();
|
||||
}
|
||||
|
||||
public boolean isDelete() {
|
||||
return !this.isAddOrChange();
|
||||
return !this.isNewOrChange();
|
||||
}
|
||||
|
||||
public boolean isNew() {
|
||||
|
||||
@@ -105,6 +105,10 @@ class BsaUnblockableDomain {
|
||||
return new BsaUnblockableDomain(parts.get(0), parts.get(1), reason);
|
||||
}
|
||||
|
||||
static BsaUnblockableDomain of(UnblockableDomain unblockable) {
|
||||
return of(unblockable.domainName(), Reason.valueOf(unblockable.reason().name()));
|
||||
}
|
||||
|
||||
static VKey<BsaUnblockableDomain> vKey(String domainName) {
|
||||
ImmutableList<String> parts = ImmutableList.copyOf(DOMAIN_SPLITTER.splitToList(domainName));
|
||||
verify(parts.size() == 2, "Invalid domain name: %s", domainName);
|
||||
|
||||
@@ -18,6 +18,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.bsa.BsaTransactions.bsaQuery;
|
||||
import static google.registry.bsa.BsaTransactions.bsaTransact;
|
||||
import static google.registry.bsa.ReservedDomainsUtils.getAllReservedNames;
|
||||
import static google.registry.bsa.ReservedDomainsUtils.isReservedDomain;
|
||||
import static google.registry.bsa.persistence.Queries.batchReadUnblockables;
|
||||
@@ -44,6 +45,7 @@ import google.registry.model.domain.Domain;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.model.tld.Tld.TldType;
|
||||
import google.registry.util.BatchedStreams;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
@@ -107,6 +109,8 @@ public final class DomainsRefresher {
|
||||
|
||||
ImmutableSet<String> upgradedDomains =
|
||||
upgrades.stream().map(UnblockableDomainChange::domainName).collect(toImmutableSet());
|
||||
// Upgrades are collected in its own txn after downgrades so need reconciliation. Conflicts are
|
||||
// unlikely but possible: domains may be recreated or reserved names added during the interval.
|
||||
ImmutableList<UnblockableDomainChange> trueDowngrades =
|
||||
downgrades.stream()
|
||||
.filter(c -> !upgradedDomains.contains(c.domainName()))
|
||||
@@ -198,22 +202,50 @@ public final class DomainsRefresher {
|
||||
}
|
||||
|
||||
public ImmutableList<UnblockableDomainChange> getNewUnblockables() {
|
||||
return bsaQuery(
|
||||
() -> {
|
||||
// TODO(weiminyu): both methods below use `queryBsaLabelByLabels`. Should combine.
|
||||
ImmutableSet<String> newCreated = getNewlyCreatedUnblockables(prevRefreshStartTime, now);
|
||||
ImmutableSet<String> newReserved =
|
||||
getNewlyReservedUnblockables(now, transactionBatchSize);
|
||||
SetView<String> reservedNotCreated = Sets.difference(newReserved, newCreated);
|
||||
return Streams.concat(
|
||||
newCreated.stream()
|
||||
.map(name -> UnblockableDomain.of(name, Reason.REGISTERED))
|
||||
.map(UnblockableDomainChange::ofNew),
|
||||
reservedNotCreated.stream()
|
||||
.map(name -> UnblockableDomain.of(name, Reason.RESERVED))
|
||||
.map(UnblockableDomainChange::ofNew))
|
||||
.collect(toImmutableList());
|
||||
});
|
||||
// TODO(weiminyu): both methods below use `queryBsaLabelByLabels`. Should combine in a single
|
||||
// query.
|
||||
HashSet<String> newCreated =
|
||||
Sets.newHashSet(bsaQuery(() -> getNewlyCreatedUnblockables(prevRefreshStartTime, now)));
|
||||
// We cannot identify new reserved unblockables so must look at all of them. There are not many
|
||||
// of these.
|
||||
HashSet<String> allReserved =
|
||||
Sets.newHashSet(bsaQuery(() -> getAllReservedUnblockables(now, transactionBatchSize)));
|
||||
HashSet<String> reservedNotCreated = Sets.newHashSet(Sets.difference(allReserved, newCreated));
|
||||
|
||||
ImmutableList.Builder<UnblockableDomainChange> changes = new ImmutableList.Builder<>();
|
||||
ImmutableList<BsaUnblockableDomain> batch;
|
||||
Optional<BsaUnblockableDomain> lastRead = Optional.empty();
|
||||
do {
|
||||
batch = batchReadUnblockables(lastRead, transactionBatchSize);
|
||||
if (batch.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
lastRead = Optional.of(batch.get(batch.size() - 1));
|
||||
for (BsaUnblockableDomain unblockable : batch) {
|
||||
String domainName = unblockable.domainName();
|
||||
if (unblockable.reason.equals(BsaUnblockableDomain.Reason.REGISTERED)) {
|
||||
newCreated.remove(domainName);
|
||||
reservedNotCreated.remove(domainName);
|
||||
} else {
|
||||
reservedNotCreated.remove(domainName);
|
||||
if (newCreated.remove(domainName)) {
|
||||
changes.add(
|
||||
UnblockableDomainChange.ofChanged(
|
||||
unblockable.toUnblockableDomain(), Reason.REGISTERED));
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (batch.size() == transactionBatchSize);
|
||||
|
||||
Streams.concat(
|
||||
newCreated.stream()
|
||||
.map(name -> UnblockableDomain.of(name, Reason.REGISTERED))
|
||||
.map(UnblockableDomainChange::ofNew),
|
||||
reservedNotCreated.stream()
|
||||
.map(name -> UnblockableDomain.of(name, Reason.RESERVED))
|
||||
.map(UnblockableDomainChange::ofNew))
|
||||
.forEach(changes::add);
|
||||
return changes.build();
|
||||
}
|
||||
|
||||
static ImmutableSet<String> getNewlyCreatedUnblockables(
|
||||
@@ -225,18 +257,18 @@ public final class DomainsRefresher {
|
||||
.collect(toImmutableSet());
|
||||
ImmutableSet<String> liveDomains =
|
||||
queryNewlyCreatedDomains(bsaEnabledTlds, prevRefreshStartTime, now);
|
||||
return getUnblockedDomainNames(liveDomains);
|
||||
return getBlockedDomainNames(liveDomains);
|
||||
}
|
||||
|
||||
static ImmutableSet<String> getNewlyReservedUnblockables(DateTime now, int batchSize) {
|
||||
static ImmutableSet<String> getAllReservedUnblockables(DateTime now, int batchSize) {
|
||||
Stream<String> allReserved = getAllReservedNames(now);
|
||||
return BatchedStreams.toBatches(allReserved, batchSize)
|
||||
.map(DomainsRefresher::getUnblockedDomainNames)
|
||||
.map(DomainsRefresher::getBlockedDomainNames)
|
||||
.flatMap(ImmutableSet::stream)
|
||||
.collect(toImmutableSet());
|
||||
}
|
||||
|
||||
static ImmutableSet<String> getUnblockedDomainNames(ImmutableCollection<String> domainNames) {
|
||||
static ImmutableSet<String> getBlockedDomainNames(ImmutableCollection<String> domainNames) {
|
||||
Map<String, List<String>> labelToNames =
|
||||
domainNames.stream().collect(groupingBy(BsaStringUtils::getLabelInDomain));
|
||||
ImmutableSet<String> bsaLabels =
|
||||
@@ -244,7 +276,7 @@ public final class DomainsRefresher {
|
||||
.map(BsaLabel::getLabel)
|
||||
.collect(toImmutableSet());
|
||||
return labelToNames.entrySet().stream()
|
||||
.filter(entry -> !bsaLabels.contains(entry.getKey()))
|
||||
.filter(entry -> bsaLabels.contains(entry.getKey()))
|
||||
.map(Entry::getValue)
|
||||
.flatMap(List::stream)
|
||||
.collect(toImmutableSet());
|
||||
@@ -257,20 +289,21 @@ public final class DomainsRefresher {
|
||||
.collect(
|
||||
groupingBy(
|
||||
change -> change.isDelete() ? "remove" : "change", toImmutableSet())));
|
||||
tm().transact(
|
||||
() -> {
|
||||
if (changesByType.containsKey("remove")) {
|
||||
tm().delete(
|
||||
changesByType.get("remove").stream()
|
||||
.map(c -> BsaUnblockableDomain.vKey(c.domainName()))
|
||||
.collect(toImmutableSet()));
|
||||
}
|
||||
if (changesByType.containsKey("change")) {
|
||||
tm().putAll(
|
||||
changesByType.get("change").stream()
|
||||
.map(UnblockableDomainChange::newValue)
|
||||
.collect(toImmutableSet()));
|
||||
}
|
||||
});
|
||||
bsaTransact(
|
||||
() -> {
|
||||
if (changesByType.containsKey("remove")) {
|
||||
tm().delete(
|
||||
changesByType.get("remove").stream()
|
||||
.map(c -> BsaUnblockableDomain.vKey(c.domainName()))
|
||||
.collect(toImmutableSet()));
|
||||
}
|
||||
if (changesByType.containsKey("change")) {
|
||||
tm().putAll(
|
||||
changesByType.get("change").stream()
|
||||
.map(UnblockableDomainChange::newValue)
|
||||
.map(BsaUnblockableDomain::of)
|
||||
.collect(toImmutableSet()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -721,6 +721,17 @@ public final class RegistryConfig {
|
||||
return "gs://" + billingBucket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns origin part of the URL of the billing invoice file
|
||||
*
|
||||
* @see google.registry.beam.billing.InvoicingPipeline
|
||||
*/
|
||||
@Provides
|
||||
@Config("billingInvoiceOriginUrl")
|
||||
public static String provideBillingInvoiceOriginUrl(RegistryConfigSettings config) {
|
||||
return config.billing.billingInvoiceOriginUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not we should publish invoices to partners automatically by default.
|
||||
*
|
||||
|
||||
@@ -173,6 +173,7 @@ public class RegistryConfigSettings {
|
||||
public List<String> invoiceEmailRecipients;
|
||||
public String invoiceReplyToEmailAddress;
|
||||
public String invoiceFilePrefix;
|
||||
public String billingInvoiceOriginUrl;
|
||||
}
|
||||
|
||||
/** Configuration for Registry Data Escrow (RDE). */
|
||||
|
||||
@@ -381,6 +381,7 @@ billing:
|
||||
# Optional return address that overrides the default.
|
||||
invoiceReplyToEmailAddress: null
|
||||
invoiceFilePrefix: REG-INV
|
||||
billingInvoiceOriginUrl: https://billing-origin-url/
|
||||
|
||||
rde:
|
||||
# URL prefix of ICANN's server to upload RDE reports to. Nomulus adds /TLD/ID
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# Example of a TLD configuration file. TLD configuration files are stored in
|
||||
# the internal repository and synced regularly to the database using the
|
||||
# release/cloudbuild-tld-sync.yaml job. The file can be created using the exact
|
||||
# output from the getTldCommand of an existing TLD.
|
||||
|
||||
addGracePeriodLength: "PT432000S"
|
||||
allowedFullyQualifiedHostNames: []
|
||||
allowedRegistrantContactIds: []
|
||||
anchorTenantAddGracePeriodLength: "PT2592000S"
|
||||
autoRenewGracePeriodLength: "PT3888000S"
|
||||
automaticTransferLength: "PT432000S"
|
||||
claimsPeriodEnd: "294247-01-10T04:00:54.775Z"
|
||||
createBillingCost:
|
||||
currency: "USD"
|
||||
amount: 8.00
|
||||
creationTime: "2017-01-03T19:52:47.770Z"
|
||||
currency: "USD"
|
||||
defaultPromoTokens: []
|
||||
dnsAPlusAaaaTtl: null
|
||||
dnsDsTtl: null
|
||||
dnsNsTtl: null
|
||||
dnsPaused: true
|
||||
dnsWriters:
|
||||
- "VoidDnsWriter"
|
||||
driveFolderId: null
|
||||
eapFeeSchedule:
|
||||
"1970-01-01T00:00:00.000Z":
|
||||
currency: "USD"
|
||||
amount: 0.00
|
||||
escrowEnabled: false
|
||||
idnTables: []
|
||||
invoicingEnabled: false
|
||||
lordnUsername: null
|
||||
numDnsPublishLocks: 1
|
||||
pendingDeleteLength: "PT432000S"
|
||||
premiumListName: null
|
||||
pricingEngineClassName: "google.registry.model.pricing.StaticPremiumListPricingEngine"
|
||||
redemptionGracePeriodLength: "PT2592000S"
|
||||
registryLockOrUnlockBillingCost:
|
||||
currency: "USD"
|
||||
amount: 0.00
|
||||
renewBillingCostTransitions:
|
||||
"1970-01-01T00:00:00.000Z":
|
||||
currency: "USD"
|
||||
amount: 8.00
|
||||
renewGracePeriodLength: "PT432000S"
|
||||
reservedListNames: []
|
||||
restoreBillingCost:
|
||||
currency: "USD"
|
||||
amount: 50.00
|
||||
roidSuffix: "EXAMPLE"
|
||||
serverStatusChangeBillingCost:
|
||||
currency: "USD"
|
||||
amount: 0.00
|
||||
tldStateTransitions:
|
||||
"1970-01-01T00:00:00.000Z": "GENERAL_AVAILABILITY"
|
||||
tldStr: "example"
|
||||
tldType: "TEST"
|
||||
tldUnicode: "example"
|
||||
transferGracePeriodLength: "PT432000S"
|
||||
+2
-3
@@ -1,10 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
|
||||
|
||||
<runtime>java8</runtime>
|
||||
<runtime>java17</runtime>
|
||||
<service>backend</service>
|
||||
<!--app-engine-apis>true</app-engine-apis-->
|
||||
<threadsafe>true</threadsafe>
|
||||
<app-engine-apis>true</app-engine-apis>
|
||||
<sessions-enabled>true</sessions-enabled>
|
||||
<instance-class>B4_1G</instance-class>
|
||||
<basic-scaling>
|
||||
|
||||
+2
-3
@@ -1,10 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
|
||||
|
||||
<runtime>java8</runtime>
|
||||
<runtime>java17</runtime>
|
||||
<service>bsa</service>
|
||||
<!--app-engine-apis>true</app-engine-apis-->
|
||||
<threadsafe>true</threadsafe>
|
||||
<app-engine-apis>true</app-engine-apis>
|
||||
<sessions-enabled>true</sessions-enabled>
|
||||
<instance-class>B4_1G</instance-class>
|
||||
<basic-scaling>
|
||||
|
||||
+2
-3
@@ -1,10 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
|
||||
|
||||
<runtime>java8</runtime>
|
||||
<runtime>java17</runtime>
|
||||
<service>default</service>
|
||||
<!--app-engine-apis>true</app-engine-apis-->
|
||||
<threadsafe>true</threadsafe>
|
||||
<app-engine-apis>true</app-engine-apis>
|
||||
<sessions-enabled>true</sessions-enabled>
|
||||
<instance-class>B4_1G</instance-class>
|
||||
<manual-scaling>
|
||||
|
||||
Vendored
+12
@@ -285,6 +285,18 @@
|
||||
<schedule>0 * * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/bsaRefresh]]></url>
|
||||
<name>bsaRefresh</name>
|
||||
<service>bsa</service>
|
||||
<description>
|
||||
Checks for changes in registered domains and reserved labels, and updates
|
||||
the unblockable domains list.
|
||||
</description>
|
||||
<!-- Runs every 30 minutes. -->
|
||||
<schedule>15,45 * * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/uploadBsaUnavailableNames]]></url>
|
||||
<name>uploadBsaUnavailableNames</name>
|
||||
|
||||
+2
-3
@@ -1,10 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
|
||||
|
||||
<runtime>java8</runtime>
|
||||
<runtime>java17</runtime>
|
||||
<service>pubapi</service>
|
||||
<!--app-engine-apis>true</app-engine-apis-->
|
||||
<threadsafe>true</threadsafe>
|
||||
<app-engine-apis>true</app-engine-apis>
|
||||
<sessions-enabled>true</sessions-enabled>
|
||||
<instance-class>B4_1G</instance-class>
|
||||
<manual-scaling>
|
||||
|
||||
+2
-3
@@ -1,10 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
|
||||
|
||||
<runtime>java8</runtime>
|
||||
<runtime>java17</runtime>
|
||||
<service>tools</service>
|
||||
<!--app-engine-apis>true</app-engine-apis-->
|
||||
<threadsafe>true</threadsafe>
|
||||
<app-engine-apis>true</app-engine-apis>
|
||||
<sessions-enabled>true</sessions-enabled>
|
||||
<instance-class>B4_1G</instance-class>
|
||||
<basic-scaling>
|
||||
|
||||
+2
-3
@@ -1,10 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
|
||||
|
||||
<runtime>java8</runtime>
|
||||
<runtime>java17</runtime>
|
||||
<service>backend</service>
|
||||
<!--app-engine-apis>true</app-engine-apis-->
|
||||
<threadsafe>true</threadsafe>
|
||||
<app-engine-apis>true</app-engine-apis>
|
||||
<sessions-enabled>true</sessions-enabled>
|
||||
<instance-class>B4</instance-class>
|
||||
<basic-scaling>
|
||||
|
||||
+2
-3
@@ -1,10 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
|
||||
|
||||
<runtime>java8</runtime>
|
||||
<runtime>java17</runtime>
|
||||
<service>bsa</service>
|
||||
<!--app-engine-apis>true</app-engine-apis-->
|
||||
<threadsafe>true</threadsafe>
|
||||
<app-engine-apis>true</app-engine-apis>
|
||||
<sessions-enabled>true</sessions-enabled>
|
||||
<instance-class>B4</instance-class>
|
||||
<basic-scaling>
|
||||
|
||||
+2
-3
@@ -1,10 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
|
||||
|
||||
<runtime>java8</runtime>
|
||||
<runtime>java17</runtime>
|
||||
<service>default</service>
|
||||
<!--app-engine-apis>true</app-engine-apis-->
|
||||
<threadsafe>true</threadsafe>
|
||||
<app-engine-apis>true</app-engine-apis>
|
||||
<sessions-enabled>true</sessions-enabled>
|
||||
<instance-class>B4_1G</instance-class>
|
||||
<manual-scaling>
|
||||
|
||||
+12
@@ -174,6 +174,18 @@
|
||||
<schedule>0 * * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/bsaRefresh]]></url>
|
||||
<name>bsaRefresh</name>
|
||||
<service>bsa</service>
|
||||
<description>
|
||||
Checks for changes in registered domains and reserved labels, and updates
|
||||
the unblockable domains list.
|
||||
</description>
|
||||
<!-- Runs every 30 minutes. -->
|
||||
<schedule>15,45 * * * *</schedule>
|
||||
</task>
|
||||
|
||||
<task>
|
||||
<url><![CDATA[/_dr/task/uploadBsaUnavailableNames]]></url>
|
||||
<name>uploadBsaUnavailableNames</name>
|
||||
|
||||
+2
-3
@@ -1,10 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
|
||||
|
||||
<runtime>java8</runtime>
|
||||
<runtime>java17</runtime>
|
||||
<service>pubapi</service>
|
||||
<!--app-engine-apis>true</app-engine-apis-->
|
||||
<threadsafe>true</threadsafe>
|
||||
<app-engine-apis>true</app-engine-apis>
|
||||
<sessions-enabled>true</sessions-enabled>
|
||||
<instance-class>B4_1G</instance-class>
|
||||
<manual-scaling>
|
||||
|
||||
+2
-3
@@ -1,10 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
|
||||
|
||||
<runtime>java8</runtime>
|
||||
<runtime>java17</runtime>
|
||||
<service>tools</service>
|
||||
<!--app-engine-apis>true</app-engine-apis-->
|
||||
<threadsafe>true</threadsafe>
|
||||
<app-engine-apis>true</app-engine-apis>
|
||||
<sessions-enabled>true</sessions-enabled>
|
||||
<instance-class>B4</instance-class>
|
||||
<basic-scaling>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -118,6 +118,14 @@ public class GcsUtils implements Serializable {
|
||||
storage().delete(blobId);
|
||||
}
|
||||
|
||||
/** Update file content type on existing GCS file */
|
||||
public void updateContentType(BlobId blobId, String contentType) throws StorageException {
|
||||
if (existsAndNotEmpty(blobId)) {
|
||||
Blob blob = storage().get(blobId);
|
||||
blob.toBuilder().setContentType(contentType).build().update();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all object names within a bucket for a given prefix.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
|
||||
+1
@@ -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.
|
||||
*
|
||||
|
||||
@@ -15,20 +15,17 @@
|
||||
package google.registry.reporting.billing;
|
||||
|
||||
import static com.google.common.base.Throwables.getRootCause;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.cloud.storage.BlobId;
|
||||
import com.google.cloud.storage.StorageException;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.io.CharStreams;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.MediaType;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.gcs.GcsUtils;
|
||||
import google.registry.groups.GmailClient;
|
||||
import google.registry.reporting.billing.BillingModule.InvoiceDirectoryPrefix;
|
||||
import google.registry.util.EmailMessage;
|
||||
import google.registry.util.EmailMessage.Attachment;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
@@ -37,6 +34,7 @@ import org.joda.time.YearMonth;
|
||||
/** Utility functions for sending emails involving monthly invoices. */
|
||||
public class BillingEmailUtils {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
private final GmailClient gmailClient;
|
||||
private final YearMonth yearMonth;
|
||||
private final InternetAddress outgoingEmailAddress;
|
||||
@@ -46,6 +44,7 @@ public class BillingEmailUtils {
|
||||
private final String billingBucket;
|
||||
private final String invoiceFilePrefix;
|
||||
private final String invoiceDirectoryPrefix;
|
||||
private final String billingInvoiceOriginUrl;
|
||||
private final GcsUtils gcsUtils;
|
||||
|
||||
@Inject
|
||||
@@ -58,6 +57,7 @@ public class BillingEmailUtils {
|
||||
@Config("invoiceReplyToEmailAddress") Optional<InternetAddress> replyToEmailAddress,
|
||||
@Config("billingBucket") String billingBucket,
|
||||
@Config("invoiceFilePrefix") String invoiceFilePrefix,
|
||||
@Config("billingInvoiceOriginUrl") String billingInvoiceOriginUrl,
|
||||
@InvoiceDirectoryPrefix String invoiceDirectoryPrefix,
|
||||
GcsUtils gcsUtils) {
|
||||
this.gmailClient = gmailClient;
|
||||
@@ -69,31 +69,36 @@ public class BillingEmailUtils {
|
||||
this.billingBucket = billingBucket;
|
||||
this.invoiceFilePrefix = invoiceFilePrefix;
|
||||
this.invoiceDirectoryPrefix = invoiceDirectoryPrefix;
|
||||
this.billingInvoiceOriginUrl = billingInvoiceOriginUrl;
|
||||
this.gcsUtils = gcsUtils;
|
||||
}
|
||||
|
||||
/** Sends an e-mail to all expected recipients with an attached overall invoice from GCS. */
|
||||
void emailOverallInvoice() {
|
||||
public void emailOverallInvoice() {
|
||||
try {
|
||||
String invoiceFile = String.format("%s-%s.csv", invoiceFilePrefix, yearMonth);
|
||||
String fileUrl = billingInvoiceOriginUrl + invoiceDirectoryPrefix + invoiceFile;
|
||||
BlobId invoiceFilename = BlobId.of(billingBucket, invoiceDirectoryPrefix + invoiceFile);
|
||||
try (InputStream in = gcsUtils.openInputStream(invoiceFilename)) {
|
||||
gmailClient.sendEmail(
|
||||
EmailMessage.newBuilder()
|
||||
.setSubject(String.format("Domain Registry invoice data %s", yearMonth))
|
||||
.setBody(
|
||||
String.format("Attached is the %s invoice for the domain registry.", yearMonth))
|
||||
.setFrom(outgoingEmailAddress)
|
||||
.setRecipients(invoiceEmailRecipients)
|
||||
.setReplyToEmailAddress(replyToEmailAddress)
|
||||
.setAttachment(
|
||||
Attachment.newBuilder()
|
||||
.setContent(CharStreams.toString(new InputStreamReader(in, UTF_8)))
|
||||
.setContentType(MediaType.CSV_UTF_8)
|
||||
.setFilename(invoiceFile)
|
||||
.build())
|
||||
.build());
|
||||
try {
|
||||
gcsUtils.updateContentType(invoiceFilename, "text/csv");
|
||||
} catch (StorageException e) {
|
||||
// We want to continue with email anyway, it just will be less convenient for billing team
|
||||
// to process the file.
|
||||
logger.atWarning().withCause(e).log("Failed to update invoice file type");
|
||||
}
|
||||
gmailClient.sendEmail(
|
||||
EmailMessage.newBuilder()
|
||||
.setSubject(String.format("Domain Registry invoice data %s", yearMonth))
|
||||
.setBody(
|
||||
String.format(
|
||||
"<p>Use the following link to download %s invoice for the domain registry -"
|
||||
+ " <a href=\"%s\">invoice</a>.</p>",
|
||||
yearMonth, fileUrl))
|
||||
.setFrom(outgoingEmailAddress)
|
||||
.setRecipients(invoiceEmailRecipients)
|
||||
.setReplyToEmailAddress(replyToEmailAddress)
|
||||
.setContentType(MediaType.HTML_UTF_8)
|
||||
.build());
|
||||
} catch (Throwable e) {
|
||||
// Strip one layer, because callWithRetry wraps in a RuntimeException
|
||||
sendAlertEmail(
|
||||
|
||||
@@ -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;
|
||||
@@ -105,11 +112,20 @@ public class ConfigureTldCommand extends MutatingCommand {
|
||||
|
||||
@Override
|
||||
protected void init() throws Exception {
|
||||
if (RegistryToolEnvironment.get().equals(RegistryToolEnvironment.PRODUCTION)) {
|
||||
checkArgument(
|
||||
buildEnv || breakGlass != null,
|
||||
"Either the --break_glass or --build_environment flag must be used when"
|
||||
+ " running the configure_tld command on Production");
|
||||
}
|
||||
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();
|
||||
@@ -140,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 =
|
||||
@@ -192,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()))
|
||||
@@ -201,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());
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.ParameterException;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import com.beust.jcommander.ParametersDelegate;
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
@@ -218,12 +219,21 @@ final class RegistryCli implements CommandRunner {
|
||||
|
||||
// Reset the JPA transaction manager after every command to avoid a situation where a test can
|
||||
// interfere with other tests
|
||||
JpaTransactionManager cachedJpaTm = tm();
|
||||
TransactionManagerFactory.setJpaTm(() -> component.nomulusToolJpaTransactionManager().get());
|
||||
TransactionManagerFactory.setReplicaJpaTm(
|
||||
() -> component.nomulusToolReplicaJpaTransactionManager().get());
|
||||
command.run();
|
||||
TransactionManagerFactory.setJpaTm(() -> cachedJpaTm);
|
||||
try {
|
||||
JpaTransactionManager cachedJpaTm = tm();
|
||||
TransactionManagerFactory.setJpaTm(() -> component.nomulusToolJpaTransactionManager().get());
|
||||
TransactionManagerFactory.setReplicaJpaTm(
|
||||
() -> component.nomulusToolReplicaJpaTransactionManager().get());
|
||||
command.run();
|
||||
TransactionManagerFactory.setJpaTm(() -> cachedJpaTm);
|
||||
} catch (Exception e) {
|
||||
String env = Ascii.toLowerCase(environment.name());
|
||||
System.err.printf(
|
||||
"Could not get tool transaction manager; try running nomulus -e %s logout "
|
||||
+ "and then nomulus -e %s login.\n",
|
||||
env, env);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
void setEnvironment(RegistryToolEnvironment environment) {
|
||||
|
||||
@@ -35,11 +35,22 @@ class UpdatePremiumListCommand extends CreateOrUpdatePremiumListCommand {
|
||||
description = "Does not execute the entity mutation")
|
||||
boolean dryRun;
|
||||
|
||||
// indicates if there is a new change made by this command
|
||||
@Parameter(
|
||||
names = {"--build_environment"},
|
||||
description =
|
||||
"DO NOT USE THIS FLAG ON THE COMMAND LINE! This flag indicates the command is being run"
|
||||
+ " by the build environment tools. This flag should never be used by a human user"
|
||||
+ " from the command line.")
|
||||
boolean buildEnv;
|
||||
|
||||
// Indicates if there is a new change made by this command
|
||||
private boolean newChange = false;
|
||||
|
||||
@Override
|
||||
protected String prompt() throws Exception {
|
||||
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,10 +14,12 @@
|
||||
|
||||
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;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import com.google.common.base.Strings;
|
||||
import google.registry.model.tld.label.ReservedList;
|
||||
@@ -28,8 +30,28 @@ import java.util.List;
|
||||
@Parameters(separators = " =", commandDescription = "Update a ReservedList.")
|
||||
final class UpdateReservedListCommand extends CreateOrUpdateReservedListCommand {
|
||||
|
||||
@Parameter(
|
||||
names = {"-d", "--dry_run"},
|
||||
description = "Does not execute the entity mutation")
|
||||
boolean dryRun;
|
||||
|
||||
@Parameter(
|
||||
names = {"--build_environment"},
|
||||
description =
|
||||
"DO NOT USE THIS FLAG ON THE COMMAND LINE! This flag indicates the command is being run"
|
||||
+ " by the build environment tools. This flag should never be used by a human user"
|
||||
+ " from the command line.")
|
||||
boolean buildEnv;
|
||||
|
||||
// indicates if there is a new change made by this command
|
||||
private boolean newChange = true;
|
||||
|
||||
@Override
|
||||
protected String prompt() throws Exception {
|
||||
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)
|
||||
@@ -54,6 +76,7 @@ final class UpdateReservedListCommand extends CreateOrUpdateReservedListCommand
|
||||
.getReservedListEntries()
|
||||
.equals(reservedList.getReservedListEntries());
|
||||
if (!shouldPublishChanged && !reservedListEntriesChanged) {
|
||||
newChange = false;
|
||||
return "No entity changes to apply.";
|
||||
}
|
||||
String result = String.format("Update reserved list for %s?\n", name);
|
||||
@@ -70,4 +93,9 @@ final class UpdateReservedListCommand extends CreateOrUpdateReservedListCommand
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean dontRunCommand() {
|
||||
return dryRun || !newChange;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,15 +118,8 @@ class BsaDownloadFunctionalTest {
|
||||
gcsClient.readBlockList(downloadJob, BlockListType.BLOCK_PLUS)) {
|
||||
assertThat(blockListFile).containsExactly(BSA_CSV_HEADER, "abc,2", "def,3");
|
||||
}
|
||||
ImmutableList<String> persistedLabels =
|
||||
ImmutableList.copyOf(
|
||||
tm().transact(
|
||||
() ->
|
||||
tm().getEntityManager()
|
||||
.createNativeQuery("SELECT label from \"BsaLabel\"")
|
||||
.getResultList()));
|
||||
// TODO(weiminyu): check intermediate files
|
||||
assertThat(persistedLabels).containsExactly("abc", "def");
|
||||
assertThat(getPersistedLabels()).containsExactly("abc", "def");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,331 @@
|
||||
// Copyright 2023 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.bsa;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.common.truth.Truth8.assertThat;
|
||||
import static google.registry.bsa.ReservedDomainsTestingUtils.addReservedDomainToList;
|
||||
import static google.registry.bsa.ReservedDomainsTestingUtils.addReservedListsToTld;
|
||||
import static google.registry.bsa.ReservedDomainsTestingUtils.createReservedList;
|
||||
import static google.registry.bsa.ReservedDomainsTestingUtils.removeReservedDomainFromList;
|
||||
import static google.registry.bsa.persistence.BsaTestingUtils.createDownloadScheduler;
|
||||
import static google.registry.bsa.persistence.BsaTestingUtils.persistBsaLabel;
|
||||
import static google.registry.bsa.persistence.BsaTestingUtils.queryUnblockableDomains;
|
||||
import static google.registry.model.tld.Tlds.getTldEntitiesOfType;
|
||||
import static google.registry.model.tld.label.ReservationType.RESERVED_FOR_SPECIFIC_USE;
|
||||
import static google.registry.testing.DatabaseHelper.createTlds;
|
||||
import static google.registry.testing.DatabaseHelper.deleteTestDomain;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
|
||||
import com.google.cloud.storage.contrib.nio.testing.LocalStorageHelper;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.bsa.api.BsaReportSender;
|
||||
import google.registry.bsa.api.UnblockableDomain;
|
||||
import google.registry.bsa.api.UnblockableDomain.Reason;
|
||||
import google.registry.bsa.api.UnblockableDomainChange;
|
||||
import google.registry.bsa.persistence.BsaTestingUtils;
|
||||
import google.registry.gcs.GcsUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.tld.Tld.TldType;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationWithCoverageExtension;
|
||||
import google.registry.request.Response;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.FakeLockHandler;
|
||||
import google.registry.testing.FakeResponse;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
/**
|
||||
* Functional tests for refreshing the unblockable domains with recent registration and reservation
|
||||
* changes.
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class BsaRefreshFunctionalTest {
|
||||
|
||||
static final DateTime TEST_START_TIME = DateTime.parse("2024-01-01T00:00:00Z");
|
||||
|
||||
static final String RESERVED_LIST_NAME = "reserved";
|
||||
|
||||
private final FakeClock fakeClock = new FakeClock(TEST_START_TIME);
|
||||
|
||||
@RegisterExtension
|
||||
JpaIntegrationWithCoverageExtension jpa =
|
||||
new JpaTestExtensions.Builder().withClock(fakeClock).buildIntegrationWithCoverageExtension();
|
||||
|
||||
@Mock BsaReportSender bsaReportSender;
|
||||
|
||||
private GcsClient gcsClient;
|
||||
private Response response;
|
||||
private BsaRefreshAction action;
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
gcsClient =
|
||||
new GcsClient(new GcsUtils(LocalStorageHelper.getOptions()), "my-bucket", "SHA-256");
|
||||
response = new FakeResponse();
|
||||
action =
|
||||
new BsaRefreshAction(
|
||||
BsaTestingUtils.createRefreshScheduler(),
|
||||
gcsClient,
|
||||
bsaReportSender,
|
||||
/* transactionBatchSize= */ 5,
|
||||
/* domainCreateTxnCommitTimeLag= */ Duration.millis(1),
|
||||
new BsaLock(
|
||||
new FakeLockHandler(/* lockSucceeds= */ true), Duration.standardSeconds(30)),
|
||||
fakeClock,
|
||||
response);
|
||||
|
||||
initDb();
|
||||
}
|
||||
|
||||
private String getRefreshJobName(DateTime jobStartTime) {
|
||||
return jobStartTime.toString() + "-refresh";
|
||||
}
|
||||
|
||||
private void initDb() {
|
||||
createTlds("app", "dev");
|
||||
getTldEntitiesOfType(TldType.REAL)
|
||||
.forEach(
|
||||
tld ->
|
||||
persistResource(
|
||||
tld.asBuilder().setBsaEnrollStartTime(Optional.of(START_OF_TIME)).build()));
|
||||
|
||||
createReservedList(RESERVED_LIST_NAME, "dummy", RESERVED_FOR_SPECIFIC_USE);
|
||||
addReservedListsToTld("app", ImmutableList.of(RESERVED_LIST_NAME));
|
||||
|
||||
persistBsaLabel("blocked1");
|
||||
persistBsaLabel("blocked2");
|
||||
// Creates a download record so that refresher will not quit immediately.
|
||||
createDownloadScheduler(fakeClock).schedule().get().updateJobStage(DownloadStage.DONE);
|
||||
fakeClock.advanceOneMilli();
|
||||
}
|
||||
|
||||
@Test
|
||||
void newReservedDomain_addedAsUnblockable() throws Exception {
|
||||
addReservedDomainToList(
|
||||
RESERVED_LIST_NAME, ImmutableMap.of("blocked1", RESERVED_FOR_SPECIFIC_USE));
|
||||
String jobName = getRefreshJobName(fakeClock.nowUtc());
|
||||
action.run();
|
||||
UnblockableDomain newUnblockable = UnblockableDomain.of("blocked1.app", Reason.RESERVED);
|
||||
assertThat(queryUnblockableDomains()).containsExactly(newUnblockable);
|
||||
assertThat(gcsClient.readRefreshChanges(jobName))
|
||||
.containsExactly(UnblockableDomainChange.ofNew(newUnblockable));
|
||||
verify(bsaReportSender, never()).removeUnblockableDomainsUpdates(anyString());
|
||||
verify(bsaReportSender, times(1))
|
||||
.addUnblockableDomainsUpdates("{\n \"reserved\": [\n \"blocked1.app\"\n ]\n}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void newRegisteredDomain_addedAsUnblockable() throws Exception {
|
||||
persistActiveDomain("blocked1.dev", fakeClock.nowUtc());
|
||||
persistActiveDomain("dummy.dev", fakeClock.nowUtc());
|
||||
String jobName = getRefreshJobName(fakeClock.nowUtc());
|
||||
action.run();
|
||||
UnblockableDomain newUnblockable = UnblockableDomain.of("blocked1.dev", Reason.REGISTERED);
|
||||
assertThat(queryUnblockableDomains()).containsExactly(newUnblockable);
|
||||
assertThat(gcsClient.readRefreshChanges(jobName))
|
||||
.containsExactly(UnblockableDomainChange.ofNew(newUnblockable));
|
||||
|
||||
verify(bsaReportSender, never()).removeUnblockableDomainsUpdates(anyString());
|
||||
verify(bsaReportSender, times(1))
|
||||
.addUnblockableDomainsUpdates("{\n \"registered\": [\n \"blocked1.dev\"\n ]\n}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void registeredUnblockable_unregistered() {
|
||||
Domain domain = persistActiveDomain("blocked1.dev", fakeClock.nowUtc());
|
||||
action.run();
|
||||
assertThat(queryUnblockableDomains())
|
||||
.containsExactly(UnblockableDomain.of("blocked1.dev", Reason.REGISTERED));
|
||||
fakeClock.advanceOneMilli();
|
||||
deleteTestDomain(domain, fakeClock.nowUtc());
|
||||
fakeClock.advanceOneMilli();
|
||||
|
||||
String jobName = getRefreshJobName(fakeClock.nowUtc());
|
||||
Mockito.reset(bsaReportSender);
|
||||
action.run();
|
||||
assertThat(queryUnblockableDomains()).isEmpty();
|
||||
assertThat(gcsClient.readRefreshChanges(jobName))
|
||||
.containsExactly(
|
||||
UnblockableDomainChange.ofDeleted(
|
||||
UnblockableDomain.of("blocked1.dev", Reason.REGISTERED)));
|
||||
|
||||
verify(bsaReportSender, never()).addUnblockableDomainsUpdates(anyString());
|
||||
verify(bsaReportSender, times(1)).removeUnblockableDomainsUpdates("[\n \"blocked1.dev\"\n]");
|
||||
}
|
||||
|
||||
@Test
|
||||
void reservedUnblockable_noLongerReserved() {
|
||||
addReservedDomainToList(
|
||||
RESERVED_LIST_NAME, ImmutableMap.of("blocked1", RESERVED_FOR_SPECIFIC_USE));
|
||||
action.run();
|
||||
assertThat(queryUnblockableDomains())
|
||||
.containsExactly(UnblockableDomain.of("blocked1.app", Reason.RESERVED));
|
||||
fakeClock.advanceOneMilli();
|
||||
removeReservedDomainFromList(RESERVED_LIST_NAME, ImmutableSet.of("blocked1"));
|
||||
|
||||
String jobName = getRefreshJobName(fakeClock.nowUtc());
|
||||
Mockito.reset(bsaReportSender);
|
||||
action.run();
|
||||
assertThat(queryUnblockableDomains()).isEmpty();
|
||||
assertThat(gcsClient.readRefreshChanges(jobName))
|
||||
.containsExactly(
|
||||
UnblockableDomainChange.ofDeleted(
|
||||
UnblockableDomain.of("blocked1.app", Reason.RESERVED)));
|
||||
|
||||
verify(bsaReportSender, never()).addUnblockableDomainsUpdates(anyString());
|
||||
verify(bsaReportSender, times(1)).removeUnblockableDomainsUpdates("[\n \"blocked1.app\"\n]");
|
||||
}
|
||||
|
||||
@Test
|
||||
void registeredAndReservedUnblockable_noLongerRegistered_stillUnblockable() throws Exception {
|
||||
addReservedDomainToList(
|
||||
RESERVED_LIST_NAME, ImmutableMap.of("blocked1", RESERVED_FOR_SPECIFIC_USE));
|
||||
Domain domain = persistActiveDomain("blocked1.app", fakeClock.nowUtc());
|
||||
action.run();
|
||||
assertThat(queryUnblockableDomains())
|
||||
.containsExactly(UnblockableDomain.of("blocked1.app", Reason.REGISTERED));
|
||||
fakeClock.advanceOneMilli();
|
||||
deleteTestDomain(domain, fakeClock.nowUtc());
|
||||
fakeClock.advanceOneMilli();
|
||||
|
||||
String jobName = getRefreshJobName(fakeClock.nowUtc());
|
||||
Mockito.reset(bsaReportSender);
|
||||
action.run();
|
||||
assertThat(queryUnblockableDomains())
|
||||
.containsExactly(UnblockableDomain.of("blocked1.app", Reason.RESERVED));
|
||||
assertThat(gcsClient.readRefreshChanges(jobName))
|
||||
.containsExactly(
|
||||
UnblockableDomainChange.ofChanged(
|
||||
UnblockableDomain.of("blocked1.app", Reason.REGISTERED), Reason.RESERVED));
|
||||
InOrder inOrder = Mockito.inOrder(bsaReportSender);
|
||||
inOrder.verify(bsaReportSender).removeUnblockableDomainsUpdates("[\n \"blocked1.app\"\n]");
|
||||
inOrder
|
||||
.verify(bsaReportSender)
|
||||
.addUnblockableDomainsUpdates("{\n \"reserved\": [\n \"blocked1.app\"\n ]\n}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void reservedUblockable_becomesRegistered_changeToRegisterd() throws Exception {
|
||||
addReservedDomainToList(
|
||||
RESERVED_LIST_NAME, ImmutableMap.of("blocked1", RESERVED_FOR_SPECIFIC_USE));
|
||||
action.run();
|
||||
assertThat(queryUnblockableDomains())
|
||||
.containsExactly(UnblockableDomain.of("blocked1.app", Reason.RESERVED));
|
||||
fakeClock.advanceOneMilli();
|
||||
persistActiveDomain("blocked1.app", fakeClock.nowUtc());
|
||||
fakeClock.advanceOneMilli();
|
||||
|
||||
Mockito.reset(bsaReportSender);
|
||||
String jobName = getRefreshJobName(fakeClock.nowUtc());
|
||||
action.run();
|
||||
UnblockableDomain changed = UnblockableDomain.of("blocked1.app", Reason.REGISTERED);
|
||||
assertThat(queryUnblockableDomains()).containsExactly(changed);
|
||||
assertThat(gcsClient.readRefreshChanges(jobName))
|
||||
.containsExactly(
|
||||
UnblockableDomainChange.ofChanged(
|
||||
UnblockableDomain.of("blocked1.app", Reason.RESERVED), Reason.REGISTERED));
|
||||
InOrder inOrder = Mockito.inOrder(bsaReportSender);
|
||||
inOrder.verify(bsaReportSender).removeUnblockableDomainsUpdates("[\n \"blocked1.app\"\n]");
|
||||
inOrder
|
||||
.verify(bsaReportSender)
|
||||
.addUnblockableDomainsUpdates("{\n \"registered\": [\n \"blocked1.app\"\n ]\n}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void newRegisteredAndReservedDomain_addedAsRegisteredUnblockable() throws Exception {
|
||||
addReservedDomainToList(
|
||||
RESERVED_LIST_NAME, ImmutableMap.of("blocked1", RESERVED_FOR_SPECIFIC_USE));
|
||||
persistActiveDomain("blocked1.app", fakeClock.nowUtc());
|
||||
String jobName = getRefreshJobName(fakeClock.nowUtc());
|
||||
action.run();
|
||||
UnblockableDomain newUnblockable = UnblockableDomain.of("blocked1.app", Reason.REGISTERED);
|
||||
assertThat(queryUnblockableDomains()).containsExactly(newUnblockable);
|
||||
assertThat(gcsClient.readRefreshChanges(jobName))
|
||||
.containsExactly(UnblockableDomainChange.ofNew(newUnblockable));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registeredAndReservedUnblockable_noLongerReserved_noChange() throws Exception {
|
||||
addReservedDomainToList(
|
||||
RESERVED_LIST_NAME, ImmutableMap.of("blocked1", RESERVED_FOR_SPECIFIC_USE));
|
||||
persistActiveDomain("blocked1.app", fakeClock.nowUtc());
|
||||
action.run();
|
||||
assertThat(queryUnblockableDomains())
|
||||
.containsExactly(UnblockableDomain.of("blocked1.app", Reason.REGISTERED));
|
||||
fakeClock.advanceOneMilli();
|
||||
removeReservedDomainFromList(RESERVED_LIST_NAME, ImmutableSet.of("blocked1"));
|
||||
fakeClock.advanceOneMilli();
|
||||
|
||||
Mockito.reset(bsaReportSender);
|
||||
String jobName = getRefreshJobName(fakeClock.nowUtc());
|
||||
action.run();
|
||||
assertThat(queryUnblockableDomains())
|
||||
.containsExactly(UnblockableDomain.of("blocked1.app", Reason.REGISTERED));
|
||||
// Verify that refresh change file does not exist (404 error) since there is no change.
|
||||
assertThat(
|
||||
assertThrows(
|
||||
UncheckedIOException.class, () -> gcsClient.readRefreshChanges(jobName).findAny()))
|
||||
.hasMessageThat()
|
||||
.contains("404");
|
||||
verifyNoInteractions(bsaReportSender);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registeredUblockable_becomesReserved_noChange() throws Exception {
|
||||
persistActiveDomain("blocked1.app", fakeClock.nowUtc());
|
||||
action.run();
|
||||
assertThat(queryUnblockableDomains())
|
||||
.containsExactly(UnblockableDomain.of("blocked1.app", Reason.REGISTERED));
|
||||
fakeClock.advanceOneMilli();
|
||||
addReservedDomainToList(
|
||||
RESERVED_LIST_NAME, ImmutableMap.of("blocked1", RESERVED_FOR_SPECIFIC_USE));
|
||||
fakeClock.advanceOneMilli();
|
||||
|
||||
Mockito.reset(bsaReportSender);
|
||||
String jobName = getRefreshJobName(fakeClock.nowUtc());
|
||||
action.run();
|
||||
assertThat(queryUnblockableDomains())
|
||||
.containsExactly(UnblockableDomain.of("blocked1.app", Reason.REGISTERED));
|
||||
// Verify that refresh change file does not exist (404 error) since there is no change.
|
||||
assertThat(
|
||||
assertThrows(
|
||||
UncheckedIOException.class, () -> gcsClient.readRefreshChanges(jobName).findAny()))
|
||||
.hasMessageThat()
|
||||
.contains("404");
|
||||
verifyNoInteractions(bsaReportSender);
|
||||
}
|
||||
}
|
||||
@@ -62,4 +62,43 @@ public final class ReservedDomainsTestingUtils {
|
||||
.build();
|
||||
persistResource(tld.asBuilder().setReservedListsByName(reservedLists).build());
|
||||
}
|
||||
|
||||
public static void addReservedDomainToList(
|
||||
String listName, ImmutableMap<String, ReservationType> reservedLabels) {
|
||||
ImmutableMap<String, ReservedListEntry> existingEntries =
|
||||
ReservedList.get(listName).get().getReservedListEntries();
|
||||
ImmutableMap<String, ReservedListEntry> newEntries =
|
||||
ImmutableMap.copyOf(
|
||||
Maps.transformEntries(
|
||||
reservedLabels, (key, value) -> ReservedListEntry.create(key, value, "")));
|
||||
|
||||
ReservedListDao.save(
|
||||
new ReservedList.Builder()
|
||||
.setName(listName)
|
||||
.setCreationTimestamp(START_OF_TIME)
|
||||
.setShouldPublish(true)
|
||||
.setReservedListMap(
|
||||
new ImmutableMap.Builder<String, ReservedListEntry>()
|
||||
.putAll(existingEntries)
|
||||
.putAll(newEntries)
|
||||
.buildKeepingLast())
|
||||
.build());
|
||||
}
|
||||
|
||||
public static void removeReservedDomainFromList(
|
||||
String listName, ImmutableSet<String> removedLabels) {
|
||||
ImmutableMap<String, ReservedListEntry> existingEntries =
|
||||
ReservedList.get(listName).get().getReservedListEntries();
|
||||
ImmutableMap<String, ReservedListEntry> newEntries =
|
||||
ImmutableMap.copyOf(
|
||||
Maps.filterEntries(existingEntries, entry -> !removedLabels.contains(entry.getKey())));
|
||||
|
||||
ReservedListDao.save(
|
||||
new ReservedList.Builder()
|
||||
.setName(listName)
|
||||
.setCreationTimestamp(START_OF_TIME)
|
||||
.setShouldPublish(true)
|
||||
.setReservedListMap(newEntries)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,10 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class UploadBsaUnavailableDomainsActionTest {
|
||||
|
||||
private static final String BUCKET = "domain-registry-bsa";
|
||||
|
||||
private static final String API_URL = "https://upload.test/bsa";
|
||||
|
||||
private final FakeClock clock = new FakeClock(DateTime.parse("2024-02-02T02:02:02Z"));
|
||||
|
||||
@RegisterExtension
|
||||
@@ -62,10 +66,6 @@ public class UploadBsaUnavailableDomainsActionTest {
|
||||
|
||||
private final GcsUtils gcsUtils = new GcsUtils(LocalStorageHelper.getOptions());
|
||||
|
||||
private final String BUCKET = "domain-registry-bsa";
|
||||
|
||||
private final String API_URL = "https://upload.test/bsa";
|
||||
|
||||
private final FakeResponse response = new FakeResponse();
|
||||
|
||||
@BeforeEach
|
||||
|
||||
@@ -14,8 +14,11 @@
|
||||
|
||||
package google.registry.bsa.persistence;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.bsa.api.UnblockableDomain;
|
||||
import google.registry.util.Clock;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
@@ -35,7 +38,21 @@ public final class BsaTestingUtils {
|
||||
tm().transact(() -> tm().put(new BsaLabel(domainLabel, BSA_LABEL_CREATION_TIME)));
|
||||
}
|
||||
|
||||
public static void persistUnblockableDomain(UnblockableDomain unblockableDomain) {
|
||||
tm().transact(() -> tm().put(BsaUnblockableDomain.of(unblockableDomain)));
|
||||
}
|
||||
|
||||
public static DownloadScheduler createDownloadScheduler(Clock clock) {
|
||||
return new DownloadScheduler(DEFAULT_DOWNLOAD_INTERVAL, DEFAULT_NOP_INTERVAL, clock);
|
||||
}
|
||||
|
||||
public static RefreshScheduler createRefreshScheduler() {
|
||||
return new RefreshScheduler();
|
||||
}
|
||||
|
||||
public static ImmutableList<UnblockableDomain> queryUnblockableDomains() {
|
||||
return tm().transact(() -> tm().loadAllOf(BsaUnblockableDomain.class)).stream()
|
||||
.map(BsaUnblockableDomain::toUnblockableDomain)
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ package google.registry.bsa.persistence;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.common.truth.Truth8.assertThat;
|
||||
import static google.registry.bsa.ReservedDomainsTestingUtils.addReservedListsToTld;
|
||||
import static google.registry.bsa.ReservedDomainsTestingUtils.createReservedList;
|
||||
import static google.registry.bsa.persistence.LabelDiffUpdates.applyLabelDiff;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
@@ -26,7 +28,6 @@ import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import google.registry.bsa.IdnChecker;
|
||||
@@ -36,9 +37,6 @@ import google.registry.bsa.api.UnblockableDomain;
|
||||
import google.registry.bsa.persistence.BsaUnblockableDomain.Reason;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.model.tld.label.ReservationType;
|
||||
import google.registry.model.tld.label.ReservedList;
|
||||
import google.registry.model.tld.label.ReservedList.ReservedListEntry;
|
||||
import google.registry.model.tld.label.ReservedListDao;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationWithCoverageExtension;
|
||||
import google.registry.testing.FakeClock;
|
||||
@@ -139,18 +137,8 @@ class LabelDiffUpdatesTest {
|
||||
@Test
|
||||
void applyLabelDiffs_newLabel() {
|
||||
persistActiveDomain("label.app");
|
||||
ReservedListDao.save(
|
||||
new ReservedList.Builder()
|
||||
.setReservedListMap(
|
||||
ImmutableMap.of(
|
||||
"label",
|
||||
ReservedListEntry.create(
|
||||
"label", ReservationType.RESERVED_FOR_SPECIFIC_USE, null)))
|
||||
.setName("page_reserved")
|
||||
.setCreationTimestamp(fakeClock.nowUtc())
|
||||
.build());
|
||||
ReservedList reservedList = ReservedList.get("page_reserved").get();
|
||||
tm().transact(() -> tm().put(page.asBuilder().setReservedLists(reservedList).build()));
|
||||
createReservedList("page_reserved", "label", ReservationType.RESERVED_FOR_SPECIFIC_USE);
|
||||
addReservedListsToTld("page", ImmutableList.of("page_reserved"));
|
||||
|
||||
when(idnChecker.getForbiddingTlds(any()))
|
||||
.thenReturn(Sets.difference(ImmutableSet.of(dev), ImmutableSet.of()).immutableCopy());
|
||||
|
||||
@@ -208,6 +208,32 @@ class QueriesTest {
|
||||
.containsExactly("d1.tld", "d2.tld");
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryNewlyCreatedDomains_onlyDomainsAfterMinCreationTimeReturned() {
|
||||
DateTime testStartTime = fakeClock.nowUtc();
|
||||
createTlds("tld");
|
||||
persistNewRegistrar("TheRegistrar");
|
||||
// time 0:
|
||||
persistResource(
|
||||
newDomain("d1.tld").asBuilder().setCreationTimeForTest(fakeClock.nowUtc()).build());
|
||||
// time 0, deletion time 1
|
||||
persistDomainAsDeleted(
|
||||
newDomain("will-delete.tld").asBuilder().setCreationTimeForTest(fakeClock.nowUtc()).build(),
|
||||
fakeClock.nowUtc().plusMillis(1));
|
||||
fakeClock.advanceOneMilli();
|
||||
// time 1
|
||||
persistResource(
|
||||
newDomain("d2.tld").asBuilder().setCreationTimeForTest(fakeClock.nowUtc()).build());
|
||||
fakeClock.advanceOneMilli();
|
||||
// Now is time 2, ask for domains created since time 1
|
||||
assertThat(
|
||||
bsaQuery(
|
||||
() ->
|
||||
queryNewlyCreatedDomains(
|
||||
ImmutableList.of("tld"), testStartTime.plusMillis(1), fakeClock.nowUtc())))
|
||||
.containsExactly("d2.tld");
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryNewlyCreatedDomains_onlyDomainsInRequestedTldsReturned() {
|
||||
DateTime testStartTime = fakeClock.nowUtc();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -21,17 +21,12 @@ import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.cloud.storage.BlobId;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.net.MediaType;
|
||||
import google.registry.gcs.GcsUtils;
|
||||
import google.registry.groups.GmailClient;
|
||||
import google.registry.util.EmailMessage;
|
||||
import google.registry.util.EmailMessage.Attachment;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Optional;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
@@ -45,18 +40,14 @@ class BillingEmailUtilsTest {
|
||||
|
||||
private GmailClient gmailClient;
|
||||
private BillingEmailUtils emailUtils;
|
||||
private GcsUtils gcsUtils;
|
||||
private ArgumentCaptor<EmailMessage> contentCaptor;
|
||||
private GcsUtils gcsUtils;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() throws Exception {
|
||||
gmailClient = mock(GmailClient.class);
|
||||
gcsUtils = mock(GcsUtils.class);
|
||||
when(gcsUtils.openInputStream(BlobId.of("test-bucket", "results/REG-INV-2017-10.csv")))
|
||||
.thenReturn(
|
||||
new ByteArrayInputStream("test,data\nhello,world".getBytes(StandardCharsets.UTF_8)));
|
||||
contentCaptor = ArgumentCaptor.forClass(EmailMessage.class);
|
||||
|
||||
gcsUtils = mock(GcsUtils.class);
|
||||
emailUtils = getEmailUtils(Optional.of(new InternetAddress("reply-to@test.com")));
|
||||
}
|
||||
|
||||
@@ -72,6 +63,7 @@ class BillingEmailUtilsTest {
|
||||
replyToAddress,
|
||||
"test-bucket",
|
||||
"REG-INV",
|
||||
"www.google.com/",
|
||||
"results/",
|
||||
gcsUtils);
|
||||
}
|
||||
@@ -89,14 +81,11 @@ class BillingEmailUtilsTest {
|
||||
ImmutableList.of(
|
||||
new InternetAddress("hello@world.com"), new InternetAddress("hola@mundo.com")))
|
||||
.setSubject("Domain Registry invoice data 2017-10")
|
||||
.setBody("Attached is the 2017-10 invoice for the domain registry.")
|
||||
.setBody(
|
||||
"<p>Use the following link to download 2017-10 invoice for the domain registry -"
|
||||
+ " <a href=\"www.google.com/results/REG-INV-2017-10.csv\">invoice</a>.</p>")
|
||||
.setReplyToEmailAddress(new InternetAddress("reply-to@test.com"))
|
||||
.setAttachment(
|
||||
Attachment.newBuilder()
|
||||
.setContent("test,data\nhello,world")
|
||||
.setContentType(MediaType.CSV_UTF_8)
|
||||
.setFilename("REG-INV-2017-10.csv")
|
||||
.build())
|
||||
.setContentType(MediaType.HTML_UTF_8)
|
||||
.build();
|
||||
assertThat(emailMessage).isEqualTo(expectedContent);
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
@@ -671,4 +739,46 @@ public class ConfigureTldCommandTest extends CommandTestCase<ConfigureTldCommand
|
||||
Tld notUpdatedTld = Tld.get("tld");
|
||||
assertThat(notUpdatedTld.getCreateBillingCost()).isEqualTo(Money.of(USD, 13));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_runCommandOnProduction_noFlag() throws Exception {
|
||||
createTld("tld");
|
||||
File tldFile = tmpDir.resolve("tld.yaml").toFile();
|
||||
Files.asCharSink(tldFile, UTF_8).write(loadFile(getClass(), "tld.yaml"));
|
||||
IllegalArgumentException thrown =
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() ->
|
||||
runCommandInEnvironment(RegistryToolEnvironment.PRODUCTION, "--input=" + tldFile));
|
||||
assertThat(thrown.getMessage())
|
||||
.isEqualTo(
|
||||
"Either the --break_glass or --build_environment flag must be used when running the"
|
||||
+ " configure_tld command on Production");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_runCommandOnProduction_breakGlassFlag() throws Exception {
|
||||
createTld("tld");
|
||||
File tldFile = tmpDir.resolve("tld.yaml").toFile();
|
||||
Files.asCharSink(tldFile, UTF_8).write(loadFile(getClass(), "tld.yaml"));
|
||||
runCommandInEnvironment(
|
||||
RegistryToolEnvironment.PRODUCTION, "--input=" + tldFile, "--break_glass=true", "-f");
|
||||
Tld updatedTld = Tld.get("tld");
|
||||
assertThat(updatedTld.getCreateBillingCost()).isEqualTo(Money.of(USD, 25));
|
||||
testTldConfiguredSuccessfully(updatedTld, "tld.yaml");
|
||||
assertThat(updatedTld.getBreakglassMode()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_runCommandOnProduction_buildEnvFlag() throws Exception {
|
||||
createTld("tld");
|
||||
File tldFile = tmpDir.resolve("tld.yaml").toFile();
|
||||
Files.asCharSink(tldFile, UTF_8).write(loadFile(getClass(), "tld.yaml"));
|
||||
runCommandInEnvironment(
|
||||
RegistryToolEnvironment.PRODUCTION, "--input=" + tldFile, "--build_environment", "-f");
|
||||
Tld updatedTld = Tld.get("tld");
|
||||
assertThat(updatedTld.getCreateBillingCost()).isEqualTo(Money.of(USD, 25));
|
||||
testTldConfiguredSuccessfully(updatedTld, "tld.yaml");
|
||||
assertThat(updatedTld.getBreakglassMode()).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -205,4 +205,39 @@ class UpdatePremiumListCommandTest<C extends UpdatePremiumListCommand>
|
||||
.comparingElementsUsing(immutableObjectCorrespondence("revisionId"))
|
||||
.containsExactly(PremiumEntry.create(0L, new BigDecimal("9090.00"), "doge"));
|
||||
}
|
||||
|
||||
@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"));
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -142,4 +142,50 @@ class UpdateReservedListCommandTest
|
||||
assertThat(command.prompt()).contains("baddies: null -> baddies,FULLY_BLOCKED");
|
||||
assertThat(command.prompt()).contains("ford: null -> ford,FULLY_BLOCKED # random comment");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_dryRun() throws Exception {
|
||||
runCommandForced("--input=" + reservedTermsPath, "--dry_run");
|
||||
assertThat(command.prompt()).contains("Update reserved list for xn--q9jyb4c_common-reserved?");
|
||||
assertThat(ReservedList.get("xn--q9jyb4c_common-reserved")).isPresent();
|
||||
ReservedList reservedList = ReservedList.get("xn--q9jyb4c_common-reserved").get();
|
||||
assertThat(reservedList.getReservedListEntries()).hasSize(1);
|
||||
assertThat(reservedList.getReservationInList("helicopter")).hasValue(FULLY_BLOCKED);
|
||||
}
|
||||
|
||||
@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: []
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -150,3 +150,5 @@ V149__add_bsa_domain_in_use_table.sql
|
||||
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 2023 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.
|
||||
|
||||
DROP TABLE IF EXISTS "BsaDomainInUse";
|
||||
@@ -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[],
|
||||
|
||||
@@ -123,18 +123,6 @@ CREATE TABLE public."BillingRecurrence" (
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: BsaDomainInUse; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public."BsaDomainInUse" (
|
||||
label text NOT NULL,
|
||||
tld text NOT NULL,
|
||||
creation_time timestamp with time zone NOT NULL,
|
||||
reason text NOT NULL
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: BsaDomainRefresh; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
@@ -1190,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
|
||||
);
|
||||
|
||||
|
||||
@@ -1369,14 +1358,6 @@ ALTER TABLE ONLY public."BillingRecurrence"
|
||||
ADD CONSTRAINT "BillingRecurrence_pkey" PRIMARY KEY (billing_recurrence_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: BsaDomainInUse BsaDomainInUse_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public."BsaDomainInUse"
|
||||
ADD CONSTRAINT "BsaDomainInUse_pkey" PRIMARY KEY (label, tld);
|
||||
|
||||
|
||||
--
|
||||
-- Name: BsaDomainRefresh BsaDomainRefresh_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
@@ -2781,14 +2762,6 @@ ALTER TABLE ONLY public."DomainHistoryHost"
|
||||
ADD CONSTRAINT fka9woh3hu8gx5x0vly6bai327n FOREIGN KEY (domain_history_domain_repo_id, domain_history_history_revision_id) REFERENCES public."DomainHistory"(domain_repo_id, history_revision_id) DEFERRABLE INITIALLY DEFERRED;
|
||||
|
||||
|
||||
--
|
||||
-- Name: BsaDomainInUse fkbsadomaininuse2label; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public."BsaDomainInUse"
|
||||
ADD CONSTRAINT fkbsadomaininuse2label FOREIGN KEY (label) REFERENCES public."BsaLabel"(label) ON DELETE CASCADE;
|
||||
|
||||
|
||||
--
|
||||
-- Name: BsaUnblockableDomain fkbsaunblockabledomainlabel; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -40,37 +40,22 @@ where:
|
||||
show show the effect of the formatting as unified diff"
|
||||
|
||||
SCRIPT_DIR="$(realpath $(dirname $0))"
|
||||
JAR_NAME="google-java-format-1.8-all-deps.jar"
|
||||
JAR_NAME="google-java-format-1.19.2-all-deps.jar"
|
||||
|
||||
# Make sure we have a valid python interpreter.
|
||||
if [ -z "$PYTHON" ]; then
|
||||
echo "You must specify the name of a python3 interpreter in the PYTHON" \
|
||||
echo "You must specify the name of a Python interpreter in the PYTHON" \
|
||||
"environment variable."
|
||||
exit 1
|
||||
elif ! "$PYTHON" -c ''; then
|
||||
echo "Invalid python interpreter: $PYTHON"
|
||||
echo "Invalid Python interpreter: $PYTHON"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Locate the java binary.
|
||||
if [ -n "$JAVA_HOME" ]; then
|
||||
JAVA_BIN="$JAVA_HOME/bin/java"
|
||||
if [ ! -x "$JAVA_BIN" ]; then
|
||||
echo "No java binary found in JAVA_HOME (JAVA_HOME is $JAVA_HOME)"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# Use java from the path.
|
||||
JAVA_BIN="$(which java)" || JAVA_BIN=""
|
||||
if [ -z "$JAVA_BIN" ]; then
|
||||
echo "JAVA_HOME is not defined and java was not found on the path"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! "$JAVA_BIN" -version 2>&1 | grep 'version "11\.' >/dev/null; then
|
||||
echo "Bad java version. Requires java 11, got:"
|
||||
"$JAVA_BIN" -version
|
||||
# Make sure we have a valid JRE binary
|
||||
if [ -z "$JAVA" ]; then
|
||||
echo "You must specify the name of a JRE binary in the JAVA" \
|
||||
"environment variable."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -80,7 +65,7 @@ function runGoogleJavaFormatAgainstDiffs() {
|
||||
|
||||
git diff -U0 "$forkPoint" | \
|
||||
"${PYTHON}" "${SCRIPT_DIR}/google-java-format-diff.py" \
|
||||
--java-binary "$JAVA_BIN" \
|
||||
--java-binary "$JAVA" \
|
||||
--google-java-format-jar "${SCRIPT_DIR}/${JAR_NAME}" \
|
||||
-p1 "$@" | \
|
||||
tee gjf.out
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user