Run the (Un)lockDomainCommand in an outer JPA txn (#688)

* Run the (Un)lockDomainCommand in an outer JPA txn

There are a couple things going on here in this commit.

First, we add an external JPA transaction in the
LockOrUnlockDomainCommand class. This doesn't appear to do much, but it
avoids a situation similar to deadlock if an error occurs in Datastore
when saving the domain object. Specifically, DomainLockUtils relies on
the fact that any error in Datastore will be re-thrown in the JPA
transaction, meaning that any Datastore error will back out of the SQL
transaction as well. However, this is no longer true if we are already
in a Datastore transaction when calling DomainLockUtils (unless, again,
we are also in a JPA transaction). Basically, we require that the outer
transaction is the JPA one.

Secondly, this just allows for more breakglass operations in the lock or
unlock domain commands -- in a situation where things possibly go
haywire, we should allow admins to make sure with certainty that a
domain is locked or unlocked.

* Add more robustness and tests for admins locking locked domains

* Fix expected exception message in tests
This commit is contained in:
gbrodman 2020-07-27 18:16:24 -04:00 committed by GitHub
parent 8ab83ed4b3
commit c2207fe7f5
8 changed files with 89 additions and 115 deletions

View file

@ -49,6 +49,7 @@ import google.registry.testing.AppEngineRule;
import google.registry.testing.DatastoreHelper;
import google.registry.testing.DeterministicStringGenerator;
import google.registry.testing.FakeClock;
import google.registry.testing.SqlHelper;
import google.registry.testing.TaskQueueHelper.TaskMatcher;
import google.registry.testing.UserInfo;
import google.registry.util.AppEngineServiceUtils;
@ -275,6 +276,37 @@ public final class DomainLockUtilsTest {
standardDays(6).plus(standardSeconds(30))));
}
@Test
void testSuccess_adminCanLockLockedDomain_withNoSavedLock() {
// in the case of inconsistencies / errors, admins should have the ability to override
// whatever statuses exist on the domain
persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
RegistryLock resultLock = domainLockUtils
.administrativelyApplyLock(DOMAIN_NAME, "TheRegistrar", POC_ID, true);
verifyProperlyLockedDomain(true);
assertThat(resultLock.getLockCompletionTimestamp()).isEqualTo(Optional.of(clock.nowUtc()));
}
@Test
void testSuccess_adminCanLockUnlockedDomain_withSavedLock() {
// in the case of inconsistencies / errors, admins should have the ability to override
// what the RegistryLock table says
SqlHelper.saveRegistryLock(new RegistryLock.Builder()
.setLockCompletionTimestamp(clock.nowUtc())
.setDomainName(DOMAIN_NAME)
.setVerificationCode("hi")
.setRegistrarId("TheRegistrar")
.setRepoId(domain.getRepoId())
.isSuperuser(false)
.setRegistrarPocId(POC_ID)
.build());
clock.advanceOneMilli();
RegistryLock resultLock = domainLockUtils
.administrativelyApplyLock(DOMAIN_NAME, "TheRegistrar", POC_ID, true);
verifyProperlyLockedDomain(true);
assertThat(resultLock.getLockCompletionTimestamp()).isEqualTo(Optional.of(clock.nowUtc()));
}
@Test
void testFailure_createUnlock_alreadyPendingUnlock() {
RegistryLock lock =
@ -317,7 +349,7 @@ public final class DomainLockUtilsTest {
domainLockUtils.saveNewRegistryLockRequest(
"asdf.tld", "TheRegistrar", POC_ID, false)))
.hasMessageThat()
.isEqualTo("Unknown domain asdf.tld");
.isEqualTo("Domain doesn't exist");
}
@Test

View file

@ -98,12 +98,9 @@ class LockDomainCommandTest extends CommandTestCase<LockDomainCommand> {
}
@Test
void testFailure_domainDoesntExist() {
IllegalArgumentException e =
assertThrows(
IllegalArgumentException.class,
() -> runCommandForced("--client=NewRegistrar", "missing.tld"));
assertThat(e).hasMessageThat().isEqualTo("Domain 'missing.tld' does not exist or is deleted");
void testFailure_domainDoesntExist() throws Exception {
runCommandForced("--client=NewRegistrar", "missing.tld");
assertInStdout("Failed domains:\n[missing.tld (Domain doesn't exist)]");
}
@Test

View file

@ -108,19 +108,16 @@ class UnlockDomainCommandTest extends CommandTestCase<UnlockDomainCommand> {
}
@Test
void testFailure_domainDoesntExist() {
IllegalArgumentException e =
assertThrows(
IllegalArgumentException.class,
() -> runCommandForced("--client=TheRegistrar", "missing.tld"));
assertThat(e).hasMessageThat().isEqualTo("Domain 'missing.tld' does not exist or is deleted");
void testFailure_domainDoesntExist() throws Exception {
runCommandForced("--client=NewRegistrar", "missing.tld");
assertInStdout("Failed domains:\n[missing.tld (Domain doesn't exist)]");
}
@Test
void testSuccess_alreadyUnlockedDomain_performsNoAction() throws Exception {
void testSuccess_alreadyUnlockedDomain_staysUnlocked() throws Exception {
DomainBase domain = persistActiveDomain("example.tld");
runCommandForced("--client=TheRegistrar", "example.tld");
assertThat(reloadResource(domain)).isEqualTo(domain);
assertThat(reloadResource(domain).getStatusValues()).containsNoneIn(REGISTRY_LOCK_STATUSES);
}
@Test

View file

@ -358,7 +358,7 @@ public final class RegistryLockPostActionTest {
"domainName", "bad.tld",
"isLock", true,
"password", "hi"));
assertFailureWithMessage(response, "Unknown domain bad.tld");
assertFailureWithMessage(response, "Domain doesn't exist");
}
@Test