Allow command to enqueue poll messages for multiple registrars (#1446)

* Allow command to enqueue poll messages for multiple registrars
This commit is contained in:
Ben McIlwain 2021-12-08 16:33:28 -05:00 committed by GitHub
parent 0a31e6e71c
commit a96db1f236
4 changed files with 137 additions and 23 deletions

View file

@ -18,14 +18,21 @@ import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.EppResourceUtils.loadByForeignKey; import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.model.reporting.HistoryEntry.Type.SYNTHETIC; import static google.registry.model.reporting.HistoryEntry.Type.SYNTHETIC;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.CollectionUtils.isNullOrEmpty;
import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters; import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import google.registry.config.RegistryConfig.Config;
import google.registry.model.domain.DomainBase; import google.registry.model.domain.DomainBase;
import google.registry.model.domain.DomainHistory; import google.registry.model.domain.DomainHistory;
import google.registry.model.poll.PollMessage; import google.registry.model.poll.PollMessage;
import google.registry.model.registrar.Registrar;
import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.HistoryEntry;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import javax.inject.Inject;
/** /**
* Tool to enqueue a poll message for a registrar. * Tool to enqueue a poll message for a registrar.
@ -54,14 +61,26 @@ class EnqueuePollMessageCommand extends MutatingCommand {
String domainName; String domainName;
@Parameter( @Parameter(
names = {"-c", "--client"}, names = {"-c", "--clients"},
description = description =
"Client identifier of the registrar to send the poll message to, if not the owning" "Comma-delimited list of the client identifier(s) of the registrar(s) to send the poll"
+ " registrar of the domain") + " message to, if not the owning registrar of the domain")
String clientId; List<String> clientIds;
@Parameter(
names = {"-a", "--all"},
description = "Whether to send the message to all real registrars",
arity = 1)
boolean sendToAll;
@Inject
@Config("registryAdminClientId")
String registryAdminClientId;
@Override @Override
protected final void init() { protected final void init() {
checkArgument(
!sendToAll || isNullOrEmpty(clientIds), "Cannot specify both --all and --clients");
tm().transact( tm().transact(
() -> { () -> {
Optional<DomainBase> domainOpt = Optional<DomainBase> domainOpt =
@ -69,27 +88,39 @@ class EnqueuePollMessageCommand extends MutatingCommand {
checkArgument( checkArgument(
domainOpt.isPresent(), "Domain %s doesn't exist or isn't active", domainName); domainOpt.isPresent(), "Domain %s doesn't exist or isn't active", domainName);
DomainBase domain = domainOpt.get(); DomainBase domain = domainOpt.get();
String registrarId = ImmutableList<String> registrarIds;
Optional.ofNullable(clientId).orElse(domain.getCurrentSponsorRegistrarId()); if (sendToAll) {
registrarIds =
Streams.stream(Registrar.loadAllCached())
.filter(r -> r.isLive() && r.getType() == Registrar.Type.REAL)
.map(Registrar::getRegistrarId)
.collect(ImmutableList.toImmutableList());
} else if (!isNullOrEmpty(clientIds)) {
registrarIds = ImmutableList.copyOf(clientIds);
} else {
registrarIds = ImmutableList.of(domain.getCurrentSponsorRegistrarId());
}
HistoryEntry historyEntry = HistoryEntry historyEntry =
new DomainHistory.Builder() new DomainHistory.Builder()
.setDomain(domain) .setDomain(domain)
.setType(SYNTHETIC) .setType(SYNTHETIC)
.setBySuperuser(true) .setBySuperuser(true)
.setReason("Manual enqueueing of poll message") .setReason("Manual enqueueing of poll message: " + message)
.setModificationTime(tm().getTransactionTime()) .setModificationTime(tm().getTransactionTime())
.setRequestedByRegistrar(false) .setRequestedByRegistrar(false)
.setRegistrarId(registrarId) .setRegistrarId(registryAdminClientId)
.build();
PollMessage.OneTime pollMessage =
new PollMessage.OneTime.Builder()
.setRegistrarId(registrarId)
.setParent(historyEntry)
.setEventTime(tm().getTransactionTime())
.setMsg(message)
.build(); .build();
stageEntityChange(null, historyEntry); stageEntityChange(null, historyEntry);
stageEntityChange(null, pollMessage); for (String registrarId : registrarIds) {
stageEntityChange(
null,
new PollMessage.OneTime.Builder()
.setRegistrarId(registrarId)
.setParent(historyEntry)
.setEventTime(tm().getTransactionTime())
.setMsg(message)
.build());
}
}); });
} }
} }

View file

@ -344,7 +344,7 @@ public class SyncRegistrarsSheetTest {
ImmutableMap<String, String> row = getOnlyElement(getOnlyElement(rowsCaptor.getAllValues())); ImmutableMap<String, String> row = getOnlyElement(getOnlyElement(rowsCaptor.getAllValues()));
assertThat(row).containsEntry("clientIdentifier", "SomeRegistrar"); assertThat(row).containsEntry("clientIdentifier", "SomeRegistrar");
assertThat(row).containsEntry("registrarName", "Some Registrar"); assertThat(row).containsEntry("registrarName", "Some Registrar");
assertThat(row).containsEntry("state", ""); assertThat(row).containsEntry("state", "ACTIVE");
assertThat(row).containsEntry("ianaIdentifier", "8"); assertThat(row).containsEntry("ianaIdentifier", "8");
assertThat(row).containsEntry("billingIdentifier", ""); assertThat(row).containsEntry("billingIdentifier", "");
assertThat(row).containsEntry("primaryContacts", ""); assertThat(row).containsEntry("primaryContacts", "");

View file

@ -96,6 +96,7 @@ import google.registry.model.index.ForeignKeyIndex;
import google.registry.model.poll.PollMessage; import google.registry.model.poll.PollMessage;
import google.registry.model.pricing.StaticPremiumListPricingEngine; import google.registry.model.pricing.StaticPremiumListPricingEngine;
import google.registry.model.registrar.Registrar; import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.Registrar.State;
import google.registry.model.registrar.RegistrarAddress; import google.registry.model.registrar.RegistrarAddress;
import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.HistoryEntry;
import google.registry.model.reporting.HistoryEntryDao; import google.registry.model.reporting.HistoryEntryDao;
@ -758,6 +759,7 @@ public class DatabaseHelper {
.setRegistrarId(registrarId) .setRegistrarId(registrarId)
.setRegistrarName(registrarName) .setRegistrarName(registrarName)
.setType(type) .setType(type)
.setState(State.ACTIVE)
.setIanaIdentifier(ianaIdentifier) .setIanaIdentifier(ianaIdentifier)
.setLocalizedAddress( .setLocalizedAddress(
new RegistrarAddress.Builder() new RegistrarAddress.Builder()

View file

@ -20,6 +20,7 @@ import static google.registry.testing.DatabaseHelper.assertPollMessages;
import static google.registry.testing.DatabaseHelper.createTld; import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.getOnlyHistoryEntryOfType; import static google.registry.testing.DatabaseHelper.getOnlyHistoryEntryOfType;
import static google.registry.testing.DatabaseHelper.persistActiveDomain; import static google.registry.testing.DatabaseHelper.persistActiveDomain;
import static google.registry.testing.DatabaseHelper.persistNewRegistrar;
import static google.registry.testing.HistoryEntrySubject.assertAboutHistoryEntries; import static google.registry.testing.HistoryEntrySubject.assertAboutHistoryEntries;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
@ -47,6 +48,8 @@ class EnqueuePollMessageCommandTest extends CommandTestCase<EnqueuePollMessageCo
createTld("tld"); createTld("tld");
inject.setStaticField(Ofy.class, "clock", fakeClock); inject.setStaticField(Ofy.class, "clock", fakeClock);
domain = persistActiveDomain("example.tld"); domain = persistActiveDomain("example.tld");
persistNewRegistrar("AdminRegistrar");
command.registryAdminClientId = "AdminRegistrar";
fakeClock.advanceOneMilli(); fakeClock.advanceOneMilli();
} }
@ -59,11 +62,11 @@ class EnqueuePollMessageCommandTest extends CommandTestCase<EnqueuePollMessageCo
.that(synthetic) .that(synthetic)
.bySuperuser(true) .bySuperuser(true)
.and() .and()
.hasMetadataReason("Manual enqueueing of poll message") .hasMetadataReason("Manual enqueueing of poll message: This domain is bad")
.and() .and()
.hasNoXml() .hasNoXml()
.and() .and()
.hasRegistrarId("TheRegistrar") .hasRegistrarId("AdminRegistrar")
.and() .and()
.hasModificationTime(fakeClock.nowUtc()) .hasModificationTime(fakeClock.nowUtc())
.and() .and()
@ -79,24 +82,35 @@ class EnqueuePollMessageCommandTest extends CommandTestCase<EnqueuePollMessageCo
} }
@TestOfyAndSql @TestOfyAndSql
void testSuccess_specifyClientId() throws Exception { void testSuccess_specifyClientIds() throws Exception {
persistNewRegistrar("foobaz");
runCommandForced( runCommandForced(
"--domain=example.tld", "--message=This domain needs work", "--client=NewRegistrar"); "--domain=example.tld",
"--message=This domain needs work",
"--clients=TheRegistrar,NewRegistrar,foobaz");
HistoryEntry synthetic = getOnlyHistoryEntryOfType(domain, SYNTHETIC); HistoryEntry synthetic = getOnlyHistoryEntryOfType(domain, SYNTHETIC);
assertAboutHistoryEntries() assertAboutHistoryEntries()
.that(synthetic) .that(synthetic)
.bySuperuser(true) .bySuperuser(true)
.and() .and()
.hasMetadataReason("Manual enqueueing of poll message") .hasMetadataReason("Manual enqueueing of poll message: This domain needs work")
.and() .and()
.hasNoXml() .hasNoXml()
.and() .and()
.hasRegistrarId("NewRegistrar") .hasRegistrarId("AdminRegistrar")
.and() .and()
.hasModificationTime(fakeClock.nowUtc()) .hasModificationTime(fakeClock.nowUtc())
.and() .and()
.hasMetadataRequestedByRegistrar(false); .hasMetadataRequestedByRegistrar(false);
assertPollMessages(
"TheRegistrar",
new PollMessage.OneTime.Builder()
.setParent(synthetic)
.setMsg("This domain needs work")
.setRegistrarId("TheRegistrar")
.setEventTime(fakeClock.nowUtc())
.build());
assertPollMessages( assertPollMessages(
"NewRegistrar", "NewRegistrar",
new PollMessage.OneTime.Builder() new PollMessage.OneTime.Builder()
@ -105,6 +119,59 @@ class EnqueuePollMessageCommandTest extends CommandTestCase<EnqueuePollMessageCo
.setRegistrarId("NewRegistrar") .setRegistrarId("NewRegistrar")
.setEventTime(fakeClock.nowUtc()) .setEventTime(fakeClock.nowUtc())
.build()); .build());
assertPollMessages(
"foobaz",
new PollMessage.OneTime.Builder()
.setParent(synthetic)
.setMsg("This domain needs work")
.setRegistrarId("foobaz")
.setEventTime(fakeClock.nowUtc())
.build());
}
@TestOfyAndSql
void testSuccess_sendToAllRegistrars() throws Exception {
persistNewRegistrar("foobaz");
runCommandForced("--domain=example.tld", "--message=This domain needs work", "--all=true");
HistoryEntry synthetic = getOnlyHistoryEntryOfType(domain, SYNTHETIC);
assertAboutHistoryEntries()
.that(synthetic)
.bySuperuser(true)
.and()
.hasMetadataReason("Manual enqueueing of poll message: This domain needs work")
.and()
.hasNoXml()
.and()
.hasRegistrarId("AdminRegistrar")
.and()
.hasModificationTime(fakeClock.nowUtc())
.and()
.hasMetadataRequestedByRegistrar(false);
assertPollMessages(
"TheRegistrar",
new PollMessage.OneTime.Builder()
.setParent(synthetic)
.setMsg("This domain needs work")
.setRegistrarId("TheRegistrar")
.setEventTime(fakeClock.nowUtc())
.build());
assertPollMessages(
"NewRegistrar",
new PollMessage.OneTime.Builder()
.setParent(synthetic)
.setMsg("This domain needs work")
.setRegistrarId("NewRegistrar")
.setEventTime(fakeClock.nowUtc())
.build());
assertPollMessages(
"foobaz",
new PollMessage.OneTime.Builder()
.setParent(synthetic)
.setMsg("This domain needs work")
.setRegistrarId("foobaz")
.setEventTime(fakeClock.nowUtc())
.build());
} }
@TestOfyAndSql @TestOfyAndSql
@ -131,4 +198,18 @@ class EnqueuePollMessageCommandTest extends CommandTestCase<EnqueuePollMessageCo
assertThrows(ParameterException.class, () -> runCommandForced("--domain=example.tld")); assertThrows(ParameterException.class, () -> runCommandForced("--domain=example.tld"));
assertThat(thrown).hasMessageThat().contains("The following option is required: -m, --message"); assertThat(thrown).hasMessageThat().contains("The following option is required: -m, --message");
} }
@TestOfyAndSql
void testCantSpecifyClientIdsAndAll() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
runCommandForced(
"--domain=example.tld",
"--message=Domain is ended",
"--all=true",
"--clients=TheRegistrar"));
assertThat(thrown).hasMessageThat().isEqualTo("Cannot specify both --all and --clients");
}
} }