mirror of
https://github.com/google/nomulus
synced 2026-01-29 17:12:17 +00:00
Compare commits
23 Commits
nomulus-20
...
nomulus-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53ece5eda4 | ||
|
|
6c220567c8 | ||
|
|
c3e3a1353b | ||
|
|
ce480a5191 | ||
|
|
f2a2b2d2e2 | ||
|
|
906b054f4b | ||
|
|
a694e247cd | ||
|
|
022f397cd9 | ||
|
|
5dc058ec99 | ||
|
|
0fd7cf29b5 | ||
|
|
bc7f3546c7 | ||
|
|
658f61bd8f | ||
|
|
8ca8fff387 | ||
|
|
8bcfb1802e | ||
|
|
a259dee986 | ||
|
|
ebc8d54f94 | ||
|
|
e9ce389269 | ||
|
|
e07be9780e | ||
|
|
31b5abb9a1 | ||
|
|
aa4e242a34 | ||
|
|
964f264c9d | ||
|
|
cc018a6dac | ||
|
|
c25adbbd9c |
@@ -12,7 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
apply from: "${rootDir.path}/projects.gradle"
|
||||
apply plugin: 'war'
|
||||
|
||||
def environment = rootProject.environment
|
||||
@@ -26,14 +25,19 @@ project.convention.plugins['war'].webAppDirName =
|
||||
|
||||
apply plugin: 'com.google.cloud.tools.appengine'
|
||||
|
||||
def coreResourcesDir = "${rootDir}/core/build/resources/main"
|
||||
|
||||
// Get the web.xml file for the service.
|
||||
war {
|
||||
webInf {
|
||||
from "../../core/src/main/java/google/registry/env/common/${project.name}/WEB-INF"
|
||||
|
||||
from("${coreResourcesDir}/META-INF/persistence.xml") {
|
||||
into "classes/META-INF"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def coreResourcesDir = "${rootDir}/core/build/resources/main"
|
||||
war {
|
||||
from("${coreResourcesDir}/google/registry/ui/html") {
|
||||
include "*.html"
|
||||
@@ -68,6 +72,10 @@ appengine {
|
||||
if (!rootProject.prodOrSandboxEnv) {
|
||||
version = 'GCLOUD_CONFIG'
|
||||
}
|
||||
|
||||
// Don't set gcpProject directly, it gets overriden in ./build.gradle.
|
||||
// Do -P environment={crash,alpha} instead. For sandbox/production,
|
||||
// use Spinnaker.
|
||||
projectId = gcpProject
|
||||
}
|
||||
}
|
||||
@@ -92,4 +100,15 @@ explodeWar.doLast {
|
||||
|
||||
rootProject.deploy.dependsOn appengineDeployAll
|
||||
rootProject.stage.dependsOn appengineStage
|
||||
appengineDeployAll.dependsOn rootProject.verifyDeployment
|
||||
|
||||
// Impose verification for all of the deployment tasks. We haven't found a
|
||||
// better way to do this other than to apply to each of them independently.
|
||||
// If a new task gets added, it will still fail if "environment" is not defined
|
||||
// because gcpProject is null. We just won't get as friendly an error message.
|
||||
appengineDeployAll.configure rootProject.verifyDeploymentConfig
|
||||
appengineDeploy.configure rootProject.verifyDeploymentConfig
|
||||
appengineDeployCron.configure rootProject.verifyDeploymentConfig
|
||||
appengineDeployDispatch.configure rootProject.verifyDeploymentConfig
|
||||
appengineDeployDos.configure rootProject.verifyDeploymentConfig
|
||||
appengineDeployIndex.configure rootProject.verifyDeploymentConfig
|
||||
appengineDeployQueue.configure rootProject.verifyDeploymentConfig
|
||||
|
||||
60
build.gradle
60
build.gradle
@@ -109,21 +109,61 @@ task deploy {
|
||||
description = 'Deploys all services to App Engine.'
|
||||
}
|
||||
|
||||
task verifyDeployment {
|
||||
group = 'deployment'
|
||||
description = 'Ensure that one cannot deploy to production or sandbox.'
|
||||
doFirst {
|
||||
if (rootProject.prodOrSandboxEnv) {
|
||||
throw new GradleException("Cannot deploy to production or sandbox.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task stage {
|
||||
group = 'deployment'
|
||||
description = 'Generates application directories for all services.'
|
||||
}
|
||||
|
||||
// App-engine environment configuration. We set up all of the variables in
|
||||
// the root project.
|
||||
|
||||
def environments = ['production', 'sandbox', 'alpha', 'crash']
|
||||
|
||||
def gcpProject = null
|
||||
|
||||
apply from: "${rootDir.path}/projects.gradle"
|
||||
|
||||
if (environment == '') {
|
||||
// Keep the project null, this will prevent deployment. Set the
|
||||
// environment to "alpha" because other code needs this property to
|
||||
// explode the war file.
|
||||
environment = 'alpha'
|
||||
} else if (environment != 'production' && environment != 'sandbox') {
|
||||
gcpProject = projects[environment]
|
||||
if (gcpProject == null) {
|
||||
throw new GradleException("-Penvironment must be one of " +
|
||||
"${projects.keySet()}.")
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.ext.environment = environment
|
||||
rootProject.ext.gcpProject = gcpProject
|
||||
rootProject.ext.prodOrSandboxEnv = environment in ['production', 'sandbox']
|
||||
|
||||
// Function to verify that the deployment parameters have been set.
|
||||
def verifyDeploymentParams() {
|
||||
if (prodOrSandboxEnv) {
|
||||
// Do not deploy to prod or sandbox. Print a prominent error in bright red.
|
||||
System.err.println('\033[31;1m-----------------------------------------------------------------')
|
||||
System.err.println('*** DANGER WILL ROBINSON!')
|
||||
System.err.println('*** You may not deploy to production or sandbox from gradle. Do a')
|
||||
System.err.println('*** release from Spinnaker, see deployment playbook.')
|
||||
System.err.println('-----------------------------------------------------------------')
|
||||
throw new GradleException('Aborting. See prominent error above.')
|
||||
} else if (gcpProject == null) {
|
||||
def error = 'You must specify -P environment={alpha,crash}'
|
||||
System.err.println("\033[33;1m${error}\033[0m")
|
||||
throw GradleException("Aborting: ${error}")
|
||||
}
|
||||
}
|
||||
|
||||
// Closure that we can just drop into all of our deployment tasks.
|
||||
rootProject.ext.verifyDeploymentConfig = {
|
||||
doFirst { verifyDeploymentParams() }
|
||||
}
|
||||
|
||||
// Subproject configuration.
|
||||
|
||||
allprojects {
|
||||
// Skip no-op project
|
||||
if (project.name == 'services') return
|
||||
|
||||
@@ -558,10 +558,38 @@ artifacts {
|
||||
testRuntime testJar
|
||||
}
|
||||
|
||||
task fragileTest(type: Test) {
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
void setTests(List<String> tests) {
|
||||
// Common exclude pattern. See README in parent directory for explanation.
|
||||
exclude "**/*TestCase.*", "**/*TestSuite.*"
|
||||
include tests
|
||||
if (project.testFilter) {
|
||||
testNameIncludePatterns = project.testFilter.split(',')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task fragileTest(type: FilteringTest) {
|
||||
// Common exclude pattern. See README in parent directory for explanation.
|
||||
exclude "**/*TestCase.*", "**/*TestSuite.*"
|
||||
include fragileTestPatterns
|
||||
tests = fragileTestPatterns
|
||||
|
||||
if (rootProject.findProperty("skipDockerIncompatibleTests") == "true") {
|
||||
exclude dockerIncompatibleTestPatterns
|
||||
@@ -571,10 +599,8 @@ task fragileTest(type: Test) {
|
||||
forkEvery 1
|
||||
}
|
||||
|
||||
task outcastTest(type: Test) {
|
||||
// Common exclude pattern. See README in parent directory for explanation.
|
||||
exclude "**/*TestCase.*", "**/*TestSuite.*"
|
||||
include outcastTestPatterns
|
||||
task outcastTest(type: FilteringTest) {
|
||||
tests = outcastTestPatterns
|
||||
|
||||
// Sets the maximum number of test executors that may exist at the same time.
|
||||
maxParallelForks 5
|
||||
@@ -630,10 +656,8 @@ task registryTool(type: JavaExec) {
|
||||
}
|
||||
}
|
||||
|
||||
task generateGoldenImages(type: Test) {
|
||||
// Common exclude pattern. See README in parent directory for explanation.
|
||||
exclude "**/*TestCase.*", "**/*TestSuite.*"
|
||||
include "**/webdriver/*"
|
||||
task generateGoldenImages(type: FilteringTest) {
|
||||
tests = ["**/webdriver/*"]
|
||||
|
||||
// Sets the maximum number of test executors that may exist at the same time.
|
||||
maxParallelForks 5
|
||||
|
||||
@@ -144,7 +144,7 @@
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportDomainLists&runInEmpty]]></url>
|
||||
<description>
|
||||
This job exports lists of all active domain names to Google Cloud Storage.
|
||||
This job exports lists of all active domain names to Google Drive and Google Cloud Storage.
|
||||
</description>
|
||||
<schedule>every 12 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportDomainLists&runInEmpty]]></url>
|
||||
<description>
|
||||
This job exports lists of all active domain names to Google Cloud Storage.
|
||||
This job exports lists of all active domain names to Google Drive and Google Cloud Storage.
|
||||
</description>
|
||||
<schedule>every 12 hours synchronized</schedule>
|
||||
<target>backend</target>
|
||||
|
||||
@@ -56,7 +56,7 @@ import javax.inject.Inject;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* A mapreduce that exports the list of active domains on all real TLDs to Google Cloud Storage.
|
||||
* A mapreduce that exports the list of active domains on all real TLDs to Google Drive and GCS.
|
||||
*
|
||||
* <p>Each TLD's active domain names are exported as a newline-delimited flat text file with the
|
||||
* name TLD.txt into the domain-lists bucket. Note that this overwrites the files in place.
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// 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.model.registry;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
|
||||
|
||||
import google.registry.schema.domain.RegistryLock;
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
/** Data access object for {@link google.registry.schema.domain.RegistryLock}. */
|
||||
public final class RegistryLockDao {
|
||||
|
||||
/**
|
||||
* Returns the most recent version of the {@link RegistryLock} referred to by the verification
|
||||
* code (there may be two instances of the same code in the database--one after lock object
|
||||
* creation and one after verification.
|
||||
*/
|
||||
public static RegistryLock getByVerificationCode(String verificationCode) {
|
||||
return jpaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
EntityManager em = jpaTm().getEntityManager();
|
||||
Long revisionId =
|
||||
em.createQuery(
|
||||
"SELECT MAX(revisionId) FROM RegistryLock WHERE verificationCode ="
|
||||
+ " :verificationCode",
|
||||
Long.class)
|
||||
.setParameter("verificationCode", verificationCode)
|
||||
.getSingleResult();
|
||||
checkNotNull(revisionId, "No registry lock with this code");
|
||||
return em.find(RegistryLock.class, revisionId);
|
||||
});
|
||||
}
|
||||
|
||||
public static void save(RegistryLock registryLock) {
|
||||
checkNotNull(registryLock, "Null registry lock cannot be saved");
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().persist(registryLock));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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.model.tmch;
|
||||
|
||||
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
|
||||
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.schema.tmch.ClaimsList;
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
/** Data access object for {@link ClaimsList}. */
|
||||
public class ClaimsListDao {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private static void save(ClaimsList 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.
|
||||
*
|
||||
* <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) {
|
||||
try {
|
||||
ClaimsListDao.save(claimsList);
|
||||
logger.atInfo().log(
|
||||
"Inserted %,d claims into Cloud SQL, created at %s",
|
||||
claimsList.getLabelsToKeys().size(), claimsList.getTmdbGenerationTime());
|
||||
} catch (Throwable e) {
|
||||
logger.atSevere().withCause(e).log("Error inserting claims into Cloud SQL");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current revision of the {@link ClaimsList} in Cloud SQL. Throws exception if there
|
||||
* is no claims in the table.
|
||||
*/
|
||||
public static ClaimsList getCurrent() {
|
||||
return jpaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
EntityManager em = jpaTm().getEntityManager();
|
||||
Long revisionId =
|
||||
em.createQuery("SELECT MAX(revisionId) FROM ClaimsList", Long.class)
|
||||
.getSingleResult();
|
||||
return em.createQuery(
|
||||
"FROM ClaimsList cl LEFT JOIN FETCH cl.labelsToKeys WHERE cl.revisionId ="
|
||||
+ " :revisionId",
|
||||
ClaimsList.class)
|
||||
.setParameter("revisionId", revisionId)
|
||||
.getSingleResult();
|
||||
});
|
||||
}
|
||||
|
||||
private ClaimsListDao() {}
|
||||
}
|
||||
@@ -217,8 +217,7 @@ public class ClaimsListShard extends ImmutableObject {
|
||||
});
|
||||
}
|
||||
|
||||
public static ClaimsListShard create(
|
||||
DateTime creationTime, ImmutableMap<String, String> labelsToKeys) {
|
||||
public static ClaimsListShard create(DateTime creationTime, Map<String, String> labelsToKeys) {
|
||||
ClaimsListShard instance = new ClaimsListShard();
|
||||
instance.id = allocateId();
|
||||
instance.creationTime = checkNotNull(creationTime);
|
||||
|
||||
@@ -15,10 +15,7 @@
|
||||
package google.registry.model.transaction;
|
||||
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.persistence.PersistenceModule.AppEngineEmf;
|
||||
import google.registry.util.Clock;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.EntityTransaction;
|
||||
@@ -26,7 +23,6 @@ import javax.persistence.PersistenceException;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Implementation of {@link JpaTransactionManager} for JPA compatible database. */
|
||||
@Singleton
|
||||
public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
@@ -39,8 +35,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
private final ThreadLocal<TransactionInfo> transactionInfo =
|
||||
ThreadLocal.withInitial(TransactionInfo::new);
|
||||
|
||||
@Inject
|
||||
JpaTransactionManagerImpl(@AppEngineEmf EntityManagerFactory emf, Clock clock) {
|
||||
public JpaTransactionManagerImpl(EntityManagerFactory emf, Clock clock) {
|
||||
this.emf = emf;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@@ -14,11 +14,8 @@
|
||||
|
||||
package google.registry.model.transaction;
|
||||
|
||||
import com.google.appengine.api.utils.SystemProperty;
|
||||
import com.google.appengine.api.utils.SystemProperty.Environment.Value;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import google.registry.model.ofy.DatastoreTransactionManager;
|
||||
import google.registry.persistence.DaggerPersistenceComponent;
|
||||
|
||||
/** Factory class to create {@link TransactionManager} instance. */
|
||||
// TODO: Rename this to PersistenceFactory and move to persistence package.
|
||||
@@ -30,11 +27,19 @@ public class TransactionManagerFactory {
|
||||
private TransactionManagerFactory() {}
|
||||
|
||||
private static JpaTransactionManager createJpaTransactionManager() {
|
||||
if (SystemProperty.environment.value() == Value.Production) {
|
||||
return DaggerPersistenceComponent.create().jpaTransactionManager();
|
||||
} else {
|
||||
return DummyJpaTransactionManager.create();
|
||||
}
|
||||
// TODO(shicong): There is currently no environment where we want to create a real JPA
|
||||
// transaction manager here. The unit tests that require one are all set up using
|
||||
// JpaTransactionManagerRule which launches its own PostgreSQL instance. When we actually have
|
||||
// PostgreSQL tables in production, ensure that all of the test environments are set up
|
||||
// correctly and restore the code that creates a JpaTransactionManager when
|
||||
// RegistryEnvironment.get() != UNITTEST.
|
||||
//
|
||||
// We removed the original code because it didn't work in sandbox (due to the absence of the
|
||||
// persistence.xml file, which has since been added), and then (after adding this) didn't work
|
||||
// in crash because the postgresql password hadn't been set up. Prior to restoring, we'll need
|
||||
// to do setup in all environments, and we probably only want to do this once we're actually
|
||||
// using Cloud SQL for one of the new tables.
|
||||
return DummyJpaTransactionManager.create();
|
||||
}
|
||||
|
||||
private static TransactionManager createTransactionManager() {
|
||||
@@ -44,6 +49,15 @@ public class TransactionManagerFactory {
|
||||
return new DatastoreTransactionManager(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets jpaTm to the implementation for Nomulus tool. Note that this method should be only used by
|
||||
* {@link google.registry.tools.RegistryCli} to initialize jpaTm.
|
||||
*/
|
||||
public static void initForTool() {
|
||||
// TODO(shicong): Uncomment the line below when we set up Cloud SQL instance in all environments
|
||||
// jpaTm = DaggerPersistenceComponent.create().nomulusToolJpaTransactionManager();
|
||||
}
|
||||
|
||||
/** Returns {@link TransactionManager} instance. */
|
||||
public static TransactionManager tm() {
|
||||
return TM;
|
||||
@@ -51,11 +65,6 @@ public class TransactionManagerFactory {
|
||||
|
||||
/** Returns {@link JpaTransactionManager} instance. */
|
||||
public static JpaTransactionManager jpaTm() {
|
||||
// TODO: Returns corresponding TransactionManager based on the runtime environment.
|
||||
// We have 3 kinds of runtime environment:
|
||||
// 1. App Engine
|
||||
// 2. Local JVM used by nomulus tool
|
||||
// 3. Unit test
|
||||
return jpaTm;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// 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.persistence;
|
||||
|
||||
import static com.google.common.base.Charsets.US_ASCII;
|
||||
import static com.google.common.hash.Funnels.stringFunnel;
|
||||
|
||||
import com.google.common.hash.BloomFilter;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.AttributeConverter;
|
||||
import javax.persistence.Converter;
|
||||
|
||||
/** JPA converter for ASCII String {@link BloomFilter}s. */
|
||||
@Converter(autoApply = true)
|
||||
public class BloomFilterConverter implements AttributeConverter<BloomFilter<String>, byte[]> {
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public byte[] convertToDatabaseColumn(@Nullable BloomFilter<String> entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
try {
|
||||
entity.writeTo(bos);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Error saving Bloom filter data", e);
|
||||
}
|
||||
return bos.toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public BloomFilter<String> convertToEntityAttribute(@Nullable byte[] columnValue) {
|
||||
if (columnValue == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return BloomFilter.readFrom(new ByteArrayInputStream(columnValue), stringFunnel(US_ASCII));
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Error loading Bloom filter data", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// 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.persistence;
|
||||
|
||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
|
||||
|
||||
import google.registry.model.CreateAutoTimestamp;
|
||||
import google.registry.util.DateTimeUtils;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.AttributeConverter;
|
||||
import javax.persistence.Converter;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** JPA converter to for storing/retrieving CreateAutoTimestamp objects. */
|
||||
@Converter(autoApply = true)
|
||||
public class CreateAutoTimestampConverter
|
||||
implements AttributeConverter<CreateAutoTimestamp, Timestamp> {
|
||||
|
||||
@Override
|
||||
public Timestamp convertToDatabaseColumn(CreateAutoTimestamp entity) {
|
||||
DateTime dateTime = firstNonNull(entity.getTimestamp(), jpaTm().getTransactionTime());
|
||||
return Timestamp.from(DateTimeUtils.toZonedDateTime(dateTime).toInstant());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public CreateAutoTimestamp convertToEntityAttribute(@Nullable Timestamp columnValue) {
|
||||
if (columnValue == null) {
|
||||
return null;
|
||||
}
|
||||
ZonedDateTime zdt = ZonedDateTime.ofInstant(columnValue.toInstant(), ZoneOffset.UTC);
|
||||
return CreateAutoTimestamp.create(DateTimeUtils.toJodaDateTime(zdt));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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.persistence;
|
||||
|
||||
import java.sql.Types;
|
||||
import org.hibernate.dialect.PostgreSQL95Dialect;
|
||||
|
||||
/** Nomulus mapping rules for column types in Postgresql. */
|
||||
public class NomulusPostgreSQLDialect extends PostgreSQL95Dialect {
|
||||
public NomulusPostgreSQLDialect() {
|
||||
super();
|
||||
registerColumnType(Types.VARCHAR, "text");
|
||||
registerColumnType(Types.TIMESTAMP_WITH_TIMEZONE, "timestamptz");
|
||||
registerColumnType(Types.TIMESTAMP, "timestamptz");
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,9 @@ import dagger.Component;
|
||||
import google.registry.config.CredentialModule;
|
||||
import google.registry.config.RegistryConfig.ConfigModule;
|
||||
import google.registry.keyring.kms.KmsModule;
|
||||
import google.registry.model.transaction.JpaTransactionManagerImpl;
|
||||
import google.registry.model.transaction.JpaTransactionManager;
|
||||
import google.registry.persistence.PersistenceModule.AppEngineJpaTm;
|
||||
import google.registry.persistence.PersistenceModule.NomulusToolJpaTm;
|
||||
import google.registry.util.UtilsModule;
|
||||
import javax.inject.Singleton;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
@@ -34,5 +36,10 @@ import javax.persistence.EntityManagerFactory;
|
||||
UtilsModule.class
|
||||
})
|
||||
public interface PersistenceComponent {
|
||||
JpaTransactionManagerImpl jpaTransactionManager();
|
||||
|
||||
@AppEngineJpaTm
|
||||
JpaTransactionManager appEngineJpaTransactionManager();
|
||||
|
||||
@NomulusToolJpaTm
|
||||
JpaTransactionManager nomulusToolJpaTransactionManager();
|
||||
}
|
||||
|
||||
@@ -29,11 +29,14 @@ import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.keyring.kms.KmsKeyring;
|
||||
import google.registry.model.transaction.JpaTransactionManager;
|
||||
import google.registry.model.transaction.JpaTransactionManagerImpl;
|
||||
import google.registry.util.Clock;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.inject.Qualifier;
|
||||
import javax.inject.Singleton;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.Persistence;
|
||||
import org.hibernate.cfg.Environment;
|
||||
@@ -61,6 +64,8 @@ public class PersistenceModule {
|
||||
// SessionFactory is created. Setting it to 'none' to turn off the feature.
|
||||
properties.put(Environment.HBM2DDL_AUTO, "none");
|
||||
|
||||
// Hibernate converts any date to this timezone when writing to the database.
|
||||
properties.put(Environment.JDBC_TIME_ZONE, "UTC");
|
||||
properties.put(
|
||||
Environment.PHYSICAL_NAMING_STRATEGY, NomulusNamingStrategy.class.getCanonicalName());
|
||||
|
||||
@@ -70,28 +75,60 @@ public class PersistenceModule {
|
||||
properties.put(HIKARI_MINIMUM_IDLE, getHibernateHikariMinimumIdle());
|
||||
properties.put(HIKARI_MAXIMUM_POOL_SIZE, getHibernateHikariMaximumPoolSize());
|
||||
properties.put(HIKARI_IDLE_TIMEOUT, getHibernateHikariIdleTimeout());
|
||||
properties.put(Environment.DIALECT, NomulusPostgreSQLDialect.class.getName());
|
||||
return properties.build();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@AppEngineEmf
|
||||
public static EntityManagerFactory providesAppEngineEntityManagerFactory(
|
||||
@Singleton
|
||||
@AppEngineJpaTm
|
||||
public static JpaTransactionManager providesAppEngineJpaTm(
|
||||
@Config("cloudSqlJdbcUrl") String jdbcUrl,
|
||||
@Config("cloudSqlUsername") String username,
|
||||
@Config("cloudSqlInstanceConnectionName") String instanceConnectionName,
|
||||
KmsKeyring kmsKeyring,
|
||||
@DefaultHibernateConfigs ImmutableMap<String, String> defaultConfigs) {
|
||||
String password = kmsKeyring.getCloudSqlPassword();
|
||||
|
||||
@DefaultHibernateConfigs ImmutableMap<String, String> defaultConfigs,
|
||||
Clock clock) {
|
||||
HashMap<String, String> overrides = Maps.newHashMap(defaultConfigs);
|
||||
|
||||
// For Java users, the Cloud SQL JDBC Socket Factory can provide authenticated connections.
|
||||
// See https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory for details.
|
||||
overrides.put("socketFactory", "com.google.cloud.sql.postgres.SocketFactory");
|
||||
overrides.put("cloudSqlInstance", instanceConnectionName);
|
||||
|
||||
EntityManagerFactory emf = create(jdbcUrl, username, password, ImmutableMap.copyOf(overrides));
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(emf::close));
|
||||
return emf;
|
||||
overrides.put(Environment.URL, jdbcUrl);
|
||||
overrides.put(Environment.USER, username);
|
||||
overrides.put(Environment.PASS, kmsKeyring.getCloudSqlPassword());
|
||||
|
||||
return new JpaTransactionManagerImpl(create(overrides), clock);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@NomulusToolJpaTm
|
||||
public static JpaTransactionManager providesNomulusToolJpaTm(
|
||||
@Config("toolsCloudSqlJdbcUrl") String jdbcUrl,
|
||||
@Config("toolsCloudSqlUsername") String username,
|
||||
@Config("cloudSqlInstanceConnectionName") String instanceConnectionName,
|
||||
KmsKeyring kmsKeyring,
|
||||
@DefaultHibernateConfigs ImmutableMap<String, String> defaultConfigs,
|
||||
Clock clock) {
|
||||
|
||||
// Cloud SQL JDBC Socket Factory library requires the jdbc url to include all connection
|
||||
// information, otherwise the connection initialization will fail. See here for more details:
|
||||
// https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory
|
||||
String fullJdbcUrl =
|
||||
new StringBuilder(jdbcUrl)
|
||||
.append("?cloudSqlInstance=" + instanceConnectionName)
|
||||
.append("&socketFactory=com.google.cloud.sql.postgres.SocketFactory")
|
||||
.append("&user=" + username)
|
||||
.append("&password=" + kmsKeyring.getCloudSqlPassword())
|
||||
.toString();
|
||||
|
||||
HashMap<String, String> overrides = Maps.newHashMap(defaultConfigs);
|
||||
overrides.put(Environment.URL, fullJdbcUrl);
|
||||
|
||||
return new JpaTransactionManagerImpl(create(overrides), clock);
|
||||
}
|
||||
|
||||
/** Constructs the {@link EntityManagerFactory} instance. */
|
||||
@@ -102,25 +139,37 @@ public class PersistenceModule {
|
||||
properties.put(Environment.URL, jdbcUrl);
|
||||
properties.put(Environment.USER, username);
|
||||
properties.put(Environment.PASS, password);
|
||||
|
||||
return create(ImmutableMap.copyOf(properties));
|
||||
}
|
||||
|
||||
private static EntityManagerFactory create(Map<String, String> properties) {
|
||||
// If there are no annotated classes, we can create the EntityManagerFactory from the generic
|
||||
// method. Otherwise we have to use a more tailored approach. Note that this adds to the set
|
||||
// of annotated classes defined in the configuration, it does not override them.
|
||||
EntityManagerFactory emf =
|
||||
Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME, properties);
|
||||
Persistence.createEntityManagerFactory(
|
||||
PERSISTENCE_UNIT_NAME, ImmutableMap.copyOf(properties));
|
||||
checkState(
|
||||
emf != null,
|
||||
"Persistence.createEntityManagerFactory() returns a null EntityManagerFactory");
|
||||
return emf;
|
||||
}
|
||||
|
||||
/** Dagger qualifier for the {@link EntityManagerFactory} used for App Engine application. */
|
||||
/** Dagger qualifier for {@link JpaTransactionManager} used for App Engine application. */
|
||||
@Qualifier
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface AppEngineEmf {}
|
||||
@interface AppEngineJpaTm {}
|
||||
|
||||
/** Dagger qualifier for {@link JpaTransactionManager} used for Nomulus tool. */
|
||||
@Qualifier
|
||||
@Documented
|
||||
@interface NomulusToolJpaTm {}
|
||||
|
||||
/** Dagger qualifier for the default Hibernate configurations. */
|
||||
// TODO(shicong): Change annotations in this class to none public or put them in a top level
|
||||
// package
|
||||
@Qualifier
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface DefaultHibernateConfigs {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// 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.persistence;
|
||||
|
||||
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
|
||||
|
||||
import google.registry.model.UpdateAutoTimestamp;
|
||||
import google.registry.util.DateTimeUtils;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.AttributeConverter;
|
||||
import javax.persistence.Converter;
|
||||
|
||||
/** JPA converter for storing/retrieving UpdateAutoTimestamp objects. */
|
||||
@Converter(autoApply = true)
|
||||
public class UpdateAutoTimestampConverter
|
||||
implements AttributeConverter<UpdateAutoTimestamp, Timestamp> {
|
||||
|
||||
@Override
|
||||
public Timestamp convertToDatabaseColumn(UpdateAutoTimestamp entity) {
|
||||
return Timestamp.from(DateTimeUtils.toZonedDateTime(jpaTm().getTransactionTime()).toInstant());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public UpdateAutoTimestamp convertToEntityAttribute(@Nullable Timestamp columnValue) {
|
||||
if (columnValue == null) {
|
||||
return null;
|
||||
}
|
||||
ZonedDateTime zdt = ZonedDateTime.ofInstant(columnValue.toInstant(), ZoneOffset.UTC);
|
||||
return UpdateAutoTimestamp.create(DateTimeUtils.toJodaDateTime(zdt));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// 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.persistence;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.AttributeConverter;
|
||||
import javax.persistence.Converter;
|
||||
|
||||
/**
|
||||
* JPA converter to for storing/retrieving {@link ZonedDateTime} objects.
|
||||
*
|
||||
* <p>Hibernate provides a default converter for {@link ZonedDateTime}, but it converts timestamp to
|
||||
* a non-normalized format, e.g., 2019-09-01T01:01:01Z will be converted to
|
||||
* 2019-09-01T01:01:01Z[UTC]. This converter solves that problem by explicitly calling {@link
|
||||
* ZoneId#normalized()} to normalize the zone id.
|
||||
*/
|
||||
@Converter(autoApply = true)
|
||||
public class ZonedDateTimeConverter implements AttributeConverter<ZonedDateTime, Timestamp> {
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Timestamp convertToDatabaseColumn(@Nullable ZonedDateTime attribute) {
|
||||
return attribute == null ? null : Timestamp.from(attribute.toInstant());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public ZonedDateTime convertToEntityAttribute(@Nullable Timestamp dbData) {
|
||||
return dbData == null
|
||||
? null
|
||||
: ZonedDateTime.ofInstant(dbData.toInstant(), ZoneId.of("UTC").normalized());
|
||||
}
|
||||
}
|
||||
@@ -15,13 +15,15 @@
|
||||
package google.registry.schema.domain;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.util.DateTimeUtils.toJodaDateTime;
|
||||
import static google.registry.util.DateTimeUtils.toZonedDateTime;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.CreateAutoTimestamp;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.util.DateTimeUtils;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Optional;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
@@ -54,14 +56,17 @@ import org.joda.time.DateTime;
|
||||
* Unique constraint to get around Hibernate's failure to handle auto-increment field in
|
||||
* composite primary key.
|
||||
*
|
||||
* <p>Note: because of this index, physical columns must be declared in the {@link Column}
|
||||
* annotations for {@link RegistryLock#revisionId} and {@link RegistryLock#repoId} fields.
|
||||
* <p>Note: indexes use the camelCase version of the field names because the {@link
|
||||
* google.registry.persistence.NomulusNamingStrategy} does not translate the field name into the
|
||||
* snake_case column name until the write itself.
|
||||
*/
|
||||
indexes =
|
||||
@Index(
|
||||
name = "idx_registry_lock_repo_id_revision_id",
|
||||
columnList = "repo_id, revision_id",
|
||||
unique = true))
|
||||
indexes = {
|
||||
@Index(
|
||||
name = "idx_registry_lock_repo_id_revision_id",
|
||||
columnList = "repoId, revisionId",
|
||||
unique = true),
|
||||
@Index(name = "idx_registry_lock_verification_code", columnList = "verificationCode")
|
||||
})
|
||||
public final class RegistryLock extends ImmutableObject implements Buildable {
|
||||
|
||||
/** Describes the action taken by the user. */
|
||||
@@ -72,11 +77,11 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "revision_id", nullable = false)
|
||||
@Column(nullable = false)
|
||||
private Long revisionId;
|
||||
|
||||
/** EPP repo ID of the domain in question. */
|
||||
@Column(name = "repo_id", nullable = false)
|
||||
@Column(nullable = false)
|
||||
private String repoId;
|
||||
|
||||
// TODO (b/140568328): remove this when everything is in Cloud SQL and we can join on "domain"
|
||||
@@ -102,7 +107,7 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
|
||||
|
||||
/** Creation timestamp is when the lock/unlock is first requested. */
|
||||
@Column(nullable = false)
|
||||
private ZonedDateTime creationTimestamp;
|
||||
private CreateAutoTimestamp creationTimestamp = CreateAutoTimestamp.create(null);
|
||||
|
||||
/**
|
||||
* Completion timestamp is when the user has verified the lock/unlock, when this object de facto
|
||||
@@ -146,11 +151,12 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
|
||||
}
|
||||
|
||||
public DateTime getCreationTimestamp() {
|
||||
return toJodaDateTime(creationTimestamp);
|
||||
return creationTimestamp.getTimestamp();
|
||||
}
|
||||
|
||||
public DateTime getCompletionTimestamp() {
|
||||
return toJodaDateTime(completionTimestamp);
|
||||
/** Returns the completion timestamp, or empty if this lock has not been completed yet. */
|
||||
public Optional<DateTime> getCompletionTimestamp() {
|
||||
return Optional.ofNullable(completionTimestamp).map(DateTimeUtils::toJodaDateTime);
|
||||
}
|
||||
|
||||
public String getVerificationCode() {
|
||||
@@ -165,9 +171,16 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
|
||||
return revisionId;
|
||||
}
|
||||
|
||||
public void setCompletionTimestamp(DateTime dateTime) {
|
||||
this.completionTimestamp = toZonedDateTime(dateTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder asBuilder() {
|
||||
return new Builder(clone(this));
|
||||
RegistryLock clone = clone(this);
|
||||
// Revision ID should be different for every object
|
||||
clone.revisionId = null;
|
||||
return new Builder(clone);
|
||||
}
|
||||
|
||||
/** Builder for {@link google.registry.schema.domain.RegistryLock}. */
|
||||
@@ -184,7 +197,6 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
|
||||
checkArgumentNotNull(getInstance().domainName, "Domain name cannot be null");
|
||||
checkArgumentNotNull(getInstance().registrarId, "Registrar ID cannot be null");
|
||||
checkArgumentNotNull(getInstance().action, "Action cannot be null");
|
||||
checkArgumentNotNull(getInstance().creationTimestamp, "Creation timestamp cannot be null");
|
||||
checkArgumentNotNull(getInstance().verificationCode, "Verification codecannot be null");
|
||||
checkArgument(
|
||||
getInstance().registrarPocId != null || getInstance().isSuperuser,
|
||||
@@ -217,8 +229,8 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setCreationTimestamp(DateTime creationTimestamp) {
|
||||
getInstance().creationTimestamp = toZonedDateTime(creationTimestamp);
|
||||
public Builder setCreationTimestamp(CreateAutoTimestamp creationTimestamp) {
|
||||
getInstance().creationTimestamp = creationTimestamp;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,10 +14,14 @@
|
||||
|
||||
package google.registry.schema.tld;
|
||||
|
||||
import static com.google.common.base.Charsets.US_ASCII;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.hash.Funnels.stringFunnel;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.hash.BloomFilter;
|
||||
import google.registry.model.CreateAutoTimestamp;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Map;
|
||||
import javax.persistence.CollectionTable;
|
||||
import javax.persistence.Column;
|
||||
@@ -26,10 +30,12 @@ import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Index;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.MapKeyColumn;
|
||||
import javax.persistence.Table;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* A list of premium prices for domain names.
|
||||
@@ -40,47 +46,55 @@ import org.joda.money.CurrencyUnit;
|
||||
* This is fine though, because we only use the list with the highest revisionId.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "PremiumList")
|
||||
@Table(indexes = {@Index(columnList = "name", name = "premiumlist_name_idx")})
|
||||
public class PremiumList {
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "revision_id")
|
||||
@Column(nullable = false)
|
||||
private Long revisionId;
|
||||
|
||||
@Column(name = "creation_timestamp", nullable = false)
|
||||
private ZonedDateTime creationTimestamp;
|
||||
@Column(nullable = false)
|
||||
private CreateAutoTimestamp creationTimestamp = CreateAutoTimestamp.create(null);
|
||||
|
||||
@Column(name = "currency", nullable = false)
|
||||
@Column(nullable = false)
|
||||
private CurrencyUnit currency;
|
||||
|
||||
@ElementCollection
|
||||
@CollectionTable(
|
||||
name = "PremiumEntry",
|
||||
joinColumns = @JoinColumn(name = "revision_id", referencedColumnName = "revision_id"))
|
||||
@MapKeyColumn(name = "domain_label")
|
||||
joinColumns = @JoinColumn(name = "revisionId", referencedColumnName = "revisionId"))
|
||||
@MapKeyColumn(name = "domainLabel")
|
||||
@Column(name = "price", nullable = false)
|
||||
private Map<String, BigDecimal> labelsToPrices;
|
||||
|
||||
private PremiumList(
|
||||
ZonedDateTime creationTimestamp,
|
||||
CurrencyUnit currency,
|
||||
Map<String, BigDecimal> labelsToPrices) {
|
||||
this.creationTimestamp = creationTimestamp;
|
||||
@Column(nullable = false)
|
||||
private BloomFilter<String> bloomFilter;
|
||||
|
||||
private PremiumList(String name, CurrencyUnit currency, Map<String, BigDecimal> labelsToPrices) {
|
||||
this.name = name;
|
||||
this.currency = currency;
|
||||
this.labelsToPrices = labelsToPrices;
|
||||
// ASCII is used for the charset because all premium list domain labels are stored punycoded.
|
||||
this.bloomFilter = BloomFilter.create(stringFunnel(US_ASCII), labelsToPrices.size());
|
||||
labelsToPrices.keySet().forEach(this.bloomFilter::put);
|
||||
}
|
||||
|
||||
// Hibernate requires this default constructor.
|
||||
private PremiumList() {}
|
||||
|
||||
// TODO(mcilwain): Change creationTimestamp to Joda DateTime.
|
||||
/** Constructs a {@link PremiumList} object. */
|
||||
public static PremiumList create(
|
||||
ZonedDateTime creationTimestamp,
|
||||
CurrencyUnit currency,
|
||||
Map<String, BigDecimal> labelsToPrices) {
|
||||
return new PremiumList(creationTimestamp, currency, labelsToPrices);
|
||||
String name, CurrencyUnit currency, Map<String, BigDecimal> labelsToPrices) {
|
||||
return new PremiumList(name, currency, labelsToPrices);
|
||||
}
|
||||
|
||||
/** Returns the name of the premium list, which is usually also a TLD string. */
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/** Returns the ID of this revision, or throws if null. */
|
||||
@@ -91,12 +105,23 @@ public class PremiumList {
|
||||
}
|
||||
|
||||
/** Returns the creation time of this revision of the premium list. */
|
||||
public ZonedDateTime getCreationTimestamp() {
|
||||
return creationTimestamp;
|
||||
public DateTime getCreationTimestamp() {
|
||||
return creationTimestamp.getTimestamp();
|
||||
}
|
||||
|
||||
/** Returns a {@link Map} of domain labels to prices. */
|
||||
public Map<String, BigDecimal> getLabelsToPrices() {
|
||||
return labelsToPrices;
|
||||
public ImmutableMap<String, BigDecimal> getLabelsToPrices() {
|
||||
return ImmutableMap.copyOf(labelsToPrices);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Bloom filter to determine whether a label might be premium, or is definitely not.
|
||||
*
|
||||
* <p>If the domain label might be premium, then the next step is to check for the existence of a
|
||||
* corresponding row in the PremiumListEntry table. Otherwise, we know for sure it's not premium,
|
||||
* and no DB load is required.
|
||||
*/
|
||||
public BloomFilter<String> getBloomFilter() {
|
||||
return bloomFilter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// 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.tld;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
|
||||
|
||||
/** Data access object class for {@link PremiumList}. */
|
||||
public class PremiumListDao {
|
||||
|
||||
/** Persist a new premium list to Cloud SQL. */
|
||||
public static void saveNew(PremiumList premiumList) {
|
||||
jpaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
checkArgument(
|
||||
!checkExists(premiumList.getName()),
|
||||
"A premium list of this name already exists: %s.",
|
||||
premiumList.getName());
|
||||
jpaTm().getEntityManager().persist(premiumList);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the premium list of the given name exists.
|
||||
*
|
||||
* <p>This means that at least one premium list revision must exist for the given name.
|
||||
*/
|
||||
public static boolean checkExists(String premiumListName) {
|
||||
return jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
jpaTm()
|
||||
.getEntityManager()
|
||||
.createQuery("SELECT 1 FROM PremiumList WHERE name = :name", Integer.class)
|
||||
.setParameter("name", premiumListName)
|
||||
.setMaxResults(1)
|
||||
.getResultList()
|
||||
.size()
|
||||
> 0);
|
||||
}
|
||||
|
||||
private PremiumListDao() {}
|
||||
}
|
||||
@@ -15,7 +15,11 @@
|
||||
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 google.registry.model.CreateAutoTimestamp;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -29,6 +33,7 @@ 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.
|
||||
@@ -40,26 +45,29 @@ import javax.persistence.Table;
|
||||
* highest {@link #revisionId}.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "ClaimsList")
|
||||
public class ClaimsList {
|
||||
@Table
|
||||
public class ClaimsList extends ImmutableObject {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "revision_id")
|
||||
@Column
|
||||
private Long revisionId;
|
||||
|
||||
@Column(name = "creation_timestamp", nullable = false)
|
||||
private ZonedDateTime creationTimestamp;
|
||||
@Column(nullable = false)
|
||||
private CreateAutoTimestamp creationTimestamp = CreateAutoTimestamp.create(null);
|
||||
|
||||
@Column(nullable = false)
|
||||
private ZonedDateTime tmdbGenerationTime;
|
||||
|
||||
@ElementCollection
|
||||
@CollectionTable(
|
||||
name = "ClaimsEntry",
|
||||
joinColumns = @JoinColumn(name = "revision_id", referencedColumnName = "revision_id"))
|
||||
@MapKeyColumn(name = "domain_label", nullable = false)
|
||||
@Column(name = "claim_key", nullable = false)
|
||||
joinColumns = @JoinColumn(name = "revisionId", referencedColumnName = "revisionId"))
|
||||
@MapKeyColumn(name = "domainLabel", nullable = false)
|
||||
@Column(name = "claimKey", nullable = false)
|
||||
private Map<String, String> labelsToKeys;
|
||||
|
||||
private ClaimsList(ZonedDateTime creationTimestamp, Map<String, String> labelsToKeys) {
|
||||
this.creationTimestamp = creationTimestamp;
|
||||
private ClaimsList(ZonedDateTime tmdbGenerationTime, Map<String, String> labelsToKeys) {
|
||||
this.tmdbGenerationTime = tmdbGenerationTime;
|
||||
this.labelsToKeys = labelsToKeys;
|
||||
}
|
||||
|
||||
@@ -67,9 +75,8 @@ public class ClaimsList {
|
||||
private ClaimsList() {}
|
||||
|
||||
/** Constructs a {@link ClaimsList} object. */
|
||||
public static ClaimsList create(
|
||||
ZonedDateTime creationTimestamp, Map<String, String> labelsToKeys) {
|
||||
return new ClaimsList(creationTimestamp, labelsToKeys);
|
||||
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. */
|
||||
@@ -79,9 +86,14 @@ public class ClaimsList {
|
||||
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 ZonedDateTime getCreationTimestamp() {
|
||||
return creationTimestamp;
|
||||
public DateTime getCreationTimestamp() {
|
||||
return creationTimestamp.getTimestamp();
|
||||
}
|
||||
|
||||
/** Returns an {@link Map} mapping domain label to its lookup key. */
|
||||
|
||||
@@ -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.model.tmch.ClaimsListShard;
|
||||
import google.registry.schema.tmch.ClaimsList;
|
||||
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 ClaimsListShard} object.
|
||||
* Converts the lines from the DNL CSV file into a {@link ClaimsList} object.
|
||||
*
|
||||
* <p>Please note that this does <b>not</b> insert the object into Datastore.
|
||||
*/
|
||||
public static ClaimsListShard parse(List<String> lines) {
|
||||
public static ClaimsList 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 ClaimsListShard.create(creationTime, builder.build());
|
||||
return ClaimsList.create(creationTime, builder.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,11 @@ import static google.registry.request.Action.Method.POST;
|
||||
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.keyring.api.KeyModule.Key;
|
||||
import google.registry.model.tmch.ClaimsListDao;
|
||||
import google.registry.model.tmch.ClaimsListShard;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.schema.tmch.ClaimsList;
|
||||
import java.io.IOException;
|
||||
import java.security.SignatureException;
|
||||
import java.util.List;
|
||||
@@ -54,10 +56,14 @@ public final class TmchDnlAction implements Runnable {
|
||||
} catch (SignatureException | IOException | PGPException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
ClaimsListShard claims = ClaimsListParser.parse(lines);
|
||||
claims.save();
|
||||
ClaimsList claims = ClaimsListParser.parse(lines);
|
||||
ClaimsListShard claimsListShard =
|
||||
ClaimsListShard.create(claims.getTmdbGenerationTime(), claims.getLabelsToKeys());
|
||||
claimsListShard.save();
|
||||
logger.atInfo().log(
|
||||
"Inserted %,d claims into Datastore, created at %s",
|
||||
claims.size(), claims.getCreationTime());
|
||||
claimsListShard.size(), claimsListShard.getCreationTime());
|
||||
|
||||
ClaimsListDao.trySave(claims);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// 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.tools;
|
||||
|
||||
/**
|
||||
* Marker interface for commands that use Cloud Sql.
|
||||
*
|
||||
* <p>Just implementing this is sufficient to use Cloud Sql; {@link RegistryTool} will install it as
|
||||
* needed.
|
||||
*/
|
||||
interface CommandWithCloudSql extends CommandWithRemoteApi {}
|
||||
@@ -16,6 +16,7 @@ package google.registry.tools;
|
||||
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static google.registry.security.JsonHttp.JSON_SAFETY_PREFIX;
|
||||
import static google.registry.tools.server.CreateOrUpdatePremiumListAction.ALSO_CLOUD_SQL_PARAM;
|
||||
import static google.registry.tools.server.CreateOrUpdatePremiumListAction.INPUT_PARAM;
|
||||
import static google.registry.tools.server.CreateOrUpdatePremiumListAction.NAME_PARAM;
|
||||
import static google.registry.util.ListNamingUtils.convertFilePathToName;
|
||||
@@ -57,6 +58,12 @@ abstract class CreateOrUpdatePremiumListCommand extends ConfirmingCommand
|
||||
required = true)
|
||||
Path inputFile;
|
||||
|
||||
@Parameter(
|
||||
names = {"--also_cloud_sql"},
|
||||
description =
|
||||
"Persist premium list to Cloud SQL in addition to Datastore; defaults to false.")
|
||||
boolean alsoCloudSql;
|
||||
|
||||
protected AppEngineConnection connection;
|
||||
protected int inputLineCount;
|
||||
|
||||
@@ -67,7 +74,7 @@ abstract class CreateOrUpdatePremiumListCommand extends ConfirmingCommand
|
||||
|
||||
abstract String getCommandPath();
|
||||
|
||||
ImmutableMap<String, ?> getParameterMap() {
|
||||
ImmutableMap<String, String> getParameterMap() {
|
||||
return ImmutableMap.of();
|
||||
}
|
||||
|
||||
@@ -88,14 +95,15 @@ abstract class CreateOrUpdatePremiumListCommand extends ConfirmingCommand
|
||||
|
||||
@Override
|
||||
public String execute() throws Exception {
|
||||
ImmutableMap.Builder<String, Object> params = new ImmutableMap.Builder<>();
|
||||
ImmutableMap.Builder<String, String> params = new ImmutableMap.Builder<>();
|
||||
params.put(NAME_PARAM, name);
|
||||
params.put(ALSO_CLOUD_SQL_PARAM, Boolean.toString(alsoCloudSql));
|
||||
String inputFileContents = new String(Files.readAllBytes(inputFile), UTF_8);
|
||||
String requestBody =
|
||||
Joiner.on('&').withKeyValueSeparator("=").join(
|
||||
ImmutableMap.of(INPUT_PARAM, URLEncoder.encode(inputFileContents, UTF_8.toString())));
|
||||
|
||||
ImmutableMap<String, ?> extraParams = getParameterMap();
|
||||
ImmutableMap<String, String> extraParams = getParameterMap();
|
||||
if (extraParams != null) {
|
||||
params.putAll(extraParams);
|
||||
}
|
||||
@@ -110,7 +118,7 @@ abstract class CreateOrUpdatePremiumListCommand extends ConfirmingCommand
|
||||
|
||||
// TODO(user): refactor this behavior into a better general-purpose
|
||||
// response validation that can be re-used across the new client/server commands.
|
||||
String extractServerResponse(String response) {
|
||||
private String extractServerResponse(String response) {
|
||||
Map<String, Object> responseMap = toMap(JSONValue.parse(stripJsonPrefix(response)));
|
||||
|
||||
// TODO(user): consider using jart's FormField Framework.
|
||||
@@ -127,7 +135,7 @@ abstract class CreateOrUpdatePremiumListCommand extends ConfirmingCommand
|
||||
}
|
||||
|
||||
// TODO(user): figure out better place to put this method to make it re-usable
|
||||
static String stripJsonPrefix(String json) {
|
||||
private static String stripJsonPrefix(String json) {
|
||||
Verify.verify(json.startsWith(JSON_SAFETY_PREFIX));
|
||||
return json.substring(JSON_SAFETY_PREFIX.length());
|
||||
}
|
||||
|
||||
@@ -36,12 +36,11 @@ public class CreatePremiumListCommand extends CreateOrUpdatePremiumListCommand {
|
||||
}
|
||||
|
||||
@Override
|
||||
ImmutableMap<String, ?> getParameterMap() {
|
||||
ImmutableMap<String, String> getParameterMap() {
|
||||
if (override) {
|
||||
return ImmutableMap.of("override", override);
|
||||
return ImmutableMap.of("override", "true");
|
||||
} else {
|
||||
return ImmutableMap.of();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,32 +19,23 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.domain.DesignatedContact;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
import google.registry.model.domain.secdns.DelegationSignerData;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.transfer.BaseTransferObject;
|
||||
import google.registry.model.transfer.TransferData;
|
||||
import google.registry.persistence.NomulusNamingStrategy;
|
||||
import google.registry.schema.domain.RegistryLock;
|
||||
import google.registry.schema.tld.PremiumList;
|
||||
import google.registry.schema.tmch.ClaimsList;
|
||||
import google.registry.persistence.NomulusPostgreSQLDialect;
|
||||
import google.registry.persistence.PersistenceModule;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.sql.Types;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.hibernate.boot.MetadataSources;
|
||||
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
|
||||
import org.hibernate.cfg.Environment;
|
||||
import org.hibernate.dialect.PostgreSQL95Dialect;
|
||||
import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
|
||||
import org.hibernate.jpa.boot.internal.PersistenceXmlParser;
|
||||
import org.hibernate.tool.hbm2ddl.SchemaExport;
|
||||
import org.hibernate.tool.schema.TargetType;
|
||||
import org.joda.time.Period;
|
||||
import org.testcontainers.containers.PostgreSQLContainer;
|
||||
|
||||
/**
|
||||
@@ -57,22 +48,6 @@ import org.testcontainers.containers.PostgreSQLContainer;
|
||||
@Parameters(separators = " =", commandDescription = "Generate PostgreSQL schema.")
|
||||
public class GenerateSqlSchemaCommand implements Command {
|
||||
|
||||
// TODO(mmuller): These should be read from persistence.xml so we don't need to maintain two
|
||||
// separate lists of all SQL table classes.
|
||||
private static final ImmutableSet<Class> SQL_TABLE_CLASSES =
|
||||
ImmutableSet.of(
|
||||
BaseTransferObject.class,
|
||||
ClaimsList.class,
|
||||
DelegationSignerData.class,
|
||||
DesignatedContact.class,
|
||||
DomainBase.class,
|
||||
GracePeriod.class,
|
||||
Period.class,
|
||||
PremiumList.class,
|
||||
RegistryLock.class,
|
||||
TransferData.class,
|
||||
Trid.class);
|
||||
|
||||
@VisibleForTesting
|
||||
public static final String DB_OPTIONS_CLASH =
|
||||
"Database host and port may not be specified along with the option to start a "
|
||||
@@ -159,7 +134,9 @@ public class GenerateSqlSchemaCommand implements Command {
|
||||
|
||||
MetadataSources metadata =
|
||||
new MetadataSources(new StandardServiceRegistryBuilder().applySettings(settings).build());
|
||||
SQL_TABLE_CLASSES.forEach(metadata::addAnnotatedClass);
|
||||
|
||||
addAnnotatedClasses(metadata, settings);
|
||||
|
||||
SchemaExport schemaExport = new SchemaExport();
|
||||
schemaExport.setHaltOnError(true);
|
||||
schemaExport.setFormat(true);
|
||||
@@ -199,12 +176,29 @@ public class GenerateSqlSchemaCommand implements Command {
|
||||
}
|
||||
}
|
||||
|
||||
/** Nomulus mapping rules for column types in Postgresql. */
|
||||
public static class NomulusPostgreSQLDialect extends PostgreSQL95Dialect {
|
||||
public NomulusPostgreSQLDialect() {
|
||||
super();
|
||||
registerColumnType(Types.VARCHAR, "text");
|
||||
registerColumnType(Types.TIMESTAMP, "timestamptz");
|
||||
private void addAnnotatedClasses(MetadataSources metadata, Map<String, String> settings) {
|
||||
ParsedPersistenceXmlDescriptor descriptor =
|
||||
PersistenceXmlParser.locatePersistenceUnits(settings).stream()
|
||||
.filter(unit -> PersistenceModule.PERSISTENCE_UNIT_NAME.equals(unit.getName()))
|
||||
.findFirst()
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new IllegalArgumentException(
|
||||
String.format(
|
||||
"Could not find persistence unit with name %s",
|
||||
PersistenceModule.PERSISTENCE_UNIT_NAME)));
|
||||
|
||||
List<String> classNames = descriptor.getManagedClassNames();
|
||||
for (String className : classNames) {
|
||||
try {
|
||||
Class<?> clazz = Class.forName(className);
|
||||
metadata.addAnnotatedClass(clazz);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format(
|
||||
"Could not load class with name %s present in persistence.xml", className),
|
||||
e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import google.registry.config.RegistryConfig;
|
||||
import google.registry.model.ofy.ObjectifyService;
|
||||
import google.registry.model.transaction.TransactionManagerFactory;
|
||||
import google.registry.tools.AuthModule.LoginRequiredException;
|
||||
import google.registry.tools.params.ParameterFactory;
|
||||
import java.io.ByteArrayInputStream;
|
||||
@@ -210,6 +211,8 @@ final class RegistryCli implements AutoCloseable, CommandRunner {
|
||||
}
|
||||
|
||||
// CommandWithRemoteApis need to have the remote api installed to work.
|
||||
// CommandWithCloudSql extends CommandWithRemoteApi so the command will also get the remote
|
||||
// api installed. This is because the DB password is stored in Datastore.
|
||||
if (command instanceof CommandWithRemoteApi) {
|
||||
if (installer == null) {
|
||||
installer = new RemoteApiInstaller();
|
||||
@@ -233,6 +236,10 @@ final class RegistryCli implements AutoCloseable, CommandRunner {
|
||||
ofy().clearSessionCache();
|
||||
}
|
||||
|
||||
if (command instanceof CommandWithCloudSql) {
|
||||
TransactionManagerFactory.initForTool();
|
||||
}
|
||||
|
||||
command.run();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,9 @@ import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.io.Files;
|
||||
import google.registry.model.tmch.ClaimsListDao;
|
||||
import google.registry.model.tmch.ClaimsListShard;
|
||||
import google.registry.schema.tmch.ClaimsList;
|
||||
import google.registry.tmch.ClaimsListParser;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -35,9 +37,14 @@ final class UploadClaimsListCommand extends ConfirmingCommand implements Command
|
||||
@Parameter(description = "Claims list filename")
|
||||
private List<String> mainParameters = new ArrayList<>();
|
||||
|
||||
@Parameter(
|
||||
names = {"--also_cloud_sql"},
|
||||
description = "Persist claims list to Cloud SQL in addition to Datastore; defaults to false.")
|
||||
boolean alsoCloudSql;
|
||||
|
||||
private String claimsListFilename;
|
||||
|
||||
private ClaimsListShard claimsList;
|
||||
private ClaimsList claimsList;
|
||||
|
||||
@Override
|
||||
protected void init() throws IOException {
|
||||
@@ -56,7 +63,10 @@ final class UploadClaimsListCommand extends ConfirmingCommand implements Command
|
||||
|
||||
@Override
|
||||
public String execute() {
|
||||
claimsList.save();
|
||||
ClaimsListShard.create(claimsList.getTmdbGenerationTime(), claimsList.getLabelsToKeys()).save();
|
||||
if (alsoCloudSql) {
|
||||
ClaimsListDao.trySave(claimsList);
|
||||
}
|
||||
return String.format("Successfully uploaded claims list %s", claimsListFilename);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,17 +14,28 @@
|
||||
|
||||
package google.registry.tools.server;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.common.flogger.LazyArgs.lazy;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.model.registry.label.PremiumList;
|
||||
import google.registry.model.registry.label.PremiumList.PremiumListEntry;
|
||||
import google.registry.request.JsonResponse;
|
||||
import google.registry.request.Parameter;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.inject.Inject;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
|
||||
/**
|
||||
* Abstract base class for actions that update premium lists.
|
||||
*/
|
||||
/** Abstract base class for actions that update premium lists. */
|
||||
public abstract class CreateOrUpdatePremiumListAction implements Runnable {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
@@ -33,24 +44,70 @@ public abstract class CreateOrUpdatePremiumListAction implements Runnable {
|
||||
|
||||
public static final String NAME_PARAM = "name";
|
||||
public static final String INPUT_PARAM = "inputData";
|
||||
public static final String ALSO_CLOUD_SQL_PARAM = "alsoCloudSql";
|
||||
|
||||
@Inject JsonResponse response;
|
||||
@Inject @Parameter("premiumListName") String name;
|
||||
@Inject @Parameter(INPUT_PARAM) String inputData;
|
||||
|
||||
@Inject
|
||||
@Parameter("premiumListName")
|
||||
String name;
|
||||
|
||||
@Inject
|
||||
@Parameter(INPUT_PARAM)
|
||||
String inputData;
|
||||
|
||||
@Inject
|
||||
@Parameter(ALSO_CLOUD_SQL_PARAM)
|
||||
boolean alsoCloudSql;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
savePremiumList();
|
||||
saveToDatastore();
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.atInfo().withCause(e).log(
|
||||
"Usage error in attempting to save premium list from nomulus tool command");
|
||||
response.setPayload(ImmutableMap.of("error", e.toString(), "status", "error"));
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
logger.atSevere().withCause(e).log(
|
||||
"Unexpected error saving premium list from nomulus tool command");
|
||||
"Unexpected error saving premium list to Datastore from nomulus tool command");
|
||||
response.setPayload(ImmutableMap.of("error", e.toString(), "status", "error"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (alsoCloudSql) {
|
||||
try {
|
||||
saveToCloudSql();
|
||||
} catch (Throwable e) {
|
||||
logger.atSevere().withCause(e).log(
|
||||
"Unexpected error saving premium list to Cloud SQL from nomulus tool command");
|
||||
response.setPayload(ImmutableMap.of("error", e.toString(), "status", "error"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
google.registry.schema.tld.PremiumList parseInputToPremiumList() {
|
||||
List<String> inputDataPreProcessed =
|
||||
Splitter.on('\n').omitEmptyStrings().splitToList(inputData);
|
||||
|
||||
ImmutableMap<String, PremiumListEntry> prices =
|
||||
new PremiumList.Builder().setName(name).build().parse(inputDataPreProcessed);
|
||||
ImmutableSet<CurrencyUnit> currencies =
|
||||
prices.values().stream()
|
||||
.map(e -> e.getValue().getCurrencyUnit())
|
||||
.distinct()
|
||||
.collect(toImmutableSet());
|
||||
checkArgument(
|
||||
currencies.size() == 1,
|
||||
"The Cloud SQL schema requires exactly one currency, but got: %s",
|
||||
ImmutableSortedSet.copyOf(currencies));
|
||||
CurrencyUnit currency = Iterables.getOnlyElement(currencies);
|
||||
|
||||
Map<String, BigDecimal> priceAmounts =
|
||||
Maps.transformValues(prices, ple -> ple.getValue().getAmount());
|
||||
return google.registry.schema.tld.PremiumList.create(name, currency, priceAmounts);
|
||||
}
|
||||
|
||||
/** Logs the premium list data at INFO, truncated if too long. */
|
||||
@@ -64,6 +121,9 @@ public abstract class CreateOrUpdatePremiumListAction implements Runnable {
|
||||
: (inputData.substring(0, MAX_LOGGING_PREMIUM_LIST_LENGTH) + "<truncated>")));
|
||||
}
|
||||
|
||||
/** Creates a new premium list or updates an existing one. */
|
||||
protected abstract void savePremiumList();
|
||||
/** Saves the premium list to Datastore. */
|
||||
protected abstract void saveToDatastore();
|
||||
|
||||
/** Saves the premium list to Cloud SQL. */
|
||||
protected abstract void saveToCloudSql();
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import google.registry.model.registry.label.PremiumList;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Parameter;
|
||||
import google.registry.request.auth.Auth;
|
||||
import google.registry.schema.tld.PremiumListDao;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -50,7 +51,7 @@ public class CreatePremiumListAction extends CreateOrUpdatePremiumListAction {
|
||||
@Inject CreatePremiumListAction() {}
|
||||
|
||||
@Override
|
||||
protected void savePremiumList() {
|
||||
protected void saveToDatastore() {
|
||||
checkArgument(
|
||||
!doesPremiumListExist(name), "A premium list of this name already exists: %s.", name);
|
||||
if (!override) {
|
||||
@@ -71,4 +72,22 @@ public class CreatePremiumListAction extends CreateOrUpdatePremiumListAction {
|
||||
logger.atInfo().log(message);
|
||||
response.setPayload(ImmutableMap.of("status", "success", "message", message));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveToCloudSql() {
|
||||
if (!override) {
|
||||
assertTldExists(name);
|
||||
}
|
||||
logger.atInfo().log("Saving premium list to Cloud SQL for TLD %s", name);
|
||||
// TODO(mcilwain): Call logInputData() here once Datastore persistence is removed.
|
||||
|
||||
google.registry.schema.tld.PremiumList premiumList = parseInputToPremiumList();
|
||||
PremiumListDao.saveNew(premiumList);
|
||||
|
||||
String message =
|
||||
String.format(
|
||||
"Saved premium list %s with %d entries", name, premiumList.getLabelsToPrices().size());
|
||||
logger.atInfo().log(message);
|
||||
// TODO(mcilwain): Call response.setPayload(...) here once Datastore persistence is removed.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,12 @@ public class ToolsServerModule {
|
||||
return extractRequiredParameter(req, CreatePremiumListAction.INPUT_PARAM);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("alsoCloudSql")
|
||||
static boolean provideAlsoCloudSql(HttpServletRequest req) {
|
||||
return extractBooleanParameter(req, CreatePremiumListAction.ALSO_CLOUD_SQL_PARAM);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter("premiumListName")
|
||||
static String provideName(HttpServletRequest req) {
|
||||
|
||||
@@ -46,7 +46,7 @@ public class UpdatePremiumListAction extends CreateOrUpdatePremiumListAction {
|
||||
@Inject UpdatePremiumListAction() {}
|
||||
|
||||
@Override
|
||||
protected void savePremiumList() {
|
||||
protected void saveToDatastore() {
|
||||
Optional<PremiumList> existingPremiumList = PremiumList.getUncached(name);
|
||||
checkArgument(
|
||||
existingPremiumList.isPresent(),
|
||||
@@ -67,4 +67,11 @@ public class UpdatePremiumListAction extends CreateOrUpdatePremiumListAction {
|
||||
logger.atInfo().log(message);
|
||||
response.setPayload(ImmutableMap.of("status", "success", "message", message));
|
||||
}
|
||||
|
||||
// TODO(mcilwain): Implement this in a subsequent PR.
|
||||
@Override
|
||||
protected void saveToCloudSql() {
|
||||
throw new UnsupportedOperationException(
|
||||
"Updating of premium lists in Cloud SQL is not supported yet");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,12 @@
|
||||
<class>google.registry.model.transfer.TransferData</class>
|
||||
<class>google.registry.model.eppcommon.Trid</class>
|
||||
|
||||
<!-- Customized type converters -->
|
||||
<class>google.registry.persistence.BloomFilterConverter</class>
|
||||
<class>google.registry.persistence.CreateAutoTimestampConverter</class>
|
||||
<class>google.registry.persistence.UpdateAutoTimestampConverter</class>
|
||||
<class>google.registry.persistence.ZonedDateTimeConverter</class>
|
||||
|
||||
<!-- TODO(weiminyu): check out application-layer validation. -->
|
||||
<validation-mode>NONE</validation-mode>
|
||||
</persistence-unit>
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
// 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.model.registry;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.testing.JUnitBackports.assertThrows;
|
||||
|
||||
import google.registry.model.transaction.JpaTransactionManagerRule;
|
||||
import google.registry.schema.domain.RegistryLock;
|
||||
import google.registry.schema.domain.RegistryLock.Action;
|
||||
import google.registry.testing.AppEngineRule;
|
||||
import java.util.UUID;
|
||||
import javax.persistence.PersistenceException;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
/** Unit tests for {@link RegistryLockDao}. */
|
||||
@RunWith(JUnit4.class)
|
||||
public final class RegistryLockDaoTest {
|
||||
|
||||
@Rule public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build();
|
||||
|
||||
@Rule
|
||||
public final JpaTransactionManagerRule jpaTmRule =
|
||||
new JpaTransactionManagerRule.Builder().build();
|
||||
|
||||
@Test
|
||||
public void testSaveAndLoad_success() {
|
||||
RegistryLock lock = createLock();
|
||||
RegistryLockDao.save(lock);
|
||||
RegistryLock fromDatabase = RegistryLockDao.getByVerificationCode(lock.getVerificationCode());
|
||||
assertThat(fromDatabase.getDomainName()).isEqualTo(lock.getDomainName());
|
||||
assertThat(fromDatabase.getVerificationCode()).isEqualTo(lock.getVerificationCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSaveAndLoad_failure_differentCode() {
|
||||
RegistryLock lock = createLock();
|
||||
RegistryLockDao.save(lock);
|
||||
PersistenceException exception =
|
||||
assertThrows(
|
||||
PersistenceException.class,
|
||||
() -> RegistryLockDao.getByVerificationCode(UUID.randomUUID().toString()));
|
||||
assertThat(exception)
|
||||
.hasCauseThat()
|
||||
.hasMessageThat()
|
||||
.isEqualTo("No registry lock with this code");
|
||||
assertThat(exception).hasCauseThat().isInstanceOf(NullPointerException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSaveTwiceAndLoad_returnsLatest() {
|
||||
RegistryLock lock = createLock();
|
||||
jpaTm().transact(() -> RegistryLockDao.save(lock));
|
||||
jpaTmRule.getTxnClock().advanceOneMilli();
|
||||
jpaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
RegistryLock secondLock =
|
||||
RegistryLockDao.getByVerificationCode(lock.getVerificationCode());
|
||||
secondLock.setCompletionTimestamp(jpaTmRule.getTxnClock().nowUtc());
|
||||
RegistryLockDao.save(secondLock);
|
||||
});
|
||||
jpaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
RegistryLock fromDatabase =
|
||||
RegistryLockDao.getByVerificationCode(lock.getVerificationCode());
|
||||
assertThat(fromDatabase.getCompletionTimestamp().get())
|
||||
.isEqualTo(jpaTmRule.getTxnClock().nowUtc());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailure_saveNull() {
|
||||
assertThrows(NullPointerException.class, () -> RegistryLockDao.save(null));
|
||||
}
|
||||
|
||||
private RegistryLock createLock() {
|
||||
return new RegistryLock.Builder()
|
||||
.setRepoId("repoId")
|
||||
.setDomainName("example.test")
|
||||
.setRegistrarId("TheRegistrar")
|
||||
.setAction(Action.LOCK)
|
||||
.setVerificationCode(UUID.randomUUID().toString())
|
||||
.isSuperuser(true)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// 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.model.tmch;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.testing.JUnitBackports.assertThrows;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.model.transaction.JpaTransactionManagerRule;
|
||||
import google.registry.schema.tmch.ClaimsList;
|
||||
import google.registry.testing.FakeClock;
|
||||
import javax.persistence.NoResultException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
/** Unit tests for {@link ClaimsListDao}. */
|
||||
@RunWith(JUnit4.class)
|
||||
public class ClaimsListDaoTest {
|
||||
|
||||
private FakeClock fakeClock = new FakeClock();
|
||||
|
||||
@Rule
|
||||
public final JpaTransactionManagerRule jpaTmRule =
|
||||
new JpaTransactionManagerRule.Builder().build();
|
||||
|
||||
@Test
|
||||
public void trySave_insertsClaimsListSuccessfully() {
|
||||
ClaimsList claimsList =
|
||||
ClaimsList.create(fakeClock.nowUtc(), ImmutableMap.of("label1", "key1", "label2", "key2"));
|
||||
ClaimsListDao.trySave(claimsList);
|
||||
ClaimsList insertedClaimsList = ClaimsListDao.getCurrent();
|
||||
assertClaimsListEquals(claimsList, insertedClaimsList);
|
||||
assertThat(insertedClaimsList.getCreationTimestamp())
|
||||
.isEqualTo(jpaTmRule.getTxnClock().nowUtc());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trySave_noExceptionThrownWhenSaveFail() {
|
||||
ClaimsList claimsList =
|
||||
ClaimsList.create(fakeClock.nowUtc(), ImmutableMap.of("label1", "key1", "label2", "key2"));
|
||||
ClaimsListDao.trySave(claimsList);
|
||||
ClaimsList insertedClaimsList = ClaimsListDao.getCurrent();
|
||||
assertClaimsListEquals(claimsList, insertedClaimsList);
|
||||
// Save ClaimsList with existing revisionId should fail because revisionId is the primary key.
|
||||
ClaimsListDao.trySave(insertedClaimsList);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trySave_claimsListWithNoEntries() {
|
||||
ClaimsList claimsList = ClaimsList.create(fakeClock.nowUtc(), ImmutableMap.of());
|
||||
ClaimsListDao.trySave(claimsList);
|
||||
ClaimsList insertedClaimsList = ClaimsListDao.getCurrent();
|
||||
assertClaimsListEquals(claimsList, insertedClaimsList);
|
||||
assertThat(insertedClaimsList.getLabelsToKeys()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCurrent_throwsNoResultExceptionIfTableIsEmpty() {
|
||||
PersistenceException thrown =
|
||||
assertThrows(PersistenceException.class, () -> ClaimsListDao.getCurrent());
|
||||
assertThat(thrown).hasCauseThat().isInstanceOf(NoResultException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCurrent_returnsLatestClaims() {
|
||||
ClaimsList oldClaimsList =
|
||||
ClaimsList.create(fakeClock.nowUtc(), ImmutableMap.of("label1", "key1", "label2", "key2"));
|
||||
ClaimsList newClaimsList =
|
||||
ClaimsList.create(fakeClock.nowUtc(), ImmutableMap.of("label3", "key3", "label4", "key4"));
|
||||
ClaimsListDao.trySave(oldClaimsList);
|
||||
ClaimsListDao.trySave(newClaimsList);
|
||||
assertClaimsListEquals(newClaimsList, ClaimsListDao.getCurrent());
|
||||
}
|
||||
|
||||
private void assertClaimsListEquals(ClaimsList left, ClaimsList right) {
|
||||
assertThat(left.getRevisionId()).isEqualTo(right.getRevisionId());
|
||||
assertThat(left.getTmdbGenerationTime()).isEqualTo(right.getTmdbGenerationTime());
|
||||
assertThat(left.getLabelsToKeys()).isEqualTo(right.getLabelsToKeys());
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,21 @@ package google.registry.model.transaction;
|
||||
|
||||
import static org.joda.time.DateTimeZone.UTC;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Maps;
|
||||
import google.registry.persistence.PersistenceModule;
|
||||
import google.registry.testing.FakeClock;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import org.hibernate.cfg.Environment;
|
||||
import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
|
||||
import org.hibernate.jpa.boot.internal.PersistenceXmlParser;
|
||||
import org.hibernate.jpa.boot.spi.Bootstrap;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.rules.ExternalResource;
|
||||
import org.junit.rules.RuleChain;
|
||||
@@ -41,12 +53,20 @@ public class JpaTransactionManagerRule extends ExternalResource {
|
||||
private final DateTime now = DateTime.now(UTC);
|
||||
private final FakeClock clock = new FakeClock(now);
|
||||
private final String initScript;
|
||||
private final ImmutableList<Class> extraEntityClasses;
|
||||
private final ImmutableMap userProperties;
|
||||
|
||||
private JdbcDatabaseContainer database;
|
||||
private EntityManagerFactory emf;
|
||||
private JpaTransactionManager cachedTm;
|
||||
|
||||
private JpaTransactionManagerRule(String initScript) {
|
||||
private JpaTransactionManagerRule(
|
||||
String initScript,
|
||||
ImmutableList<Class> extraEntityClasses,
|
||||
ImmutableMap<String, String> userProperties) {
|
||||
this.initScript = initScript;
|
||||
this.extraEntityClasses = extraEntityClasses;
|
||||
this.userProperties = userProperties;
|
||||
}
|
||||
|
||||
/** Wraps {@link JpaTransactionManagerRule} in a {@link PostgreSQLContainer}. */
|
||||
@@ -60,12 +80,21 @@ public class JpaTransactionManagerRule extends ExternalResource {
|
||||
|
||||
@Override
|
||||
public void before() {
|
||||
ImmutableMap properties = PersistenceModule.providesDefaultDatabaseConfigs();
|
||||
if (!userProperties.isEmpty()) {
|
||||
// If there are user properties, create a new properties object with these added.
|
||||
ImmutableMap.Builder builder = properties.builder();
|
||||
builder.putAll(userProperties);
|
||||
properties = builder.build();
|
||||
}
|
||||
|
||||
emf =
|
||||
PersistenceModule.create(
|
||||
createEntityManagerFactory(
|
||||
database.getJdbcUrl(),
|
||||
database.getUsername(),
|
||||
database.getPassword(),
|
||||
PersistenceModule.providesDefaultDatabaseConfigs());
|
||||
properties,
|
||||
extraEntityClasses);
|
||||
JpaTransactionManagerImpl txnManager = new JpaTransactionManagerImpl(emf, clock);
|
||||
cachedTm = TransactionManagerFactory.jpaTm;
|
||||
TransactionManagerFactory.jpaTm = txnManager;
|
||||
@@ -80,6 +109,33 @@ public class JpaTransactionManagerRule extends ExternalResource {
|
||||
cachedTm = null;
|
||||
}
|
||||
|
||||
/** Constructs the {@link EntityManagerFactory} instance. */
|
||||
private static EntityManagerFactory createEntityManagerFactory(
|
||||
String jdbcUrl,
|
||||
String username,
|
||||
String password,
|
||||
ImmutableMap<String, String> configs,
|
||||
ImmutableList<Class> extraEntityClasses) {
|
||||
HashMap<String, String> properties = Maps.newHashMap(configs);
|
||||
properties.put(Environment.URL, jdbcUrl);
|
||||
properties.put(Environment.USER, username);
|
||||
properties.put(Environment.PASS, password);
|
||||
|
||||
ParsedPersistenceXmlDescriptor descriptor =
|
||||
PersistenceXmlParser.locatePersistenceUnits(properties).stream()
|
||||
.filter(unit -> PersistenceModule.PERSISTENCE_UNIT_NAME.equals(unit.getName()))
|
||||
.findFirst()
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new IllegalArgumentException(
|
||||
String.format(
|
||||
"Could not find persistence unit with name %s",
|
||||
PersistenceModule.PERSISTENCE_UNIT_NAME)));
|
||||
|
||||
extraEntityClasses.stream().map(Class::getName).forEach(descriptor::addClasses);
|
||||
return Bootstrap.getEntityManagerFactoryBuilder(descriptor, properties).build();
|
||||
}
|
||||
|
||||
/** Returns the {@link FakeClock} used by the underlying {@link JpaTransactionManagerImpl}. */
|
||||
public FakeClock getTxnClock() {
|
||||
return clock;
|
||||
@@ -88,6 +144,8 @@ public class JpaTransactionManagerRule extends ExternalResource {
|
||||
/** Builder for {@link JpaTransactionManagerRule}. */
|
||||
public static class Builder {
|
||||
private String initScript;
|
||||
private List<Class> extraEntityClasses = new ArrayList<Class>();
|
||||
private Map<String, String> userProperties = new HashMap<String, String>();
|
||||
|
||||
/**
|
||||
* Sets the SQL script to be used to initialize the database. If not set,
|
||||
@@ -98,12 +156,27 @@ public class JpaTransactionManagerRule extends ExternalResource {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Adds annotated class(es) to the known entities for the database. */
|
||||
public Builder withEntityClass(Class... classes) {
|
||||
this.extraEntityClasses.addAll(ImmutableSet.copyOf(classes));
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Adds the specified property to those used to initialize the transaction manager. */
|
||||
public Builder withProperty(String name, String value) {
|
||||
this.userProperties.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Builds a {@link JpaTransactionManagerRule} instance. */
|
||||
public JpaTransactionManagerRule build() {
|
||||
if (initScript == null) {
|
||||
initScript = SCHEMA_GOLDEN_SQL;
|
||||
}
|
||||
return new JpaTransactionManagerRule(initScript);
|
||||
return new JpaTransactionManagerRule(
|
||||
initScript,
|
||||
ImmutableList.copyOf(extraEntityClasses),
|
||||
ImmutableMap.copyOf(userProperties));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,12 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.testing.JUnitBackports.assertThrows;
|
||||
|
||||
import google.registry.model.ImmutableObject;
|
||||
import java.util.List;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.PersistenceException;
|
||||
import org.hibernate.cfg.Environment;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -31,7 +35,10 @@ public class JpaTransactionManagerRuleTest {
|
||||
|
||||
@Rule
|
||||
public final JpaTransactionManagerRule jpaTmRule =
|
||||
new JpaTransactionManagerRule.Builder().build();
|
||||
new JpaTransactionManagerRule.Builder()
|
||||
.withEntityClass(TestEntity.class)
|
||||
.withProperty(Environment.HBM2DDL_AUTO, "update")
|
||||
.build();
|
||||
|
||||
@Test
|
||||
public void verifiesRuleWorks() {
|
||||
@@ -56,4 +63,28 @@ public class JpaTransactionManagerRuleTest {
|
||||
assertThat(results).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtraParameters() {
|
||||
// This test verifies that 1) withEntityClass() has registered TestEntity and 2) The table
|
||||
// has been created, implying withProperty(HBM2DDL_AUTO, "update") worked.
|
||||
TestEntity original = new TestEntity("key", "value");
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().persist(original));
|
||||
TestEntity retrieved =
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "key"));
|
||||
assertThat(retrieved).isEqualTo(original);
|
||||
}
|
||||
|
||||
@Entity(name = "TestEntity") // Specify name to avoid nested class naming issues.
|
||||
static class TestEntity extends ImmutableObject {
|
||||
@Id String key;
|
||||
String value;
|
||||
|
||||
TestEntity(String key, String value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
TestEntity() {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// 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.persistence;
|
||||
|
||||
import static com.google.common.base.Charsets.US_ASCII;
|
||||
import static com.google.common.hash.Funnels.stringFunnel;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.hash.BloomFilter;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.transaction.JpaTransactionManagerRule;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import org.hibernate.cfg.Environment;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class BloomFilterConverterTest {
|
||||
|
||||
@Rule
|
||||
public final JpaTransactionManagerRule jpaTmRule =
|
||||
new JpaTransactionManagerRule.Builder()
|
||||
.withEntityClass(TestEntity.class)
|
||||
.withProperty(Environment.HBM2DDL_AUTO, "update")
|
||||
.build();
|
||||
|
||||
@Test
|
||||
public void roundTripConversion_returnsSameBloomFilter() {
|
||||
BloomFilter<String> bloomFilter = BloomFilter.create(stringFunnel(US_ASCII), 3);
|
||||
ImmutableSet.of("foo", "bar", "baz").forEach(bloomFilter::put);
|
||||
TestEntity entity = new TestEntity(bloomFilter);
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().persist(entity));
|
||||
TestEntity persisted =
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "id"));
|
||||
assertThat(persisted.bloomFilter).isEqualTo(bloomFilter);
|
||||
}
|
||||
|
||||
@Entity(name = "TestEntity") // Override entity name to avoid the nested class reference.
|
||||
public static class TestEntity extends ImmutableObject {
|
||||
|
||||
@Id String name = "id";
|
||||
|
||||
BloomFilter<String> bloomFilter;
|
||||
|
||||
public TestEntity() {}
|
||||
|
||||
public TestEntity(BloomFilter<String> bloomFilter) {
|
||||
this.bloomFilter = bloomFilter;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// 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.persistence;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
|
||||
|
||||
import google.registry.model.CreateAutoTimestamp;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.transaction.JpaTransactionManagerRule;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import org.hibernate.cfg.Environment;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class CreateAutoTimestampConverterTest {
|
||||
|
||||
@Rule
|
||||
public final JpaTransactionManagerRule jpaTmRule =
|
||||
new JpaTransactionManagerRule.Builder()
|
||||
.withEntityClass(TestEntity.class)
|
||||
.withProperty(Environment.HBM2DDL_AUTO, "update")
|
||||
.build();
|
||||
|
||||
@Test
|
||||
public void testTypeConversion() {
|
||||
CreateAutoTimestamp ts = CreateAutoTimestamp.create(DateTime.parse("2019-09-9T11:39:00Z"));
|
||||
TestEntity ent = new TestEntity("myinst", ts);
|
||||
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().persist(ent));
|
||||
TestEntity result =
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "myinst"));
|
||||
assertThat(result).isEqualTo(new TestEntity("myinst", ts));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoInitialization() {
|
||||
CreateAutoTimestamp ts = CreateAutoTimestamp.create(null);
|
||||
TestEntity ent = new TestEntity("autoinit", ts);
|
||||
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().persist(ent));
|
||||
|
||||
TestEntity result =
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "autoinit"));
|
||||
assertThat(result.cat.getTimestamp()).isEqualTo(jpaTmRule.getTxnClock().nowUtc());
|
||||
}
|
||||
|
||||
@Entity(name = "TestEntity") // Override entity name to avoid the nested class reference.
|
||||
public static class TestEntity extends ImmutableObject {
|
||||
|
||||
@Id String name;
|
||||
|
||||
CreateAutoTimestamp cat;
|
||||
|
||||
public TestEntity() {}
|
||||
|
||||
public TestEntity(String name, CreateAutoTimestamp cat) {
|
||||
this.name = name;
|
||||
this.cat = cat;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// 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.persistence;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
|
||||
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UpdateAutoTimestamp;
|
||||
import google.registry.model.transaction.JpaTransactionManagerRule;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import org.hibernate.cfg.Environment;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class UpdateAutoTimestampConverterTest {
|
||||
|
||||
@Rule
|
||||
public final JpaTransactionManagerRule jpaTmRule =
|
||||
new JpaTransactionManagerRule.Builder()
|
||||
.withEntityClass(TestEntity.class)
|
||||
.withProperty(Environment.HBM2DDL_AUTO, "update")
|
||||
.build();
|
||||
|
||||
@Test
|
||||
public void testTypeConversion() {
|
||||
TestEntity ent = new TestEntity("myinst", null);
|
||||
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().persist(ent));
|
||||
|
||||
TestEntity result =
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "myinst"));
|
||||
|
||||
assertThat(result.name).isEqualTo("myinst");
|
||||
assertThat(result.uat.getTimestamp()).isEqualTo(jpaTmRule.getTxnClock().nowUtc());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTimeChangesOnSubsequentTransactions() {
|
||||
TestEntity ent1 = new TestEntity("myinst1", null);
|
||||
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().persist(ent1));
|
||||
|
||||
TestEntity result1 =
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "myinst1"));
|
||||
|
||||
jpaTmRule.getTxnClock().advanceOneMilli();
|
||||
|
||||
TestEntity ent2 = new TestEntity("myinst2", result1.uat);
|
||||
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().persist(ent2));
|
||||
|
||||
TestEntity result2 =
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "myinst2"));
|
||||
|
||||
assertThat(result1.uat.getTimestamp()).isNotEqualTo(result2.uat.getTimestamp());
|
||||
assertThat(result2.uat.getTimestamp()).isEqualTo(jpaTmRule.getTxnClock().nowUtc());
|
||||
}
|
||||
|
||||
@Entity(name = "TestEntity") // Override entity name to avoid the nested class reference.
|
||||
public static class TestEntity extends ImmutableObject {
|
||||
|
||||
@Id String name;
|
||||
|
||||
UpdateAutoTimestamp uat;
|
||||
|
||||
public TestEntity() {}
|
||||
|
||||
public TestEntity(String name, UpdateAutoTimestamp uat) {
|
||||
this.name = name;
|
||||
this.uat = uat;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// 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.persistence;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
|
||||
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.transaction.JpaTransactionManagerRule;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Instant;
|
||||
import java.time.ZonedDateTime;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import org.hibernate.cfg.Environment;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
/** Unit tests for {@link ZonedDateTimeConverter}. */
|
||||
@RunWith(JUnit4.class)
|
||||
public class ZonedDateTimeConverterTest {
|
||||
|
||||
@Rule
|
||||
public final JpaTransactionManagerRule jpaTmRule =
|
||||
new JpaTransactionManagerRule.Builder()
|
||||
.withEntityClass(TestEntity.class)
|
||||
.withProperty(Environment.HBM2DDL_AUTO, "update")
|
||||
.build();
|
||||
|
||||
private final ZonedDateTimeConverter converter = new ZonedDateTimeConverter();
|
||||
|
||||
@Test
|
||||
public void convertToDatabaseColumn_returnsNullIfInputIsNull() {
|
||||
assertThat(converter.convertToDatabaseColumn(null)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertToDatabaseColumn_convertsCorrectly() {
|
||||
ZonedDateTime zonedDateTime = ZonedDateTime.parse("2019-09-01T01:01:01Z");
|
||||
assertThat(converter.convertToDatabaseColumn(zonedDateTime).toInstant())
|
||||
.isEqualTo(zonedDateTime.toInstant());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertToEntityAttribute_returnsNullIfInputIsNull() {
|
||||
assertThat(converter.convertToEntityAttribute(null)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertToEntityAttribute_convertsCorrectly() {
|
||||
ZonedDateTime zonedDateTime = ZonedDateTime.parse("2019-09-01T01:01:01Z");
|
||||
Instant instant = zonedDateTime.toInstant();
|
||||
assertThat(converter.convertToEntityAttribute(Timestamp.from(instant)))
|
||||
.isEqualTo(zonedDateTime);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void converter_generatesTimestampWithNormalizedZone() {
|
||||
ZonedDateTime zdt = ZonedDateTime.parse("2019-09-01T01:01:01Z");
|
||||
TestEntity entity = new TestEntity("normalized_utc_time", zdt);
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().persist(entity));
|
||||
TestEntity retrievedEntity =
|
||||
jpaTm()
|
||||
.transact(
|
||||
() -> jpaTm().getEntityManager().find(TestEntity.class, "normalized_utc_time"));
|
||||
assertThat(retrievedEntity.zdt.toString()).isEqualTo("2019-09-01T01:01:01Z");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void converter_convertsNonNormalizedZoneCorrectly() {
|
||||
ZonedDateTime zdt = ZonedDateTime.parse("2019-09-01T01:01:01Z[UTC]");
|
||||
TestEntity entity = new TestEntity("non_normalized_utc_time", zdt);
|
||||
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().persist(entity));
|
||||
TestEntity retrievedEntity =
|
||||
jpaTm()
|
||||
.transact(
|
||||
() -> jpaTm().getEntityManager().find(TestEntity.class, "non_normalized_utc_time"));
|
||||
assertThat(retrievedEntity.zdt.toString()).isEqualTo("2019-09-01T01:01:01Z");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void converter_convertsNonUtcZoneCorrectly() {
|
||||
ZonedDateTime zdt = ZonedDateTime.parse("2019-09-01T01:01:01+05:00");
|
||||
TestEntity entity = new TestEntity("new_york_time", zdt);
|
||||
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().persist(entity));
|
||||
TestEntity retrievedEntity =
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "new_york_time"));
|
||||
assertThat(retrievedEntity.zdt.toString()).isEqualTo("2019-08-31T20:01:01Z");
|
||||
}
|
||||
|
||||
@Entity(name = "TestEntity") // Override entity name to avoid the nested class reference.
|
||||
private static class TestEntity extends ImmutableObject {
|
||||
|
||||
@Id String name;
|
||||
|
||||
ZonedDateTime zdt;
|
||||
|
||||
public TestEntity() {}
|
||||
|
||||
public TestEntity(String name, ZonedDateTime zdt) {
|
||||
this.name = name;
|
||||
this.zdt = zdt;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.FakeLockHandler;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
@@ -54,9 +55,11 @@ public class EscrowTaskRunnerTest {
|
||||
private final EscrowTask task = mock(EscrowTask.class);
|
||||
private final FakeClock clock = new FakeClock(DateTime.parse("2000-01-01TZ"));
|
||||
|
||||
private DateTimeZone previousDateTimeZone;
|
||||
private EscrowTaskRunner runner;
|
||||
private Registry registry;
|
||||
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
createTld("lol");
|
||||
@@ -64,9 +67,15 @@ public class EscrowTaskRunnerTest {
|
||||
runner = new EscrowTaskRunner();
|
||||
runner.clock = clock;
|
||||
runner.lockHandler = new FakeLockHandler(true);
|
||||
previousDateTimeZone = DateTimeZone.getDefault();
|
||||
DateTimeZone.setDefault(DateTimeZone.forID("America/New_York")); // Make sure UTC stuff works.
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
DateTimeZone.setDefault(previousDateTimeZone);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRun_cursorIsToday_advancesCursorToTomorrow() throws Exception {
|
||||
clock.setTo(DateTime.parse("2006-06-06T00:30:00Z"));
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
// 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.tld;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.testing.JUnitBackports.assertThrows;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.model.transaction.JpaTransactionManagerRule;
|
||||
import java.math.BigDecimal;
|
||||
import javax.persistence.PersistenceException;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
/** Unit tests for {@link PremiumListDao}. */
|
||||
@RunWith(JUnit4.class)
|
||||
public class PremiumListDaoTest {
|
||||
|
||||
@Rule
|
||||
public final JpaTransactionManagerRule jpaTmRule =
|
||||
new JpaTransactionManagerRule.Builder().build();
|
||||
|
||||
private static final ImmutableMap<String, BigDecimal> TEST_PRICES =
|
||||
ImmutableMap.of(
|
||||
"silver",
|
||||
BigDecimal.valueOf(10.23),
|
||||
"gold",
|
||||
BigDecimal.valueOf(1305.47),
|
||||
"palladium",
|
||||
BigDecimal.valueOf(1552.78));
|
||||
|
||||
@Test
|
||||
public void saveNew_worksSuccessfully() {
|
||||
PremiumList premiumList = PremiumList.create("testname", CurrencyUnit.USD, TEST_PRICES);
|
||||
PremiumListDao.saveNew(premiumList);
|
||||
jpaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
PremiumList persistedList =
|
||||
jpaTm()
|
||||
.getEntityManager()
|
||||
.createQuery(
|
||||
"SELECT pl FROM PremiumList pl WHERE pl.name = :name", PremiumList.class)
|
||||
.setParameter("name", "testname")
|
||||
.getSingleResult();
|
||||
assertThat(persistedList.getLabelsToPrices()).containsExactlyEntriesIn(TEST_PRICES);
|
||||
assertThat(persistedList.getCreationTimestamp())
|
||||
.isEqualTo(jpaTmRule.getTxnClock().nowUtc());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveNew_throwsWhenPremiumListAlreadyExists() {
|
||||
PremiumListDao.saveNew(PremiumList.create("testlist", CurrencyUnit.USD, TEST_PRICES));
|
||||
PersistenceException thrown =
|
||||
assertThrows(
|
||||
PersistenceException.class,
|
||||
() ->
|
||||
PremiumListDao.saveNew(
|
||||
PremiumList.create("testlist", CurrencyUnit.USD, TEST_PRICES)));
|
||||
assertThat(thrown)
|
||||
.hasCauseThat()
|
||||
.hasMessageThat()
|
||||
.contains("A premium list of this name already exists");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkExists_worksSuccessfully() {
|
||||
assertThat(PremiumListDao.checkExists("testlist")).isFalse();
|
||||
PremiumListDao.saveNew(PremiumList.create("testlist", CurrencyUnit.USD, TEST_PRICES));
|
||||
assertThat(PremiumListDao.checkExists("testlist")).isTrue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// 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.tld;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.hash.BloomFilter;
|
||||
import java.math.BigDecimal;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
/** Unit tests for {@link PremiumList}. */
|
||||
@RunWith(JUnit4.class)
|
||||
public class PremiumListTest {
|
||||
|
||||
private static final ImmutableMap<String, BigDecimal> TEST_PRICES =
|
||||
ImmutableMap.of(
|
||||
"silver",
|
||||
BigDecimal.valueOf(10.23),
|
||||
"gold",
|
||||
BigDecimal.valueOf(1305.47),
|
||||
"palladium",
|
||||
BigDecimal.valueOf(1552.78));
|
||||
|
||||
@Test
|
||||
public void bloomFilter_worksCorrectly() {
|
||||
BloomFilter<String> bloomFilter =
|
||||
PremiumList.create("testname", CurrencyUnit.USD, TEST_PRICES).getBloomFilter();
|
||||
ImmutableSet.of("silver", "gold", "palladium")
|
||||
.forEach(l -> assertThat(bloomFilter.mightContain(l)).isTrue());
|
||||
ImmutableSet.of("dirt", "pyrite", "zirconia")
|
||||
.forEach(l -> assertThat(bloomFilter.mightContain(l)).isFalse());
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,13 @@ public class CreatePremiumListCommandTest<C extends CreatePremiumListCommand>
|
||||
verifySentParams(
|
||||
connection,
|
||||
servletPath,
|
||||
ImmutableMap.of("name", "foo", "inputData", generateInputData(premiumTermsPath)));
|
||||
ImmutableMap.of(
|
||||
"name",
|
||||
"foo",
|
||||
"inputData",
|
||||
generateInputData(premiumTermsPath),
|
||||
"alsoCloudSql",
|
||||
"false"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -78,7 +84,28 @@ public class CreatePremiumListCommandTest<C extends CreatePremiumListCommand>
|
||||
connection,
|
||||
servletPath,
|
||||
ImmutableMap.of(
|
||||
"name", "example_premium_terms", "inputData", generateInputData(premiumTermsPath)));
|
||||
"name",
|
||||
"example_premium_terms",
|
||||
"inputData",
|
||||
generateInputData(premiumTermsPath),
|
||||
"alsoCloudSql",
|
||||
"false"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRun_alsoCloudSql() throws Exception {
|
||||
runCommandForced("-i=" + premiumTermsPath, "-n=foo", "--also_cloud_sql");
|
||||
assertInStdout("Successfully");
|
||||
verifySentParams(
|
||||
connection,
|
||||
servletPath,
|
||||
ImmutableMap.of(
|
||||
"name",
|
||||
"foo",
|
||||
"inputData",
|
||||
generateInputData(premiumTermsPath),
|
||||
"alsoCloudSql",
|
||||
"true"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -57,7 +57,13 @@ public class UpdatePremiumListCommandTest<C extends UpdatePremiumListCommand>
|
||||
verifySentParams(
|
||||
connection,
|
||||
servletPath,
|
||||
ImmutableMap.of("name", "foo", "inputData", generateInputData(premiumTermsPath)));
|
||||
ImmutableMap.of(
|
||||
"name",
|
||||
"foo",
|
||||
"inputData",
|
||||
generateInputData(premiumTermsPath),
|
||||
"alsoCloudSql",
|
||||
"false"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -68,6 +74,11 @@ public class UpdatePremiumListCommandTest<C extends UpdatePremiumListCommand>
|
||||
connection,
|
||||
servletPath,
|
||||
ImmutableMap.of(
|
||||
"name", "example_premium_terms", "inputData", generateInputData(premiumTermsPath)));
|
||||
"name",
|
||||
"example_premium_terms",
|
||||
"inputData",
|
||||
generateInputData(premiumTermsPath),
|
||||
"alsoCloudSql",
|
||||
"false"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.tools.server;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.common.truth.Truth8.assertThat;
|
||||
import static google.registry.testing.JUnitBackports.assertThrows;
|
||||
|
||||
import google.registry.schema.tld.PremiumList;
|
||||
import google.registry.testing.AppEngineRule;
|
||||
import google.registry.testing.FakeJsonResponse;
|
||||
import java.math.BigDecimal;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
/** Unit tests for {@link CreateOrUpdatePremiumListAction}. */
|
||||
@RunWith(JUnit4.class)
|
||||
public class CreateOrUpdatePremiumListActionTest {
|
||||
|
||||
@Rule public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build();
|
||||
|
||||
private CreatePremiumListAction action;
|
||||
private FakeJsonResponse response;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
action = new CreatePremiumListAction();
|
||||
response = new FakeJsonResponse();
|
||||
action.response = response;
|
||||
action.name = "testlist";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseInputToPremiumList_works() {
|
||||
action.inputData = "foo,USD 99.50\n" + "bar,USD 30\n" + "baz,USD 10\n";
|
||||
PremiumList premiumList = action.parseInputToPremiumList();
|
||||
assertThat(premiumList.getName()).isEqualTo("testlist");
|
||||
assertThat(premiumList.getLabelsToPrices())
|
||||
.containsExactly("foo", twoDigits(99.50), "bar", twoDigits(30), "baz", twoDigits(10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseInputToPremiumList_throwsOnInconsistentCurrencies() {
|
||||
action.inputData = "foo,USD 99.50\n" + "bar,USD 30\n" + "baz,JPY 990\n";
|
||||
IllegalArgumentException thrown =
|
||||
assertThrows(IllegalArgumentException.class, () -> action.parseInputToPremiumList());
|
||||
assertThat(thrown)
|
||||
.hasMessageThat()
|
||||
.isEqualTo("The Cloud SQL schema requires exactly one currency, but got: [JPY, USD]");
|
||||
}
|
||||
|
||||
private static BigDecimal twoDigits(double num) {
|
||||
return BigDecimal.valueOf((long) (num * 100.0), 2);
|
||||
}
|
||||
}
|
||||
33
db/README.md
33
db/README.md
@@ -3,6 +3,23 @@
|
||||
This project contains Nomulus's Cloud SQL schema and schema-deployment
|
||||
utilities.
|
||||
|
||||
### Database Roles and Privileges
|
||||
|
||||
Nomulus uses the 'postgres' database in the 'public' schema. The following
|
||||
users/roles are defined:
|
||||
|
||||
* postgres: the initial user is used for admin and schema deployment.
|
||||
* In Cloud SQL, we do not control superusers. The initial 'postgres' user
|
||||
is a regular user with create-role/create-db privileges. Therefore,
|
||||
it is not possible to separate admin user and schema-deployment user.
|
||||
* readwrite is a role with read-write privileges on all data tables and
|
||||
sequences. However, it does not have write access to admin tables. Nor
|
||||
can it create new tables.
|
||||
* The Registry server user is granted this role.
|
||||
* readonly is a role with SELECT privileges on all tables.
|
||||
* Reporting job user and individual human readers may be granted
|
||||
this role.
|
||||
|
||||
### Schema DDL Scripts
|
||||
|
||||
Currently we use Flyway for schema deployment. Versioned incremental update
|
||||
@@ -12,18 +29,18 @@ changes not yet deployed are pushed.
|
||||
|
||||
Below are the steps to submit a schema change:
|
||||
|
||||
* Define the incremental DDL script that would update the existing schema to
|
||||
the new one.
|
||||
* Add the script to the src/main/resource/flyway folder. Its name should
|
||||
follow the V{id}__{description text}.sql, where {id} is a number that is
|
||||
higher than all existing scripts in that folder. Also note that it is a
|
||||
1. Write the incremental DDL script that makes your changes to the existing
|
||||
schema. It should be stored in a new file in the
|
||||
`db/src/main/resources/sql/flyway` folder using the naming pattern
|
||||
`V{id}__{description text}.sql`, where `{id}` is the next highest number
|
||||
following the existing scripts in that folder. Also note that it is a
|
||||
**double** underscore in the naming pattern.
|
||||
* Run the `:db:test` task from the Gradle root project. The SchemaTest will
|
||||
2. Run the `:db:test` task from the Gradle root project. The SchemaTest will
|
||||
fail because the new schema does not match the golden file.
|
||||
* Copy db/build/resources/test/testcontainer/mount/dump.txt to the golden file
|
||||
3. Copy db/build/resources/test/testcontainer/mount/dump.txt to the golden file
|
||||
(db/src/main/resources/sql/schema/nomulus.golden.sql). Diff it against the
|
||||
old version and verify that all changes are expected.
|
||||
* Rerun the `:db:test` task. This time all tests should pass.
|
||||
4. Rerun the `:db:test` task. This time all tests should pass.
|
||||
|
||||
Relevant files (under db/src/main/resources/sql/schema/):
|
||||
|
||||
|
||||
@@ -12,19 +12,45 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import org.gradle.api.internal.tasks.userinput.UserInputHandler
|
||||
|
||||
plugins {
|
||||
id "org.flywaydb.flyway" version "6.0.1"
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
ext {
|
||||
Set restrictedDbEnv =
|
||||
[ 'sandbox', 'production' ].asUnmodifiable()
|
||||
Set allDbEnv =
|
||||
[ 'alpha', 'crash' ].plus(restrictedDbEnv).asUnmodifiable()
|
||||
|
||||
def dbServerProperty = 'dbServer'
|
||||
def dbNameProperty = 'dbName'
|
||||
|
||||
def dbServer = findProperty(dbServerProperty)
|
||||
def dbServer = findProperty(dbServerProperty).toString().toLowerCase()
|
||||
def dbName = findProperty(dbNameProperty)
|
||||
|
||||
reconfirmRestrictedDbEnv = {
|
||||
if (!restrictedDbEnv.contains(dbServer)) {
|
||||
return
|
||||
}
|
||||
// For restricted environments, ask the user to type again to confirm.
|
||||
// The following statement uses Gradle internal API to get around the
|
||||
// missing console bug when Gradle Daemon is in use. Another option is
|
||||
// to use the ant.input task. For details please refer to
|
||||
// https://github.com/gradle/gradle/issues/1251.
|
||||
def dbServerAgain = services.get(UserInputHandler.class).askQuestion(
|
||||
"""\
|
||||
Are you sure? Operating on ${dbServer} from desktop is unsafe.
|
||||
Please type '${dbServer}' again to proceed: """.stripIndent(),
|
||||
'').trim()
|
||||
if (dbServer != dbServerAgain) {
|
||||
throw new RuntimeException(
|
||||
"Failed to confirm for restricted database environment. Operation aborted.")
|
||||
}
|
||||
}
|
||||
|
||||
getAccessInfoByHostPort = { hostAndPort ->
|
||||
return [
|
||||
url: "jdbc:postgresql://${hostAndPort}/${dbName}",
|
||||
@@ -32,8 +58,8 @@ ext {
|
||||
password: findProperty('dbPassword')]
|
||||
}
|
||||
|
||||
getSocketFactoryAccessInfo = {
|
||||
def cred = getCloudSqlCredential('alpha', 'superuser').split(' ')
|
||||
getSocketFactoryAccessInfo = { env ->
|
||||
def cred = getCloudSqlCredential(env, 'admin').split(' ')
|
||||
def sqlInstance = cred[0]
|
||||
return [
|
||||
url: """\
|
||||
@@ -47,11 +73,10 @@ ext {
|
||||
}
|
||||
|
||||
getJdbcAccessInfo = {
|
||||
switch (dbServer.toString().toLowerCase()) {
|
||||
case 'alpha':
|
||||
return getSocketFactoryAccessInfo()
|
||||
default:
|
||||
return getAccessInfoByHostPort(dbServer)
|
||||
if (allDbEnv.contains(dbServer)) {
|
||||
return getSocketFactoryAccessInfo(dbServer)
|
||||
} else {
|
||||
return getAccessInfoByHostPort(dbServer)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,18 +88,50 @@ ext {
|
||||
// later).
|
||||
getCloudSqlCredential = { env, role ->
|
||||
env = env == 'production' ? '' : "-${env}"
|
||||
def keyProject = env == '-crash'
|
||||
? 'domain-registry-crash-kms-keys'
|
||||
: "domain-registry${env}-keys"
|
||||
def command =
|
||||
"""gsutil cp \
|
||||
gs://domain-registry${env}-cloudsql-credentials/${role}.enc - | \
|
||||
gs://domain-registry${env}-cloudsql-credentials/${role}_credential.enc - | \
|
||||
gcloud kms decrypt --location global --keyring nomulus \
|
||||
--key sql-credentials-on-gcs-key --plaintext-file=- \
|
||||
--ciphertext-file=- \
|
||||
--project=domain-registry${env}-keys"""
|
||||
--project=${keyProject}"""
|
||||
|
||||
return execInBash(command, '/tmp')
|
||||
}
|
||||
}
|
||||
|
||||
task schemaJar(type: Jar) {
|
||||
archiveBaseName = 'schema'
|
||||
from(sourceSets.main.resources) {
|
||||
include 'sql/flyway/**'
|
||||
include 'sql/schema/nomulus.golden.sql'
|
||||
}
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives schemaJar
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
url project.schema_jar_repo
|
||||
}
|
||||
}
|
||||
publications {
|
||||
schemaOrmPublication(MavenPublication) {
|
||||
groupId 'google.registry'
|
||||
artifactId 'schema'
|
||||
version project.schema_version
|
||||
artifact schemaJar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
flyway {
|
||||
def accessInfo = project.ext.getJdbcAccessInfo()
|
||||
|
||||
@@ -86,6 +143,13 @@ flyway {
|
||||
locations = [ "classpath:sql/flyway" ]
|
||||
}
|
||||
|
||||
tasks.flywayMigrate.dependsOn(
|
||||
tasks.create('confirmMigrateOnRestrictedDb') {
|
||||
doLast {
|
||||
project.ext.reconfirmRestrictedDbEnv()
|
||||
}
|
||||
})
|
||||
|
||||
dependencies {
|
||||
def deps = rootProject.dependencyMap
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
-- 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.
|
||||
|
||||
create index if not exists idx_registry_lock_verification_code ON "RegistryLock"
|
||||
using btree (verification_code);
|
||||
@@ -12,19 +12,6 @@
|
||||
-- See the License for the specific language governing permissions and
|
||||
-- limitations under the License.
|
||||
|
||||
CREATE TABLE "RegistryLock" (
|
||||
revision_id BIGSERIAL NOT NULL,
|
||||
action TEXT NOT NULL,
|
||||
completion_timestamp TIMESTAMPTZ,
|
||||
creation_timestamp TIMESTAMPTZ NOT NULL,
|
||||
domain_name TEXT NOT NULL,
|
||||
is_superuser BOOLEAN NOT NULL,
|
||||
registrar_id TEXT NOT NULL,
|
||||
registrar_poc_id TEXT,
|
||||
repo_id TEXT NOT NULL,
|
||||
verification_code TEXT NOT NULL,
|
||||
PRIMARY KEY (revision_id)
|
||||
);
|
||||
alter table "PremiumList" add column if not exists name text not null;
|
||||
|
||||
ALTER TABLE IF EXISTS "RegistryLock"
|
||||
ADD CONSTRAINT idx_registry_lock_repo_id_revision_id UNIQUE (repo_id, revision_id);
|
||||
create index if not exists premiumlist_name_idx ON "PremiumList" (name);
|
||||
@@ -0,0 +1,15 @@
|
||||
-- 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.
|
||||
|
||||
alter table "PremiumList" add column if not exists bloom_filter bytea not null;
|
||||
15
db/src/main/resources/sql/flyway/V7__update_claims_list.sql
Normal file
15
db/src/main/resources/sql/flyway/V7__update_claims_list.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
-- 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.
|
||||
|
||||
alter table "ClaimsList" add column if not exists tmdb_generation_time timestamp with time zone not null;
|
||||
@@ -130,8 +130,10 @@
|
||||
|
||||
create table "PremiumList" (
|
||||
revision_id bigserial not null,
|
||||
bloom_filter bytea not null,
|
||||
creation_timestamp timestamptz not null,
|
||||
currency bytea not null,
|
||||
name text not null,
|
||||
primary key (revision_id)
|
||||
);
|
||||
|
||||
@@ -157,6 +159,7 @@
|
||||
|
||||
alter table if exists "Domain_GracePeriod"
|
||||
add constraint UK_4ps2u4y8i5r91wu2n1x2xea28 unique (grace_periods_id);
|
||||
create index premiumlist_name_idx on "PremiumList" (name);
|
||||
|
||||
alter table if exists "RegistryLock"
|
||||
add constraint idx_registry_lock_repo_id_revision_id unique (repo_id, revision_id);
|
||||
|
||||
@@ -50,7 +50,8 @@ CREATE TABLE public."ClaimsEntry" (
|
||||
|
||||
CREATE TABLE public."ClaimsList" (
|
||||
revision_id bigint NOT NULL,
|
||||
creation_timestamp timestamp with time zone NOT NULL
|
||||
creation_timestamp timestamp with time zone NOT NULL,
|
||||
tmdb_generation_time timestamp with time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
@@ -91,7 +92,9 @@ CREATE TABLE public."PremiumEntry" (
|
||||
CREATE TABLE public."PremiumList" (
|
||||
revision_id bigint NOT NULL,
|
||||
creation_timestamp timestamp with time zone NOT NULL,
|
||||
currency bytea NOT NULL
|
||||
currency bytea NOT NULL,
|
||||
name text NOT NULL,
|
||||
bloom_filter bytea NOT NULL
|
||||
);
|
||||
|
||||
|
||||
@@ -220,6 +223,20 @@ ALTER TABLE ONLY public."RegistryLock"
|
||||
ADD CONSTRAINT idx_registry_lock_repo_id_revision_id UNIQUE (repo_id, revision_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: idx_registry_lock_verification_code; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX idx_registry_lock_verification_code ON public."RegistryLock" USING btree (verification_code);
|
||||
|
||||
|
||||
--
|
||||
-- Name: premiumlist_name_idx; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX premiumlist_name_idx ON public."PremiumList" USING btree (name);
|
||||
|
||||
|
||||
--
|
||||
-- Name: ClaimsEntry fk6sc6at5hedffc0nhdcab6ivuq; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
@@ -12,10 +12,12 @@
|
||||
-- See the License for the specific language governing permissions and
|
||||
-- limitations under the License.
|
||||
--
|
||||
-- Script to create a user with read-only permission to all tables.
|
||||
-- Script to create a user with read-only permission to all tables. The
|
||||
-- initialize_roles.sql script creates the readonly role used here.
|
||||
|
||||
-- Comment out line below if user already exists:
|
||||
CREATE USER :username ENCRYPTED PASSWORD :'password';
|
||||
GRANT CONNECT ON DATABASE postgres TO :username;
|
||||
GRANT USAGE ON SCHEMA public TO :username;
|
||||
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO :username;
|
||||
GRANT SELECT ON ALL TABLES IN SCHEMA public TO :username;
|
||||
-- Comment out line above and uncomment line below if user has been created
|
||||
-- from Cloud Dashboard:
|
||||
-- ALTER USER :username NOCREATEDB NOCREATEROLE;
|
||||
GRANT readonly TO :username;
|
||||
|
||||
@@ -12,10 +12,13 @@
|
||||
-- See the License for the specific language governing permissions and
|
||||
-- limitations under the License.
|
||||
--
|
||||
-- Script to create a user with read-write permission to all tables.
|
||||
-- Script to create a user with read-write permission to all tables (except for
|
||||
-- WRITE permissions to flyway_schema_history). The initialize_roles.sql script
|
||||
-- creates the readwrite role used here.
|
||||
|
||||
-- Comment out line below if user already exists:
|
||||
CREATE USER :username ENCRYPTED PASSWORD :'password';
|
||||
GRANT CONNECT ON DATABASE postgres TO :username;
|
||||
GRANT USAGE ON SCHEMA public TO :username;
|
||||
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO :username;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO :username;
|
||||
-- Comment out line above and uncomment line below if user has been created
|
||||
-- from Cloud Dashboard:
|
||||
-- ALTER USER :username NOCREATEDB NOCREATEROLE;
|
||||
GRANT readwrite TO :username;
|
||||
|
||||
@@ -14,9 +14,7 @@
|
||||
--
|
||||
-- Script to delete a user from the database.
|
||||
|
||||
REVOKE ALL PRIVILEGES ON DATABASE postgres FROM :username;
|
||||
REVOKE ALL PRIVILEGES ON SCHEMA public FROM :username;
|
||||
REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM :username;
|
||||
REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public FROM :username;
|
||||
REVOKE ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA public FROM :username;
|
||||
-- Ignore warnings like :username is not a member of role readonly/write.
|
||||
REVOKE readonly FROM :username;
|
||||
REVOKE readwrite FROM :username;
|
||||
DROP USER :username;
|
||||
|
||||
48
db/src/main/resources/sql/user/initialize_roles.sql
Normal file
48
db/src/main/resources/sql/user/initialize_roles.sql
Normal file
@@ -0,0 +1,48 @@
|
||||
-- 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.
|
||||
--
|
||||
-- Initializes roles and their privileges in the postgres database in Cloud SQL.
|
||||
-- This script should run once under the **'postgres'** user before any other
|
||||
-- roles or users are created.
|
||||
|
||||
-- Prevent backdoor grants through the implicit 'public' role.
|
||||
REVOKE ALL PRIVILEGES ON SCHEMA public from public;
|
||||
|
||||
CREATE ROLE readonly;
|
||||
GRANT CONNECT ON DATABASE postgres TO readonly;
|
||||
GRANT USAGE ON SCHEMA public TO readonly;
|
||||
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO readonly;
|
||||
ALTER DEFAULT PRIVILEGES
|
||||
IN SCHEMA public
|
||||
FOR USER postgres
|
||||
GRANT USAGE, SELECT ON SEQUENCES TO readonly;
|
||||
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;
|
||||
ALTER DEFAULT PRIVILEGES
|
||||
IN SCHEMA public
|
||||
FOR USER postgres
|
||||
GRANT SELECT ON TABLES TO readonly;
|
||||
|
||||
CREATE ROLE readwrite;
|
||||
GRANT CONNECT ON DATABASE postgres TO readwrite;
|
||||
GRANT USAGE ON SCHEMA public TO readwrite;
|
||||
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO readwrite;
|
||||
ALTER DEFAULT PRIVILEGES
|
||||
IN SCHEMA public
|
||||
FOR USER postgres
|
||||
GRANT USAGE, SELECT ON SEQUENCES TO readwrite;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO readwrite;
|
||||
ALTER DEFAULT PRIVILEGES
|
||||
IN SCHEMA public
|
||||
FOR USER postgres
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO readwrite;
|
||||
@@ -12,11 +12,9 @@
|
||||
-- See the License for the specific language governing permissions and
|
||||
-- limitations under the License.
|
||||
--
|
||||
-- Script to create a user with read-write permission to schema 'public' and
|
||||
-- all tables.
|
||||
-- Removes write privileges to Flyway admin table from roles.
|
||||
-- This script is run once under 'postgres' after initialize_roles.sql
|
||||
-- has been run AND the initial schema deployment by Flyway is done.
|
||||
|
||||
CREATE USER :username ENCRYPTED PASSWORD :'password';
|
||||
GRANT CONNECT ON DATABASE postgres TO :username;
|
||||
GRANT ALL PRIVILEGES ON SCHEMA public TO :username;
|
||||
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO :username;
|
||||
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO :username;
|
||||
REVOKE INSERT, UPDATE, DELETE ON TABLE public.flyway_schema_history FROM readonly;
|
||||
REVOKE INSERT, UPDATE, DELETE ON TABLE public.flyway_schema_history FROM readwrite;
|
||||
@@ -8,6 +8,12 @@ verboseTestOutput=false
|
||||
flowDocsFile=
|
||||
enableDependencyLocking=true
|
||||
|
||||
# Comma separated list of test patterns, if specified run only these.
|
||||
testFilter=
|
||||
|
||||
# GAE Environment for deployment and staging.
|
||||
environment=
|
||||
|
||||
# Cloud SQL properties
|
||||
|
||||
# A registry environment name (e.g., 'alpha') or a host[:port] string
|
||||
@@ -15,3 +21,8 @@ dbServer=
|
||||
dbName=postgres
|
||||
dbUser=
|
||||
dbPassword=
|
||||
|
||||
# Maven repository of the Cloud SQL schema jar, which contains the
|
||||
# SQL DDL scripts.
|
||||
schema_jar_repo=
|
||||
schema_version=
|
||||
|
||||
@@ -12,26 +12,10 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
def environments = ['production', 'sandbox', 'alpha', 'crash']
|
||||
// Mapping from environment names to GCP projects.
|
||||
// Replace the values with the names of your deployment environments.
|
||||
|
||||
def projects = ['production': 'domain-registry',
|
||||
'sandbox' : 'domain-registry-sandbox',
|
||||
'alpha' : 'domain-registry-alpha',
|
||||
'crash' : 'domain-registry-crash']
|
||||
|
||||
|
||||
def environment = rootProject.findProperty("environment")
|
||||
|
||||
if (environment == null) {
|
||||
environment = 'production'
|
||||
}
|
||||
|
||||
def gcpProject = projects[environment]
|
||||
|
||||
if (gcpProject == null) {
|
||||
throw new GradleException("-Penvironment must be one of ${environments}.")
|
||||
}
|
||||
|
||||
rootProject.ext.environment = environment
|
||||
rootProject.ext.gcpProject = gcpProject
|
||||
rootProject.ext.prodOrSandboxEnv = ['production', 'sandbox'].contains(environment)
|
||||
rootProject.ext.projects = ['production': 'your-production-project',
|
||||
'sandbox' : 'your-sandbox-project',
|
||||
'alpha' : 'your-alpha-project',
|
||||
'crash' : 'your-crash-project']
|
||||
|
||||
Reference in New Issue
Block a user