Skip to content

Commit

Permalink
Add a transactional parameter to @action
Browse files Browse the repository at this point in the history
This also removes inner transactions on some Registrar methods with some
resultant refactoring.
  • Loading branch information
CydeWeys committed Nov 13, 2023
1 parent 9e3c589 commit 61eb640
Show file tree
Hide file tree
Showing 15 changed files with 264 additions and 117 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,9 @@ public void run() {
!RegistryEnvironment.get().equals(PRODUCTION),
"This action is not safe to run on PRODUCTION.");

tm().transact(
() -> {
LOAD_TEST_REGISTRARS.forEach(this::deletePollMessages);
tm().loadAllOfStream(Contact.class).forEach(this::deleteContact);
tm().loadAllOfStream(Host.class).forEach(this::deleteHost);
});
LOAD_TEST_REGISTRARS.forEach(this::deletePollMessages);
tm().loadAllOfStream(Contact.class).forEach(this::deleteContact);
tm().loadAllOfStream(Host.class).forEach(this::deleteHost);
}

private void deletePollMessages(String registrarId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ public Optional<RegistrarPoc> getWhoisAbuseContact() {
}

private ImmutableSet<RegistrarPoc> getContactPocs() {
return tm().transact(
return tm().reTransact(
() ->
tm().query("FROM RegistrarPoc WHERE registrarId = :registrarId", RegistrarPoc.class)
.setParameter("registrarId", registrarId)
Expand Down Expand Up @@ -944,7 +944,7 @@ public static String checkValidEmail(String email) {

/** Loads all registrar entities directly from the database. */
public static Iterable<Registrar> loadAll() {
return tm().transact(() -> tm().loadAllOf(Registrar.class));
return tm().loadAllOf(Registrar.class);
}

/** Loads all registrar entities using an in-memory cache. */
Expand All @@ -962,7 +962,7 @@ public static ImmutableSet<VKey<Registrar>> loadAllKeysCached() {
/** Loads and returns a registrar entity by its id directly from the database. */
public static Optional<Registrar> loadByRegistrarId(String registrarId) {
checkArgument(!Strings.isNullOrEmpty(registrarId), "registrarId must be specified");
return tm().transact(() -> tm().loadByKeyIfPresent(createVKey(registrarId)));
return tm().reTransact(() -> tm().loadByKeyIfPresent(createVKey(registrarId)));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public final class UpdateRegistrarRdapBaseUrlsAction implements Runnable {
public void run() {
try {
ImmutableMap<String, String> ianaIdsToUrls = getIanaIdsToUrls();
tm().transact(() -> processAllRegistrars(ianaIdsToUrls));
processAllRegistrars(ianaIdsToUrls);
} catch (Exception e) {
throw new InternalServerErrorException("Error when retrieving RDAP base URL CSV file", e);
}
Expand Down
3 changes: 3 additions & 0 deletions core/src/main/java/google/registry/request/Action.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ public String getServiceId() {
/** HTTP methods that request processor should allow. */
Method[] method() default Method.GET;

/** Whether to create a wrapping DB transaction around the execution of the action. */
boolean transactional() default true;

/**
* Indicates request processor should print "OK" to the HTTP client on success.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED;
Expand Down Expand Up @@ -155,7 +156,12 @@ public void handleRequest(HttpServletRequest req, HttpServletResponse rsp) throw
boolean success = true;
DateTime startTime = clock.nowUtc();
try {
route.get().instantiator().apply(component).run();
Runnable action = route.get().instantiator().apply(component);
if (route.get().action().transactional()) {
tm().transact(action::run);
} else {
action.run();
}
if (route.get().action().automaticallyPrintOk()) {
rsp.setContentType(PLAIN_TEXT_UTF_8.toString());
rsp.getWriter().write("OK\n");
Expand Down
56 changes: 42 additions & 14 deletions core/src/main/java/google/registry/tools/ConfirmingCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@

package google.registry.tools;

import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.tools.CommandUtilities.promptForYes;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.commons.lang3.exception.ExceptionUtils.rethrow;

import com.beust.jcommander.Parameter;
import com.google.common.base.Strings;
Expand Down Expand Up @@ -44,20 +46,46 @@ protected ConfirmingCommand() {

@Override
public final void run() throws Exception {
if (checkExecutionState()) {
init();
printLineIfNotEmpty(prompt(), printStream);
if (dontRunCommand()) {
// This typically happens when all of the work is accomplished inside of prompt(), so do
// nothing further.
return;
} else if (force || promptForYes("Perform this command?")) {
printStream.println("Running ... ");
printStream.println(execute());
printLineIfNotEmpty(postExecute(), printStream);
} else {
printStream.println("Command aborted.");
}
if (!checkExecutionState()) {
return;
}

tm().transact(
() -> {
try {
init();
printLineIfNotEmpty(prompt(), printStream);
} catch (Exception e) {
rethrow(e);
}
});

if (dontRunCommand()) {
// This typically happens when all of the work is accomplished inside of
// prompt(), so do
// nothing further.
return;
} else if (force || promptForYes("Perform this command?")) {
printStream.println("Running ... ");
tm().transact(
() -> {
try {
printStream.println(execute());
} catch (Exception e) {
rethrow(e);
}
});

tm().transact(
() -> {
try {
printLineIfNotEmpty(postExecute(), printStream);
} catch (Exception e) {
rethrow(e);
}
});
} else {
printStream.println("Command aborted.");
}
printStream.close();
errorPrintStream.close();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

package google.registry.tools;

import static google.registry.persistence.transaction.TransactionManagerFactory.tm;

import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.base.Joiner;
Expand Down Expand Up @@ -43,6 +45,8 @@ final class WhoisQueryCommand implements Command {

@Override
public void run() {
System.out.println(whois.lookup(Joiner.on(' ').join(mainParameters), unicode, fullOutput));
System.out.println(
tm().transact(
() -> whois.lookup(Joiner.on(' ').join(mainParameters), unicode, fullOutput)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@
service = Action.Service.DEFAULT,
path = ConsoleOteSetupAction.PATH,
method = {Method.POST, Method.GET},
auth = Auth.AUTH_PUBLIC_LEGACY)
auth = Auth.AUTH_PUBLIC_LEGACY,
transactional = false // Needs to explicitly run more than one transaction sequentially.
)
public final class ConsoleOteSetupAction extends HtmlAction {

public static final String PATH = "/registrar-ote-setup";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@
service = Action.Service.DEFAULT,
path = RegistrarSettingsAction.PATH,
method = Action.Method.POST,
auth = Auth.AUTH_PUBLIC_LOGGED_IN)
auth = Auth.AUTH_PUBLIC_LOGGED_IN,
transactional = false // Needs to explicitly run more than one transaction sequentially.
)
public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonAction {

public static final String PATH = "/registrar-settings";
Expand Down Expand Up @@ -157,9 +159,9 @@ public Map<String, Object> handleJsonRequest(Map<String, ?> input) {
try {
switch (op) {
case "update":
return update(args, registrarId).toJsonResponse();
return update(args, registrarId);
case "read":
return read(registrarId).toJsonResponse();
return read(registrarId);
default:
throw new IllegalArgumentException("Unknown or unsupported operation: " + op);
}
Expand Down Expand Up @@ -215,8 +217,11 @@ static EmailInfo create(
}
}

private RegistrarResult read(String registrarId) {
return RegistrarResult.create("Success", loadRegistrarUnchecked(registrarId));
private Map<String, Object> read(String registrarId) {
return tm().transact(
() ->
RegistrarResult.create("Success", loadRegistrarUnchecked(registrarId))
.toJsonResponse());
}

private Registrar loadRegistrarUnchecked(String registrarId) {
Expand All @@ -227,15 +232,19 @@ private Registrar loadRegistrarUnchecked(String registrarId) {
}
}

private RegistrarResult update(final Map<String, ?> args, String registrarId) {
private Map<String, Object> update(final Map<String, ?> args, String registrarId) {
// Email the updates
sendExternalUpdatesIfNecessary(tm().transact(() -> saveUpdates(args, registrarId)));
// Reload the result outside the transaction to get the most recent version
return RegistrarResult.create("Saved " + registrarId, loadRegistrarUnchecked(registrarId));
return tm().transact(
() ->
RegistrarResult.create("Saved " + registrarId, loadRegistrarUnchecked(registrarId))
.toJsonResponse());
}

/** Saves the updates and returns info needed for the update email */
private EmailInfo saveUpdates(final Map<String, ?> args, String registrarId) {
tm().assertInTransaction();
// We load the registrar here rather than outside the transaction - to make
// sure we have the latest version. This one is loaded inside the transaction, so it's
// guaranteed to not change before we update it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ private void runAction() {
action.gSuiteDomainName = "domain-registry.example";
action.response = response;
action.retrier = new Retrier(new FakeSleeper(new FakeClock()), 3);
action.run();
tm().transact(action::run);
}

@Test
Expand Down Expand Up @@ -182,7 +182,11 @@ void test_doPost_addsAndRemovesContacts_acrossMultipleRegistrars() throws Except
"[email protected]",
Role.MEMBER);
verify(response).setStatus(SC_OK);
assertThat(Iterables.filter(Registrar.loadAll(), Registrar::getContactsRequireSyncing))
assertThat(
tm().transact(
() ->
Iterables.filter(
Registrar.loadAll(), Registrar::getContactsRequireSyncing)))
.isEmpty();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2544,11 +2544,13 @@ void testFailure_disabledRegistrarCantCreateDomain() {
private void doFailingTest_invalidRegistrarState(State registrarState) {
persistContactsAndHosts();
persistResource(
Registrar.loadByRegistrarId("TheRegistrar")
.get()
.asBuilder()
.setState(registrarState)
.build());
tm().transact(
() ->
Registrar.loadByRegistrarId("TheRegistrar")
.get()
.asBuilder()
.setState(registrarState)
.build()));
EppException thrown =
assertThrows(RegistrarMustBeActiveForThisOperationException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -977,9 +977,9 @@ public static <R extends ImmutableObject> R persistResource(final R resource) {
assertWithMessage("Attempting to persist a Builder is almost certainly an error in test code")
.that(resource)
.isNotInstanceOf(Buildable.Builder.class);
tm().transact(() -> tm().put(resource));
tm().reTransact(() -> tm().put(resource));
maybeAdvanceClock();
return tm().transact(() -> tm().loadByEntity(resource));
return tm().reTransact(() -> tm().loadByEntity(resource));
}

/** Persists the specified resources to the DB. */
Expand Down Expand Up @@ -1161,7 +1161,7 @@ public static ImmutableMap<String, PremiumEntry> loadPremiumEntries(PremiumList
/** Loads and returns the registrar with the given client ID, or throws IAE if not present. */
public static Registrar loadRegistrar(String registrarId) {
return checkArgumentPresent(
Registrar.loadByRegistrarId(registrarId),
tm().reTransact(() -> Registrar.loadByRegistrarId(registrarId)),
"Error in tests: Registrar %s does not exist",
registrarId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import static google.registry.persistence.transaction.JpaTransactionManagerExtension.makeRegistrar2;
import static google.registry.persistence.transaction.JpaTransactionManagerExtension.makeRegistrarContact2;
import static google.registry.persistence.transaction.JpaTransactionManagerExtension.makeRegistrarContact3;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.server.Fixture.BASIC;
import static google.registry.server.Route.route;
import static google.registry.testing.DatabaseHelper.createTld;
Expand Down Expand Up @@ -172,13 +173,15 @@ void settingsContactEdit_setRegistryLockPassword() throws Throwable {
driver.diffPage("contact_view");

RegistrarPoc contact =
loadRegistrar("TheRegistrar").getContacts().stream()
.filter(c -> "[email protected]".equals(c.getEmailAddress()))
.findFirst()
.get();
assertThat(contact.verifyRegistryLockPassword("password")).isTrue();
assertThat(contact.getRegistryLockEmailAddress())
.isEqualTo(Optional.of("[email protected]"));
tm().transact(
() ->
loadRegistrar("TheRegistrar").getContacts().stream()
.filter(c -> "[email protected]".equals(c.getEmailAddress()))
.findFirst()
.get());
assertThat(contact.verifyRegistryLockPassword("password")).isTrue();
assertThat(contact.getRegistryLockEmailAddress())
.isEqualTo(Optional.of("[email protected]"));
}

@RetryingTest(3)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package google.registry.webdriver;

import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.server.Fixture.BASIC;
import static google.registry.server.Route.route;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
Expand Down Expand Up @@ -176,7 +177,8 @@ void testWhoisSettingsEdit() throws Throwable {
void testContactSettingsView() throws Throwable {
driver.get(server.getUrl("/registrar#contact-settings"));
driver.waitForDisplayedElement(By.id("reg-app-btn-add"));
ImmutableList<RegistrarPoc> contacts = loadRegistrar("TheRegistrar").getContacts().asList();
ImmutableList<RegistrarPoc> contacts =
tm().transact(() -> loadRegistrar("TheRegistrar").getContacts().asList());
for (RegistrarPoc contact : contacts) {
assertEltTextPresent(By.id("contacts[0].name"), contact.getName());
assertEltTextPresent(By.id("contacts[0].emailAddress"), contact.getEmailAddress());
Expand Down
Loading

0 comments on commit 61eb640

Please sign in to comment.