mirror of
https://github.com/google/nomulus
synced 2026-05-24 16:51:49 +00:00
Compare commits
38 Commits
nomulus-20
...
proxy-2020
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
553d5717cb | ||
|
|
1056fdbb64 | ||
|
|
4aaf31be9f | ||
|
|
e30c0f9a11 | ||
|
|
2a5d9c8ef5 | ||
|
|
597f5746a4 | ||
|
|
5bff53a711 | ||
|
|
933394e8c3 | ||
|
|
3b841bbb5b | ||
|
|
798879d031 | ||
|
|
1a63d50b82 | ||
|
|
054571a625 | ||
|
|
7468a9915b | ||
|
|
157d9f75c1 | ||
|
|
b2e4f07bb9 | ||
|
|
5488e1b323 | ||
|
|
5ab0f97351 | ||
|
|
f7b65327da | ||
|
|
36482ce94f | ||
|
|
125f509b46 | ||
|
|
fb7ba80b86 | ||
|
|
a86fcf79f7 | ||
|
|
dc8e095e55 | ||
|
|
cdf2c7f7cb | ||
|
|
ecafebdc3d | ||
|
|
c6c8d21281 | ||
|
|
5f6ea2cbf2 | ||
|
|
393c388e0d | ||
|
|
5a08ce498e | ||
|
|
5db8cbc994 | ||
|
|
bbcafea98e | ||
|
|
1bba68dd96 | ||
|
|
0423c7ae22 | ||
|
|
266bd43792 | ||
|
|
df15b38a1e | ||
|
|
daa8bb6b2c | ||
|
|
ea2a6165e5 | ||
|
|
c36f0c89c8 |
@@ -260,7 +260,7 @@ subprojects {
|
||||
// in the 'configurations' block, the following code must run after
|
||||
// project evaluation, when all configurations have been created.
|
||||
configurations.each {
|
||||
if (it.name != 'dependencyLicenseReport') {
|
||||
if (it.name != 'dependencyLicenseReport' && it.name != 'integration') {
|
||||
it.resolutionStrategy.activateDependencyLocking()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ Pseudo-commands:
|
||||
"""
|
||||
|
||||
# Define all of our special gradle properties here.
|
||||
# TODO(b/169318491): use consistent naming style for properties and variables.
|
||||
PROPERTIES = [
|
||||
Property('mavenUrl',
|
||||
'URL to use for the main maven repository (defaults to maven '
|
||||
@@ -124,6 +125,9 @@ PROPERTIES = [
|
||||
'server/schema integration tests. Please refer to <a '
|
||||
'href="./integration/README.md">integration project</a> for more '
|
||||
'information.'),
|
||||
Property('baseSchemaTag',
|
||||
'The nomulus version tag of the schema for use in the schema'
|
||||
'deployment integration test (:db:schemaIncrementalDeployTest)'),
|
||||
Property('schema_version',
|
||||
'The nomulus version tag of the schema for use in a database'
|
||||
'integration test.'),
|
||||
|
||||
@@ -63,8 +63,6 @@ def dockerIncompatibleTestPatterns = [
|
||||
// methods, so we exclude the whole test class.
|
||||
"google/registry/tools/params/PathParameterTest.*",
|
||||
"google/registry/persistence/PersistenceModuleTest.*",
|
||||
// This test is failing in docker when using Java 11. The cause is unclear.
|
||||
"google/registry/tools/DomainLockUtilsTest.*",
|
||||
]
|
||||
|
||||
// Tests that conflict with members of both the main test suite and the
|
||||
@@ -635,131 +633,6 @@ artifacts {
|
||||
testRuntime testJar
|
||||
}
|
||||
|
||||
/**
|
||||
* We have to break out the test suites because some of the tests conflict
|
||||
* with one another, but unfortunately this breaks the "--tests" flag. The
|
||||
* --tests flag only applies to the task named on the command line (usually
|
||||
* just "test"), not for all tasks of type "Test".
|
||||
*
|
||||
* As a better solution, FilteringTest sets testNameIncludePatterns (the
|
||||
* internal property that --tests sets) from the value of the "testFilter"
|
||||
* property, allowing us to filter across all the tests in core without
|
||||
* explicitly specifying a test task or causing errors because there are no
|
||||
* matching tests in the main task.
|
||||
*
|
||||
* To use it, define "testFilter" to be a comma-separated collection of class
|
||||
* names (wildcards are allowed):
|
||||
*
|
||||
* ./gradlew test -P testFilter=*.FooBar,google.registry.tools.ShellCommandTest
|
||||
*/
|
||||
class FilteringTest extends Test {
|
||||
|
||||
FilteringTest() {
|
||||
useJUnitPlatform();
|
||||
}
|
||||
|
||||
private void applyTestFilter() {
|
||||
if (project.testFilter) {
|
||||
testNameIncludePatterns = project.testFilter.split(',')
|
||||
|
||||
// By default, gradle test tasks will produce a failure if no tests
|
||||
// match the include/exclude/filter rules. Since test filtering allows us
|
||||
// to select a set of tests from a particular task, we don't want this
|
||||
// behavior.
|
||||
filter.failOnNoMatchingTests = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to false if you also want to include TestCase and TestSuite classes.
|
||||
*
|
||||
* <p>Must be defined before "test", if at all.
|
||||
*/
|
||||
boolean excludeTestCases = true
|
||||
|
||||
void setTests(List<String> tests) {
|
||||
// Common exclude pattern. See README in parent directory for explanation.
|
||||
if (excludeTestCases) {
|
||||
exclude "**/*TestCase.*", "**/*TestSuite.*"
|
||||
}
|
||||
include tests
|
||||
applyTestFilter()
|
||||
}
|
||||
|
||||
/**
|
||||
* Include all of the tests (except Test{Case,TestSuite}). This actually
|
||||
* doesn't explicitly "include" anything, in which cast the Test class tries
|
||||
* to include everything that is not explicitly excluded.
|
||||
*/
|
||||
void includeAllTests() {
|
||||
exclude "**/*TestCase.*", "**/*TestSuite.*"
|
||||
applyTestFilter()
|
||||
}
|
||||
}
|
||||
|
||||
task fragileTest(type: FilteringTest) {
|
||||
// Common exclude pattern. See README in parent directory for explanation.
|
||||
tests = fragileTestPatterns
|
||||
|
||||
if (rootProject.findProperty("skipDockerIncompatibleTests") == "true") {
|
||||
exclude dockerIncompatibleTestPatterns
|
||||
}
|
||||
|
||||
// Run every test class in a freshly started process.
|
||||
forkEvery 1
|
||||
|
||||
doFirst {
|
||||
new File(screenshotsDir).deleteDir()
|
||||
}
|
||||
}
|
||||
|
||||
task outcastTest(type: FilteringTest) {
|
||||
tests = outcastTestPatterns
|
||||
|
||||
// Sets the maximum number of test executors that may exist at the same time.
|
||||
// Note that this number appears to contribute to NoClassDefFoundError
|
||||
// exceptions on certain machines and distros. The root cause is unclear.
|
||||
// Try reducing this number if you experience similar problems.
|
||||
maxParallelForks 3
|
||||
}
|
||||
|
||||
// Whitebox test verifying that RegistryTool can be instantiated. Note the
|
||||
// use of runtimeClasspath. This test emulates the logic in RegistryCli#run.
|
||||
// A to-do is added there to refactor.
|
||||
// TODO(weiminyu): Need a similar test for Registry server.
|
||||
task registryToolIntegrationTest {
|
||||
dependsOn compileJava
|
||||
doLast {
|
||||
def classLoader =
|
||||
new URLClassLoader(sourceSets.main.runtimeClasspath.collect {
|
||||
it.toURI().toURL()
|
||||
} as URL[])
|
||||
def commandClasses =
|
||||
(classLoader.loadClass('google.registry.tools.RegistryTool')
|
||||
.getDeclaredField('COMMAND_MAP').get(null) as Map).values()
|
||||
|
||||
commandClasses.each {
|
||||
try {
|
||||
Constructor<?> c = ((Class<?>) it).getDeclaredConstructor()
|
||||
c.setAccessible(true)
|
||||
c.newInstance()
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException("Failed to instantiate ${it}:\n ${e}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dedicated test suite for schema-dependent tests.
|
||||
task sqlIntegrationTest(type: FilteringTest) {
|
||||
// TestSuite still requires a JUnit 4 runner, which knows how to handle JUnit 5 tests.
|
||||
// Here we need to override parent's choice of JUnit 5. If changing this, remember to
|
||||
// change :integration:sqlIntegrationTest too.
|
||||
useJUnit()
|
||||
excludeTestCases = false
|
||||
tests = ['google/registry/schema/integration/SqlIntegrationTestSuite.*']
|
||||
}
|
||||
|
||||
task findGoldenImages(type: JavaExec) {
|
||||
classpath = sourceSets.test.runtimeClasspath
|
||||
main = 'google.registry.webdriver.GoldenImageFinder'
|
||||
@@ -879,39 +752,6 @@ task generateGoldenImages(type: FilteringTest) {
|
||||
}
|
||||
generateGoldenImages.finalizedBy(findGoldenImages)
|
||||
|
||||
task standardTest(type: FilteringTest) {
|
||||
includeAllTests()
|
||||
exclude fragileTestPatterns
|
||||
exclude outcastTestPatterns
|
||||
// See SqlIntegrationTestSuite.java
|
||||
exclude '**/*BeforeSuiteTest.*', '**/*AfterSuiteTest.*'
|
||||
|
||||
if (rootProject.findProperty("skipDockerIncompatibleTests") == "true") {
|
||||
exclude dockerIncompatibleTestPatterns
|
||||
}
|
||||
|
||||
// Run every test class in its own process.
|
||||
// Uncomment to unblock build while troubleshooting inexplicable test errors.
|
||||
// This setting makes the build take 35 minutes, without it it takes about 10.
|
||||
// forkEvery 1
|
||||
|
||||
// Sets the maximum number of test executors that may exist at the same time.
|
||||
// Also, Gradle executes tests in 1 thread and some of our test infrastructures
|
||||
// depend on that, e.g. DualDatabaseTestInvocationContextProvider injects
|
||||
// different implementation of TransactionManager into TransactionManagerFactory.
|
||||
maxParallelForks 5
|
||||
|
||||
systemProperty 'test.projectRoot', rootProject.projectRootDir
|
||||
systemProperty 'test.resourcesDir', resourcesDir
|
||||
}
|
||||
|
||||
test {
|
||||
// Don't run any tests from this task, all testing gets done in the
|
||||
// FilteringTest tasks.
|
||||
exclude "**"
|
||||
// TODO(weiminyu): Remove dependency on sqlIntegrationTest
|
||||
}.dependsOn(fragileTest, outcastTest, standardTest, registryToolIntegrationTest, sqlIntegrationTest)
|
||||
|
||||
createUberJar('nomulus', 'nomulus', 'google.registry.tools.RegistryTool')
|
||||
|
||||
// A jar with classes and resources from main sourceSet, excluding internal
|
||||
@@ -1046,6 +886,147 @@ task runTestServer(dependsOn: copyJsFilesForTestServer, type: JavaExec) {
|
||||
classpath = sourceSets.test.runtimeClasspath
|
||||
}
|
||||
|
||||
/**
|
||||
* We have to break out the test suites because some of the tests conflict
|
||||
* with one another, but unfortunately this breaks the "--tests" flag. The
|
||||
* --tests flag only applies to the task named on the command line (usually
|
||||
* just "test"), not for all tasks of type "Test".
|
||||
*
|
||||
* As a better solution, FilteringTest sets testNameIncludePatterns (the
|
||||
* internal property that --tests sets) from the value of the "testFilter"
|
||||
* property, allowing us to filter across all the tests in core without
|
||||
* explicitly specifying a test task or causing errors because there are no
|
||||
* matching tests in the main task.
|
||||
*
|
||||
* To use it, define "testFilter" to be a comma-separated collection of class
|
||||
* names (wildcards are allowed):
|
||||
*
|
||||
* ./gradlew test -P testFilter=*.FooBar,google.registry.tools.ShellCommandTest
|
||||
*/
|
||||
class FilteringTest extends Test {
|
||||
|
||||
FilteringTest() {
|
||||
useJUnitPlatform();
|
||||
}
|
||||
|
||||
private void applyTestFilter() {
|
||||
if (project.testFilter) {
|
||||
testNameIncludePatterns = project.testFilter.split(',')
|
||||
|
||||
// By default, gradle test tasks will produce a failure if no tests
|
||||
// match the include/exclude/filter rules. Since test filtering allows us
|
||||
// to select a set of tests from a particular task, we don't want this
|
||||
// behavior.
|
||||
filter.failOnNoMatchingTests = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to false if you also want to include TestCase and TestSuite classes.
|
||||
*
|
||||
* <p>Must be defined before "test", if at all.
|
||||
*/
|
||||
boolean excludeTestCases = true
|
||||
|
||||
void setTests(List<String> tests) {
|
||||
// Common exclude pattern. See README in parent directory for explanation.
|
||||
if (excludeTestCases) {
|
||||
exclude "**/*TestCase.*", "**/*TestSuite.*"
|
||||
}
|
||||
include tests
|
||||
applyTestFilter()
|
||||
}
|
||||
|
||||
/**
|
||||
* Include all of the tests (except Test{Case,TestSuite}). This actually
|
||||
* doesn't explicitly "include" anything, in which cast the Test class tries
|
||||
* to include everything that is not explicitly excluded.
|
||||
*/
|
||||
void includeAllTests() {
|
||||
exclude "**/*TestCase.*", "**/*TestSuite.*"
|
||||
applyTestFilter()
|
||||
}
|
||||
}
|
||||
|
||||
task fragileTest(type: FilteringTest) {
|
||||
// Common exclude pattern. See README in parent directory for explanation.
|
||||
tests = fragileTestPatterns
|
||||
|
||||
if (rootProject.findProperty("skipDockerIncompatibleTests") == "true") {
|
||||
exclude dockerIncompatibleTestPatterns
|
||||
}
|
||||
|
||||
// Run every test class in a freshly started process.
|
||||
forkEvery 1
|
||||
|
||||
doFirst {
|
||||
new File(screenshotsDir).deleteDir()
|
||||
}
|
||||
}
|
||||
|
||||
task outcastTest(type: FilteringTest) {
|
||||
tests = outcastTestPatterns
|
||||
|
||||
// Sets the maximum number of test executors that may exist at the same time.
|
||||
// Note that this number appears to contribute to NoClassDefFoundError
|
||||
// exceptions on certain machines and distros. The root cause is unclear.
|
||||
// Try reducing this number if you experience similar problems.
|
||||
maxParallelForks 3
|
||||
}
|
||||
|
||||
// Dedicated test suite for schema-dependent tests.
|
||||
task sqlIntegrationTest(type: FilteringTest) {
|
||||
// TestSuite still requires a JUnit 4 runner, which knows how to handle JUnit 5 tests.
|
||||
// Here we need to override parent's choice of JUnit 5. If changing this, remember to
|
||||
// change :integration:sqlIntegrationTest too.
|
||||
useJUnit()
|
||||
excludeTestCases = false
|
||||
tests = ['google/registry/schema/integration/SqlIntegrationTestSuite.*']
|
||||
}
|
||||
|
||||
// Verifies that RegistryTool can be instantiated:
|
||||
// - All dependencies are packaged in nomulus.jar
|
||||
// - JPA setup succeeds.
|
||||
task registryToolIntegrationTest(dependsOn: nomulus, type: FilteringTest) {
|
||||
tests = ['google/registry/tools/RegistryToolTest.*']
|
||||
testClassesDirs = sourceSets.test.output.classesDirs
|
||||
classpath = nomulus.outputs.files.plus(configurations.testRuntimeClasspath)
|
||||
.plus(files(testClassesDirs))
|
||||
}
|
||||
|
||||
task standardTest(type: FilteringTest) {
|
||||
includeAllTests()
|
||||
exclude fragileTestPatterns
|
||||
exclude outcastTestPatterns
|
||||
// See SqlIntegrationTestSuite.java
|
||||
exclude '**/*BeforeSuiteTest.*', '**/*AfterSuiteTest.*'
|
||||
|
||||
if (rootProject.findProperty("skipDockerIncompatibleTests") == "true") {
|
||||
exclude dockerIncompatibleTestPatterns
|
||||
}
|
||||
|
||||
// Run every test class in its own process.
|
||||
// Uncomment to unblock build while troubleshooting inexplicable test errors.
|
||||
// This setting makes the build take 35 minutes, without it it takes about 10.
|
||||
// forkEvery 1
|
||||
|
||||
// Sets the maximum number of test executors that may exist at the same time.
|
||||
// Also, Gradle executes tests in 1 thread and some of our test infrastructures
|
||||
// depend on that, e.g. DualDatabaseTestInvocationContextProvider injects
|
||||
// different implementation of TransactionManager into TransactionManagerFactory.
|
||||
maxParallelForks 5
|
||||
|
||||
systemProperty 'test.projectRoot', rootProject.projectRootDir
|
||||
systemProperty 'test.resourcesDir', resourcesDir
|
||||
}
|
||||
|
||||
test {
|
||||
// Don't run any tests from this task, all testing gets done in the
|
||||
// FilteringTest tasks.
|
||||
exclude "**"
|
||||
// TODO(weiminyu): Remove dependency on sqlIntegrationTest
|
||||
}.dependsOn(fragileTest, outcastTest, standardTest, registryToolIntegrationTest, sqlIntegrationTest)
|
||||
|
||||
project.build.dependsOn devtool
|
||||
project.build.dependsOn buildToolImage
|
||||
project.build.dependsOn ':stage'
|
||||
|
||||
@@ -6,9 +6,9 @@ aopalliance:aopalliance:1.0
|
||||
args4j:args4j:2.33
|
||||
cglib:cglib-nodep:2.2
|
||||
com.beust:jcommander:1.60
|
||||
com.fasterxml.jackson.core:jackson-annotations:2.10.2
|
||||
com.fasterxml.jackson.core:jackson-core:2.10.2
|
||||
com.fasterxml.jackson.core:jackson-databind:2.10.2
|
||||
com.fasterxml.jackson.core:jackson-annotations:2.11.2
|
||||
com.fasterxml.jackson.core:jackson-core:2.11.2
|
||||
com.fasterxml.jackson.core:jackson-databind:2.11.2
|
||||
com.fasterxml:classmate:1.5.1
|
||||
com.github.jnr:jffi:1.2.23
|
||||
com.github.jnr:jnr-a64asm:1.0.0
|
||||
|
||||
@@ -6,9 +6,9 @@ aopalliance:aopalliance:1.0
|
||||
args4j:args4j:2.33
|
||||
cglib:cglib-nodep:2.2
|
||||
com.beust:jcommander:1.60
|
||||
com.fasterxml.jackson.core:jackson-annotations:2.10.2
|
||||
com.fasterxml.jackson.core:jackson-core:2.10.2
|
||||
com.fasterxml.jackson.core:jackson-databind:2.10.2
|
||||
com.fasterxml.jackson.core:jackson-annotations:2.11.2
|
||||
com.fasterxml.jackson.core:jackson-core:2.11.2
|
||||
com.fasterxml.jackson.core:jackson-databind:2.11.2
|
||||
com.fasterxml:classmate:1.5.1
|
||||
com.github.jnr:jffi:1.2.23
|
||||
com.github.jnr:jnr-a64asm:1.0.0
|
||||
|
||||
@@ -6,9 +6,9 @@ aopalliance:aopalliance:1.0
|
||||
args4j:args4j:2.33
|
||||
cglib:cglib-nodep:2.2
|
||||
com.beust:jcommander:1.60
|
||||
com.fasterxml.jackson.core:jackson-annotations:2.10.2
|
||||
com.fasterxml.jackson.core:jackson-core:2.10.2
|
||||
com.fasterxml.jackson.core:jackson-databind:2.10.2
|
||||
com.fasterxml.jackson.core:jackson-annotations:2.11.2
|
||||
com.fasterxml.jackson.core:jackson-core:2.11.2
|
||||
com.fasterxml.jackson.core:jackson-databind:2.11.2
|
||||
com.fasterxml:classmate:1.5.1
|
||||
com.github.jnr:jffi:1.2.23
|
||||
com.github.jnr:jnr-a64asm:1.0.0
|
||||
|
||||
@@ -6,9 +6,9 @@ aopalliance:aopalliance:1.0
|
||||
args4j:args4j:2.33
|
||||
cglib:cglib-nodep:2.2
|
||||
com.beust:jcommander:1.60
|
||||
com.fasterxml.jackson.core:jackson-annotations:2.10.2
|
||||
com.fasterxml.jackson.core:jackson-core:2.10.2
|
||||
com.fasterxml.jackson.core:jackson-databind:2.10.2
|
||||
com.fasterxml.jackson.core:jackson-annotations:2.11.2
|
||||
com.fasterxml.jackson.core:jackson-core:2.11.2
|
||||
com.fasterxml.jackson.core:jackson-databind:2.11.2
|
||||
com.fasterxml:classmate:1.5.1
|
||||
com.github.jnr:jffi:1.2.23
|
||||
com.github.jnr:jnr-a64asm:1.0.0
|
||||
|
||||
@@ -169,16 +169,20 @@ public final class AsyncTaskEnqueuer {
|
||||
lock.getRelockDuration().isPresent(),
|
||||
"Lock with ID %s not configured for relock",
|
||||
lock.getRevisionId());
|
||||
enqueueDomainRelock(lock.getRelockDuration().get(), lock.getRevisionId(), 0);
|
||||
}
|
||||
|
||||
/** Enqueues a task to asynchronously re-lock a registry-locked domain after it was unlocked. */
|
||||
void enqueueDomainRelock(Duration countdown, long lockRevisionId, int previousAttempts) {
|
||||
String backendHostname = appEngineServiceUtils.getServiceHostname("backend");
|
||||
addTaskToQueueWithRetry(
|
||||
asyncActionsPushQueue,
|
||||
TaskOptions.Builder.withUrl(RelockDomainAction.PATH)
|
||||
.method(Method.POST)
|
||||
.header("Host", backendHostname)
|
||||
.param(
|
||||
RelockDomainAction.OLD_UNLOCK_REVISION_ID_PARAM,
|
||||
String.valueOf(lock.getRevisionId()))
|
||||
.countdownMillis(lock.getRelockDuration().get().getMillis()));
|
||||
.param(RelockDomainAction.OLD_UNLOCK_REVISION_ID_PARAM, String.valueOf(lockRevisionId))
|
||||
.param(RelockDomainAction.PREVIOUS_ATTEMPTS_PARAM, String.valueOf(previousAttempts))
|
||||
.countdownMillis(countdown.getMillis()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,6 +21,7 @@ import static google.registry.batch.AsyncTaskEnqueuer.PARAM_RESOURCE_KEY;
|
||||
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_ACTIONS;
|
||||
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_DELETE;
|
||||
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_HOST_RENAME;
|
||||
import static google.registry.request.RequestParameters.extractIntParameter;
|
||||
import static google.registry.request.RequestParameters.extractLongParameter;
|
||||
import static google.registry.request.RequestParameters.extractOptionalBooleanParameter;
|
||||
import static google.registry.request.RequestParameters.extractOptionalIntParameter;
|
||||
@@ -94,9 +95,15 @@ public class BatchModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("oldUnlockRevisionId")
|
||||
@Parameter(RelockDomainAction.OLD_UNLOCK_REVISION_ID_PARAM)
|
||||
static long provideOldUnlockRevisionId(HttpServletRequest req) {
|
||||
return extractLongParameter(req, "oldUnlockRevisionId");
|
||||
return extractLongParameter(req, RelockDomainAction.OLD_UNLOCK_REVISION_ID_PARAM);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter(RelockDomainAction.PREVIOUS_ATTEMPTS_PARAM)
|
||||
static int providePreviousAttempts(HttpServletRequest req) {
|
||||
return extractIntParameter(req, RelockDomainAction.PREVIOUS_ATTEMPTS_PARAM);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -464,7 +464,7 @@ public class DeleteContactsAndHostsAction implements Runnable {
|
||||
HostResource host = (HostResource) existingResource;
|
||||
if (host.isSubordinate()) {
|
||||
dnsQueue.addHostRefreshTask(host.getHostName());
|
||||
tm().saveNewOrUpdate(
|
||||
tm().put(
|
||||
tm().load(host.getSuperordinateDomain())
|
||||
.asBuilder()
|
||||
.removeSubordinateHost(host.getHostName())
|
||||
|
||||
@@ -15,19 +15,23 @@
|
||||
package google.registry.batch;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.request.Action.Method.POST;
|
||||
import static google.registry.tools.LockOrUnlockDomainCommand.REGISTRY_LOCK_STATUSES;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.MediaType;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarContact;
|
||||
import google.registry.model.registry.RegistryLockDao;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Parameter;
|
||||
@@ -36,11 +40,15 @@ import google.registry.request.auth.Auth;
|
||||
import google.registry.schema.domain.RegistryLock;
|
||||
import google.registry.tools.DomainLockUtils;
|
||||
import google.registry.util.DateTimeUtils;
|
||||
import google.registry.util.EmailMessage;
|
||||
import google.registry.util.SendEmailService;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
import javax.mail.internet.AddressException;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import org.joda.time.Duration;
|
||||
|
||||
/**
|
||||
* Task that relocks a previously-Registry-Locked domain after some predetermined period of time.
|
||||
*/
|
||||
/** Task that re-locks a previously-Registry-Locked domain after a predetermined period of time. */
|
||||
@Action(
|
||||
service = Action.Service.BACKEND,
|
||||
path = RelockDomainAction.PATH,
|
||||
@@ -51,30 +59,78 @@ public class RelockDomainAction implements Runnable {
|
||||
|
||||
public static final String PATH = "/_dr/task/relockDomain";
|
||||
public static final String OLD_UNLOCK_REVISION_ID_PARAM = "oldUnlockRevisionId";
|
||||
public static final String PREVIOUS_ATTEMPTS_PARAM = "previousAttempts";
|
||||
|
||||
static final int ATTEMPTS_BEFORE_SLOWDOWN = 36; // every ten minutes for six hours then every hour
|
||||
static final int FAILURES_BEFORE_EMAIL = 2; // email after three failures, one half hour
|
||||
private static final Duration TEN_MINUTES = Duration.standardMinutes(10);
|
||||
private static final Duration ONE_HOUR = Duration.standardHours(1);
|
||||
|
||||
private static final String RELOCK_SUCCESS_EMAIL_TEMPLATE =
|
||||
"The domain %s was successfully re-locked.\n\nPlease contact support at %s if you have any "
|
||||
+ "questions.";
|
||||
private static final String RELOCK_NON_RETRYABLE_FAILURE_EMAIL_TEMPLATE =
|
||||
"There was an error when automatically re-locking %s. Error message: %s\n\nPlease contact "
|
||||
+ "support at %s if you have any questions.";
|
||||
private static final String RELOCK_TRANSIENT_FAILURE_EMAIL_TEMPLATE =
|
||||
"There was an unexpected error when automatically re-locking %s. We will continue retrying "
|
||||
+ "the lock for five hours. Please contact support at %s if you have any questions";
|
||||
private static final String RELOCK_UNKNOWN_ID_FAILURE_EMAIL_TEMPLATE =
|
||||
"The old lock with revision ID %d is not present or is not accessible";
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private final long oldUnlockRevisionId;
|
||||
private final int previousAttempts;
|
||||
private final InternetAddress alertRecipientAddress;
|
||||
private final InternetAddress gSuiteOutgoingEmailAddress;
|
||||
private final String supportEmail;
|
||||
private final SendEmailService sendEmailService;
|
||||
private final DomainLockUtils domainLockUtils;
|
||||
private final Response response;
|
||||
private final AsyncTaskEnqueuer asyncTaskEnqueuer;
|
||||
|
||||
@Inject
|
||||
public RelockDomainAction(
|
||||
@Parameter(OLD_UNLOCK_REVISION_ID_PARAM) long oldUnlockRevisionId,
|
||||
@Parameter(PREVIOUS_ATTEMPTS_PARAM) int previousAttempts,
|
||||
@Config("alertRecipientEmailAddress") InternetAddress alertRecipientAddress,
|
||||
@Config("gSuiteOutgoingEmailAddress") InternetAddress gSuiteOutgoingEmailAddress,
|
||||
@Config("supportEmail") String supportEmail,
|
||||
SendEmailService sendEmailService,
|
||||
DomainLockUtils domainLockUtils,
|
||||
Response response) {
|
||||
Response response,
|
||||
AsyncTaskEnqueuer asyncTaskEnqueuer) {
|
||||
this.oldUnlockRevisionId = oldUnlockRevisionId;
|
||||
this.previousAttempts = previousAttempts;
|
||||
this.alertRecipientAddress = alertRecipientAddress;
|
||||
this.gSuiteOutgoingEmailAddress = gSuiteOutgoingEmailAddress;
|
||||
this.supportEmail = supportEmail;
|
||||
this.sendEmailService = sendEmailService;
|
||||
this.domainLockUtils = domainLockUtils;
|
||||
this.response = response;
|
||||
this.asyncTaskEnqueuer = asyncTaskEnqueuer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
jpaTm().transact(this::relockDomain);
|
||||
/* We wish to manually control our retry behavior, in order to limit the number of retries
|
||||
* and/or notify registrars / support only after a certain number of retries, or only
|
||||
* with a certain type of failure. AppEngine will automatically retry on any non-2xx status
|
||||
* code, so return SC_NO_CONTENT (204) by default to avoid this auto-retry.
|
||||
*
|
||||
* See https://cloud.google.com/appengine/docs/standard/java/taskqueue/push/retrying-tasks
|
||||
* for more details on retry behavior. */
|
||||
response.setStatus(SC_NO_CONTENT);
|
||||
response.setContentType(MediaType.PLAIN_TEXT_UTF_8);
|
||||
|
||||
// nb: DomainLockUtils relies on the JPA transaction being the outermost transaction
|
||||
jpaTm().transact(() -> tm().transact(this::relockDomain));
|
||||
}
|
||||
|
||||
private void relockDomain() {
|
||||
RegistryLock oldLock;
|
||||
RegistryLock oldLock = null;
|
||||
DomainBase domain;
|
||||
try {
|
||||
oldLock =
|
||||
RegistryLockDao.getByRevisionId(oldUnlockRevisionId)
|
||||
@@ -82,87 +138,187 @@ public class RelockDomainAction implements Runnable {
|
||||
() ->
|
||||
new IllegalArgumentException(
|
||||
String.format("Unknown revision ID %d", oldUnlockRevisionId)));
|
||||
DomainBase domain =
|
||||
domain =
|
||||
ofy()
|
||||
.load()
|
||||
.type(DomainBase.class)
|
||||
.id(oldLock.getRepoId())
|
||||
.now()
|
||||
.cloneProjectedAtTime(jpaTm().getTransactionTime());
|
||||
|
||||
if (domain.getStatusValues().containsAll(REGISTRY_LOCK_STATUSES)
|
||||
|| oldLock.getRelock() != null) {
|
||||
// The domain was manually locked, so we shouldn't worry about relocking
|
||||
String message =
|
||||
String.format(
|
||||
"Domain %s is already manually relocked, skipping automated relock.",
|
||||
domain.getDomainName());
|
||||
logger.atInfo().log(message);
|
||||
// SC_NO_CONTENT (204) skips retry -- see the comment below
|
||||
response.setStatus(SC_NO_CONTENT);
|
||||
response.setContentType(MediaType.PLAIN_TEXT_UTF_8);
|
||||
response.setPayload(message);
|
||||
return;
|
||||
}
|
||||
verifyDomainAndLockState(oldLock, domain);
|
||||
} catch (Throwable t) {
|
||||
/* If there's a bad verification code or the domain is in a bad state, we won't want to retry.
|
||||
* AppEngine will retry on non-2xx error codes, so we return SC_NO_CONTENT (204) to avoid it.
|
||||
*
|
||||
* See https://cloud.google.com/appengine/docs/standard/java/taskqueue/push/retrying-tasks
|
||||
* for more details on retry behavior. */
|
||||
logger.atWarning().withCause(t).log(
|
||||
"Exception when attempting to relock domain with old revision ID %d.",
|
||||
oldUnlockRevisionId);
|
||||
response.setStatus(SC_NO_CONTENT);
|
||||
response.setContentType(MediaType.PLAIN_TEXT_UTF_8);
|
||||
response.setPayload(String.format("Relock failed: %s", t.getMessage()));
|
||||
handleTransientFailure(Optional.ofNullable(oldLock), t);
|
||||
return;
|
||||
}
|
||||
applyRelock(oldLock);
|
||||
|
||||
if (domain.getStatusValues().containsAll(REGISTRY_LOCK_STATUSES)
|
||||
|| oldLock.getRelock() != null) {
|
||||
// The domain was manually locked, so we shouldn't worry about re-locking
|
||||
String message =
|
||||
String.format(
|
||||
"Domain %s is already manually re-locked, skipping automated re-lock.",
|
||||
domain.getDomainName());
|
||||
logger.atInfo().log(message);
|
||||
response.setPayload(message);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
verifyDomainAndLockState(oldLock, domain);
|
||||
} catch (Throwable t) {
|
||||
// If the domain was, for example, transferred, then notify the old registrar and don't retry.
|
||||
handleNonRetryableFailure(oldLock, t);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
applyRelock(oldLock);
|
||||
} catch (Throwable t) {
|
||||
handleTransientFailure(Optional.of(oldLock), t);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyRelock(RegistryLock oldLock) {
|
||||
try {
|
||||
domainLockUtils.administrativelyApplyLock(
|
||||
oldLock.getDomainName(),
|
||||
oldLock.getRegistrarId(),
|
||||
oldLock.getRegistrarPocId(),
|
||||
oldLock.isSuperuser());
|
||||
logger.atInfo().log("Relocked domain %s.", oldLock.getDomainName());
|
||||
response.setStatus(SC_OK);
|
||||
} catch (Throwable t) {
|
||||
// Any errors that occur here are unexpected, so we should retry. Return a non-2xx
|
||||
// error code to get AppEngine to retry
|
||||
logger.atSevere().withCause(t).log(
|
||||
"Exception when attempting to relock domain %s.", oldLock.getDomainName());
|
||||
response.setStatus(SC_INTERNAL_SERVER_ERROR);
|
||||
response.setContentType(MediaType.PLAIN_TEXT_UTF_8);
|
||||
response.setPayload(String.format("Relock failed: %s", t.getMessage()));
|
||||
domainLockUtils.administrativelyApplyLock(
|
||||
oldLock.getDomainName(),
|
||||
oldLock.getRegistrarId(),
|
||||
oldLock.getRegistrarPocId(),
|
||||
oldLock.isSuperuser());
|
||||
logger.atInfo().log("Re-locked domain %s.", oldLock.getDomainName());
|
||||
response.setStatus(SC_OK);
|
||||
// Only send a success email if we previously sent a failure email
|
||||
if (previousAttempts > FAILURES_BEFORE_EMAIL) {
|
||||
sendSuccessEmail(oldLock);
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyDomainAndLockState(RegistryLock oldLock, DomainBase domain) {
|
||||
// Domain shouldn't be deleted or have a pending transfer/delete
|
||||
String domainName = domain.getDomainName();
|
||||
checkArgument(
|
||||
!DateTimeUtils.isAtOrAfter(jpaTm().getTransactionTime(), domain.getDeletionTime()),
|
||||
"Domain %s has been deleted",
|
||||
domainName);
|
||||
ImmutableSet<StatusValue> statusValues = domain.getStatusValues();
|
||||
checkArgument(
|
||||
!statusValues.contains(StatusValue.PENDING_DELETE),
|
||||
"Domain %s has a pending delete",
|
||||
"Domain %s has a pending delete.",
|
||||
domainName);
|
||||
checkArgument(
|
||||
!DateTimeUtils.isAtOrAfter(jpaTm().getTransactionTime(), domain.getDeletionTime()),
|
||||
"Domain %s has been deleted.",
|
||||
domainName);
|
||||
checkArgument(
|
||||
!statusValues.contains(StatusValue.PENDING_TRANSFER),
|
||||
"Domain %s has a pending transfer",
|
||||
"Domain %s has a pending transfer.",
|
||||
domainName);
|
||||
checkArgument(
|
||||
domain.getCurrentSponsorClientId().equals(oldLock.getRegistrarId()),
|
||||
"Domain %s has been transferred from registrar %s to registrar %s since the unlock",
|
||||
"Domain %s has been transferred from registrar %s to registrar %s since the unlock.",
|
||||
domainName,
|
||||
oldLock.getRegistrarId(),
|
||||
domain.getCurrentSponsorClientId());
|
||||
}
|
||||
|
||||
private void handleNonRetryableFailure(RegistryLock oldLock, Throwable t) {
|
||||
logger.atWarning().withCause(t).log(
|
||||
"Exception thrown when attempting to re-lock domain with old revision ID %d.",
|
||||
oldUnlockRevisionId);
|
||||
response.setPayload(String.format("Re-lock failed: %s", t.getMessage()));
|
||||
|
||||
String body =
|
||||
String.format(
|
||||
RELOCK_NON_RETRYABLE_FAILURE_EMAIL_TEMPLATE,
|
||||
oldLock.getDomainName(),
|
||||
t.getMessage(),
|
||||
supportEmail);
|
||||
sendEmailService.sendEmail(
|
||||
EmailMessage.newBuilder()
|
||||
.setFrom(gSuiteOutgoingEmailAddress)
|
||||
.setBody(body)
|
||||
.setSubject(String.format("Error re-locking domain %s", oldLock.getDomainName()))
|
||||
.setRecipients(getEmailRecipients(oldLock.getRegistrarId()))
|
||||
.build());
|
||||
}
|
||||
|
||||
private void handleTransientFailure(Optional<RegistryLock> oldLock, Throwable t) {
|
||||
String message = String.format("Re-lock failed: %s", t.getMessage());
|
||||
logger.atSevere().withCause(t).log(message);
|
||||
response.setPayload(message);
|
||||
|
||||
if (previousAttempts == FAILURES_BEFORE_EMAIL) {
|
||||
if (oldLock.isPresent()) {
|
||||
sendGenericTransientFailureEmail(oldLock.get());
|
||||
} else {
|
||||
// if the old lock isn't present, something has gone horribly wrong
|
||||
sendUnknownRevisionIdAlertEmail();
|
||||
}
|
||||
}
|
||||
Duration timeBeforeRetry = previousAttempts < ATTEMPTS_BEFORE_SLOWDOWN ? TEN_MINUTES : ONE_HOUR;
|
||||
asyncTaskEnqueuer.enqueueDomainRelock(
|
||||
timeBeforeRetry, oldUnlockRevisionId, previousAttempts + 1);
|
||||
}
|
||||
|
||||
private void sendSuccessEmail(RegistryLock oldLock) {
|
||||
String body =
|
||||
String.format(RELOCK_SUCCESS_EMAIL_TEMPLATE, oldLock.getDomainName(), supportEmail);
|
||||
|
||||
sendEmailService.sendEmail(
|
||||
EmailMessage.newBuilder()
|
||||
.setFrom(gSuiteOutgoingEmailAddress)
|
||||
.setBody(body)
|
||||
.setSubject(String.format("Successful re-lock of domain %s", oldLock.getDomainName()))
|
||||
.setRecipients(getEmailRecipients(oldLock.getRegistrarId()))
|
||||
.build());
|
||||
}
|
||||
|
||||
private void sendGenericTransientFailureEmail(RegistryLock oldLock) {
|
||||
String body =
|
||||
String.format(
|
||||
RELOCK_TRANSIENT_FAILURE_EMAIL_TEMPLATE, oldLock.getDomainName(), supportEmail);
|
||||
// For an unexpected failure, notify both the lock-enabled contacts and our alerting email
|
||||
ImmutableSet<InternetAddress> allRecipients =
|
||||
new ImmutableSet.Builder<InternetAddress>()
|
||||
.addAll(getEmailRecipients(oldLock.getRegistrarId()))
|
||||
.add(alertRecipientAddress)
|
||||
.build();
|
||||
sendEmailService.sendEmail(
|
||||
EmailMessage.newBuilder()
|
||||
.setFrom(gSuiteOutgoingEmailAddress)
|
||||
.setBody(body)
|
||||
.setSubject(String.format("Error re-locking domain %s", oldLock.getDomainName()))
|
||||
.setRecipients(allRecipients)
|
||||
.build());
|
||||
}
|
||||
|
||||
private void sendUnknownRevisionIdAlertEmail() {
|
||||
sendEmailService.sendEmail(
|
||||
EmailMessage.newBuilder()
|
||||
.setFrom(gSuiteOutgoingEmailAddress)
|
||||
.setBody(String.format(RELOCK_UNKNOWN_ID_FAILURE_EMAIL_TEMPLATE, oldUnlockRevisionId))
|
||||
.setSubject("Error re-locking domain")
|
||||
.setRecipients(ImmutableSet.of(alertRecipientAddress))
|
||||
.build());
|
||||
}
|
||||
|
||||
private ImmutableSet<InternetAddress> getEmailRecipients(String registrarId) {
|
||||
Registrar registrar =
|
||||
Registrar.loadByClientIdCached(registrarId)
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new IllegalStateException(String.format("Unknown registrar %s", registrarId)));
|
||||
|
||||
ImmutableSet<String> registryLockEmailAddresses =
|
||||
registrar.getContacts().stream()
|
||||
.filter(RegistrarContact::isRegistryLockAllowed)
|
||||
.map(RegistrarContact::getRegistryLockEmailAddress)
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.collect(toImmutableSet());
|
||||
|
||||
ImmutableSet.Builder<InternetAddress> builder = new ImmutableSet.Builder<>();
|
||||
// can't use streams due to the 'throws' in the InternetAddress constructor
|
||||
for (String registryLockEmailAddress : registryLockEmailAddresses) {
|
||||
try {
|
||||
builder.add(new InternetAddress(registryLockEmailAddress));
|
||||
} catch (AddressException e) {
|
||||
// This shouldn't stop any other emails going out, so swallow it
|
||||
logger.atWarning().log("Invalid email address %s", registryLockEmailAddress);
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,7 +437,7 @@ public final class Transforms {
|
||||
.map(Optional::get)
|
||||
.map(ofy::toPojo)
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
retry(() -> jpaTm().transact(() -> jpaTm().saveNewOrUpdateAll(ofyEntities)));
|
||||
retry(() -> jpaTm().transact(() -> jpaTm().putAll(ofyEntities)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,9 +55,7 @@ FROM (
|
||||
FROM
|
||||
`%PROJECT_ID%.%DATASTORE_EXPORT_DATA_SET%.%REGISTRY_TABLE%`
|
||||
WHERE
|
||||
-- TODO(b/18092292): Add a filter for tldState (not PDT/PREDELEGATION)
|
||||
tldType = 'REAL'
|
||||
AND disableInvoicing is not TRUE) ) AS BillingEvent
|
||||
enableInvoicing IS TRUE) ) AS BillingEvent
|
||||
-- Gather billing ID from registrar table
|
||||
-- This is a 'JOIN' as opposed to 'LEFT JOIN' to filter out
|
||||
-- non-billable registrars
|
||||
|
||||
@@ -215,7 +215,7 @@ public class Spec11Pipeline implements Serializable {
|
||||
.setRegistrarId(subdomain.registrarId())
|
||||
.build();
|
||||
JpaTransactionManager jpaTransactionManager = jpaSupplierFactory.get();
|
||||
jpaTransactionManager.transact(() -> jpaTransactionManager.saveNew(threatMatch));
|
||||
jpaTransactionManager.transact(() -> jpaTransactionManager.insert(threatMatch));
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -173,7 +173,7 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
|
||||
existingDomain, newExpirationTime, autorenewEvent, autorenewPollMessage, now, clientId);
|
||||
updateForeignKeyIndexDeletionTime(newDomain);
|
||||
entitiesToSave.add(newDomain, historyEntry, autorenewEvent, autorenewPollMessage);
|
||||
tm().saveNewOrUpdateAll(entitiesToSave.build());
|
||||
tm().putAll(entitiesToSave.build());
|
||||
tm().delete(existingDomain.getDeletePollMessage());
|
||||
dnsQueue.addDomainRefreshTask(existingDomain.getDomainName());
|
||||
return responseBuilder
|
||||
|
||||
@@ -68,6 +68,7 @@ import google.registry.model.domain.DomainCommand.Update.Change;
|
||||
import google.registry.model.domain.fee.FeeUpdateCommandExtension;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.domain.secdns.SecDnsUpdateExtension;
|
||||
import google.registry.model.domain.superuser.DomainUpdateSuperuserExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppinput.EppInput;
|
||||
@@ -152,7 +153,8 @@ public final class DomainUpdateFlow implements TransactionalFlow {
|
||||
extensionManager.register(
|
||||
FeeUpdateCommandExtension.class,
|
||||
MetadataExtension.class,
|
||||
SecDnsUpdateExtension.class);
|
||||
SecDnsUpdateExtension.class,
|
||||
DomainUpdateSuperuserExtension.class);
|
||||
flowCustomLogic.beforeValidation();
|
||||
extensionManager.validate();
|
||||
validateClientIsLoggedIn(clientId);
|
||||
@@ -251,6 +253,15 @@ public final class DomainUpdateFlow implements TransactionalFlow {
|
||||
.removeContacts(remove.getContacts())
|
||||
.setRegistrant(firstNonNull(change.getRegistrant(), domain.getRegistrant()))
|
||||
.setAuthInfo(firstNonNull(change.getAuthInfo(), domain.getAuthInfo()));
|
||||
Optional<DomainUpdateSuperuserExtension> superuserExt =
|
||||
eppInput.getSingleExtension(DomainUpdateSuperuserExtension.class);
|
||||
if (superuserExt.isPresent()) {
|
||||
if (superuserExt.get().getAutorenews().isPresent()) {
|
||||
boolean autorenews = superuserExt.get().getAutorenews().get();
|
||||
domainBuilder.setAutorenewEndTime(
|
||||
Optional.ofNullable(autorenews ? null : domain.getRegistrationExpirationTime()));
|
||||
}
|
||||
}
|
||||
return domainBuilder.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -285,7 +285,7 @@ public final class HostUpdateFlow implements TransactionalFlow {
|
||||
&& newHost.isSubordinate()
|
||||
&& Objects.equals(
|
||||
existingHost.getSuperordinateDomain(), newHost.getSuperordinateDomain())) {
|
||||
tm().saveNewOrUpdate(
|
||||
tm().put(
|
||||
tm().load(existingHost.getSuperordinateDomain())
|
||||
.asBuilder()
|
||||
.removeSubordinateHost(existingHost.getHostName())
|
||||
@@ -294,14 +294,14 @@ public final class HostUpdateFlow implements TransactionalFlow {
|
||||
return;
|
||||
}
|
||||
if (existingHost.isSubordinate()) {
|
||||
tm().saveNewOrUpdate(
|
||||
tm().put(
|
||||
tm().load(existingHost.getSuperordinateDomain())
|
||||
.asBuilder()
|
||||
.removeSubordinateHost(existingHost.getHostName())
|
||||
.build());
|
||||
}
|
||||
if (newHost.isSubordinate()) {
|
||||
tm().saveNewOrUpdate(
|
||||
tm().put(
|
||||
tm().load(newHost.getSuperordinateDomain())
|
||||
.asBuilder()
|
||||
.addSubordinateHost(newHost.getHostName())
|
||||
|
||||
@@ -64,7 +64,7 @@ public final class PollFlowUtils {
|
||||
// and re-save it for future autorenew poll messages to be delivered. Otherwise, this
|
||||
// autorenew poll message has no more events to deliver and should be deleted.
|
||||
if (nextEventTime.isBefore(autorenewPollMessage.getAutorenewEndTime())) {
|
||||
tm().saveNewOrUpdate(autorenewPollMessage.asBuilder().setEventTime(nextEventTime).build());
|
||||
tm().put(autorenewPollMessage.asBuilder().setEventTime(nextEventTime).build());
|
||||
includeAckedMessageInCount = isBeforeOrAt(nextEventTime, tm().getTransactionTime());
|
||||
} else {
|
||||
tm().delete(autorenewPollMessage.createVKey());
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Script: Arab
|
||||
# Version: 1.0
|
||||
# Effective Date: 04-12-2012
|
||||
# Contact: tas-contact.google.com
|
||||
# Contact: iana-contact@google.com
|
||||
# Address: 1600 Amphitheatre Parkway Mountain View, CA 94043, USA
|
||||
# Telephone: +1 (650) 253-0000
|
||||
# Website: www.google.com
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Script: Armn
|
||||
# Version: 1.0
|
||||
# Effective Date: 04-12-2012
|
||||
# Contact: tas-contact.google.com
|
||||
# Contact: iana-contact@google.com
|
||||
# Address: 1600 Amphitheatre Parkway Mountain View, CA 94043, USA
|
||||
# Telephone: +1 (650) 253-0000
|
||||
# Website: www.google.com
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Script: Beng
|
||||
# Version: 1.0
|
||||
# Effective Date: 04-12-2012
|
||||
# Contact: tas-contact.google.com
|
||||
# Contact: iana-contact@google.com
|
||||
# Address: 1600 Amphitheatre Parkway Mountain View, CA 94043, USA
|
||||
# Telephone: +1 (650) 253-0000
|
||||
# Website: www.google.com
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
# Registry: Charleston Road Registry Inc.
|
||||
# Script: Chinese
|
||||
# Script: zh-Hans
|
||||
# Version: 1.0
|
||||
# Effective Date: 04-12-2012
|
||||
#
|
||||
# Contact: tas-contact.google.com
|
||||
# Contact: iana-contact@google.com
|
||||
# Address: 1600 Amphitheatre Parkway Mountain View, CA 94043, USA
|
||||
# Telephone: +1 (650) 253-0000
|
||||
# Website: www.google.com
|
||||
# Notes: This table describes codepoints allowed for the Chinese script.
|
||||
|
||||
Reference 0 Unicode 3.2
|
||||
Reference 1 A Complete Set of Simplified Chinese Characters
|
||||
Reference 2 Chinese Variants Collation Table
|
||||
Reference 3 Chinese Big Dictionary
|
||||
Reference 4 Chinese Relationship Table for Unihan Project
|
||||
Reference 5 GB2312
|
||||
Reference 6 General Table for Modern Chinese
|
||||
Reference 7 International Chinese Standard Big Dictionary
|
||||
Reference 8 Unihan Database
|
||||
Reference 9 BIG5
|
||||
# Reference 0 Unicode 3.2
|
||||
# Reference 1 A Complete Set of Simplified Chinese Characters
|
||||
# Reference 2 Chinese Variants Collation Table
|
||||
# Reference 3 Chinese Big Dictionary
|
||||
# Reference 4 Chinese Relationship Table for Unihan Project
|
||||
# Reference 5 GB2312
|
||||
# Reference 6 General Table for Modern Chinese
|
||||
# Reference 7 International Chinese Standard Big Dictionary
|
||||
# Reference 8 Unihan Database
|
||||
# Reference 9 BIG5
|
||||
|
||||
U+002D(0);U+002D(0);
|
||||
U+0030(0);U+0030(0);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Script: Cyrl
|
||||
# Version: 1.0
|
||||
# Effective Date: 04-12-2012
|
||||
# Contact: tas-contact.google.com
|
||||
# Contact: iana-contact@google.com
|
||||
# Address: 1600 Amphitheatre Parkway Mountain View, CA 94043, USA
|
||||
# Telephone: +1 (650) 253-0000
|
||||
# Website: www.google.com
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Script: Deva
|
||||
# Version: 1.0
|
||||
# Effective Date: 04-12-2012
|
||||
# Contact: tas-contact.google.com
|
||||
# Contact: iana-contact@google.com
|
||||
# Address: 1600 Amphitheatre Parkway Mountain View, CA 94043, USA
|
||||
# Telephone: +1 (650) 253-0000
|
||||
# Website: www.google.com
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Script: Ethi
|
||||
# Version: 1.0
|
||||
# Effective Date: 04-12-2012
|
||||
# Contact: tas-contact.google.com
|
||||
# Contact: iana-contact@google.com
|
||||
# Address: 1600 Amphitheatre Parkway Mountain View, CA 94043, USA
|
||||
# Telephone: +1 (650) 253-0000
|
||||
# Website: www.google.com
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# Version: 1.0
|
||||
# Effective Date: 04-12-2012
|
||||
# Contact: tas-contact.google.com
|
||||
# Contact: iana-contact@google.com
|
||||
# Address: 1600 Amphitheatre Parkway Mountain View, CA 94043, USA
|
||||
# Telephone: +1 (650) 253-0000
|
||||
# Website: www.google.com
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Script: Grek
|
||||
# Version: 1.0
|
||||
# Effective Date: 04-12-2012
|
||||
# Contact: tas-contact.google.com
|
||||
# Contact: iana-contact@google.com
|
||||
# Address: 1600 Amphitheatre Parkway Mountain View, CA 94043, USA
|
||||
# Telephone: +1 (650) 253-0000
|
||||
# Website: www.google.com
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Script: Guru
|
||||
# Version: 1.0
|
||||
# Effective Date: 04-12-2012
|
||||
# Contact: tas-contact.google.com
|
||||
# Contact: iana-contact@google.com
|
||||
# Address: 1600 Amphitheatre Parkway Mountain View, CA 94043, USA
|
||||
# Telephone: +1 (650) 253-0000
|
||||
# Website: www.google.com
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Script: Hebr
|
||||
# Version: 1.0
|
||||
# Effective Date: 04-12-2012
|
||||
# Contact: tas-contact.google.com
|
||||
# Contact: iana-contact@google.com
|
||||
# Address: 1600 Amphitheatre Parkway Mountain View, CA 94043, USA
|
||||
# Telephone: +1 (650) 253-0000
|
||||
# Website: www.google.com
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Script: Knda
|
||||
# Version: 1.0
|
||||
# Effective Date: 04-12-2012
|
||||
# Contact: tas-contact.google.com
|
||||
# Contact: iana-contact@google.com
|
||||
# Address: 1600 Amphitheatre Parkway Mountain View, CA 94043, USA
|
||||
# Telephone: +1 (650) 253-0000
|
||||
# Website: www.google.com
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Script: Khmr
|
||||
# Version: 1.0
|
||||
# Effective Date: 04-12-2012
|
||||
# Contact: tas-contact.google.com
|
||||
# Contact: iana-contact@google.com
|
||||
# Address: 1600 Amphitheatre Parkway Mountain View, CA 94043, USA
|
||||
#
|
||||
# Telephone: +1 (650) 253-0000
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Script: Kore
|
||||
# Version: 1.0
|
||||
# Effective Date: 04-12-2012
|
||||
# Contact: tas-contact.google.com
|
||||
# Contact: iana-contact@google.com
|
||||
# Address: 1600 Amphitheatre Parkway Mountain View, CA 94043, USA
|
||||
# Telephone: +1 (650) 253-0000
|
||||
# Website: www.google.com
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Script: Mlym
|
||||
# Version: 1.0
|
||||
# Effective Date: 04-12-2012
|
||||
# Contact: tas-contact.google.com
|
||||
# Contact: iana-contact@google.com
|
||||
# Address: 1600 Amphitheatre Parkway Mountain View, CA 94043, USA
|
||||
# Telephone: +1 (650) 253-0000
|
||||
# Website: www.google.com
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Version: 1.0
|
||||
# Effective Date: 04-12-2012
|
||||
#
|
||||
# Contact: tas-contact.google.com
|
||||
# Contact: iana-contact@google.com
|
||||
# Address: 1600 Amphitheatre Parkway Mountain View, CA 94043, USA
|
||||
# Telephone: +1 (650) 253-0000
|
||||
# Website: www.google.com
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Script: Orya
|
||||
# Version: 1.0
|
||||
# Effective Date: 04-12-2012
|
||||
# Contact: tas-contact.google.com
|
||||
# Contact: iana-contact@google.com
|
||||
# Address: 1600 Amphitheatre Parkway Mountain View, CA 94043, USA
|
||||
# Telephone: +1 (650) 253-0000
|
||||
# Website: www.google.com
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Script: Sinh
|
||||
# Version: 1.0
|
||||
# Effective Date: 04-12-2012
|
||||
# Contact: tas-contact.google.com
|
||||
# Contact: iana-contact@google.com
|
||||
# Address: 1600 Amphitheatre Parkway Mountain View, CA 94043, USA
|
||||
# Telephone: +1 (650) 253-0000
|
||||
# Website: www.google.com
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Script: Taml
|
||||
# Version: 1.0
|
||||
# Effective Date: 04-12-2012
|
||||
# Contact: tas-contact.google.com
|
||||
# Contact: iana-contact@google.com
|
||||
# Address: 1600 Amphitheatre Parkway Mountain View, CA 94043, USA
|
||||
# Telephone: +1 (650) 253-0000
|
||||
# Website: www.google.com
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Script: Telu
|
||||
# Version: 1.0
|
||||
# Effective Date: 04-12-2012
|
||||
# Contact: tas-contact.google.com
|
||||
# Contact: iana-contact@google.com
|
||||
# Address: 1600 Amphitheatre Parkway Mountain View, CA 94043, USA
|
||||
# Telephone: +1 (650) 253-0000
|
||||
# Website: www.google.com
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Script: Thai
|
||||
# Version: 1.0
|
||||
# Effective Date: 04-12-2012
|
||||
# Contact: tas-contact.google.com
|
||||
# Contact: iana-contact@google.com
|
||||
# Address: 1600 Amphitheatre Parkway Mountain View, CA 94043, USA
|
||||
# Telephone: +1 (650) 253-0000
|
||||
# Website: www.google.com
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Script: Tibt
|
||||
# Version: 1.0
|
||||
# Effective Date: 04-12-2012
|
||||
# Contact: tas-contact.google.com
|
||||
# Contact: iana-contact@google.com
|
||||
# Address: 1600 Amphitheatre Parkway Mountain View, CA 94043, USA
|
||||
# Telephone: +1 (650) 253-0000
|
||||
# Website: www.google.com
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
# Registry: Charleston Road Registry Inc.
|
||||
# Script: Traditional Chinese
|
||||
# Script: zh-Hant
|
||||
# Version: 1.0
|
||||
# Effective Date: 04-12-2012
|
||||
# Contact: tas-contact.google.com
|
||||
# Contact: iana-contact@google.com
|
||||
# Address: 1600 Amphitheatre Parkway Mountain View, CA 94043, USA
|
||||
# Telephone: +1 (650) 253-0000
|
||||
# Website: www.google.com
|
||||
# Notes: This table describes codepoints allowed for the Traditional Chinese script.
|
||||
|
||||
Reference 0 Unicode 3.2
|
||||
Reference 1 A Complete Set of Simplified Chinese Characters
|
||||
Reference 2 Chinese Variants Collation Table
|
||||
Reference 3 Chinese Big Dictionary
|
||||
Reference 4 Chinese Relationship Table for Unihan Project
|
||||
Reference 5 GB2312
|
||||
Reference 6 General Table for Modern Chinese
|
||||
Reference 7 International Chinese Standard Big Dictionary
|
||||
Reference 8 Unihan Database
|
||||
Reference 9 BIG5
|
||||
# Reference 0 Unicode 3.2
|
||||
# Reference 1 A Complete Set of Simplified Chinese Characters
|
||||
# Reference 2 Chinese Variants Collation Table
|
||||
# Reference 3 Chinese Big Dictionary
|
||||
# Reference 4 Chinese Relationship Table for Unihan Project
|
||||
# Reference 5 GB2312
|
||||
# Reference 6 General Table for Modern Chinese
|
||||
# Reference 7 International Chinese Standard Big Dictionary
|
||||
# Reference 8 Unihan Database
|
||||
# Reference 9 BIG5
|
||||
|
||||
U+002D(0);U+002D(0);
|
||||
U+0030(0);U+0030(0);
|
||||
|
||||
@@ -19,9 +19,12 @@ import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.common.Cursor;
|
||||
import google.registry.model.common.EntityGroupRoot;
|
||||
import google.registry.model.common.GaeUserIdConverter;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.model.host.HostHistory;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.model.index.EppResourceIndex;
|
||||
import google.registry.model.index.EppResourceIndexBucket;
|
||||
@@ -68,9 +71,11 @@ public final class EntityClasses {
|
||||
CommitLogCheckpointRoot.class,
|
||||
CommitLogManifest.class,
|
||||
CommitLogMutation.class,
|
||||
ContactHistory.class,
|
||||
ContactResource.class,
|
||||
Cursor.class,
|
||||
DomainBase.class,
|
||||
DomainHistory.class,
|
||||
EntityGroupRoot.class,
|
||||
EppResourceIndex.class,
|
||||
EppResourceIndexBucket.class,
|
||||
@@ -79,6 +84,7 @@ public final class EntityClasses {
|
||||
ForeignKeyIndex.ForeignKeyHostIndex.class,
|
||||
GaeUserIdConverter.class,
|
||||
HistoryEntry.class,
|
||||
HostHistory.class,
|
||||
HostResource.class,
|
||||
KmsSecret.class,
|
||||
KmsSecretRevision.class,
|
||||
|
||||
@@ -62,23 +62,32 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
|
||||
/**
|
||||
* Unique identifier in the registry for this resource.
|
||||
*
|
||||
* <p>Not persisted so that we can store these in references to other objects. Subclasses that
|
||||
* wish to use this as the primary key should create a getter method annotated with @Id
|
||||
*
|
||||
* <p>This is in the (\w|_){1,80}-\w{1,8} format specified by RFC 5730 for roidType.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc5730">RFC 5730</a>
|
||||
*/
|
||||
@Id
|
||||
// not persisted so that we can store these in references to other objects. Subclasses that wish
|
||||
// to use this as the primary key should create a getter method annotated with @Id
|
||||
@Transient
|
||||
String repoId;
|
||||
@Id @Transient String repoId;
|
||||
|
||||
/** The ID of the registrar that is currently sponsoring this resource. */
|
||||
/**
|
||||
* The ID of the registrar that is currently sponsoring this resource.
|
||||
*
|
||||
* <p>This can be null in the case of pre-Registry-3.0-migration history objects with null
|
||||
* resource fields.
|
||||
*/
|
||||
@Index
|
||||
@Column(name = "currentSponsorRegistrarId", nullable = false)
|
||||
@Column(name = "currentSponsorRegistrarId")
|
||||
String currentSponsorClientId;
|
||||
|
||||
/** The ID of the registrar that created this resource. */
|
||||
@Column(name = "creationRegistrarId", nullable = false)
|
||||
/**
|
||||
* The ID of the registrar that created this resource.
|
||||
*
|
||||
* <p>This can be null in the case of pre-Registry-3.0-migration history objects with null
|
||||
* resource fields.
|
||||
*/
|
||||
@Column(name = "creationRegistrarId")
|
||||
String creationClientId;
|
||||
|
||||
/**
|
||||
@@ -91,13 +100,17 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
|
||||
@Column(name = "lastEppUpdateRegistrarId")
|
||||
String lastEppUpdateClientId;
|
||||
|
||||
/** The time when this resource was created. */
|
||||
// Map the method to XML, not the field, because if we map the field (with an adaptor class) it
|
||||
// will never be omitted from the xml even if the timestamp inside creationTime is null and we
|
||||
// return null from the adaptor. (Instead it gets written as an empty tag.)
|
||||
@Column(nullable = false)
|
||||
@Index
|
||||
CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null);
|
||||
/**
|
||||
* The time when this resource was created.
|
||||
*
|
||||
* <p>Map the method to XML, not the field, because if we map the field (with an adaptor class) it
|
||||
* will never be omitted from the xml even if the timestamp inside creationTime is null and we
|
||||
* return null from the adaptor (instead it gets written as an empty tag).
|
||||
*
|
||||
* <p>This can be null in the case of pre-Registry-3.0-migration history objects with null
|
||||
* resource fields.
|
||||
*/
|
||||
@Index CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null);
|
||||
|
||||
/**
|
||||
* The time when this resource was or will be deleted.
|
||||
@@ -112,8 +125,7 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
|
||||
* out of the index at that time, as long as we query for resources whose deletion time is before
|
||||
* now.
|
||||
*/
|
||||
@Index
|
||||
DateTime deletionTime;
|
||||
@Index DateTime deletionTime;
|
||||
|
||||
/**
|
||||
* The time that this resource was last updated.
|
||||
@@ -144,7 +156,7 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
|
||||
return repoId;
|
||||
}
|
||||
|
||||
// Hibernate needs this to populate the repo ID, but no one else should ever use it
|
||||
/** This method exists solely to satisfy Hibernate. Use {@link Builder} instead. */
|
||||
@SuppressWarnings("UnusedMethod")
|
||||
private void setRepoId(String repoId) {
|
||||
this.repoId = repoId;
|
||||
|
||||
@@ -33,12 +33,14 @@ import com.googlecode.objectify.annotation.Id;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import com.googlecode.objectify.annotation.IgnoreSave;
|
||||
import com.googlecode.objectify.annotation.Index;
|
||||
import com.googlecode.objectify.annotation.OnLoad;
|
||||
import com.googlecode.objectify.annotation.Parent;
|
||||
import com.googlecode.objectify.condition.IfNull;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.annotations.ReportedOn;
|
||||
import google.registry.model.common.TimeOfYear;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.domain.token.AllocationToken;
|
||||
@@ -46,6 +48,7 @@ import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.transfer.TransferData.TransferServerApproveEntity;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.WithLongVKey;
|
||||
import google.registry.schema.replay.DatastoreAndSqlEntity;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
@@ -56,9 +59,8 @@ import javax.persistence.Column;
|
||||
import javax.persistence.Embedded;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.MappedSuperclass;
|
||||
import javax.persistence.PostLoad;
|
||||
import javax.persistence.Transient;
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
@@ -107,10 +109,7 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
}
|
||||
|
||||
/** Entity id. */
|
||||
@Id
|
||||
@javax.persistence.Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
Long id;
|
||||
@Id @javax.persistence.Id Long id;
|
||||
|
||||
@Parent @DoNotHydrate @Transient Key<HistoryEntry> parent;
|
||||
|
||||
@@ -148,6 +147,21 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
@Nullable
|
||||
Set<Flag> flags;
|
||||
|
||||
@PostLoad
|
||||
void postLoad() {
|
||||
parent =
|
||||
Key.create(
|
||||
Key.create(DomainBase.class, domainRepoId),
|
||||
HistoryEntry.class,
|
||||
domainHistoryRevisionId);
|
||||
}
|
||||
|
||||
@OnLoad
|
||||
void onLoad() {
|
||||
domainHistoryRevisionId = parent.getId();
|
||||
domainRepoId = parent.getParent().getName();
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
@@ -244,7 +258,6 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
}
|
||||
|
||||
public B setParent(Key<HistoryEntry> parentKey) {
|
||||
// TODO(shicong): Figure out how to set domainHistoryRevisionId and domainRepoId
|
||||
getInstance().parent = parentKey;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
@@ -257,6 +270,11 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
checkNotNull(instance.eventTime, "Event time must be set");
|
||||
checkNotNull(instance.targetId, "Target ID must be set");
|
||||
checkNotNull(instance.parent, "Parent must be set");
|
||||
checkNotNull(instance.parent.getParent(), "parent.getParent() must be set");
|
||||
checkNotNull(
|
||||
instance.parent.getParent().getName(), "parent.getParent().getName() must be set");
|
||||
instance.domainHistoryRevisionId = instance.parent.getId();
|
||||
instance.domainRepoId = instance.parent.getParent().getName();
|
||||
return super.build();
|
||||
}
|
||||
}
|
||||
@@ -274,7 +292,7 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
@javax.persistence.Index(columnList = "allocation_token_id")
|
||||
})
|
||||
@AttributeOverride(name = "id", column = @Column(name = "billing_event_id"))
|
||||
public static class OneTime extends BillingEvent {
|
||||
public static class OneTime extends BillingEvent implements DatastoreAndSqlEntity {
|
||||
|
||||
/** The billable value. */
|
||||
@AttributeOverrides({
|
||||
@@ -450,7 +468,7 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
@javax.persistence.Index(columnList = "recurrence_time_of_year")
|
||||
})
|
||||
@AttributeOverride(name = "id", column = @Column(name = "billing_recurrence_id"))
|
||||
public static class Recurring extends BillingEvent {
|
||||
public static class Recurring extends BillingEvent implements DatastoreAndSqlEntity {
|
||||
|
||||
/**
|
||||
* The billing event recurs every year between {@link #eventTime} and this time on the
|
||||
@@ -544,7 +562,7 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
@javax.persistence.Index(columnList = "billingTime")
|
||||
})
|
||||
@AttributeOverride(name = "id", column = @Column(name = "billing_cancellation_id"))
|
||||
public static class Cancellation extends BillingEvent {
|
||||
public static class Cancellation extends BillingEvent implements DatastoreAndSqlEntity {
|
||||
|
||||
/** The billing time of the charge that is being cancelled. */
|
||||
@Index
|
||||
@@ -664,7 +682,7 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
/** An event representing a modification of an existing one-time billing event. */
|
||||
@ReportedOn
|
||||
@Entity
|
||||
public static class Modification extends BillingEvent {
|
||||
public static class Modification extends BillingEvent implements DatastoreAndSqlEntity {
|
||||
|
||||
/** The change in cost that should be applied to the original billing event. */
|
||||
Money cost;
|
||||
|
||||
@@ -15,11 +15,20 @@
|
||||
package google.registry.model.contact;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.EntitySubclass;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.PostLoad;
|
||||
|
||||
/**
|
||||
* A persisted history entry representing an EPP modification to a contact.
|
||||
@@ -36,16 +45,33 @@ import javax.persistence.Entity;
|
||||
@javax.persistence.Index(columnList = "historyType"),
|
||||
@javax.persistence.Index(columnList = "historyModificationTime")
|
||||
})
|
||||
@EntitySubclass
|
||||
@Access(AccessType.FIELD)
|
||||
public class ContactHistory extends HistoryEntry {
|
||||
|
||||
// Store ContactBase instead of ContactResource so we don't pick up its @Id
|
||||
ContactBase contactBase;
|
||||
@Nullable ContactBase contactBase;
|
||||
|
||||
@Column(nullable = false)
|
||||
VKey<ContactResource> contactRepoId;
|
||||
|
||||
/** The state of the {@link ContactBase} object at this point in time. */
|
||||
public ContactBase getContactBase() {
|
||||
return contactBase;
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TempHistorySequenceGenerator")
|
||||
@Column(name = "historyRevisionId")
|
||||
@Access(AccessType.PROPERTY)
|
||||
@Override
|
||||
public long getId() {
|
||||
return super.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* The values of all the fields on the {@link ContactBase} object after the action represented by
|
||||
* this history object was executed.
|
||||
*
|
||||
* <p>Will be absent for objects created prior to the Registry 3.0 SQL migration.
|
||||
*/
|
||||
public Optional<ContactBase> getContactBase() {
|
||||
return Optional.ofNullable(contactBase);
|
||||
}
|
||||
|
||||
/** The key to the {@link ContactResource} this is based off of. */
|
||||
@@ -53,6 +79,20 @@ public class ContactHistory extends HistoryEntry {
|
||||
return contactRepoId;
|
||||
}
|
||||
|
||||
@PostLoad
|
||||
void postLoad() {
|
||||
// Normally Hibernate would see that the contact fields are all null and would fill contactBase
|
||||
// with a null object. Unfortunately, the updateTimestamp is never null in SQL.
|
||||
if (contactBase != null && contactBase.getContactId() == null) {
|
||||
contactBase = null;
|
||||
}
|
||||
// Fill in the full, symmetric, parent repo ID key
|
||||
Key<ContactResource> parentKey =
|
||||
Key.create(ContactResource.class, (String) contactRepoId.getSqlKey());
|
||||
parent = parentKey;
|
||||
contactRepoId = VKey.create(ContactResource.class, contactRepoId.getSqlKey(), parentKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder asBuilder() {
|
||||
return new Builder(clone(this));
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
package google.registry.model.domain;
|
||||
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.EppResource.ForeignKeyedEppResource;
|
||||
@@ -82,12 +81,26 @@ public class DomainBase extends DomainContent
|
||||
return super.nsHosts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of {@link GracePeriod} associated with the domain.
|
||||
*
|
||||
* <p>This is the getter method specific for Hibernate to access the field so it is set to
|
||||
* private. The caller can use the public {@link #getGracePeriods()} to get the grace periods.
|
||||
*
|
||||
* <p>Note that we need to set `insertable = false, updatable = false` for @JoinColumn, otherwise
|
||||
* Hibernate would try to set the foreign key to null(through an UPDATE TABLE sql) instead of
|
||||
* deleting the whole entry from the table when the {@link GracePeriod} is removed from the set.
|
||||
*/
|
||||
@Access(AccessType.PROPERTY)
|
||||
@OneToMany(
|
||||
cascade = {CascadeType.ALL},
|
||||
fetch = FetchType.EAGER,
|
||||
orphanRemoval = true)
|
||||
@JoinColumn(name = "domainRepoId", referencedColumnName = "repoId")
|
||||
@JoinColumn(
|
||||
name = "domainRepoId",
|
||||
referencedColumnName = "repoId",
|
||||
insertable = false,
|
||||
updatable = false)
|
||||
@SuppressWarnings("UnusedMethod")
|
||||
private Set<GracePeriod> getInternalGracePeriods() {
|
||||
return gracePeriods;
|
||||
|
||||
@@ -280,12 +280,10 @@ public class DomainContent extends EppResource
|
||||
// object will have a null hashcode so that it can get a recalculated hashcode
|
||||
// when its hashCode() is invoked.
|
||||
// TODO(b/162739503): Remove this after fully migrating to Cloud SQL.
|
||||
if (gracePeriods != null) {
|
||||
gracePeriods =
|
||||
gracePeriods.stream()
|
||||
.map(gracePeriod -> gracePeriod.cloneWithDomainRepoId(getRepoId()))
|
||||
.collect(toImmutableSet());
|
||||
}
|
||||
gracePeriods =
|
||||
nullToEmptyImmutableCopy(gracePeriods).stream()
|
||||
.map(gracePeriod -> gracePeriod.cloneWithDomainRepoId(getRepoId()))
|
||||
.collect(toImmutableSet());
|
||||
}
|
||||
|
||||
@PostLoad
|
||||
@@ -698,7 +696,13 @@ public class DomainContent extends EppResource
|
||||
}
|
||||
checkArgumentNotNull(instance.getRegistrant(), "Missing registrant");
|
||||
instance.tld = getTldFromDomainName(instance.fullyQualifiedDomainName);
|
||||
return super.build();
|
||||
|
||||
T newDomain = super.build();
|
||||
// Hibernate throws exception if gracePeriods is null because we enabled all cascadable
|
||||
// operations and orphan removal.
|
||||
newDomain.gracePeriods =
|
||||
newDomain.gracePeriods == null ? ImmutableSet.of() : newDomain.gracePeriods;
|
||||
return newDomain;
|
||||
}
|
||||
|
||||
public B setDomainName(String domainName) {
|
||||
|
||||
@@ -14,19 +14,40 @@
|
||||
|
||||
package google.registry.model.domain;
|
||||
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.EntitySubclass;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.domain.DomainHistory.DomainHistoryId;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.model.reporting.DomainTransactionRecord;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.io.Serializable;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
import javax.persistence.AttributeOverride;
|
||||
import javax.persistence.AttributeOverrides;
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.ElementCollection;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.IdClass;
|
||||
import javax.persistence.Index;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.JoinTable;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.PostLoad;
|
||||
import javax.persistence.Table;
|
||||
|
||||
/**
|
||||
* A persisted history entry representing an EPP modification to a domain.
|
||||
@@ -36,43 +57,151 @@ import javax.persistence.JoinTable;
|
||||
* the foreign-keyed fields in that class can refer to this object.
|
||||
*/
|
||||
@Entity
|
||||
@javax.persistence.Table(
|
||||
@Table(
|
||||
indexes = {
|
||||
@javax.persistence.Index(columnList = "creationTime"),
|
||||
@javax.persistence.Index(columnList = "historyRegistrarId"),
|
||||
@javax.persistence.Index(columnList = "historyType"),
|
||||
@javax.persistence.Index(columnList = "historyModificationTime")
|
||||
@Index(columnList = "creationTime"),
|
||||
@Index(columnList = "historyRegistrarId"),
|
||||
@Index(columnList = "historyType"),
|
||||
@Index(columnList = "historyModificationTime")
|
||||
})
|
||||
@EntitySubclass
|
||||
@Access(AccessType.FIELD)
|
||||
@IdClass(DomainHistoryId.class)
|
||||
public class DomainHistory extends HistoryEntry {
|
||||
|
||||
// Store DomainContent instead of DomainBase so we don't pick up its @Id
|
||||
DomainContent domainContent;
|
||||
@Nullable DomainContent domainContent;
|
||||
|
||||
@Column(nullable = false)
|
||||
VKey<DomainBase> domainRepoId;
|
||||
@Id String domainRepoId;
|
||||
|
||||
// We could have reused domainContent.nsHosts here, but Hibernate throws a weird exception after
|
||||
// we change to use a composite primary key.
|
||||
// TODO(b/166776754): Investigate if we can reuse domainContent.nsHosts for storing host keys.
|
||||
@Ignore
|
||||
@ElementCollection
|
||||
@JoinTable(name = "DomainHistoryHost")
|
||||
@Access(AccessType.PROPERTY)
|
||||
@Column(name = "host_repo_id")
|
||||
Set<VKey<HostResource>> nsHosts;
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
@Access(AccessType.PROPERTY)
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(
|
||||
name = "unit",
|
||||
column = @Column(name = "historyPeriodUnit")),
|
||||
@AttributeOverride(
|
||||
name = "value",
|
||||
column = @Column(name = "historyPeriodValue"))
|
||||
})
|
||||
public Period getPeriod() {
|
||||
return super.getPeriod();
|
||||
}
|
||||
|
||||
/**
|
||||
* For transfers, the id of the other registrar.
|
||||
*
|
||||
* <p>For requests and cancels, the other registrar is the losing party (because the registrar
|
||||
* sending the EPP transfer command is the gaining party). For approves and rejects, the other
|
||||
* registrar is the gaining party.
|
||||
*/
|
||||
@Nullable
|
||||
@Access(AccessType.PROPERTY)
|
||||
@Column(name = "historyOtherRegistrarId")
|
||||
public String getOtherRegistrarId() {
|
||||
return super.getOtherClientId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Logging field for transaction reporting.
|
||||
*
|
||||
* <p>This will be empty for any DomainHistory/HistoryEntry generated before this field was added,
|
||||
* mid-2017, as well as any action that does not generate billable events (e.g. updates).
|
||||
*/
|
||||
@Access(AccessType.PROPERTY)
|
||||
@OneToMany(cascade = {CascadeType.ALL})
|
||||
@JoinColumn(name = "historyRevisionId", referencedColumnName = "historyRevisionId")
|
||||
@JoinColumn(name = "domainRepoId", referencedColumnName = "domainRepoId")
|
||||
@Override
|
||||
public Set<DomainTransactionRecord> getDomainTransactionRecords() {
|
||||
return super.getDomainTransactionRecords();
|
||||
}
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TempHistorySequenceGenerator")
|
||||
@Column(name = "historyRevisionId")
|
||||
@Access(AccessType.PROPERTY)
|
||||
@Override
|
||||
public long getId() {
|
||||
return super.getId();
|
||||
}
|
||||
|
||||
/** Returns keys to the {@link HostResource} that are the nameservers for the domain. */
|
||||
public Set<VKey<HostResource>> getNsHosts() {
|
||||
return domainContent.nsHosts;
|
||||
return nsHosts;
|
||||
}
|
||||
|
||||
/** The state of the {@link DomainContent} object at this point in time. */
|
||||
public DomainContent getDomainContent() {
|
||||
return domainContent;
|
||||
/**
|
||||
* The values of all the fields on the {@link DomainContent} object after the action represented
|
||||
* by this history object was executed.
|
||||
*
|
||||
* <p>Will be absent for objects created prior to the Registry 3.0 SQL migration.
|
||||
*/
|
||||
public Optional<DomainContent> getDomainContent() {
|
||||
return Optional.ofNullable(domainContent);
|
||||
}
|
||||
|
||||
/** The key to the {@link ContactResource} this is based off of. */
|
||||
/** The key to the {@link DomainBase} this is based off of. */
|
||||
public VKey<DomainBase> getDomainRepoId() {
|
||||
return domainRepoId;
|
||||
return VKey.create(DomainBase.class, domainRepoId, Key.create(DomainBase.class, domainRepoId));
|
||||
}
|
||||
|
||||
// Hibernate needs this in order to populate nsHosts but no one else should ever use it
|
||||
@SuppressWarnings("UnusedMethod")
|
||||
private void setNsHosts(Set<VKey<HostResource>> nsHosts) {
|
||||
public VKey<DomainHistory> createVKey() {
|
||||
return VKey.createSql(DomainHistory.class, new DomainHistoryId(domainRepoId, getId()));
|
||||
}
|
||||
|
||||
@PostLoad
|
||||
void postLoad() {
|
||||
if (domainContent != null) {
|
||||
domainContent.nsHosts = nsHosts;
|
||||
domainContent.nsHosts = nullToEmptyImmutableCopy(nsHosts);
|
||||
// Normally Hibernate would see that the domain fields are all null and would fill
|
||||
// domainContent with a null object. Unfortunately, the updateTimestamp is never null in SQL.
|
||||
if (domainContent.getDomainName() == null) {
|
||||
domainContent = null;
|
||||
}
|
||||
}
|
||||
parent = Key.create(DomainBase.class, domainRepoId);
|
||||
}
|
||||
|
||||
/** Class to represent the composite primary key of {@link DomainHistory} entity. */
|
||||
static class DomainHistoryId extends ImmutableObject implements Serializable {
|
||||
|
||||
private String domainRepoId;
|
||||
|
||||
private Long id;
|
||||
|
||||
/** Hibernate requires this default constructor. */
|
||||
private DomainHistoryId() {}
|
||||
|
||||
DomainHistoryId(String domainRepoId, long id) {
|
||||
this.domainRepoId = domainRepoId;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
String getDomainRepoId() {
|
||||
return domainRepoId;
|
||||
}
|
||||
|
||||
void setDomainRepoId(String domainRepoId) {
|
||||
this.domainRepoId = domainRepoId;
|
||||
}
|
||||
|
||||
long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,12 +220,15 @@ public class DomainHistory extends HistoryEntry {
|
||||
|
||||
public Builder setDomainContent(DomainContent domainContent) {
|
||||
getInstance().domainContent = domainContent;
|
||||
if (domainContent != null) {
|
||||
getInstance().nsHosts = nullToEmptyImmutableCopy(domainContent.nsHosts);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setDomainRepoId(VKey<DomainBase> domainRepoId) {
|
||||
public Builder setDomainRepoId(String domainRepoId) {
|
||||
getInstance().domainRepoId = domainRepoId;
|
||||
domainRepoId.maybeGetOfyKey().ifPresent(parent -> getInstance().parent = parent);
|
||||
getInstance().parent = Key.create(DomainBase.class, domainRepoId);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -104,8 +236,7 @@ public class DomainHistory extends HistoryEntry {
|
||||
@Override
|
||||
public Builder setParent(Key<? extends EppResource> parent) {
|
||||
super.setParent(parent);
|
||||
getInstance().domainRepoId =
|
||||
VKey.create(DomainBase.class, parent.getName(), (Key<DomainBase>) parent);
|
||||
getInstance().domainRepoId = parent.getName();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,9 +31,9 @@ public class Period extends ImmutableObject {
|
||||
@XmlAttribute
|
||||
Unit unit;
|
||||
|
||||
@XmlValue
|
||||
Integer value;
|
||||
@XmlValue Integer value;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
public Unit getUnit() {
|
||||
return unit;
|
||||
}
|
||||
@@ -42,6 +42,18 @@ public class Period extends ImmutableObject {
|
||||
return value;
|
||||
}
|
||||
|
||||
/** This method exists solely to satisfy Hibernate. Use {@link #create(int, Unit)} instead. */
|
||||
@SuppressWarnings("UnusedMethod")
|
||||
private void setUnit(Unit unit) {
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
/** This method exists solely to satisfy Hibernate. Use {@link #create(int, Unit)} instead. */
|
||||
@SuppressWarnings("UnusedMethod")
|
||||
private void setValue(Integer value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/** The unit enum. */
|
||||
public enum Unit {
|
||||
@XmlEnumValue("y")
|
||||
|
||||
@@ -20,6 +20,7 @@ import javax.xml.bind.annotation.XmlRootElement;
|
||||
/** A superuser extension that may be present on domain delete commands. */
|
||||
@XmlRootElement(name = "domainDelete")
|
||||
public class DomainDeleteSuperuserExtension extends SuperuserExtension {
|
||||
|
||||
@XmlElement(name = "redemptionGracePeriodDays")
|
||||
int redemptionGracePeriodDays;
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import javax.xml.bind.annotation.XmlRootElement;
|
||||
/** A superuser extension that may be present on domain transfer request commands. */
|
||||
@XmlRootElement(name = "domainTransferRequest")
|
||||
public class DomainTransferRequestSuperuserExtension extends SuperuserExtension {
|
||||
|
||||
// We need to specify the period here because the transfer object's period cannot be set to zero.
|
||||
@XmlElement(name = "renewalPeriod")
|
||||
Period renewalPeriod;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.model.domain.superuser;
|
||||
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
|
||||
/** A superuser extension that may be present on domain update commands. */
|
||||
@XmlRootElement(name = "domainUpdate")
|
||||
public class DomainUpdateSuperuserExtension extends SuperuserExtension {
|
||||
|
||||
@XmlElement(name = "autorenews")
|
||||
@Nullable
|
||||
String autorenews;
|
||||
|
||||
public Optional<Boolean> getAutorenews() {
|
||||
return Optional.ofNullable(isNullOrEmpty(autorenews) ? null : Boolean.valueOf(autorenews));
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,7 @@ import google.registry.model.domain.secdns.SecDnsCreateExtension;
|
||||
import google.registry.model.domain.secdns.SecDnsUpdateExtension;
|
||||
import google.registry.model.domain.superuser.DomainDeleteSuperuserExtension;
|
||||
import google.registry.model.domain.superuser.DomainTransferRequestSuperuserExtension;
|
||||
import google.registry.model.domain.superuser.DomainUpdateSuperuserExtension;
|
||||
import google.registry.model.domain.token.AllocationTokenExtension;
|
||||
import google.registry.model.eppinput.ResourceCommand.ResourceCheck;
|
||||
import google.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
||||
@@ -309,53 +310,62 @@ public class EppInput extends ImmutableObject {
|
||||
@XmlType(propOrder = {"command", "extension", "clTRID"})
|
||||
public static class CommandWrapper extends ImmutableObject {
|
||||
@XmlElements({
|
||||
@XmlElement(name = "check", type = Check.class),
|
||||
@XmlElement(name = "create", type = Create.class),
|
||||
@XmlElement(name = "delete", type = Delete.class),
|
||||
@XmlElement(name = "info", type = Info.class),
|
||||
@XmlElement(name = "login", type = Login.class),
|
||||
@XmlElement(name = "logout", type = Logout.class),
|
||||
@XmlElement(name = "poll", type = Poll.class),
|
||||
@XmlElement(name = "renew", type = Renew.class),
|
||||
@XmlElement(name = "transfer", type = Transfer.class),
|
||||
@XmlElement(name = "update", type = Update.class) })
|
||||
@XmlElement(name = "check", type = Check.class),
|
||||
@XmlElement(name = "create", type = Create.class),
|
||||
@XmlElement(name = "delete", type = Delete.class),
|
||||
@XmlElement(name = "info", type = Info.class),
|
||||
@XmlElement(name = "login", type = Login.class),
|
||||
@XmlElement(name = "logout", type = Logout.class),
|
||||
@XmlElement(name = "poll", type = Poll.class),
|
||||
@XmlElement(name = "renew", type = Renew.class),
|
||||
@XmlElement(name = "transfer", type = Transfer.class),
|
||||
@XmlElement(name = "update", type = Update.class)
|
||||
})
|
||||
InnerCommand command;
|
||||
|
||||
/** Zero or more command extensions. */
|
||||
@XmlElementRefs({
|
||||
// allocation token extension
|
||||
@XmlElementRef(type = AllocationTokenExtension.class),
|
||||
// fee extension version 0.6
|
||||
@XmlElementRef(type = FeeCheckCommandExtensionV06.class),
|
||||
@XmlElementRef(type = FeeInfoCommandExtensionV06.class),
|
||||
@XmlElementRef(type = FeeCreateCommandExtensionV06.class),
|
||||
@XmlElementRef(type = FeeRenewCommandExtensionV06.class),
|
||||
@XmlElementRef(type = FeeTransferCommandExtensionV06.class),
|
||||
@XmlElementRef(type = FeeUpdateCommandExtensionV06.class),
|
||||
// fee extension version 0.11
|
||||
@XmlElementRef(type = FeeCheckCommandExtensionV11.class),
|
||||
@XmlElementRef(type = FeeCreateCommandExtensionV11.class),
|
||||
@XmlElementRef(type = FeeRenewCommandExtensionV11.class),
|
||||
@XmlElementRef(type = FeeTransferCommandExtensionV11.class),
|
||||
@XmlElementRef(type = FeeUpdateCommandExtensionV11.class),
|
||||
// fee extension version 0.12
|
||||
@XmlElementRef(type = FeeCheckCommandExtensionV12.class),
|
||||
@XmlElementRef(type = FeeCreateCommandExtensionV12.class),
|
||||
@XmlElementRef(type = FeeRenewCommandExtensionV12.class),
|
||||
@XmlElementRef(type = FeeTransferCommandExtensionV12.class),
|
||||
@XmlElementRef(type = FeeUpdateCommandExtensionV12.class),
|
||||
// other extensions
|
||||
@XmlElementRef(type = LaunchCheckExtension.class),
|
||||
@XmlElementRef(type = LaunchCreateExtension.class),
|
||||
@XmlElementRef(type = LaunchDeleteExtension.class),
|
||||
@XmlElementRef(type = LaunchInfoExtension.class),
|
||||
@XmlElementRef(type = LaunchUpdateExtension.class),
|
||||
@XmlElementRef(type = MetadataExtension.class),
|
||||
@XmlElementRef(type = RgpUpdateExtension.class),
|
||||
@XmlElementRef(type = SecDnsCreateExtension.class),
|
||||
@XmlElementRef(type = SecDnsUpdateExtension.class),
|
||||
@XmlElementRef(type = DomainTransferRequestSuperuserExtension.class),
|
||||
@XmlElementRef(type = DomainDeleteSuperuserExtension.class) })
|
||||
// Fee extension version 0.6
|
||||
@XmlElementRef(type = FeeCheckCommandExtensionV06.class),
|
||||
@XmlElementRef(type = FeeInfoCommandExtensionV06.class),
|
||||
@XmlElementRef(type = FeeCreateCommandExtensionV06.class),
|
||||
@XmlElementRef(type = FeeRenewCommandExtensionV06.class),
|
||||
@XmlElementRef(type = FeeTransferCommandExtensionV06.class),
|
||||
@XmlElementRef(type = FeeUpdateCommandExtensionV06.class),
|
||||
|
||||
// Fee extension version 0.11
|
||||
@XmlElementRef(type = FeeCheckCommandExtensionV11.class),
|
||||
@XmlElementRef(type = FeeCreateCommandExtensionV11.class),
|
||||
@XmlElementRef(type = FeeRenewCommandExtensionV11.class),
|
||||
@XmlElementRef(type = FeeTransferCommandExtensionV11.class),
|
||||
@XmlElementRef(type = FeeUpdateCommandExtensionV11.class),
|
||||
|
||||
// Fee extension version 0.12
|
||||
@XmlElementRef(type = FeeCheckCommandExtensionV12.class),
|
||||
@XmlElementRef(type = FeeCreateCommandExtensionV12.class),
|
||||
@XmlElementRef(type = FeeRenewCommandExtensionV12.class),
|
||||
@XmlElementRef(type = FeeTransferCommandExtensionV12.class),
|
||||
@XmlElementRef(type = FeeUpdateCommandExtensionV12.class),
|
||||
|
||||
// Launch phase extensions
|
||||
@XmlElementRef(type = LaunchCheckExtension.class),
|
||||
@XmlElementRef(type = LaunchCreateExtension.class),
|
||||
@XmlElementRef(type = LaunchDeleteExtension.class),
|
||||
@XmlElementRef(type = LaunchInfoExtension.class),
|
||||
@XmlElementRef(type = LaunchUpdateExtension.class),
|
||||
|
||||
// Superuser extensions
|
||||
@XmlElementRef(type = DomainDeleteSuperuserExtension.class),
|
||||
@XmlElementRef(type = DomainTransferRequestSuperuserExtension.class),
|
||||
@XmlElementRef(type = DomainUpdateSuperuserExtension.class),
|
||||
|
||||
// Other extensions
|
||||
@XmlElementRef(type = AllocationTokenExtension.class),
|
||||
@XmlElementRef(type = MetadataExtension.class),
|
||||
@XmlElementRef(type = RgpUpdateExtension.class),
|
||||
@XmlElementRef(type = SecDnsCreateExtension.class),
|
||||
@XmlElementRef(type = SecDnsUpdateExtension.class)
|
||||
})
|
||||
@XmlElementWrapper
|
||||
List<CommandExtension> extension;
|
||||
|
||||
|
||||
@@ -15,11 +15,20 @@
|
||||
package google.registry.model.host;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.EntitySubclass;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.PostLoad;
|
||||
|
||||
/**
|
||||
* A persisted history entry representing an EPP modification to a host.
|
||||
@@ -37,17 +46,33 @@ import javax.persistence.Entity;
|
||||
@javax.persistence.Index(columnList = "historyType"),
|
||||
@javax.persistence.Index(columnList = "historyModificationTime")
|
||||
})
|
||||
@EntitySubclass
|
||||
@Access(AccessType.FIELD)
|
||||
public class HostHistory extends HistoryEntry {
|
||||
|
||||
// Store HostBase instead of HostResource so we don't pick up its @Id
|
||||
HostBase hostBase;
|
||||
@Nullable HostBase hostBase;
|
||||
|
||||
@Column(nullable = false)
|
||||
VKey<HostResource> hostRepoId;
|
||||
|
||||
/** The state of the {@link HostBase} object at this point in time. */
|
||||
public HostBase getHostBase() {
|
||||
return hostBase;
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TempHistorySequenceGenerator")
|
||||
@Column(name = "historyRevisionId")
|
||||
@Access(AccessType.PROPERTY)
|
||||
@Override
|
||||
public long getId() {
|
||||
return super.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* The values of all the fields on the {@link HostBase} object after the action represented by
|
||||
* this history object was executed.
|
||||
*
|
||||
* <p>Will be absent for objects created prior to the Registry 3.0 SQL migration.
|
||||
*/
|
||||
public Optional<HostBase> getHostBase() {
|
||||
return Optional.ofNullable(hostBase);
|
||||
}
|
||||
|
||||
/** The key to the {@link google.registry.model.host.HostResource} this is based off of. */
|
||||
@@ -55,6 +80,19 @@ public class HostHistory extends HistoryEntry {
|
||||
return hostRepoId;
|
||||
}
|
||||
|
||||
@PostLoad
|
||||
void postLoad() {
|
||||
// Normally Hibernate would see that the host fields are all null and would fill hostBase
|
||||
// with a null object. Unfortunately, the updateTimestamp is never null in SQL.
|
||||
if (hostBase != null && hostBase.getHostName() == null) {
|
||||
hostBase = null;
|
||||
}
|
||||
// Fill in the full, symmetric, parent repo ID key
|
||||
Key<HostResource> parentKey = Key.create(HostResource.class, (String) hostRepoId.getSqlKey());
|
||||
parent = parentKey;
|
||||
hostRepoId = VKey.create(HostResource.class, hostRepoId.getSqlKey(), parentKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder asBuilder() {
|
||||
return new Builder(clone(this));
|
||||
|
||||
@@ -32,7 +32,7 @@ import javax.persistence.AccessType;
|
||||
*/
|
||||
@ReportedOn
|
||||
@Entity
|
||||
@javax.persistence.Entity
|
||||
@javax.persistence.Entity(name = "Host")
|
||||
@ExternalMessagingName("host")
|
||||
@WithStringVKey
|
||||
@Access(AccessType.FIELD) // otherwise it'll use the default if the repoId (property)
|
||||
|
||||
@@ -17,6 +17,7 @@ package google.registry.model.index;
|
||||
import static google.registry.util.TypeUtils.instantiate;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Entity;
|
||||
import com.googlecode.objectify.annotation.Id;
|
||||
@@ -25,11 +26,13 @@ import com.googlecode.objectify.annotation.Parent;
|
||||
import google.registry.model.BackupGroupRoot;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.annotations.ReportedOn;
|
||||
import google.registry.schema.replay.DatastoreEntity;
|
||||
import google.registry.schema.replay.SqlEntity;
|
||||
|
||||
/** An index that allows for quick enumeration of all EppResource entities (e.g. via map reduce). */
|
||||
@ReportedOn
|
||||
@Entity
|
||||
public class EppResourceIndex extends BackupGroupRoot {
|
||||
public class EppResourceIndex extends BackupGroupRoot implements DatastoreEntity {
|
||||
|
||||
@Id
|
||||
String id;
|
||||
@@ -74,4 +77,9 @@ public class EppResourceIndex extends BackupGroupRoot {
|
||||
public static <T extends EppResource> EppResourceIndex create(Key<T> resourceKey) {
|
||||
return create(EppResourceIndexBucket.getBucketKey(resourceKey), resourceKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableList<SqlEntity> toSqlEntities() {
|
||||
return ImmutableList.of(); // not relevant in SQL
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,16 +24,23 @@ import com.googlecode.objectify.annotation.Id;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.annotations.VirtualEntity;
|
||||
import google.registry.schema.replay.DatastoreEntity;
|
||||
import google.registry.schema.replay.SqlEntity;
|
||||
|
||||
/** A virtual entity to represent buckets to which EppResourceIndex objects are randomly added. */
|
||||
@Entity
|
||||
@VirtualEntity
|
||||
public class EppResourceIndexBucket extends ImmutableObject {
|
||||
public class EppResourceIndexBucket extends ImmutableObject implements DatastoreEntity {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Id
|
||||
private long bucketId;
|
||||
|
||||
@Override
|
||||
public ImmutableList<SqlEntity> toSqlEntities() {
|
||||
return ImmutableList.of(); // not relevant in SQL
|
||||
}
|
||||
|
||||
/**
|
||||
* Deterministic function that returns a bucket id based on the resource's roid.
|
||||
* NB: At the moment, nothing depends on this being deterministic, so we have the ability to
|
||||
|
||||
@@ -43,6 +43,8 @@ import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.schema.replay.DatastoreEntity;
|
||||
import google.registry.schema.replay.SqlEntity;
|
||||
import google.registry.util.NonFinalForTesting;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -61,28 +63,44 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
|
||||
/** The {@link ForeignKeyIndex} type for {@link ContactResource} entities. */
|
||||
@ReportedOn
|
||||
@Entity
|
||||
public static class ForeignKeyContactIndex extends ForeignKeyIndex<ContactResource> {}
|
||||
public static class ForeignKeyContactIndex extends ForeignKeyIndex<ContactResource>
|
||||
implements DatastoreEntity {
|
||||
@Override
|
||||
public ImmutableList<SqlEntity> toSqlEntities() {
|
||||
return ImmutableList.of(); // not relevant in SQL
|
||||
}
|
||||
}
|
||||
|
||||
/** The {@link ForeignKeyIndex} type for {@link DomainBase} entities. */
|
||||
@ReportedOn
|
||||
@Entity
|
||||
public static class ForeignKeyDomainIndex extends ForeignKeyIndex<DomainBase> {}
|
||||
public static class ForeignKeyDomainIndex extends ForeignKeyIndex<DomainBase>
|
||||
implements DatastoreEntity {
|
||||
@Override
|
||||
public ImmutableList<SqlEntity> toSqlEntities() {
|
||||
return ImmutableList.of(); // not relevant in SQL
|
||||
}
|
||||
}
|
||||
|
||||
/** The {@link ForeignKeyIndex} type for {@link HostResource} entities. */
|
||||
@ReportedOn
|
||||
@Entity
|
||||
public static class ForeignKeyHostIndex extends ForeignKeyIndex<HostResource> {}
|
||||
public static class ForeignKeyHostIndex extends ForeignKeyIndex<HostResource>
|
||||
implements DatastoreEntity {
|
||||
@Override
|
||||
public ImmutableList<SqlEntity> toSqlEntities() {
|
||||
return ImmutableList.of(); // not relevant in SQL
|
||||
}
|
||||
}
|
||||
|
||||
static final ImmutableMap<
|
||||
Class<? extends EppResource>, Class<? extends ForeignKeyIndex<?>>>
|
||||
static final ImmutableMap<Class<? extends EppResource>, Class<? extends ForeignKeyIndex<?>>>
|
||||
RESOURCE_CLASS_TO_FKI_CLASS =
|
||||
ImmutableMap.of(
|
||||
ContactResource.class, ForeignKeyContactIndex.class,
|
||||
DomainBase.class, ForeignKeyDomainIndex.class,
|
||||
HostResource.class, ForeignKeyHostIndex.class);
|
||||
|
||||
@Id
|
||||
String foreignKey;
|
||||
@Id String foreignKey;
|
||||
|
||||
/**
|
||||
* The deletion time of this {@link ForeignKeyIndex}.
|
||||
@@ -90,8 +108,7 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
|
||||
* <p>This will generally be equal to the deletion time of {@link #topReference}. However, in the
|
||||
* case of a {@link HostResource} that was renamed, this field will hold the time of the rename.
|
||||
*/
|
||||
@Index
|
||||
DateTime deletionTime;
|
||||
@Index DateTime deletionTime;
|
||||
|
||||
/**
|
||||
* The referenced resource.
|
||||
|
||||
@@ -24,13 +24,16 @@ import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.googlecode.objectify.Key;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.host.HostHistory;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.transaction.TransactionManager;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.StreamSupport;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Datastore implementation of {@link TransactionManager}. */
|
||||
@@ -98,31 +101,28 @@ public class DatastoreTransactionManager implements TransactionManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveNew(Object entity) {
|
||||
checkArgumentNotNull(entity, "entity must be specified");
|
||||
getOfy().save().entity(entity);
|
||||
public void insert(Object entity) {
|
||||
saveEntity(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveAllNew(ImmutableCollection<?> entities) {
|
||||
public void insertAll(ImmutableCollection<?> entities) {
|
||||
getOfy().save().entities(entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveNewOrUpdate(Object entity) {
|
||||
checkArgumentNotNull(entity, "entity must be specified");
|
||||
getOfy().save().entity(entity);
|
||||
public void put(Object entity) {
|
||||
saveEntity(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveNewOrUpdateAll(ImmutableCollection<?> entities) {
|
||||
public void putAll(ImmutableCollection<?> entities) {
|
||||
getOfy().save().entities(entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Object entity) {
|
||||
checkArgumentNotNull(entity, "entity must be specified");
|
||||
getOfy().save().entity(entity);
|
||||
saveEntity(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -131,13 +131,13 @@ public class DatastoreTransactionManager implements TransactionManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkExists(Object entity) {
|
||||
public boolean exists(Object entity) {
|
||||
return getOfy().load().key(Key.create(entity)).now() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> boolean checkExists(VKey<T> key) {
|
||||
return getOfy().load().key(key.getOfyKey()).now() != null;
|
||||
public <T> boolean exists(VKey<T> key) {
|
||||
return loadNullable(key) != null;
|
||||
}
|
||||
|
||||
// TODO: add tests for these methods. They currently have some degree of test coverage because
|
||||
@@ -146,12 +146,12 @@ public class DatastoreTransactionManager implements TransactionManager {
|
||||
// interface tests that are applied to both the datastore and SQL implementations.
|
||||
@Override
|
||||
public <T> Optional<T> maybeLoad(VKey<T> key) {
|
||||
return Optional.ofNullable(getOfy().load().key(key.getOfyKey()).now());
|
||||
return Optional.ofNullable(loadNullable(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T load(VKey<T> key) {
|
||||
T result = getOfy().load().key(key.getOfyKey()).now();
|
||||
T result = loadNullable(key);
|
||||
if (result == null) {
|
||||
throw new NoSuchElementException(key.toString());
|
||||
}
|
||||
@@ -167,7 +167,10 @@ public class DatastoreTransactionManager implements TransactionManager {
|
||||
.collect(toImmutableMap(key -> (Key<T>) key.getOfyKey(), Functions.identity()));
|
||||
|
||||
return getOfy().load().keys(keyMap.keySet()).entrySet().stream()
|
||||
.collect(ImmutableMap.toImmutableMap(entry -> keyMap.get(entry.getKey()), Entry::getValue));
|
||||
.collect(
|
||||
toImmutableMap(
|
||||
entry -> keyMap.get(entry.getKey()),
|
||||
entry -> toChildHistoryEntryIfPossible(entry.getValue())));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -191,4 +194,37 @@ public class DatastoreTransactionManager implements TransactionManager {
|
||||
.collect(toImmutableList());
|
||||
getOfy().delete().keys(list).now();
|
||||
}
|
||||
|
||||
/**
|
||||
* The following three methods exist due to the migration to Cloud SQL.
|
||||
*
|
||||
* <p>In Cloud SQL, {@link HistoryEntry} objects are represented instead as {@link DomainHistory},
|
||||
* {@link ContactHistory}, and {@link HostHistory} objects. During the migration, we do not wish
|
||||
* to change the Datastore schema so all of these objects are stored in Datastore as HistoryEntry
|
||||
* objects. They are converted to/from the appropriate classes upon retrieval, and converted to
|
||||
* HistoryEntry on save. See go/r3.0-history-objects for more details.
|
||||
*/
|
||||
private void saveEntity(Object entity) {
|
||||
checkArgumentNotNull(entity, "entity must be specified");
|
||||
if (entity instanceof HistoryEntry) {
|
||||
entity = ((HistoryEntry) entity).asHistoryEntry();
|
||||
}
|
||||
getOfy().save().entity(entity);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T toChildHistoryEntryIfPossible(@Nullable T obj) {
|
||||
// NB: The Key of the object in question may not necessarily be the resulting class that we
|
||||
// wish to have. Because all *History classes are @EntitySubclasses, their Keys will have type
|
||||
// HistoryEntry -- even if you create them based off the *History class.
|
||||
if (obj != null && HistoryEntry.class.isAssignableFrom(obj.getClass())) {
|
||||
return (T) ((HistoryEntry) obj).toChildHistoryEntity();
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private <T> T loadNullable(VKey<T> key) {
|
||||
return toChildHistoryEntryIfPossible(getOfy().load().key(key.getOfyKey()).now());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ import google.registry.model.transfer.TransferResponse.ContactTransferResponse;
|
||||
import google.registry.model.transfer.TransferResponse.DomainTransferResponse;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.WithLongVKey;
|
||||
import google.registry.schema.replay.DatastoreAndSqlEntity;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import javax.persistence.AttributeOverride;
|
||||
@@ -92,7 +93,7 @@ import org.joda.time.DateTime;
|
||||
@javax.persistence.Index(columnList = "eventTime")
|
||||
})
|
||||
public abstract class PollMessage extends ImmutableObject
|
||||
implements Buildable, TransferServerApproveEntity {
|
||||
implements Buildable, DatastoreAndSqlEntity, TransferServerApproveEntity {
|
||||
|
||||
/** Entity id. */
|
||||
@Id
|
||||
|
||||
@@ -70,6 +70,13 @@ import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.AttributeOverride;
|
||||
import javax.persistence.AttributeOverrides;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.PostLoad;
|
||||
import javax.persistence.Transient;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
@@ -78,21 +85,31 @@ import org.joda.time.Duration;
|
||||
/** Persisted per-TLD configuration data. */
|
||||
@ReportedOn
|
||||
@Entity
|
||||
@javax.persistence.Entity(name = "Tld")
|
||||
public class Registry extends ImmutableObject implements Buildable {
|
||||
|
||||
@Parent Key<EntityGroupRoot> parent = getCrossTldKey();
|
||||
@Parent @Transient Key<EntityGroupRoot> parent = getCrossTldKey();
|
||||
|
||||
/**
|
||||
* The canonical string representation of the TLD associated with this {@link Registry}, which is
|
||||
* the standard ASCII for regular TLDs and punycoded ASCII for IDN TLDs.
|
||||
*/
|
||||
@Id String tldStrId;
|
||||
@Id
|
||||
@javax.persistence.Id
|
||||
@Column(name = "tld_name", nullable = false)
|
||||
String tldStrId;
|
||||
|
||||
/**
|
||||
* A duplicate of {@link #tldStrId}, to simplify BigQuery reporting since the id field becomes
|
||||
* {@code __key__.name} rather than being exported as a named field.
|
||||
*/
|
||||
String tldStr;
|
||||
@Transient String tldStr;
|
||||
|
||||
/** Sets the Datastore specific field, tldStr, when the entity is loaded from Cloud SQL */
|
||||
@PostLoad
|
||||
void postLoad() {
|
||||
tldStr = tldStrId;
|
||||
}
|
||||
|
||||
/** The suffix that identifies roids as belonging to this specific tld, e.g. -HOW for .how. */
|
||||
String roidSuffix;
|
||||
@@ -116,6 +133,7 @@ public class Registry extends ImmutableObject implements Buildable {
|
||||
public static final Money DEFAULT_RENEW_BILLING_COST = Money.of(USD, 8);
|
||||
public static final Money DEFAULT_RESTORE_BILLING_COST = Money.of(USD, 100);
|
||||
public static final Money DEFAULT_SERVER_STATUS_CHANGE_BILLING_COST = Money.of(USD, 20);
|
||||
public static final Money DEFAULT_REGISTRY_LOCK_OR_UNLOCK_BILLING_COST = Money.of(USD, 0);
|
||||
|
||||
/** The type of TLD, which determines things like backups and escrow policy. */
|
||||
public enum TldType {
|
||||
@@ -289,6 +307,7 @@ public class Registry extends ImmutableObject implements Buildable {
|
||||
* <p>All entries of this list must be valid keys for the map of {@code DnsWriter}s injected by
|
||||
* <code>@Inject Map<String, DnsWriter></code>
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
Set<String> dnsWriters;
|
||||
|
||||
/**
|
||||
@@ -312,6 +331,7 @@ public class Registry extends ImmutableObject implements Buildable {
|
||||
* <p>Failure to do so can result in parallel writes to the {@link
|
||||
* google.registry.dns.writer.DnsWriter}, which may be dangerous depending on your implementation.
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
int numDnsPublishLocks;
|
||||
|
||||
/** Updates an unset numDnsPublishLocks (0) to the standard default of 1. */
|
||||
@@ -327,6 +347,7 @@ public class Registry extends ImmutableObject implements Buildable {
|
||||
* <p>This will be equal to {@link #tldStr} for ASCII TLDs, but will be non-ASCII for IDN TLDs. We
|
||||
* store this in a field so that it will be retained upon import into BigQuery.
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
String tldUnicode;
|
||||
|
||||
/**
|
||||
@@ -334,31 +355,37 @@ public class Registry extends ImmutableObject implements Buildable {
|
||||
*
|
||||
* <p>This is optional; if not configured, then information won't be exported for this TLD.
|
||||
*/
|
||||
String driveFolderId;
|
||||
@Nullable String driveFolderId;
|
||||
|
||||
/** The type of the TLD, whether it's real or for testing. */
|
||||
@Column(nullable = false)
|
||||
@Enumerated(EnumType.STRING)
|
||||
TldType tldType = TldType.REAL;
|
||||
|
||||
/**
|
||||
* Whether to disable invoicing for a {@link TldType#REAL} TLD.
|
||||
* Whether to enable invoicing for this TLD.
|
||||
*
|
||||
* <p>Note that invoicing is always disabled for {@link TldType#TEST} TLDs. Setting this field has
|
||||
* no effect for {@link TldType#TEST} TLDs.
|
||||
* <p>Note that this boolean is the sole determiner on whether invoices should be generated for a
|
||||
* TLD. This applies to {@link TldType#TEST} TLDs as well.
|
||||
*/
|
||||
boolean disableInvoicing = false;
|
||||
@Column(nullable = false)
|
||||
boolean invoicingEnabled = false;
|
||||
|
||||
/**
|
||||
* A property that transitions to different TldStates at different times. Stored as a list of
|
||||
* TldStateTransition embedded objects using the @Mapify annotation.
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
@Mapify(TimedTransitionProperty.TimeMapper.class)
|
||||
TimedTransitionProperty<TldState, TldStateTransition> tldStateTransitions =
|
||||
TimedTransitionProperty.forMapify(DEFAULT_TLD_STATE, TldStateTransition.class);
|
||||
|
||||
/** An automatically managed creation timestamp. */
|
||||
@Column(nullable = false)
|
||||
CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null);
|
||||
|
||||
/** The set of reserved lists that are applicable to this registry. */
|
||||
@Column(name = "reserved_list_names", nullable = false)
|
||||
Set<Key<ReservedList>> reservedLists;
|
||||
|
||||
/** Retrieves an ImmutableSet of all ReservedLists associated with this tld. */
|
||||
@@ -367,12 +394,15 @@ public class Registry extends ImmutableObject implements Buildable {
|
||||
}
|
||||
|
||||
/** The static {@link PremiumList} for this TLD, if there is one. */
|
||||
@Column(name = "premium_list_name", nullable = true)
|
||||
Key<PremiumList> premiumList;
|
||||
|
||||
/** Should RDE upload a nightly escrow deposit for this TLD? */
|
||||
@Column(nullable = false)
|
||||
boolean escrowEnabled = DEFAULT_ESCROW_ENABLED;
|
||||
|
||||
/** Whether the pull queue that writes to authoritative DNS is paused for this TLD. */
|
||||
@Column(nullable = false)
|
||||
boolean dnsPaused = DEFAULT_DNS_PAUSED;
|
||||
|
||||
/**
|
||||
@@ -381,41 +411,85 @@ public class Registry extends ImmutableObject implements Buildable {
|
||||
* <p>Domain deletes are free and effective immediately so long as they take place within this
|
||||
* amount of time following creation.
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
Duration addGracePeriodLength = DEFAULT_ADD_GRACE_PERIOD;
|
||||
|
||||
/** The length of the anchor tenant add grace period for this TLD. */
|
||||
@Column(nullable = false)
|
||||
Duration anchorTenantAddGracePeriodLength = DEFAULT_ANCHOR_TENANT_ADD_GRACE_PERIOD;
|
||||
|
||||
/** The length of the auto renew grace period for this TLD. */
|
||||
@Column(nullable = false)
|
||||
Duration autoRenewGracePeriodLength = DEFAULT_AUTO_RENEW_GRACE_PERIOD;
|
||||
|
||||
/** The length of the redemption grace period for this TLD. */
|
||||
@Column(nullable = false)
|
||||
Duration redemptionGracePeriodLength = DEFAULT_REDEMPTION_GRACE_PERIOD;
|
||||
|
||||
/** The length of the renew grace period for this TLD. */
|
||||
@Column(nullable = false)
|
||||
Duration renewGracePeriodLength = DEFAULT_RENEW_GRACE_PERIOD;
|
||||
|
||||
/** The length of the transfer grace period for this TLD. */
|
||||
@Column(nullable = false)
|
||||
Duration transferGracePeriodLength = DEFAULT_TRANSFER_GRACE_PERIOD;
|
||||
|
||||
/** The length of time before a transfer is automatically approved for this TLD. */
|
||||
@Column(nullable = false)
|
||||
Duration automaticTransferLength = DEFAULT_AUTOMATIC_TRANSFER_LENGTH;
|
||||
|
||||
/** The length of time a domain spends in the non-redeemable pending delete phase for this TLD. */
|
||||
@Column(nullable = false)
|
||||
Duration pendingDeleteLength = DEFAULT_PENDING_DELETE_LENGTH;
|
||||
|
||||
/** The currency unit for all costs associated with this TLD. */
|
||||
@Column(nullable = false)
|
||||
CurrencyUnit currency = DEFAULT_CURRENCY;
|
||||
|
||||
/** The per-year billing cost for registering a new domain name. */
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(
|
||||
name = "money.amount",
|
||||
column = @Column(name = "create_billing_cost_amount")),
|
||||
@AttributeOverride(
|
||||
name = "money.currency",
|
||||
column = @Column(name = "create_billing_cost_currency"))
|
||||
})
|
||||
Money createBillingCost = DEFAULT_CREATE_BILLING_COST;
|
||||
|
||||
/** The one-time billing cost for restoring a domain name from the redemption grace period. */
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(
|
||||
name = "money.amount",
|
||||
column = @Column(name = "restore_billing_cost_amount")),
|
||||
@AttributeOverride(
|
||||
name = "money.currency",
|
||||
column = @Column(name = "restore_billing_cost_currency"))
|
||||
})
|
||||
Money restoreBillingCost = DEFAULT_RESTORE_BILLING_COST;
|
||||
|
||||
/** The one-time billing cost for changing the server status (i.e. lock). */
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(
|
||||
name = "money.amount",
|
||||
column = @Column(name = "server_status_change_billing_cost_amount")),
|
||||
@AttributeOverride(
|
||||
name = "money.currency",
|
||||
column = @Column(name = "server_status_change_billing_cost_currency"))
|
||||
})
|
||||
Money serverStatusChangeBillingCost = DEFAULT_SERVER_STATUS_CHANGE_BILLING_COST;
|
||||
|
||||
/** The one-time billing cost for a registry lock/unlock action initiated by a registrar. */
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(
|
||||
name = "money.amount",
|
||||
column = @Column(name = "registry_lock_or_unlock_cost_amount")),
|
||||
@AttributeOverride(
|
||||
name = "money.currency",
|
||||
column = @Column(name = "registry_lock_or_unlock_cost_currency"))
|
||||
})
|
||||
Money registryLockOrUnlockBillingCost = DEFAULT_REGISTRY_LOCK_OR_UNLOCK_BILLING_COST;
|
||||
|
||||
/**
|
||||
* A property that transitions to different renew billing costs at different times. Stored as a
|
||||
* list of BillingCostTransition embedded objects using the @Mapify annotation.
|
||||
@@ -424,11 +498,13 @@ public class Registry extends ImmutableObject implements Buildable {
|
||||
* name. This cost is also used to compute costs for transfers, since each transfer includes a
|
||||
* renewal to ensure transfers have a cost.
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
@Mapify(TimedTransitionProperty.TimeMapper.class)
|
||||
TimedTransitionProperty<Money, BillingCostTransition> renewBillingCostTransitions =
|
||||
TimedTransitionProperty.forMapify(DEFAULT_RENEW_BILLING_COST, BillingCostTransition.class);
|
||||
|
||||
/** A property that tracks the EAP fee schedule (if any) for the TLD. */
|
||||
@Column(nullable = false)
|
||||
@Mapify(TimedTransitionProperty.TimeMapper.class)
|
||||
TimedTransitionProperty<Money, BillingCostTransition> eapFeeSchedule =
|
||||
TimedTransitionProperty.forMapify(DEFAULT_EAP_BILLING_COST, BillingCostTransition.class);
|
||||
@@ -437,13 +513,14 @@ public class Registry extends ImmutableObject implements Buildable {
|
||||
String lordnUsername;
|
||||
|
||||
/** The end of the claims period (at or after this time, claims no longer applies). */
|
||||
@Column(nullable = false)
|
||||
DateTime claimsPeriodEnd = END_OF_TIME;
|
||||
|
||||
/** An allow list of clients allowed to be used on domains on this TLD (ignored if empty). */
|
||||
Set<String> allowedRegistrantContactIds;
|
||||
@Nullable Set<String> allowedRegistrantContactIds;
|
||||
|
||||
/** An allow list of hosts allowed to be used on domains on this TLD (ignored if empty). */
|
||||
Set<String> allowedFullyQualifiedHostNames;
|
||||
@Nullable Set<String> allowedFullyQualifiedHostNames;
|
||||
|
||||
public String getTldStr() {
|
||||
return tldStr;
|
||||
@@ -566,6 +643,11 @@ public class Registry extends ImmutableObject implements Buildable {
|
||||
return serverStatusChangeBillingCost;
|
||||
}
|
||||
|
||||
/** Returns the cost of a registry lock/unlock. */
|
||||
public Money getRegistryLockOrUnlockBillingCost() {
|
||||
return registryLockOrUnlockBillingCost;
|
||||
}
|
||||
|
||||
public ImmutableSortedMap<DateTime, TldState> getTldStateTransitions() {
|
||||
return tldStateTransitions.toValueMap();
|
||||
}
|
||||
@@ -646,8 +728,8 @@ public class Registry extends ImmutableObject implements Buildable {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setDisableInvoicing(boolean disableInvoicing) {
|
||||
getInstance().disableInvoicing = disableInvoicing;
|
||||
public Builder setInvoicingEnabled(boolean invoicingEnabled) {
|
||||
getInstance().invoicingEnabled = invoicingEnabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -867,6 +949,12 @@ public class Registry extends ImmutableObject implements Buildable {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setRegistryLockOrUnlockBillingCost(Money amount) {
|
||||
checkArgument(amount.isPositiveOrZero(), "Registry lock/unlock cost cannot be negative");
|
||||
getInstance().registryLockOrUnlockBillingCost = amount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setLordnUsername(String username) {
|
||||
getInstance().lordnUsername = username;
|
||||
return this;
|
||||
@@ -918,6 +1006,9 @@ public class Registry extends ImmutableObject implements Buildable {
|
||||
checkArgument(
|
||||
instance.getServerStatusChangeCost().getCurrencyUnit().equals(instance.currency),
|
||||
"Server status change cost must be in the registry's currency");
|
||||
checkArgument(
|
||||
instance.getRegistryLockOrUnlockBillingCost().getCurrencyUnit().equals(instance.currency),
|
||||
"Registry lock/unlock cost must be in the registry's currency");
|
||||
Predicate<Money> currencyCheck =
|
||||
(Money money) -> money.getCurrencyUnit().equals(instance.currency);
|
||||
checkArgument(
|
||||
|
||||
@@ -114,7 +114,7 @@ public final class PremiumList extends BaseDomainLabelList<Money, PremiumList.Pr
|
||||
/** Virtual parent entity for premium list entry entities associated with a single revision. */
|
||||
@ReportedOn
|
||||
@Entity
|
||||
public static class PremiumListRevision extends ImmutableObject {
|
||||
public static class PremiumListRevision extends ImmutableObject implements DatastoreEntity {
|
||||
|
||||
@Parent Key<PremiumList> parent;
|
||||
|
||||
@@ -171,6 +171,11 @@ public final class PremiumList extends BaseDomainLabelList<Money, PremiumList.Pr
|
||||
}
|
||||
return revision;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableList<SqlEntity> toSqlEntities() {
|
||||
return ImmutableList.of(); // not persisted in SQL
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,7 +43,7 @@ public class ReservedListDualWriteDao {
|
||||
|
||||
/** Persist a new reserved list to Cloud SQL. */
|
||||
public static void save(ReservedList reservedList) {
|
||||
ofyTm().transact(() -> ofyTm().saveNewOrUpdate(reservedList));
|
||||
ofyTm().transact(() -> ofyTm().put(reservedList));
|
||||
try {
|
||||
logger.atInfo().log("Saving reserved list %s to Cloud SQL", reservedList.getName());
|
||||
ReservedListSqlDao.save(reservedList);
|
||||
|
||||
@@ -31,7 +31,7 @@ public class ReservedListSqlDao {
|
||||
/** Persist a new reserved list to Cloud SQL. */
|
||||
public static void save(ReservedList reservedList) {
|
||||
checkArgumentNotNull(reservedList, "Must specify reservedList");
|
||||
jpaTm().transact(() -> jpaTm().saveNew(reservedList));
|
||||
jpaTm().transact(() -> jpaTm().insert(reservedList));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,8 +19,16 @@ import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
@@ -34,9 +42,16 @@ import org.joda.time.DateTime;
|
||||
* uses HistoryEntry.otherClientId because the losing party in a transfer is always the otherClient.
|
||||
*/
|
||||
@Embed
|
||||
@Entity
|
||||
public class DomainTransactionRecord extends ImmutableObject implements Buildable {
|
||||
|
||||
@Id
|
||||
@Ignore
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
Long id;
|
||||
|
||||
/** The TLD this record operates on. */
|
||||
@Column(nullable = false)
|
||||
String tld;
|
||||
|
||||
/**
|
||||
@@ -50,9 +65,12 @@ public class DomainTransactionRecord extends ImmutableObject implements Buildabl
|
||||
* href="https://www.icann.org/resources/unthemed-pages/registry-agmt-appc-10-2001-05-11-en">
|
||||
* Grace period spec</a>
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
DateTime reportingTime;
|
||||
|
||||
/** The transaction report field we add reportAmount to for this registrar. */
|
||||
@Column(nullable = false)
|
||||
@Enumerated(value = EnumType.STRING)
|
||||
TransactionReportField reportField;
|
||||
|
||||
/**
|
||||
@@ -67,6 +85,7 @@ public class DomainTransactionRecord extends ImmutableObject implements Buildabl
|
||||
* original SUCCESSFUL transfer counters. Finally, if we explicitly allow a transfer, the report
|
||||
* amount is 0, as we've already counted the transfer in the original request.
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
Integer reportAmount;
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,8 +14,11 @@
|
||||
|
||||
package google.registry.model.reporting;
|
||||
|
||||
import static com.googlecode.objectify.Key.getKind;
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Entity;
|
||||
@@ -28,21 +31,28 @@ import google.registry.model.Buildable;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.annotations.ReportedOn;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.Period;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.host.HostHistory;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.WithStringVKey;
|
||||
import google.registry.schema.replay.DatastoreEntity;
|
||||
import google.registry.schema.replay.SqlEntity;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
import javax.persistence.AttributeOverride;
|
||||
import javax.persistence.AttributeOverrides;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.MappedSuperclass;
|
||||
import javax.persistence.SequenceGenerator;
|
||||
import javax.persistence.Transient;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
@@ -51,7 +61,8 @@ import org.joda.time.DateTime;
|
||||
@Entity
|
||||
@MappedSuperclass
|
||||
@WithStringVKey // TODO(b/162229294): This should be resolved during the course of that bug
|
||||
public class HistoryEntry extends ImmutableObject implements Buildable {
|
||||
@Access(AccessType.FIELD)
|
||||
public class HistoryEntry extends ImmutableObject implements Buildable, DatastoreEntity, SqlEntity {
|
||||
|
||||
/** Represents the type of history entry. */
|
||||
public enum Type {
|
||||
@@ -102,16 +113,13 @@ public class HistoryEntry extends ImmutableObject implements Buildable {
|
||||
SYNTHETIC
|
||||
}
|
||||
|
||||
/** The autogenerated id of this event. */
|
||||
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "HistorySequenceGenerator")
|
||||
@SequenceGenerator(
|
||||
name = "HistorySequenceGenerator",
|
||||
sequenceName = "history_id_sequence",
|
||||
allocationSize = 1)
|
||||
@Id
|
||||
@javax.persistence.Id
|
||||
@Column(name = "historyRevisionId")
|
||||
Long id;
|
||||
/**
|
||||
* The autogenerated id of this event. Note that, this field is marked as {@link Transient} in the
|
||||
* SQL schema, this is because the child class of {@link HistoryEntry}, e.g. {@link
|
||||
* DomainHistory}, uses a composite primary key which the id is part of, and Hibernate requires
|
||||
* that all the {@link javax.persistence.Id} fields must be put in the exact same class.
|
||||
*/
|
||||
@Id @Transient @VisibleForTesting public Long id;
|
||||
|
||||
/** The resource this event mutated. */
|
||||
@Parent @Transient protected Key<? extends EppResource> parent;
|
||||
@@ -170,7 +178,7 @@ public class HistoryEntry extends ImmutableObject implements Buildable {
|
||||
boolean bySuperuser;
|
||||
|
||||
/** Reason for the change. */
|
||||
@Column(nullable = false, name = "historyReason")
|
||||
@Column(name = "historyReason")
|
||||
String reason;
|
||||
|
||||
/** Whether this change was requested by a registrar. */
|
||||
@@ -187,8 +195,17 @@ public class HistoryEntry extends ImmutableObject implements Buildable {
|
||||
@Transient // domain-specific
|
||||
Set<DomainTransactionRecord> domainTransactionRecords;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
public long getId() {
|
||||
// For some reason, Hibernate throws NPE during some initialization phase if we don't deal with
|
||||
// the null case. Setting the id to 0L when it is null should be fine because 0L for primitive
|
||||
// type is considered as null for wrapper class in the Hibernate context.
|
||||
return id == null ? 0L : id;
|
||||
}
|
||||
|
||||
/** This method exists solely to satisfy Hibernate. Use the {@link Builder} instead. */
|
||||
@SuppressWarnings("UnusedMethod")
|
||||
private void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Key<? extends EppResource> getParent() {
|
||||
@@ -237,10 +254,28 @@ public class HistoryEntry extends ImmutableObject implements Buildable {
|
||||
return requestedByRegistrar;
|
||||
}
|
||||
|
||||
public ImmutableSet<DomainTransactionRecord> getDomainTransactionRecords() {
|
||||
public Set<DomainTransactionRecord> getDomainTransactionRecords() {
|
||||
return nullToEmptyImmutableCopy(domainTransactionRecords);
|
||||
}
|
||||
|
||||
/** This method exists solely to satisfy Hibernate. Use the {@link Builder} instead. */
|
||||
@SuppressWarnings("UnusedMethod")
|
||||
private void setPeriod(Period period) {
|
||||
this.period = period;
|
||||
}
|
||||
|
||||
/** This method exists solely to satisfy Hibernate. Use the {@link Builder} instead. */
|
||||
@SuppressWarnings("UnusedMethod")
|
||||
private void setOtherRegistrarId(String otherRegistrarId) {
|
||||
this.otherClientId = otherRegistrarId;
|
||||
}
|
||||
|
||||
/** This method exists solely to satisfy Hibernate. Use the {@link Builder} instead. */
|
||||
@SuppressWarnings("UnusedMethod")
|
||||
private void setDomainTransactionRecords(Set<DomainTransactionRecord> domainTransactionRecords) {
|
||||
this.domainTransactionRecords = ImmutableSet.copyOf(domainTransactionRecords);
|
||||
}
|
||||
|
||||
public static VKey<HistoryEntry> createVKey(Key<HistoryEntry> key) {
|
||||
// TODO(b/159207551): This will likely need some revision. As it stands, this method was
|
||||
// introduced purely to facilitate testing of VKey specialization in VKeyTranslatorFactory.
|
||||
@@ -259,6 +294,52 @@ public class HistoryEntry extends ImmutableObject implements Buildable {
|
||||
return new Builder(clone(this));
|
||||
}
|
||||
|
||||
public HistoryEntry asHistoryEntry() {
|
||||
return new Builder().copyFrom(this).build();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public HistoryEntry toChildHistoryEntity() {
|
||||
String parentKind = getParent().getKind();
|
||||
final HistoryEntry resultEntity;
|
||||
// can't use a switch statement since we're calling getKind()
|
||||
if (parentKind.equals(getKind(DomainBase.class))) {
|
||||
resultEntity =
|
||||
new DomainHistory.Builder().copyFrom(this).setDomainRepoId(parent.getName()).build();
|
||||
} else if (parentKind.equals(getKind(HostResource.class))) {
|
||||
resultEntity =
|
||||
new HostHistory.Builder()
|
||||
.copyFrom(this)
|
||||
.setHostRepoId(
|
||||
VKey.create(HostResource.class, parent.getName(), (Key<HostResource>) parent))
|
||||
.build();
|
||||
} else if (parentKind.equals(getKind(ContactResource.class))) {
|
||||
resultEntity =
|
||||
new ContactHistory.Builder()
|
||||
.copyFrom(this)
|
||||
.setContactRepoId(
|
||||
VKey.create(
|
||||
ContactResource.class, parent.getName(), (Key<ContactResource>) parent))
|
||||
.build();
|
||||
} else {
|
||||
throw new IllegalStateException(
|
||||
String.format("Unknown kind of HistoryEntry parent %s", parentKind));
|
||||
}
|
||||
return resultEntity;
|
||||
}
|
||||
|
||||
// In SQL, save the child type
|
||||
@Override
|
||||
public ImmutableList<SqlEntity> toSqlEntities() {
|
||||
return ImmutableList.of(toChildHistoryEntity());
|
||||
}
|
||||
|
||||
// In Datastore, save as a HistoryEntry object regardless of this object's type
|
||||
@Override
|
||||
public ImmutableList<DatastoreEntity> toDatastoreEntities() {
|
||||
return ImmutableList.of(asHistoryEntry());
|
||||
}
|
||||
|
||||
/** A builder for {@link HistoryEntry} since it is immutable */
|
||||
public static class Builder<T extends HistoryEntry, B extends Builder<?, ?>>
|
||||
extends GenericBuilder<T, B> {
|
||||
@@ -268,11 +349,38 @@ public class HistoryEntry extends ImmutableObject implements Buildable {
|
||||
super(instance);
|
||||
}
|
||||
|
||||
// Used to fill out the fields in this object from an object which may not be exactly the same
|
||||
// as the class T, where both classes still subclass HistoryEntry
|
||||
public B copyFrom(HistoryEntry historyEntry) {
|
||||
setId(historyEntry.id);
|
||||
setParent(historyEntry.parent);
|
||||
setType(historyEntry.type);
|
||||
setPeriod(historyEntry.period);
|
||||
setXmlBytes(historyEntry.xmlBytes);
|
||||
setModificationTime(historyEntry.modificationTime);
|
||||
setClientId(historyEntry.clientId);
|
||||
setOtherClientId(historyEntry.otherClientId);
|
||||
setTrid(historyEntry.trid);
|
||||
setBySuperuser(historyEntry.bySuperuser);
|
||||
setReason(historyEntry.reason);
|
||||
setRequestedByRegistrar(historyEntry.requestedByRegistrar);
|
||||
setDomainTransactionRecords(
|
||||
historyEntry.domainTransactionRecords == null
|
||||
? null
|
||||
: ImmutableSet.copyOf(historyEntry.domainTransactionRecords));
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T build() {
|
||||
return super.build();
|
||||
}
|
||||
|
||||
public B setId(long id) {
|
||||
getInstance().id = id;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setParent(EppResource parent) {
|
||||
getInstance().parent = Key.create(parent);
|
||||
return thisCastToDerived();
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.schema.tmch;
|
||||
package google.registry.model.tmch;
|
||||
|
||||
import static google.registry.config.RegistryConfig.getDomainLabelListCacheDuration;
|
||||
import static google.registry.model.CacheUtils.tryMemoizeWithExpiration;
|
||||
@@ -24,28 +24,28 @@ import google.registry.util.NonFinalForTesting;
|
||||
import java.util.Optional;
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
/** Data access object for {@link ClaimsList}. */
|
||||
/** Data access object for {@link ClaimsListShard}. */
|
||||
public class ClaimsListDao {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
/** In-memory cache for claims list. */
|
||||
@NonFinalForTesting
|
||||
private static Supplier<Optional<ClaimsList>> cacheClaimsList =
|
||||
private static Supplier<Optional<ClaimsListShard>> cacheClaimsList =
|
||||
tryMemoizeWithExpiration(getDomainLabelListCacheDuration(), ClaimsListDao::getLatestRevision);
|
||||
|
||||
private static void save(ClaimsList claimsList) {
|
||||
private static void save(ClaimsListShard claimsList) {
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().persist(claimsList));
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to save the given {@link ClaimsList} into Cloud SQL. If the save fails, the error will be
|
||||
* logged but no exception will be thrown.
|
||||
* Try to save the given {@link ClaimsListShard} into Cloud SQL. If the save fails, the error will
|
||||
* be logged but no exception will be thrown.
|
||||
*
|
||||
* <p>This method is used during the dual-write phase of database migration as Datastore is still
|
||||
* the authoritative database.
|
||||
*/
|
||||
public static void trySave(ClaimsList claimsList) {
|
||||
static void trySave(ClaimsListShard claimsList) {
|
||||
try {
|
||||
ClaimsListDao.save(claimsList);
|
||||
logger.atInfo().log(
|
||||
@@ -57,12 +57,12 @@ public class ClaimsListDao {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the most recent revision of the {@link ClaimsList} in Cloud SQL, if it exists.
|
||||
* Returns the most recent revision of the {@link ClaimsListShard} in Cloud SQL, if it exists.
|
||||
* TODO(shicong): Change this method to package level access after dual-read phase.
|
||||
* ClaimsListShard uses this method to retrieve claims list in Cloud SQL for the comparison, and
|
||||
* ClaimsListShard is not in this package.
|
||||
*/
|
||||
public static Optional<ClaimsList> getLatestRevision() {
|
||||
public static Optional<ClaimsListShard> getLatestRevision() {
|
||||
return jpaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
@@ -73,15 +73,15 @@ public class ClaimsListDao {
|
||||
return em.createQuery(
|
||||
"FROM ClaimsList cl LEFT JOIN FETCH cl.labelsToKeys WHERE cl.revisionId ="
|
||||
+ " :revisionId",
|
||||
ClaimsList.class)
|
||||
ClaimsListShard.class)
|
||||
.setParameter("revisionId", revisionId)
|
||||
.getResultStream()
|
||||
.findFirst();
|
||||
});
|
||||
}
|
||||
|
||||
/** Returns the most recent revision of the {@link ClaimsList}, from cache. */
|
||||
public static Optional<ClaimsList> getLatestRevisionCached() {
|
||||
/** Returns the most recent revision of the {@link ClaimsListShard}, from cache. */
|
||||
public static Optional<ClaimsListShard> getLatestRevisionCached() {
|
||||
return cacheClaimsList.get();
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import com.googlecode.objectify.annotation.Id;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import com.googlecode.objectify.annotation.OnSave;
|
||||
import com.googlecode.objectify.annotation.Parent;
|
||||
import google.registry.model.CreateAutoTimestamp;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.annotations.NotBackedUp;
|
||||
import google.registry.model.annotations.NotBackedUp.Reason;
|
||||
@@ -47,8 +48,6 @@ import google.registry.model.annotations.VirtualEntity;
|
||||
import google.registry.model.common.CrossTldSingleton;
|
||||
import google.registry.schema.replay.DatastoreEntity;
|
||||
import google.registry.schema.replay.SqlEntity;
|
||||
import google.registry.schema.tmch.ClaimsList;
|
||||
import google.registry.schema.tmch.ClaimsListDao;
|
||||
import google.registry.util.CollectionUtils;
|
||||
import google.registry.util.Concurrent;
|
||||
import google.registry.util.Retrier;
|
||||
@@ -59,6 +58,15 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.CollectionTable;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.ElementCollection;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.MapKeyColumn;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Transient;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
@@ -74,10 +82,21 @@ import org.joda.time.DateTime;
|
||||
* 10MB per transaction limit.
|
||||
*
|
||||
* <p>Therefore, it is never OK to save an instance of this class directly to Datastore. Instead you
|
||||
* must use the {@link #save} method to do it for you.
|
||||
* must use the {@link #saveToDatastore} method to do it for you.
|
||||
*
|
||||
* <p>Note that the primary key of this entity is {@link #revisionId}, which is auto-generated by
|
||||
* the database. So, if a retry of insertion happens after the previous attempt unexpectedly
|
||||
* succeeds, we will end up with having two exact same claims list with only different {@link
|
||||
* #revisionId}. However, this is not an actual problem because we only use the claims list with
|
||||
* highest {@link #revisionId}.
|
||||
*
|
||||
* <p>TODO(b/162007765): Rename the class to ClaimsList and remove Datastore related fields and
|
||||
* methods.
|
||||
*/
|
||||
@Entity
|
||||
@NotBackedUp(reason = Reason.EXTERNALLY_SOURCED)
|
||||
@javax.persistence.Entity(name = "ClaimsList")
|
||||
@Table
|
||||
public class ClaimsListShard extends ImmutableObject implements DatastoreEntity {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
@@ -85,22 +104,44 @@ public class ClaimsListShard extends ImmutableObject implements DatastoreEntity
|
||||
/** The number of claims list entries to store per shard. */
|
||||
private static final int SHARD_SIZE = 10000;
|
||||
|
||||
@Id
|
||||
long id;
|
||||
@Transient @Id long id;
|
||||
|
||||
@Parent
|
||||
Key<ClaimsListRevision> parent;
|
||||
@Transient @Parent Key<ClaimsListRevision> parent;
|
||||
|
||||
/** When the claims list was last updated. */
|
||||
@Ignore
|
||||
@javax.persistence.Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
Long revisionId;
|
||||
|
||||
@Ignore
|
||||
@Column(nullable = false)
|
||||
CreateAutoTimestamp creationTimestamp = CreateAutoTimestamp.create(null);
|
||||
|
||||
/**
|
||||
* When the claims list was last updated.
|
||||
*
|
||||
* <p>Note that the value of this field is parsed from the claims list file(See this <a
|
||||
* href="https://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-6.1">RFC</>), it is
|
||||
* the DNL List creation datetime from the rfc. Since this field has been used by Datastore, we
|
||||
* cannot change its name until we finish the migration.
|
||||
*
|
||||
* <p>TODO(b/166784536): Rename this field to tmdbGenerationTime.
|
||||
*/
|
||||
@Column(name = "tmdb_generation_time", nullable = false)
|
||||
DateTime creationTime;
|
||||
|
||||
/** A map from labels to claims keys. */
|
||||
@EmbedMap
|
||||
@ElementCollection
|
||||
@CollectionTable(
|
||||
name = "ClaimsEntry",
|
||||
joinColumns = @JoinColumn(name = "revisionId", referencedColumnName = "revisionId"))
|
||||
@MapKeyColumn(name = "domainLabel", nullable = false)
|
||||
@Column(name = "claimKey", nullable = false)
|
||||
Map<String, String> labelsToKeys;
|
||||
|
||||
/** Indicates that this is a shard rather than a "full" list. */
|
||||
@Ignore
|
||||
boolean isShard = false;
|
||||
@Ignore @Transient boolean isShard = false;
|
||||
|
||||
private static final Retrier LOADER_RETRIER = new Retrier(new SystemSleeper(), 2);
|
||||
|
||||
@@ -164,10 +205,10 @@ public class ClaimsListShard extends ImmutableObject implements DatastoreEntity
|
||||
return datastoreList;
|
||||
};
|
||||
|
||||
private static final void loadAndCompareCloudSqlList(ClaimsListShard datastoreList) {
|
||||
Optional<ClaimsList> maybeCloudSqlList = ClaimsListDao.getLatestRevision();
|
||||
private static void loadAndCompareCloudSqlList(ClaimsListShard datastoreList) {
|
||||
Optional<ClaimsListShard> maybeCloudSqlList = ClaimsListDao.getLatestRevision();
|
||||
if (maybeCloudSqlList.isPresent()) {
|
||||
ClaimsList cloudSqlList = maybeCloudSqlList.get();
|
||||
ClaimsListShard cloudSqlList = maybeCloudSqlList.get();
|
||||
MapDifference<String, String> diff =
|
||||
Maps.difference(datastoreList.labelsToKeys, cloudSqlList.getLabelsToKeys());
|
||||
if (!diff.areEqual()) {
|
||||
@@ -206,15 +247,34 @@ public class ClaimsListShard extends ImmutableObject implements DatastoreEntity
|
||||
memoizeWithShortExpiration(
|
||||
() -> LOADER_RETRIER.callWithRetry(LOADER_CALLABLE, IllegalStateException.class));
|
||||
|
||||
public DateTime getCreationTime() {
|
||||
/** Returns the revision id of this claims list, or throws exception if it is null. */
|
||||
public Long getRevisionId() {
|
||||
checkState(
|
||||
revisionId != null, "revisionId is null because it is not persisted in the database");
|
||||
return revisionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time when the external TMDB service generated this revision of the claims list.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-6.1">DNL List
|
||||
* creation datetime</a>
|
||||
*/
|
||||
public DateTime getTmdbGenerationTime() {
|
||||
return creationTime;
|
||||
}
|
||||
|
||||
/** Returns the creation time of this claims list. */
|
||||
public DateTime getCreationTimestamp() {
|
||||
return creationTimestamp.getTimestamp();
|
||||
}
|
||||
|
||||
/** Returns the claim key for a given domain if there is one, empty otherwise. */
|
||||
public Optional<String> getClaimKey(String label) {
|
||||
return Optional.ofNullable(labelsToKeys.get(label));
|
||||
}
|
||||
|
||||
/** Returns an {@link Map} mapping domain label to its lookup key. */
|
||||
public ImmutableMap<String, String> getLabelsToKeys() {
|
||||
return ImmutableMap.copyOf(labelsToKeys);
|
||||
}
|
||||
@@ -229,11 +289,12 @@ public class ClaimsListShard extends ImmutableObject implements DatastoreEntity
|
||||
* switching over to using them atomically, then deleting the old ones.
|
||||
*/
|
||||
public void save() {
|
||||
save(SHARD_SIZE);
|
||||
saveToDatastore(SHARD_SIZE);
|
||||
ClaimsListDao.trySave(this);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void save(int shardSize) {
|
||||
void saveToDatastore(int shardSize) {
|
||||
// Figure out what the next versionId should be based on which ones already exist.
|
||||
final Key<ClaimsListRevision> oldRevision = getCurrentRevision();
|
||||
final Key<ClaimsListRevision> parentKey = ClaimsListRevision.createKey();
|
||||
@@ -270,10 +331,11 @@ public class ClaimsListShard extends ImmutableObject implements DatastoreEntity
|
||||
});
|
||||
}
|
||||
|
||||
public static ClaimsListShard create(DateTime creationTime, Map<String, String> labelsToKeys) {
|
||||
public static ClaimsListShard create(
|
||||
DateTime tmdbGenerationTime, Map<String, String> labelsToKeys) {
|
||||
ClaimsListShard instance = new ClaimsListShard();
|
||||
instance.id = allocateId();
|
||||
instance.creationTime = checkNotNull(creationTime);
|
||||
instance.creationTime = checkNotNull(tmdbGenerationTime);
|
||||
instance.labelsToKeys = checkNotNull(labelsToKeys);
|
||||
return instance;
|
||||
}
|
||||
|
||||
@@ -59,6 +59,9 @@ public class HibernateSchemaExporter {
|
||||
settings.put(Environment.USER, username);
|
||||
settings.put(Environment.PASS, password);
|
||||
settings.put(Environment.HBM2DDL_AUTO, "none");
|
||||
// Register driver explicitly to work around ServiceLoader change after Java 8.
|
||||
// Driver self-registration only works if driver is declared in a module.
|
||||
settings.put(Environment.DRIVER, "org.postgresql.Driver");
|
||||
settings.put(Environment.SHOW_SQL, "true");
|
||||
settings.put(
|
||||
Environment.PHYSICAL_NAMING_STRATEGY, NomulusNamingStrategy.class.getCanonicalName());
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2020 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.persistence;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.hibernate.boot.archive.internal.StandardArchiveDescriptorFactory;
|
||||
import org.hibernate.boot.archive.scan.internal.ScanResultImpl;
|
||||
import org.hibernate.boot.archive.scan.internal.StandardScanner;
|
||||
import org.hibernate.boot.archive.scan.spi.ScanEnvironment;
|
||||
import org.hibernate.boot.archive.scan.spi.ScanOptions;
|
||||
import org.hibernate.boot.archive.scan.spi.ScanParameters;
|
||||
import org.hibernate.boot.archive.scan.spi.ScanResult;
|
||||
import org.hibernate.boot.archive.scan.spi.Scanner;
|
||||
|
||||
/**
|
||||
* A do-nothing {@link Scanner} for Hibernate that works around bugs in Hibernate's default
|
||||
* implementation. This is required for the Nomulus tool.
|
||||
*
|
||||
* <p>Please refer to <a href="../../../../resources/META-INF/persistence.xml">persistence.xml</a>
|
||||
* for more information.
|
||||
*/
|
||||
public class NoopJpaEntityScanner extends StandardScanner {
|
||||
|
||||
public NoopJpaEntityScanner() {
|
||||
super(StandardArchiveDescriptorFactory.INSTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScanResult scan(
|
||||
ScanEnvironment environment, ScanOptions options, ScanParameters parameters) {
|
||||
return new ScanResultImpl(ImmutableSet.of(), ImmutableSet.of(), ImmutableSet.of());
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,11 @@ public class CreateAutoTimestampConverter
|
||||
implements AttributeConverter<CreateAutoTimestamp, Timestamp> {
|
||||
|
||||
@Override
|
||||
public Timestamp convertToDatabaseColumn(CreateAutoTimestamp entity) {
|
||||
@Nullable
|
||||
public Timestamp convertToDatabaseColumn(@Nullable CreateAutoTimestamp entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
DateTime dateTime = firstNonNull(entity.getTimestamp(), jpaTm().getTransactionTime());
|
||||
return Timestamp.from(DateTimeUtils.toZonedDateTime(dateTime).toInstant());
|
||||
}
|
||||
|
||||
@@ -40,11 +40,17 @@ public class DurationConverter implements AttributeConverter<Duration, PGInterva
|
||||
if (duration == null) {
|
||||
return new PGInterval();
|
||||
}
|
||||
// When the period is created from duration by calling duration.toPeriod(), only precise fields
|
||||
// in the period type will be used. Thus, only the hour, minute, second and millisecond fields
|
||||
// on the period will be used. The year, month, week and day fields will not be populated:
|
||||
// 1. If the duration is small, less than one day, then this method will just set
|
||||
// hours/minutes/seconds correctly.
|
||||
// 2. If the duration is larger than one day then all the remaining duration will
|
||||
// be stored in the largest available field, hours in this case.
|
||||
// So, when we convert the period to a PGInterval instance, we set the days field by extracting
|
||||
// it from period's hours field.
|
||||
Period period = duration.toPeriod();
|
||||
PGInterval interval = new PGInterval();
|
||||
Period period = new Period(duration);
|
||||
// For some reason when the period is created from the duration, it does not set days, but
|
||||
// instead just a total number of hours. Years and months are not created because those can
|
||||
// differ in length of milliseconds.
|
||||
interval.setDays(period.getHours() / 24);
|
||||
interval.setHours(period.getHours() % 24);
|
||||
interval.setMinutes(period.getMinutes());
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2020 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.persistence.converter;
|
||||
|
||||
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import google.registry.model.registry.label.PremiumList;
|
||||
import javax.persistence.AttributeConverter;
|
||||
import javax.persistence.Converter;
|
||||
|
||||
/** JPA converter for a {@link Key} containing a {@link PremiumList} */
|
||||
@Converter(autoApply = true)
|
||||
public class PremiumListKeyConverter implements AttributeConverter<Key<PremiumList>, String> {
|
||||
|
||||
@Override
|
||||
public String convertToDatabaseColumn(Key<PremiumList> attribute) {
|
||||
return (attribute == null) ? null : attribute.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key<PremiumList> convertToEntityAttribute(String dbData) {
|
||||
return (dbData == null) ? null : Key.create(getCrossTldKey(), PremiumList.class, dbData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2020 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.persistence.converter;
|
||||
|
||||
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import google.registry.model.registry.label.ReservedList;
|
||||
import javax.persistence.Converter;
|
||||
|
||||
/** JPA converter for a set of {@link Key} containing a {@link ReservedList} */
|
||||
@Converter(autoApply = true)
|
||||
public class ReservedListKeySetConverter extends StringSetConverterBase<Key<ReservedList>> {
|
||||
|
||||
@Override
|
||||
String toString(Key<ReservedList> key) {
|
||||
return key.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
Key<ReservedList> fromString(String value) {
|
||||
return Key.create(getCrossTldKey(), ReservedList.class, value);
|
||||
}
|
||||
}
|
||||
@@ -224,7 +224,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveNew(Object entity) {
|
||||
public void insert(Object entity) {
|
||||
checkArgumentNotNull(entity, "entity must be specified");
|
||||
assertInTransaction();
|
||||
getEntityManager().persist(entity);
|
||||
@@ -232,14 +232,14 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveAllNew(ImmutableCollection<?> entities) {
|
||||
public void insertAll(ImmutableCollection<?> entities) {
|
||||
checkArgumentNotNull(entities, "entities must be specified");
|
||||
assertInTransaction();
|
||||
entities.forEach(this::saveNew);
|
||||
entities.forEach(this::insert);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveNewOrUpdate(Object entity) {
|
||||
public void put(Object entity) {
|
||||
checkArgumentNotNull(entity, "entity must be specified");
|
||||
assertInTransaction();
|
||||
getEntityManager().merge(entity);
|
||||
@@ -247,17 +247,17 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveNewOrUpdateAll(ImmutableCollection<?> entities) {
|
||||
public void putAll(ImmutableCollection<?> entities) {
|
||||
checkArgumentNotNull(entities, "entities must be specified");
|
||||
assertInTransaction();
|
||||
entities.forEach(this::saveNewOrUpdate);
|
||||
entities.forEach(this::put);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Object entity) {
|
||||
checkArgumentNotNull(entity, "entity must be specified");
|
||||
assertInTransaction();
|
||||
checkArgument(checkExists(entity), "Given entity does not exist");
|
||||
checkArgument(exists(entity), "Given entity does not exist");
|
||||
getEntityManager().merge(entity);
|
||||
transactionInfo.get().addUpdate(entity);
|
||||
}
|
||||
@@ -270,22 +270,22 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> boolean checkExists(VKey<T> key) {
|
||||
public <T> boolean exists(VKey<T> key) {
|
||||
checkArgumentNotNull(key, "key must be specified");
|
||||
EntityType<?> entityType = getEntityType(key.getKind());
|
||||
ImmutableSet<EntityId> entityIds = getEntityIdsFromSqlKey(entityType, key.getSqlKey());
|
||||
return checkExists(entityType.getName(), entityIds);
|
||||
return exists(entityType.getName(), entityIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkExists(Object entity) {
|
||||
public boolean exists(Object entity) {
|
||||
checkArgumentNotNull(entity, "entity must be specified");
|
||||
EntityType<?> entityType = getEntityType(entity.getClass());
|
||||
ImmutableSet<EntityId> entityIds = getEntityIdsFromEntity(entityType, entity);
|
||||
return checkExists(entityType.getName(), entityIds);
|
||||
return exists(entityType.getName(), entityIds);
|
||||
}
|
||||
|
||||
private boolean checkExists(String entityName, ImmutableSet<EntityId> entityIds) {
|
||||
private boolean exists(String entityName, ImmutableSet<EntityId> entityIds) {
|
||||
assertInTransaction();
|
||||
TypedQuery<Integer> query =
|
||||
getEntityManager()
|
||||
@@ -391,7 +391,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
private static ImmutableSet<EntityId> getEntityIdsFromEntity(
|
||||
EntityType<?> entityType, Object entity) {
|
||||
if (entityType.hasSingleIdAttribute()) {
|
||||
String idName = entityType.getDeclaredId(entityType.getIdType().getJavaType()).getName();
|
||||
String idName = entityType.getId(entityType.getIdType().getJavaType()).getName();
|
||||
Object idValue = getFieldValue(entity, idName);
|
||||
return ImmutableSet.of(new EntityId(idName, idValue));
|
||||
} else {
|
||||
@@ -402,7 +402,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
private static ImmutableSet<EntityId> getEntityIdsFromSqlKey(
|
||||
EntityType<?> entityType, Object sqlKey) {
|
||||
if (entityType.hasSingleIdAttribute()) {
|
||||
String idName = entityType.getDeclaredId(entityType.getIdType().getJavaType()).getName();
|
||||
String idName = entityType.getId(entityType.getIdType().getJavaType()).getName();
|
||||
return ImmutableSet.of(new EntityId(idName, sqlKey));
|
||||
} else {
|
||||
return getEntityIdsFromIdContainer(entityType, sqlKey);
|
||||
@@ -429,7 +429,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
|
||||
private static Object getFieldValue(Object object, String fieldName) {
|
||||
try {
|
||||
Field field = object.getClass().getDeclaredField(fieldName);
|
||||
Field field = getField(object.getClass(), fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(object);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
@@ -437,6 +437,21 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets the field definition from clazz or any superclass. */
|
||||
private static Field getField(Class clazz, String fieldName) throws NoSuchFieldException {
|
||||
try {
|
||||
// Note that we have to use getDeclaredField() for this, getField() just finds public fields.
|
||||
return clazz.getDeclaredField(fieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
Class base = clazz.getSuperclass();
|
||||
if (base != null) {
|
||||
return getField(base, fieldName);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class TransactionInfo {
|
||||
EntityManager entityManager;
|
||||
boolean inTransaction = false;
|
||||
|
||||
@@ -208,7 +208,7 @@ public class Transaction extends ImmutableObject implements Buildable {
|
||||
|
||||
@Override
|
||||
public void writeToDatastore() {
|
||||
ofyTm().saveNewOrUpdate(entity);
|
||||
ofyTm().put(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
|
||||
package google.registry.persistence.transaction;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.schema.replay.DatastoreEntity;
|
||||
import google.registry.schema.replay.SqlEntity;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
@@ -27,7 +30,7 @@ import javax.persistence.Table;
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "Transaction")
|
||||
public class TransactionEntity {
|
||||
public class TransactionEntity implements SqlEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@@ -40,4 +43,9 @@ public class TransactionEntity {
|
||||
TransactionEntity(byte[] contents) {
|
||||
this.contents = contents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableList<DatastoreEntity> toDatastoreEntities() {
|
||||
return ImmutableList.of(); // not stored in Datastore per se
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,16 +86,16 @@ public interface TransactionManager {
|
||||
DateTime getTransactionTime();
|
||||
|
||||
/** Persists a new entity in the database, throws exception if the entity already exists. */
|
||||
void saveNew(Object entity);
|
||||
void insert(Object entity);
|
||||
|
||||
/** Persists all new entities in the database, throws exception if any entity already exists. */
|
||||
void saveAllNew(ImmutableCollection<?> entities);
|
||||
void insertAll(ImmutableCollection<?> entities);
|
||||
|
||||
/** Persists a new entity or update the existing entity in the database. */
|
||||
void saveNewOrUpdate(Object entity);
|
||||
void put(Object entity);
|
||||
|
||||
/** Persists all new entities or update the existing entities in the database. */
|
||||
void saveNewOrUpdateAll(ImmutableCollection<?> entities);
|
||||
void putAll(ImmutableCollection<?> entities);
|
||||
|
||||
/** Updates an entity in the database, throws exception if the entity does not exist. */
|
||||
void update(Object entity);
|
||||
@@ -104,10 +104,10 @@ public interface TransactionManager {
|
||||
void updateAll(ImmutableCollection<?> entities);
|
||||
|
||||
/** Returns whether the given entity with same ID exists. */
|
||||
boolean checkExists(Object entity);
|
||||
boolean exists(Object entity);
|
||||
|
||||
/** Returns whether the entity of given key exists. */
|
||||
<T> boolean checkExists(VKey<T> key);
|
||||
<T> boolean exists(VKey<T> key);
|
||||
|
||||
/** Loads the entity by its id, returns empty if the entity doesn't exist. */
|
||||
<T> Optional<T> maybeLoad(VKey<T> key);
|
||||
|
||||
@@ -24,6 +24,8 @@ import com.google.api.client.http.GenericUrl;
|
||||
import com.google.api.client.http.HttpHeaders;
|
||||
import com.google.api.client.http.HttpRequest;
|
||||
import com.google.api.client.http.HttpResponse;
|
||||
import com.google.api.client.http.HttpResponseException;
|
||||
import com.google.api.client.http.HttpStatusCodes;
|
||||
import com.google.api.client.http.HttpTransport;
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.base.Splitter;
|
||||
@@ -80,6 +82,7 @@ public class IcannHttpReporter {
|
||||
headers.setContentType(CSV_UTF_8.toString());
|
||||
request.setHeaders(headers);
|
||||
request.setFollowRedirects(false);
|
||||
request.setThrowExceptionOnExecuteError(false);
|
||||
|
||||
HttpResponse response = null;
|
||||
logger.atInfo().log(
|
||||
@@ -87,6 +90,12 @@ public class IcannHttpReporter {
|
||||
boolean success = true;
|
||||
try {
|
||||
response = request.execute();
|
||||
// Only responses with a 200 or 400 status have a body. For everything else, throw so that
|
||||
// the caller catches it and prints the stack trace.
|
||||
if (response.getStatusCode() != HttpStatusCodes.STATUS_CODE_OK
|
||||
&& response.getStatusCode() != HttpStatusCodes.STATUS_CODE_BAD_REQUEST) {
|
||||
throw new HttpResponseException(response);
|
||||
}
|
||||
byte[] content;
|
||||
try {
|
||||
content = ByteStreams.toByteArray(response.getContent());
|
||||
@@ -94,16 +103,23 @@ public class IcannHttpReporter {
|
||||
response.getContent().close();
|
||||
}
|
||||
logger.atInfo().log(
|
||||
"Received response code %d with content: %s\n\nResponse content in hex: %s",
|
||||
"Received response code %d\n\n"
|
||||
+ "Response headers: %s\n\n"
|
||||
+ "Response content in UTF-8: %s\n\n"
|
||||
+ "Response content in HEX: %s",
|
||||
response.getStatusCode(),
|
||||
response.getHeaders(),
|
||||
new String(content, UTF_8),
|
||||
BaseEncoding.base16().encode(content));
|
||||
XjcIirdeaResult result = parseResult(content);
|
||||
if (result.getCode().getValue() != 1000) {
|
||||
// For reasons unclear at the moment, when we parse the response content using UTF-8 we get
|
||||
// garbled texts. Since we know that an HTTP 200 response can only contain a result code of
|
||||
// 1000 (i. e. success), there is no need to parse it.
|
||||
if (response.getStatusCode() == HttpStatusCodes.STATUS_CODE_BAD_REQUEST) {
|
||||
success = false;
|
||||
XjcIirdeaResult result = parseResult(content);
|
||||
logger.atWarning().log(
|
||||
"PUT rejected, status code %s:\n%s\n%s",
|
||||
result.getCode(), result.getMsg(), result.getDescription());
|
||||
result.getCode().getValue(), result.getMsg(), result.getDescription());
|
||||
}
|
||||
} finally {
|
||||
if (response != null) {
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
// Copyright 2019 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.schema.tmch;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static google.registry.util.DateTimeUtils.toJodaDateTime;
|
||||
import static google.registry.util.DateTimeUtils.toZonedDateTime;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.model.CreateAutoTimestamp;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.schema.replay.DatastoreEntity;
|
||||
import google.registry.schema.replay.SqlEntity;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import javax.persistence.CollectionTable;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.ElementCollection;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.MapKeyColumn;
|
||||
import javax.persistence.Table;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* A list of TMCH claims labels and their associated claims keys.
|
||||
*
|
||||
* <p>Note that the primary key of this entity is {@link #revisionId}, which is auto-generated by
|
||||
* the database. So, if a retry of insertion happens after the previous attempt unexpectedly
|
||||
* succeeds, we will end up with having two exact same claims list with only different {@link
|
||||
* #revisionId}. However, this is not an actual problem because we only use the claims list with
|
||||
* highest {@link #revisionId}.
|
||||
*/
|
||||
@Entity
|
||||
@Table
|
||||
public class ClaimsList extends ImmutableObject implements SqlEntity {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column
|
||||
private Long revisionId;
|
||||
|
||||
@Column(nullable = false)
|
||||
private CreateAutoTimestamp creationTimestamp = CreateAutoTimestamp.create(null);
|
||||
|
||||
@Column(nullable = false)
|
||||
private ZonedDateTime tmdbGenerationTime;
|
||||
|
||||
@ElementCollection
|
||||
@CollectionTable(
|
||||
name = "ClaimsEntry",
|
||||
joinColumns = @JoinColumn(name = "revisionId", referencedColumnName = "revisionId"))
|
||||
@MapKeyColumn(name = "domainLabel", nullable = false)
|
||||
@Column(name = "claimKey", nullable = false)
|
||||
private Map<String, String> labelsToKeys;
|
||||
|
||||
private ClaimsList(ZonedDateTime tmdbGenerationTime, Map<String, String> labelsToKeys) {
|
||||
this.tmdbGenerationTime = tmdbGenerationTime;
|
||||
this.labelsToKeys = labelsToKeys;
|
||||
}
|
||||
|
||||
// Hibernate requires this default constructor.
|
||||
private ClaimsList() {}
|
||||
|
||||
/** Constructs a {@link ClaimsList} object. */
|
||||
public static ClaimsList create(DateTime creationTimestamp, Map<String, String> labelsToKeys) {
|
||||
return new ClaimsList(toZonedDateTime(creationTimestamp), labelsToKeys);
|
||||
}
|
||||
|
||||
/** Returns the revision id of this claims list, or throws exception if it is null. */
|
||||
public Long getRevisionId() {
|
||||
checkState(
|
||||
revisionId != null, "revisionId is null because it is not persisted in the database");
|
||||
return revisionId;
|
||||
}
|
||||
|
||||
/** Returns the TMDB generation time of this claims list. */
|
||||
public DateTime getTmdbGenerationTime() {
|
||||
return toJodaDateTime(tmdbGenerationTime);
|
||||
}
|
||||
|
||||
/** Returns the creation time of this claims list. */
|
||||
public DateTime getCreationTimestamp() {
|
||||
return creationTimestamp.getTimestamp();
|
||||
}
|
||||
|
||||
/** Returns an {@link Map} mapping domain label to its lookup key. */
|
||||
public Map<String, String> getLabelsToKeys() {
|
||||
return labelsToKeys;
|
||||
}
|
||||
|
||||
/** Returns the claim key for a given domain if there is one, empty otherwise. */
|
||||
public Optional<String> getClaimKey(String label) {
|
||||
return Optional.ofNullable(labelsToKeys.get(label));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableList<DatastoreEntity> toDatastoreEntities() {
|
||||
return ImmutableList.of(); // ClaimsList is dual-written
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.schema.tmch.ClaimsList;
|
||||
import google.registry.model.tmch.ClaimsListShard;
|
||||
import java.util.List;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
@@ -34,11 +34,11 @@ import org.joda.time.DateTime;
|
||||
public class ClaimsListParser {
|
||||
|
||||
/**
|
||||
* Converts the lines from the DNL CSV file into a {@link ClaimsList} object.
|
||||
* Converts the lines from the DNL CSV file into a {@link ClaimsListShard} object.
|
||||
*
|
||||
* <p>Please note that this does <b>not</b> insert the object into Datastore.
|
||||
*/
|
||||
public static ClaimsList parse(List<String> lines) {
|
||||
public static ClaimsListShard parse(List<String> lines) {
|
||||
ImmutableMap.Builder<String, String> builder = new ImmutableMap.Builder<>();
|
||||
|
||||
// First line: <version>,<DNL List creation datetime>
|
||||
@@ -74,6 +74,6 @@ public class ClaimsListParser {
|
||||
builder.put(label, lookupKey);
|
||||
}
|
||||
|
||||
return ClaimsList.create(creationTime, builder.build());
|
||||
return ClaimsListShard.create(creationTime, builder.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,6 @@ import google.registry.keyring.api.KeyModule.Key;
|
||||
import google.registry.model.tmch.ClaimsListShard;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.schema.tmch.ClaimsList;
|
||||
import google.registry.schema.tmch.ClaimsListDao;
|
||||
import java.io.IOException;
|
||||
import java.security.SignatureException;
|
||||
import java.util.List;
|
||||
@@ -56,14 +54,10 @@ public final class TmchDnlAction implements Runnable {
|
||||
} catch (SignatureException | IOException | PGPException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
ClaimsList claims = ClaimsListParser.parse(lines);
|
||||
ClaimsListShard claimsListShard =
|
||||
ClaimsListShard.create(claims.getTmdbGenerationTime(), claims.getLabelsToKeys());
|
||||
claimsListShard.save();
|
||||
ClaimsListShard claims = ClaimsListParser.parse(lines);
|
||||
claims.save();
|
||||
logger.atInfo().log(
|
||||
"Inserted %,d claims into Datastore, created at %s",
|
||||
claimsListShard.size(), claimsListShard.getCreationTime());
|
||||
|
||||
ClaimsListDao.trySave(claims);
|
||||
claims.size(), claims.getTmdbGenerationTime());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +110,12 @@ abstract class CreateOrUpdateTldCommand extends MutatingCommand {
|
||||
description = "One-time billing cost for a server status change")
|
||||
private Money serverStatusChangeCost;
|
||||
|
||||
@Nullable
|
||||
@Parameter(
|
||||
names = "--registry_lock_or_unlock_cost",
|
||||
description = "One-time billing cost for a registry lock or unlock")
|
||||
private Money registryLockOrUnlockCost;
|
||||
|
||||
@Nullable
|
||||
@Parameter(
|
||||
names = "--tld_type",
|
||||
@@ -118,10 +124,10 @@ abstract class CreateOrUpdateTldCommand extends MutatingCommand {
|
||||
|
||||
@Nullable
|
||||
@Parameter(
|
||||
names = "--disable_invoicing",
|
||||
description = "Whether invoicing is disabled for a REAL tld.",
|
||||
names = "--invoicing_enabled",
|
||||
description = "Whether invoicing is enabled for this tld.",
|
||||
arity = 1)
|
||||
private Boolean disableInvoicing;
|
||||
private Boolean invoicingEnabled;
|
||||
|
||||
@Nullable
|
||||
@Parameter(
|
||||
@@ -326,8 +332,10 @@ abstract class CreateOrUpdateTldCommand extends MutatingCommand {
|
||||
Optional.ofNullable(roidSuffix).ifPresent(builder::setRoidSuffix);
|
||||
Optional.ofNullable(serverStatusChangeCost)
|
||||
.ifPresent(builder::setServerStatusChangeBillingCost);
|
||||
Optional.ofNullable(registryLockOrUnlockCost)
|
||||
.ifPresent(builder::setRegistryLockOrUnlockBillingCost);
|
||||
Optional.ofNullable(tldType).ifPresent(builder::setTldType);
|
||||
Optional.ofNullable(disableInvoicing).ifPresent(builder::setDisableInvoicing);
|
||||
Optional.ofNullable(invoicingEnabled).ifPresent(builder::setInvoicingEnabled);
|
||||
Optional.ofNullable(lordnUsername).ifPresent(u -> builder.setLordnUsername(u.orElse(null)));
|
||||
Optional.ofNullable(claimsPeriodEnd).ifPresent(builder::setClaimsPeriodEnd);
|
||||
Optional.ofNullable(numDnsPublishShards).ifPresent(builder::setNumDnsPublishLocks);
|
||||
|
||||
@@ -72,7 +72,7 @@ final class CreateRegistrarCommand extends CreateOrUpdateRegistrarCommand
|
||||
|
||||
@Override
|
||||
void saveToCloudSql(Registrar registrar) {
|
||||
jpaTm().saveNew(registrar);
|
||||
jpaTm().insert(registrar);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -380,7 +380,7 @@ public final class DomainLockUtils {
|
||||
.setReason(Reason.SERVER_STATUS)
|
||||
.setTargetId(domain.getForeignKey())
|
||||
.setClientId(domain.getCurrentSponsorClientId())
|
||||
.setCost(Registry.get(domain.getTld()).getServerStatusChangeCost())
|
||||
.setCost(Registry.get(domain.getTld()).getRegistryLockOrUnlockBillingCost())
|
||||
.setEventTime(now)
|
||||
.setBillingTime(now)
|
||||
.setParent(historyEntry)
|
||||
|
||||
@@ -112,8 +112,6 @@ final class RegistryCli implements AutoCloseable, CommandRunner {
|
||||
// Create all command instances. It would be preferrable to do this in the constructor, but
|
||||
// JCommander mutates the command instances and doesn't reset them so we have to do it for every
|
||||
// run.
|
||||
// TODO(weiminyu): extract this into a standalone static method to simplify
|
||||
// :core:registryToolIntegrationTest
|
||||
try {
|
||||
for (Map.Entry<String, ? extends Class<? extends Command>> entry : commands.entrySet()) {
|
||||
Command command = entry.getValue().getDeclaredConstructor().newInstance();
|
||||
|
||||
@@ -17,10 +17,11 @@ 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.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.model.domain.rgp.GracePeriodStatus.AUTO_RENEW;
|
||||
import static google.registry.model.eppcommon.StatusValue.SERVER_UPDATE_PROHIBITED;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
|
||||
import static org.joda.time.DateTimeZone.UTC;
|
||||
import static java.util.function.Predicate.isEqual;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
@@ -31,15 +32,19 @@ import com.google.common.flogger.FluentLogger;
|
||||
import com.google.template.soy.data.SoyMapData;
|
||||
import google.registry.model.domain.DesignatedContact;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.GracePeriodBase;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.tools.params.NameserversParameter;
|
||||
import google.registry.tools.soy.DomainUpdateSoyInfo;
|
||||
import google.registry.util.Clock;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** A command to update a new domain via EPP. */
|
||||
@@ -48,6 +53,8 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
@Inject Clock clock;
|
||||
|
||||
@Parameter(names = "--statuses", description = "Comma-separated list of statuses to set.")
|
||||
private List<String> statuses = new ArrayList<>();
|
||||
|
||||
@@ -123,6 +130,15 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
|
||||
)
|
||||
boolean clearDsRecords = false;
|
||||
|
||||
@Nullable
|
||||
@Parameter(
|
||||
names = "--autorenews",
|
||||
arity = 1,
|
||||
description =
|
||||
"Whether the domain autorenews. If false, the domain will automatically be"
|
||||
+ " deleted at the end of its current registration period.")
|
||||
Boolean autorenews;
|
||||
|
||||
@Override
|
||||
protected void initMutatingEppToolCommand() {
|
||||
if (!nameservers.isEmpty()) {
|
||||
@@ -159,7 +175,18 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
|
||||
clearDsRecords = true;
|
||||
}
|
||||
|
||||
ImmutableSet.Builder<String> autorenewGracePeriodWarningDomains = new ImmutableSet.Builder<>();
|
||||
DateTime now = clock.nowUtc();
|
||||
for (String domain : domains) {
|
||||
Optional<DomainBase> domainOptional = loadByForeignKey(DomainBase.class, domain, now);
|
||||
checkArgumentPresent(domainOptional, "Domain '%s' does not exist or is deleted", domain);
|
||||
DomainBase domainBase = domainOptional.get();
|
||||
checkArgument(
|
||||
!domainBase.getStatusValues().contains(SERVER_UPDATE_PROHIBITED),
|
||||
"The domain '%s' has status SERVER_UPDATE_PROHIBITED. Verify that you are allowed "
|
||||
+ "to make updates, and if so, use the domain_unlock command to enable updates.",
|
||||
domain);
|
||||
|
||||
// Use TreeSets so that the results are always in the same order (this makes testing easier).
|
||||
Set<String> addAdminsThisDomain = new TreeSet<>(addAdmins);
|
||||
Set<String> removeAdminsThisDomain = new TreeSet<>(removeAdmins);
|
||||
@@ -171,16 +198,6 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
|
||||
Set<String> removeStatusesThisDomain = new TreeSet<>(removeStatuses);
|
||||
|
||||
if (!nameservers.isEmpty() || !admins.isEmpty() || !techs.isEmpty() || !statuses.isEmpty()) {
|
||||
DateTime now = DateTime.now(UTC);
|
||||
Optional<DomainBase> domainOptional =
|
||||
loadByForeignKey(DomainBase.class, domain, now);
|
||||
checkArgumentPresent(domainOptional, "Domain '%s' does not exist or is deleted", domain);
|
||||
DomainBase domainBase = domainOptional.get();
|
||||
checkArgument(
|
||||
!domainBase.getStatusValues().contains(SERVER_UPDATE_PROHIBITED),
|
||||
"The domain '%s' has status SERVER_UPDATE_PROHIBITED. Verify that you are allowed "
|
||||
+ "to make updates, and if so, use the domain_unlock command to enable updates.",
|
||||
domain);
|
||||
if (!nameservers.isEmpty()) {
|
||||
ImmutableSortedSet<String> existingNameservers = domainBase.loadNameserverHostNames();
|
||||
populateAddRemoveLists(
|
||||
@@ -232,33 +249,41 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
|
||||
}
|
||||
|
||||
boolean add =
|
||||
!addNameserversThisDomain.isEmpty()
|
||||
(!addNameserversThisDomain.isEmpty()
|
||||
|| !addAdminsThisDomain.isEmpty()
|
||||
|| !addTechsThisDomain.isEmpty()
|
||||
|| !addStatusesThisDomain.isEmpty();
|
||||
|| !addStatusesThisDomain.isEmpty());
|
||||
|
||||
boolean remove =
|
||||
!removeNameserversThisDomain.isEmpty()
|
||||
(!removeNameserversThisDomain.isEmpty()
|
||||
|| !removeAdminsThisDomain.isEmpty()
|
||||
|| !removeTechsThisDomain.isEmpty()
|
||||
|| !removeStatusesThisDomain.isEmpty();
|
||||
|| !removeStatusesThisDomain.isEmpty());
|
||||
|
||||
boolean change = registrant != null || password != null;
|
||||
|
||||
boolean secdns =
|
||||
!addDsRecords.isEmpty()
|
||||
boolean change = (registrant != null || password != null);
|
||||
boolean secDns =
|
||||
(!addDsRecords.isEmpty()
|
||||
|| !removeDsRecords.isEmpty()
|
||||
|| !dsRecords.isEmpty()
|
||||
|| clearDsRecords;
|
||||
|| clearDsRecords);
|
||||
|
||||
if (!add && !remove && !change && !secdns) {
|
||||
if (!add && !remove && !change && !secDns && autorenews == null) {
|
||||
logger.atInfo().log("No changes need to be made to domain %s", domain);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If autorenew is being turned off and this domain is already in the autorenew grace period,
|
||||
// then we want to warn the user that they might want to delete it instead.
|
||||
if (Boolean.FALSE.equals(autorenews)) {
|
||||
if (domainBase.getGracePeriods().stream()
|
||||
.map(GracePeriodBase::getType)
|
||||
.anyMatch(isEqual(AUTO_RENEW))) {
|
||||
autorenewGracePeriodWarningDomains.add(domain);
|
||||
}
|
||||
}
|
||||
|
||||
setSoyTemplate(DomainUpdateSoyInfo.getInstance(), DomainUpdateSoyInfo.DOMAINUPDATE);
|
||||
addSoyRecord(
|
||||
clientId,
|
||||
SoyMapData soyMapData =
|
||||
new SoyMapData(
|
||||
"domain", domain,
|
||||
"add", add,
|
||||
@@ -274,14 +299,27 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
|
||||
"change", change,
|
||||
"registrant", registrant,
|
||||
"password", password,
|
||||
"secdns", secdns,
|
||||
"secdns", secDns,
|
||||
"addDsRecords", DsRecord.convertToSoy(addDsRecords),
|
||||
"removeDsRecords", DsRecord.convertToSoy(removeDsRecords),
|
||||
"removeAllDsRecords", clearDsRecords));
|
||||
"removeAllDsRecords", clearDsRecords);
|
||||
if (autorenews != null) {
|
||||
soyMapData.put("autorenews", autorenews.toString());
|
||||
}
|
||||
addSoyRecord(clientId, soyMapData);
|
||||
}
|
||||
|
||||
ImmutableSet<String> domainsToWarn = autorenewGracePeriodWarningDomains.build();
|
||||
if (!domainsToWarn.isEmpty()) {
|
||||
logger.atWarning().log(
|
||||
"The following domains are in autorenew grace periods. Consider aborting this command"
|
||||
+ " and running `nomulus delete_domain` instead to terminate autorenewal immediately"
|
||||
+ " rather than in one year, if desired:\n%s",
|
||||
String.join(", ", domainsToWarn));
|
||||
}
|
||||
}
|
||||
|
||||
protected void populateAddRemoveLists(
|
||||
private void populateAddRemoveLists(
|
||||
Set<String> targetSet, Set<String> oldSet, Set<String> addSet, Set<String> removeSet) {
|
||||
addSet.addAll(Sets.difference(targetSet, oldSet));
|
||||
removeSet.addAll(Sets.difference(oldSet, targetSet));
|
||||
|
||||
@@ -22,8 +22,6 @@ import com.beust.jcommander.Parameters;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.io.Files;
|
||||
import google.registry.model.tmch.ClaimsListShard;
|
||||
import google.registry.schema.tmch.ClaimsList;
|
||||
import google.registry.schema.tmch.ClaimsListDao;
|
||||
import google.registry.tmch.ClaimsListParser;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -39,7 +37,7 @@ final class UploadClaimsListCommand extends ConfirmingCommand implements Command
|
||||
|
||||
private String claimsListFilename;
|
||||
|
||||
private ClaimsList claimsList;
|
||||
private ClaimsListShard claimsList;
|
||||
|
||||
@Override
|
||||
protected void init() throws IOException {
|
||||
@@ -58,8 +56,7 @@ final class UploadClaimsListCommand extends ConfirmingCommand implements Command
|
||||
|
||||
@Override
|
||||
public String execute() {
|
||||
ClaimsListShard.create(claimsList.getTmdbGenerationTime(), claimsList.getLabelsToKeys()).save();
|
||||
ClaimsListDao.trySave(claimsList);
|
||||
claimsList.save();
|
||||
return String.format("Successfully uploaded claims list %s", claimsListFilename);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,10 +42,6 @@ public abstract class XjcObject {
|
||||
XjcXmlTransformer.marshalStrict(this, out, encoding);
|
||||
}
|
||||
|
||||
public void marshalLenient(OutputStream out, Charset encoding) throws XmlException {
|
||||
XjcXmlTransformer.marshalLenient(this, out, encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns object into a formatted XML string <i>by any means necessary</i>.
|
||||
*
|
||||
|
||||
@@ -47,4 +47,12 @@
|
||||
</all>
|
||||
</complexType>
|
||||
|
||||
<element name="domainUpdate" type="superuser:domainUpdateType" />
|
||||
|
||||
<complexType name="domainUpdateType">
|
||||
<all>
|
||||
<element name="autorenews" minOccurs="0" type="boolean" />
|
||||
</all>
|
||||
</complexType>
|
||||
|
||||
</schema>
|
||||
|
||||
@@ -56,7 +56,8 @@ registry.registrar.RegistryLock.prototype.runAfterRender = function(objArgs) {
|
||||
} else {
|
||||
goog.soy.renderElement(
|
||||
goog.dom.getRequiredElement('locks-content'),
|
||||
registry.soy.registrar.registrylock.lockNotAllowedOnRegistrar);
|
||||
registry.soy.registrar.registrylock.lockNotAllowedOnRegistrar,
|
||||
{supportEmail: objArgs.supportEmail});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
<basic name="amount" access="FIELD"/>
|
||||
</attributes>
|
||||
</embeddable>
|
||||
|
||||
<sequence-generator name="HistorySequenceGenerator" sequence-name="history_id_sequence"/>
|
||||
|
||||
<!-- TODO(shicong): Drop this sequence and change all history tables to use the above one. -->
|
||||
<sequence-generator name="TempHistorySequenceGenerator" sequence-name="temp_history_id_sequence"/>
|
||||
|
||||
<persistence-unit-metadata>
|
||||
<persistence-unit-defaults>
|
||||
<entity-listeners>
|
||||
|
||||
@@ -11,14 +11,33 @@
|
||||
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
|
||||
|
||||
<!--
|
||||
All JPA entities must be enumerated here. JPA does not support auto detection.
|
||||
All JPA entity-mapping files and annotated classes must be enumerated
|
||||
here. Automatic entity detection is not part of the JPA spec. Explicit
|
||||
declaration makes it easier to migrate to another provider.
|
||||
|
||||
Note that Hibernate's auto detection functionality (hibernate.archive.autodection)
|
||||
does not meet our needs. It only scans archives, not the 'classes' folders. So we
|
||||
are left with two options:
|
||||
* Move tests to another (sub)project. This is not a big problem, but feels unnatural.
|
||||
* Use Hibernate's ServiceRegistry for bootstrapping (not JPA-compliant)
|
||||
Although Hibernate provides the auto detection functionality (configured by
|
||||
the hibernate.archive.autodetection property), it relies on a fragile
|
||||
scanner that can be broken by certain classes. For example, in the uber jar
|
||||
for the Nomulus tool, a repackaged Guava class ( {@code
|
||||
com.google.appengine.repackaged.com.google.common.html.LinkDetector})
|
||||
from appengine-api-1.0-sdk:1.9.81 can break the scanner in
|
||||
hibernate-core:5.4.17.Final. The large number of third-party classes also
|
||||
makes JPA setup noticeably slower in the tool.
|
||||
|
||||
When auto detection is enabled in Hibernate, we also need a separate
|
||||
persistence.xml for tests. See <a
|
||||
href="https://stackoverflow.com/questions/61127082/hibernate-doesnt-find-entities-in-test">
|
||||
this webpage</a> for an example.
|
||||
|
||||
Because of the reasons above, we disable auto detection in Hibernate.
|
||||
|
||||
When auto detection is disabled, Hibernate still invokes the scanner which always
|
||||
goes over the archive that has this file. We need to override the default scanner
|
||||
with an NOOP one for Nomulus tool.
|
||||
-->
|
||||
|
||||
<mapping-file>META-INF/orm.xml</mapping-file>
|
||||
|
||||
<class>google.registry.model.billing.BillingEvent$Cancellation</class>
|
||||
<class>google.registry.model.billing.BillingEvent$OneTime</class>
|
||||
<class>google.registry.model.billing.BillingEvent$Recurring</class>
|
||||
@@ -26,25 +45,27 @@
|
||||
<class>google.registry.model.contact.ContactResource</class>
|
||||
<class>google.registry.model.domain.DomainBase</class>
|
||||
<class>google.registry.model.domain.DomainHistory</class>
|
||||
<class>google.registry.model.domain.GracePeriod</class>
|
||||
<class>google.registry.model.domain.secdns.DelegationSignerData</class>
|
||||
<class>google.registry.model.domain.token.AllocationToken</class>
|
||||
<class>google.registry.model.host.HostHistory</class>
|
||||
<class>google.registry.model.host.HostResource</class>
|
||||
<class>google.registry.model.registrar.Registrar</class>
|
||||
<class>google.registry.model.registrar.RegistrarContact</class>
|
||||
<class>google.registry.model.registry.label.PremiumList</class>
|
||||
<class>google.registry.model.reporting.Spec11ThreatMatch</class>
|
||||
<class>google.registry.persistence.transaction.TransactionEntity</class>
|
||||
<class>google.registry.schema.domain.RegistryLock</class>
|
||||
<class>google.registry.schema.tmch.ClaimsList</class>
|
||||
<class>google.registry.schema.cursor.Cursor</class>
|
||||
<class>google.registry.schema.server.Lock</class>
|
||||
<class>google.registry.schema.tld.PremiumEntry</class>
|
||||
<class>google.registry.model.domain.secdns.DelegationSignerData</class>
|
||||
<class>google.registry.model.domain.GracePeriod</class>
|
||||
<class>google.registry.model.poll.PollMessage</class>
|
||||
<class>google.registry.model.poll.PollMessage$OneTime</class>
|
||||
<class>google.registry.model.poll.PollMessage$Autorenew</class>
|
||||
<class>google.registry.model.registrar.Registrar</class>
|
||||
<class>google.registry.model.registrar.RegistrarContact</class>
|
||||
<class>google.registry.model.registry.label.PremiumList</class>
|
||||
<class>google.registry.model.registry.label.ReservedList</class>
|
||||
<class>google.registry.model.registry.Registry</class>
|
||||
<class>google.registry.model.reporting.DomainTransactionRecord</class>
|
||||
<class>google.registry.model.reporting.Spec11ThreatMatch</class>
|
||||
<class>google.registry.model.tmch.ClaimsListShard</class>
|
||||
<class>google.registry.persistence.transaction.TransactionEntity</class>
|
||||
<class>google.registry.schema.cursor.Cursor</class>
|
||||
<class>google.registry.schema.domain.RegistryLock</class>
|
||||
<class>google.registry.schema.server.Lock</class>
|
||||
<class>google.registry.schema.tld.PremiumEntry</class>
|
||||
|
||||
<!-- Customized type converters -->
|
||||
<class>google.registry.persistence.converter.AllocationTokenStatusTransitionConverter</class>
|
||||
@@ -60,7 +81,9 @@
|
||||
<class>google.registry.persistence.converter.InetAddressSetConverter</class>
|
||||
<class>google.registry.persistence.converter.LocalDateConverter</class>
|
||||
<class>google.registry.persistence.converter.PostalInfoChoiceListConverter</class>
|
||||
<class>google.registry.persistence.converter.PremiumListKeyConverter</class>
|
||||
<class>google.registry.persistence.converter.RegistrarPocSetConverter</class>
|
||||
<class>google.registry.persistence.converter.ReservedListKeySetConverter</class>
|
||||
<class>google.registry.persistence.converter.Spec11ThreatMatchThreatTypeSetConverter</class>
|
||||
<class>google.registry.persistence.converter.StatusValueSetConverter</class>
|
||||
<class>google.registry.persistence.converter.StringListConverter</class>
|
||||
@@ -82,5 +105,12 @@
|
||||
|
||||
<!-- TODO(weiminyu): check out application-layer validation. -->
|
||||
<validation-mode>NONE</validation-mode>
|
||||
<properties>
|
||||
<!-- Disables auto detection. -->
|
||||
<property name="hibernate.archive.autodetection" value=""/>
|
||||
<!-- NOOP scanner needed for Nomulus tool. -->
|
||||
<property name="hibernate.archive.scanner"
|
||||
value="google.registry.persistence.NoopJpaEntityScanner"/>
|
||||
</properties>
|
||||
</persistence-unit>
|
||||
</persistence>
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
{@param addDsRecords: list<[keyTag:int, alg:int, digestType:int, digest:string]>}
|
||||
{@param removeDsRecords: list<[keyTag:int, alg:int, digestType:int, digest:string]>}
|
||||
{@param removeAllDsRecords: bool}
|
||||
{@param? autorenews: string}
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||
@@ -96,39 +97,46 @@
|
||||
{/if}
|
||||
</domain:update>
|
||||
</update>
|
||||
{if $secdns}
|
||||
{if $secdns or $autorenews}
|
||||
<extension>
|
||||
<secDNS:update xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1">
|
||||
{if $removeAllDsRecords}
|
||||
<secDNS:rem>
|
||||
<secDNS:all>true</secDNS:all>
|
||||
</secDNS:rem>
|
||||
{/if}
|
||||
{if length($removeDsRecords) > 0}
|
||||
<secDNS:rem>
|
||||
{for $dsRecord in $removeDsRecords}
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>{$dsRecord.keyTag}</secDNS:keyTag>
|
||||
<secDNS:alg>{$dsRecord.alg}</secDNS:alg>
|
||||
<secDNS:digestType>{$dsRecord.digestType}</secDNS:digestType>
|
||||
<secDNS:digest>{$dsRecord.digest}</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
{/for}
|
||||
</secDNS:rem>
|
||||
{/if}
|
||||
{if length($addDsRecords) > 0}
|
||||
<secDNS:add>
|
||||
{for $dsRecord in $addDsRecords}
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>{$dsRecord.keyTag}</secDNS:keyTag>
|
||||
<secDNS:alg>{$dsRecord.alg}</secDNS:alg>
|
||||
<secDNS:digestType>{$dsRecord.digestType}</secDNS:digestType>
|
||||
<secDNS:digest>{$dsRecord.digest}</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
{/for}
|
||||
</secDNS:add>
|
||||
{/if}
|
||||
</secDNS:update>
|
||||
{if $secdns}
|
||||
<secDNS:update xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1">
|
||||
{if $removeAllDsRecords}
|
||||
<secDNS:rem>
|
||||
<secDNS:all>true</secDNS:all>
|
||||
</secDNS:rem>
|
||||
{/if}
|
||||
{if length($removeDsRecords) > 0}
|
||||
<secDNS:rem>
|
||||
{for $dsRecord in $removeDsRecords}
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>{$dsRecord.keyTag}</secDNS:keyTag>
|
||||
<secDNS:alg>{$dsRecord.alg}</secDNS:alg>
|
||||
<secDNS:digestType>{$dsRecord.digestType}</secDNS:digestType>
|
||||
<secDNS:digest>{$dsRecord.digest}</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
{/for}
|
||||
</secDNS:rem>
|
||||
{/if}
|
||||
{if length($addDsRecords) > 0}
|
||||
<secDNS:add>
|
||||
{for $dsRecord in $addDsRecords}
|
||||
<secDNS:dsData>
|
||||
<secDNS:keyTag>{$dsRecord.keyTag}</secDNS:keyTag>
|
||||
<secDNS:alg>{$dsRecord.alg}</secDNS:alg>
|
||||
<secDNS:digestType>{$dsRecord.digestType}</secDNS:digestType>
|
||||
<secDNS:digest>{$dsRecord.digest}</secDNS:digest>
|
||||
</secDNS:dsData>
|
||||
{/for}
|
||||
</secDNS:add>
|
||||
{/if}
|
||||
</secDNS:update>
|
||||
{/if}
|
||||
{if $autorenews}
|
||||
<superuser:domainUpdate xmlns:superuser="urn:google:params:xml:ns:superuser-1.0">
|
||||
<superuser:autorenews>{$autorenews}</superuser:autorenews>
|
||||
</superuser:domainUpdate>
|
||||
{/if}
|
||||
</extension>
|
||||
{/if}
|
||||
<clTRID>RegistryTool</clTRID>
|
||||
|
||||
@@ -163,5 +163,7 @@
|
||||
|
||||
/** Content if the registrar is not allowed to use registry lock. */
|
||||
{template .lockNotAllowedOnRegistrar}
|
||||
<h2>Registry Lock is coming soon; please stay tuned for updates.</h2>
|
||||
{@param supportEmail: string}
|
||||
<h2>Sorry, your registrar hasn't enrolled in registry lock yet. To do so, please
|
||||
contact {$supportEmail}.</h2>
|
||||
{/template}
|
||||
|
||||
@@ -31,7 +31,7 @@ import static google.registry.testing.TestLogHandlerUtils.assertLogMessage;
|
||||
import static org.joda.time.Duration.standardDays;
|
||||
import static org.joda.time.Duration.standardHours;
|
||||
import static org.joda.time.Duration.standardSeconds;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
@@ -159,7 +159,7 @@ public class AsyncTaskEnqueuerTest {
|
||||
.setRegistrarPocId("someone@example.com")
|
||||
.setVerificationCode("hi")
|
||||
.build());
|
||||
asyncTaskEnqueuer.enqueueDomainRelock(lock);
|
||||
asyncTaskEnqueuer.enqueueDomainRelock(lock.getRelockDuration().get(), lock.getRevisionId(), 0);
|
||||
assertTasksEnqueued(
|
||||
QUEUE_ASYNC_ACTIONS,
|
||||
new TaskMatcher()
|
||||
@@ -169,6 +169,7 @@ public class AsyncTaskEnqueuerTest {
|
||||
.param(
|
||||
RelockDomainAction.OLD_UNLOCK_REVISION_ID_PARAM,
|
||||
String.valueOf(lock.getRevisionId()))
|
||||
.param(RelockDomainAction.PREVIOUS_ATTEMPTS_PARAM, "0")
|
||||
.etaDelta(
|
||||
standardHours(6).minus(standardSeconds(30)),
|
||||
standardHours(6).plus(standardSeconds(30))));
|
||||
@@ -188,9 +189,9 @@ public class AsyncTaskEnqueuerTest {
|
||||
.setVerificationCode("hi")
|
||||
.build());
|
||||
assertThat(
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> asyncTaskEnqueuer.enqueueDomainRelock(lockWithoutDuration)))
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> asyncTaskEnqueuer.enqueueDomainRelock(lockWithoutDuration)))
|
||||
.hasMessageThat()
|
||||
.isEqualTo(
|
||||
String.format(
|
||||
|
||||
@@ -15,10 +15,12 @@
|
||||
package google.registry.batch;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_ACTIONS;
|
||||
import static google.registry.model.eppcommon.StatusValue.PENDING_DELETE;
|
||||
import static google.registry.model.eppcommon.StatusValue.PENDING_TRANSFER;
|
||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static google.registry.testing.DatastoreHelper.createTlds;
|
||||
import static google.registry.testing.DatastoreHelper.deleteResource;
|
||||
import static google.registry.testing.DatastoreHelper.newDomainBase;
|
||||
import static google.registry.testing.DatastoreHelper.persistActiveHost;
|
||||
import static google.registry.testing.DatastoreHelper.persistDomainAsDeleted;
|
||||
@@ -26,9 +28,16 @@ import static google.registry.testing.DatastoreHelper.persistResource;
|
||||
import static google.registry.testing.SqlHelper.getMostRecentVerifiedRegistryLockByRepoId;
|
||||
import static google.registry.testing.SqlHelper.getRegistryLockByVerificationCode;
|
||||
import static google.registry.testing.SqlHelper.saveRegistryLock;
|
||||
import static google.registry.testing.TaskQueueHelper.assertNoTasksEnqueued;
|
||||
import static google.registry.testing.TaskQueueHelper.assertTasksEnqueued;
|
||||
import static google.registry.tools.LockOrUnlockDomainCommand.REGISTRY_LOCK_STATUSES;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static org.joda.time.Duration.standardSeconds;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
@@ -38,17 +47,27 @@ import google.registry.testing.AppEngineExtension;
|
||||
import google.registry.testing.DeterministicStringGenerator;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.FakeResponse;
|
||||
import google.registry.testing.TaskQueueHelper.TaskMatcher;
|
||||
import google.registry.testing.UserInfo;
|
||||
import google.registry.tools.DomainLockUtils;
|
||||
import google.registry.util.AppEngineServiceUtils;
|
||||
import google.registry.util.EmailMessage;
|
||||
import google.registry.util.SendEmailService;
|
||||
import google.registry.util.StringGenerator.Alphabets;
|
||||
import java.util.Optional;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
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.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
/** Unit tests for {@link RelockDomainAction}. */
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class RelockDomainActionTest {
|
||||
|
||||
private static final String DOMAIN_NAME = "example.tld";
|
||||
@@ -56,7 +75,7 @@ public class RelockDomainActionTest {
|
||||
private static final String POC_ID = "marla.singer@example.com";
|
||||
|
||||
private final FakeResponse response = new FakeResponse();
|
||||
private final FakeClock clock = new FakeClock();
|
||||
private final FakeClock clock = new FakeClock(DateTime.parse("2015-05-18T12:34:56Z"));
|
||||
private final DomainLockUtils domainLockUtils =
|
||||
new DomainLockUtils(
|
||||
new DeterministicStringGenerator(Alphabets.BASE_58),
|
||||
@@ -68,15 +87,18 @@ public class RelockDomainActionTest {
|
||||
public final AppEngineExtension appEngineRule =
|
||||
AppEngineExtension.builder()
|
||||
.withDatastoreAndCloudSql()
|
||||
.withTaskQueue()
|
||||
.withUserService(UserInfo.create(POC_ID, "12345"))
|
||||
.build();
|
||||
|
||||
private DomainBase domain;
|
||||
private RegistryLock oldLock;
|
||||
@Mock private SendEmailService sendEmailService;
|
||||
private AsyncTaskEnqueuer asyncTaskEnqueuer;
|
||||
private RelockDomainAction action;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
void beforeEach() throws Exception {
|
||||
createTlds("tld", "net");
|
||||
HostResource host = persistActiveHost("ns1.example.net");
|
||||
domain = persistResource(newDomainBase(DOMAIN_NAME, host));
|
||||
@@ -88,9 +110,22 @@ public class RelockDomainActionTest {
|
||||
domainLockUtils.administrativelyApplyUnlock(
|
||||
DOMAIN_NAME, CLIENT_ID, false, Optional.empty());
|
||||
assertThat(reloadDomain(domain).getStatusValues()).containsNoneIn(REGISTRY_LOCK_STATUSES);
|
||||
|
||||
AppEngineServiceUtils appEngineServiceUtils = mock(AppEngineServiceUtils.class);
|
||||
lenient()
|
||||
.when(appEngineServiceUtils.getServiceHostname("backend"))
|
||||
.thenReturn("backend.hostname.fake");
|
||||
|
||||
asyncTaskEnqueuer =
|
||||
AsyncTaskEnqueuerTest.createForTesting(appEngineServiceUtils, clock, Duration.ZERO);
|
||||
action = createAction(oldLock.getRevisionId());
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void afterEach() {
|
||||
verifyNoMoreInteractions(sendEmailService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLock() {
|
||||
action.run();
|
||||
@@ -104,29 +139,36 @@ public class RelockDomainActionTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_unknownCode() {
|
||||
void testFailure_unknownCode() throws Exception {
|
||||
action = createAction(12128675309L);
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_NO_CONTENT);
|
||||
assertThat(response.getPayload()).isEqualTo("Relock failed: Unknown revision ID 12128675309");
|
||||
assertThat(response.getPayload()).isEqualTo("Re-lock failed: Unknown revision ID 12128675309");
|
||||
assertTaskEnqueued(1, 12128675309L, Duration.standardMinutes(10)); // should retry, transient
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_pendingDelete() {
|
||||
void testFailure_pendingDelete() throws Exception {
|
||||
persistResource(domain.asBuilder().setStatusValues(ImmutableSet.of(PENDING_DELETE)).build());
|
||||
action.run();
|
||||
String expectedFailureMessage = "Domain example.tld has a pending delete.";
|
||||
assertThat(response.getStatus()).isEqualTo(SC_NO_CONTENT);
|
||||
assertThat(response.getPayload())
|
||||
.isEqualTo(String.format("Relock failed: Domain %s has a pending delete", DOMAIN_NAME));
|
||||
.isEqualTo(String.format("Re-lock failed: %s", expectedFailureMessage));
|
||||
assertNonTransientFailureEmail(expectedFailureMessage);
|
||||
assertNoTasksEnqueued(QUEUE_ASYNC_ACTIONS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_pendingTransfer() {
|
||||
void testFailure_pendingTransfer() throws Exception {
|
||||
persistResource(domain.asBuilder().setStatusValues(ImmutableSet.of(PENDING_TRANSFER)).build());
|
||||
action.run();
|
||||
String expectedFailureMessage = "Domain example.tld has a pending transfer.";
|
||||
assertThat(response.getStatus()).isEqualTo(SC_NO_CONTENT);
|
||||
assertThat(response.getPayload())
|
||||
.isEqualTo(String.format("Relock failed: Domain %s has a pending transfer", DOMAIN_NAME));
|
||||
.isEqualTo(String.format("Re-lock failed: %s", expectedFailureMessage));
|
||||
assertNonTransientFailureEmail(expectedFailureMessage);
|
||||
assertNoTasksEnqueued(QUEUE_ASYNC_ACTIONS);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -135,29 +177,64 @@ public class RelockDomainActionTest {
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_NO_CONTENT);
|
||||
assertThat(response.getPayload())
|
||||
.isEqualTo("Domain example.tld is already manually relocked, skipping automated relock.");
|
||||
.isEqualTo("Domain example.tld is already manually re-locked, skipping automated re-lock.");
|
||||
assertNoTasksEnqueued(QUEUE_ASYNC_ACTIONS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_domainDeleted() {
|
||||
void testFailure_domainDeleted() throws Exception {
|
||||
persistDomainAsDeleted(domain, clock.nowUtc());
|
||||
action.run();
|
||||
String expectedFailureMessage = "Domain example.tld has been deleted.";
|
||||
assertThat(response.getStatus()).isEqualTo(SC_NO_CONTENT);
|
||||
assertThat(response.getPayload())
|
||||
.isEqualTo(String.format("Relock failed: Domain %s has been deleted", DOMAIN_NAME));
|
||||
.isEqualTo(String.format("Re-lock failed: %s", expectedFailureMessage));
|
||||
assertNonTransientFailureEmail(expectedFailureMessage);
|
||||
assertNoTasksEnqueued(QUEUE_ASYNC_ACTIONS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_domainTransferred() {
|
||||
void testFailure_domainTransferred() throws Exception {
|
||||
persistResource(domain.asBuilder().setPersistedCurrentSponsorClientId("NewRegistrar").build());
|
||||
action.run();
|
||||
String expectedFailureMessage =
|
||||
"Domain example.tld has been transferred from registrar TheRegistrar to registrar "
|
||||
+ "NewRegistrar since the unlock.";
|
||||
assertThat(response.getStatus()).isEqualTo(SC_NO_CONTENT);
|
||||
assertThat(response.getPayload())
|
||||
.isEqualTo(
|
||||
String.format(
|
||||
"Relock failed: Domain %s has been transferred from registrar %s to registrar "
|
||||
+ "%s since the unlock",
|
||||
DOMAIN_NAME, CLIENT_ID, "NewRegistrar"));
|
||||
.isEqualTo(String.format("Re-lock failed: %s", expectedFailureMessage));
|
||||
assertNonTransientFailureEmail(expectedFailureMessage);
|
||||
assertNoTasksEnqueued(QUEUE_ASYNC_ACTIONS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailure_transientFailure_enqueuesTask() {
|
||||
// Hard-delete the domain to simulate a DB failure
|
||||
deleteResource(domain);
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_NO_CONTENT);
|
||||
assertThat(response.getPayload()).isEqualTo("Re-lock failed: null");
|
||||
assertTaskEnqueued(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_sufficientTransientFailures_sendsEmail() throws Exception {
|
||||
// Hard-delete the domain to simulate a DB failure
|
||||
deleteResource(domain);
|
||||
action = createAction(oldLock.getRevisionId(), RelockDomainAction.FAILURES_BEFORE_EMAIL);
|
||||
action.run();
|
||||
assertTaskEnqueued(RelockDomainAction.FAILURES_BEFORE_EMAIL + 1);
|
||||
assertTransientFailureEmail();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_NO_CONTENT);
|
||||
assertThat(response.getPayload()).isEqualTo("Re-lock failed: null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_afterSufficientFailures_sendsEmail() throws Exception {
|
||||
action = createAction(oldLock.getRevisionId(), RelockDomainAction.FAILURES_BEFORE_EMAIL + 1);
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_OK);
|
||||
assertSuccessEmailSent();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -170,14 +247,108 @@ public class RelockDomainActionTest {
|
||||
action.run();
|
||||
assertThat(response.getStatus()).isEqualTo(SC_NO_CONTENT);
|
||||
assertThat(response.getPayload())
|
||||
.isEqualTo("Domain example.tld is already manually relocked, skipping automated relock.");
|
||||
.isEqualTo("Domain example.tld is already manually re-locked, skipping automated re-lock.");
|
||||
assertNoTasksEnqueued(QUEUE_ASYNC_ACTIONS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_slowsDown() throws Exception {
|
||||
deleteResource(domain);
|
||||
action = createAction(oldLock.getRevisionId(), RelockDomainAction.ATTEMPTS_BEFORE_SLOWDOWN);
|
||||
action.run();
|
||||
assertTaskEnqueued(
|
||||
RelockDomainAction.ATTEMPTS_BEFORE_SLOWDOWN + 1,
|
||||
oldLock.getRevisionId(),
|
||||
Duration.standardHours(1));
|
||||
}
|
||||
|
||||
private void assertSuccessEmailSent() throws Exception {
|
||||
EmailMessage expectedEmail =
|
||||
EmailMessage.newBuilder()
|
||||
.setSubject("Successful re-lock of domain example.tld")
|
||||
.setBody(
|
||||
"The domain example.tld was successfully re-locked.\n\nPlease "
|
||||
+ "contact support at support@example.com if you have any questions.")
|
||||
.setRecipients(
|
||||
ImmutableSet.of(new InternetAddress("Marla.Singer.RegistryLock@crr.com")))
|
||||
.setFrom(new InternetAddress("outgoing@example.com"))
|
||||
.build();
|
||||
verify(sendEmailService).sendEmail(expectedEmail);
|
||||
}
|
||||
|
||||
private void assertNonTransientFailureEmail(String exceptionMessage) throws Exception {
|
||||
String expectedBody =
|
||||
String.format(
|
||||
"There was an error when automatically re-locking example.tld. Error message: %s\n\n"
|
||||
+ "Please contact support at support@example.com if you have any questions.",
|
||||
exceptionMessage);
|
||||
assertFailureEmailWithBody(
|
||||
expectedBody, ImmutableSet.of(new InternetAddress("Marla.Singer.RegistryLock@crr.com")));
|
||||
}
|
||||
|
||||
private void assertTransientFailureEmail() throws Exception {
|
||||
String expectedBody =
|
||||
"There was an unexpected error when automatically re-locking example.tld. We will continue "
|
||||
+ "retrying the lock for five hours. Please contact support at support@example.com if "
|
||||
+ "you have any questions";
|
||||
assertFailureEmailWithBody(
|
||||
expectedBody,
|
||||
ImmutableSet.of(
|
||||
new InternetAddress("Marla.Singer.RegistryLock@crr.com"),
|
||||
new InternetAddress("alerts@example.com")));
|
||||
}
|
||||
|
||||
private void assertFailureEmailWithBody(String body, ImmutableSet<InternetAddress> recipients)
|
||||
throws Exception {
|
||||
EmailMessage expectedEmail =
|
||||
EmailMessage.newBuilder()
|
||||
.setSubject("Error re-locking domain example.tld")
|
||||
.setBody(body)
|
||||
.setRecipients(recipients)
|
||||
.setFrom(new InternetAddress("outgoing@example.com"))
|
||||
.build();
|
||||
verify(sendEmailService).sendEmail(expectedEmail);
|
||||
}
|
||||
|
||||
private void assertTaskEnqueued(int numAttempts) {
|
||||
assertTaskEnqueued(numAttempts, oldLock.getRevisionId(), Duration.standardMinutes(10));
|
||||
}
|
||||
|
||||
private void assertTaskEnqueued(int numAttempts, long oldUnlockRevisionId, Duration duration) {
|
||||
assertTasksEnqueued(
|
||||
QUEUE_ASYNC_ACTIONS,
|
||||
new TaskMatcher()
|
||||
.url(RelockDomainAction.PATH)
|
||||
.method("POST")
|
||||
.header("Host", "backend.hostname.fake")
|
||||
.param(
|
||||
RelockDomainAction.OLD_UNLOCK_REVISION_ID_PARAM,
|
||||
String.valueOf(oldUnlockRevisionId))
|
||||
.param(RelockDomainAction.PREVIOUS_ATTEMPTS_PARAM, String.valueOf(numAttempts))
|
||||
.etaDelta(duration.minus(standardSeconds(30)), duration.plus(standardSeconds(30))));
|
||||
}
|
||||
|
||||
private DomainBase reloadDomain(DomainBase domain) {
|
||||
return ofy().load().entity(domain).now();
|
||||
}
|
||||
|
||||
private RelockDomainAction createAction(Long oldUnlockRevisionId) {
|
||||
return new RelockDomainAction(oldUnlockRevisionId, domainLockUtils, response);
|
||||
private RelockDomainAction createAction(Long oldUnlockRevisionId) throws Exception {
|
||||
return createAction(oldUnlockRevisionId, 0);
|
||||
}
|
||||
|
||||
private RelockDomainAction createAction(Long oldUnlockRevisionId, int previousAttempts)
|
||||
throws Exception {
|
||||
InternetAddress alertRecipientAddress = new InternetAddress("alerts@example.com");
|
||||
InternetAddress gSuiteOutgoingAddress = new InternetAddress("outgoing@example.com");
|
||||
return new RelockDomainAction(
|
||||
oldUnlockRevisionId,
|
||||
previousAttempts,
|
||||
alertRecipientAddress,
|
||||
gSuiteOutgoingAddress,
|
||||
"support@example.com",
|
||||
sendEmailService,
|
||||
domainLockUtils,
|
||||
response,
|
||||
asyncTaskEnqueuer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ class WriteToSqlTest implements Serializable {
|
||||
// Required for contacts created below.
|
||||
Registrar ofyRegistrar = AppEngineExtension.makeRegistrar2();
|
||||
store.insertOrUpdate(ofyRegistrar);
|
||||
jpaTm().transact(() -> jpaTm().saveNewOrUpdate(store.loadAsOfyEntity(ofyRegistrar)));
|
||||
jpaTm().transact(() -> jpaTm().put(store.loadAsOfyEntity(ofyRegistrar)));
|
||||
|
||||
ImmutableList.Builder<Entity> builder = new ImmutableList.Builder<>();
|
||||
|
||||
|
||||
@@ -285,7 +285,7 @@ class Spec11PipelineTest {
|
||||
.build();
|
||||
|
||||
verify(mockJpaTm).transact(any(Runnable.class));
|
||||
verify(mockJpaTm).saveNew(expected);
|
||||
verify(mockJpaTm).insert(expected);
|
||||
verifyNoMoreInteractions(mockJpaTm);
|
||||
}
|
||||
|
||||
|
||||
@@ -597,6 +597,7 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, DomainBa
|
||||
.setRestoreBillingCost(Money.of(EUR, 11))
|
||||
.setRenewBillingCostTransitions(ImmutableSortedMap.of(START_OF_TIME, Money.of(EUR, 7)))
|
||||
.setEapFeeSchedule(ImmutableSortedMap.of(START_OF_TIME, Money.zero(EUR)))
|
||||
.setRegistryLockOrUnlockBillingCost(Money.of(EUR, 20))
|
||||
.setServerStatusChangeBillingCost(Money.of(EUR, 19))
|
||||
.build());
|
||||
persistDomain();
|
||||
@@ -615,6 +616,7 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, DomainBa
|
||||
.setRestoreBillingCost(Money.of(EUR, 11))
|
||||
.setRenewBillingCostTransitions(ImmutableSortedMap.of(START_OF_TIME, Money.of(EUR, 7)))
|
||||
.setEapFeeSchedule(ImmutableSortedMap.of(START_OF_TIME, Money.zero(EUR)))
|
||||
.setRegistryLockOrUnlockBillingCost(Money.of(EUR, 20))
|
||||
.setServerStatusChangeBillingCost(Money.of(EUR, 19))
|
||||
.build());
|
||||
persistDomain();
|
||||
@@ -633,6 +635,7 @@ class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, DomainBa
|
||||
.setRestoreBillingCost(Money.of(EUR, 11))
|
||||
.setRenewBillingCostTransitions(ImmutableSortedMap.of(START_OF_TIME, Money.of(EUR, 7)))
|
||||
.setEapFeeSchedule(ImmutableSortedMap.of(START_OF_TIME, Money.zero(EUR)))
|
||||
.setRegistryLockOrUnlockBillingCost(Money.of(EUR, 20))
|
||||
.setServerStatusChangeBillingCost(Money.of(EUR, 19))
|
||||
.build());
|
||||
persistDomain();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user