Make losing client ID optional in bulk transfers (#3169)

When executing bulk domain transfers with an explicit list of domain names or
a domain names file, enforcing by losing registrar ID is often unnecessary
and redundant (b/537294004).

This commit makes --losing_registrar_id an optional command-line parameter
and updates BulkDomainTransferAction and BatchModule to handle an optional
losing sponsor ID. Existing behavior is preserved when the parameter is
explicitly supplied.

BUG= http://b/537294004
This commit is contained in:
Ben McIlwain
2026-07-29 02:00:39 +00:00
committed by GitHub
parent 0c79414a31
commit 29def8d78d
5 changed files with 60 additions and 14 deletions
@@ -169,8 +169,8 @@ public class BatchModule {
@Provides
@Parameter("losingRegistrarId")
static String provideLosingRegistrarId(HttpServletRequest req) {
return extractRequiredParameter(req, "losingRegistrarId");
static Optional<String> provideLosingRegistrarId(HttpServletRequest req) {
return extractOptionalParameter(req, "losingRegistrarId");
}
@Provides
@@ -109,7 +109,7 @@ public class BulkDomainTransferAction implements Runnable {
private final RateLimiter rateLimiter;
private final ImmutableList<String> bulkTransferDomainNames;
private final String gainingRegistrarId;
private final String losingRegistrarId;
private final Optional<String> losingRegistrarId;
private final boolean requestedByRegistrar;
private final String reason;
private final Response response;
@@ -127,7 +127,7 @@ public class BulkDomainTransferAction implements Runnable {
@Named("standardRateLimiter") RateLimiter rateLimiter,
@Parameter("bulkTransferDomainNames") ImmutableList<String> bulkTransferDomainNames,
@Parameter("gainingRegistrarId") String gainingRegistrarId,
@Parameter("losingRegistrarId") String losingRegistrarId,
@Parameter("losingRegistrarId") Optional<String> losingRegistrarId,
@Parameter("requestedByRegistrar") boolean requestedByRegistrar,
@Parameter("reason") String reason,
Response response) {
@@ -225,7 +225,7 @@ public class BulkDomainTransferAction implements Runnable {
alreadyTransferred++;
return true;
}
if (!currentRegistrarId.equals(losingRegistrarId)) {
if (losingRegistrarId.isPresent() && !currentRegistrarId.equals(losingRegistrarId.get())) {
logger.atWarning().log(
"Domain '%s' had unexpected registrar '%s'", domainName, currentRegistrarId);
errors++;
@@ -33,6 +33,7 @@ import google.registry.util.DomainNameUtils;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
/**
* A command to bulk-transfer any number of domains from one registrar to another.
@@ -76,8 +77,7 @@ public class BulkDomainTransferCommand extends ConfirmingCommand implements Comm
@Parameter(
names = {"-l", "--losing_registrar_id"},
description = "The ID of the registrar from which domains should be transferred",
required = true)
description = "The ID of the registrar from which domains should be transferred")
private String losingRegistrarId;
@Parameter(
@@ -119,14 +119,17 @@ public class BulkDomainTransferCommand extends ConfirmingCommand implements Comm
Registrar.loadByRegistrarIdCached(gainingRegistrarId).isPresent(),
"Gaining registrar %s doesn't exist",
gainingRegistrarId);
checkArgument(
Registrar.loadByRegistrarIdCached(losingRegistrarId).isPresent(),
"Losing registrar %s doesn't exist",
losingRegistrarId);
if (losingRegistrarId != null) {
checkArgument(
Registrar.loadByRegistrarIdCached(losingRegistrarId).isPresent(),
"Losing registrar %s doesn't exist",
losingRegistrarId);
}
ImmutableMap.Builder<String, Object> paramsBuilder = new ImmutableMap.Builder<>();
paramsBuilder.put("gainingRegistrarId", gainingRegistrarId);
paramsBuilder.put("losingRegistrarId", losingRegistrarId);
Optional.ofNullable(losingRegistrarId)
.ifPresent(id -> paramsBuilder.put("losingRegistrarId", id));
paramsBuilder.put("requestedByRegistrar", requestedByRegistrar);
paramsBuilder.put("reason", reason);
if (maxQps > 0) {
@@ -39,6 +39,7 @@ import google.registry.testing.FakeClock;
import google.registry.testing.FakeLockHandler;
import google.registry.testing.FakeResponse;
import java.time.Instant;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
@@ -127,7 +128,22 @@ public class BulkDomainTransferActionTest {
assertThat(deletedDomain.getUpdateTimestamp().getTimestamp()).isEqualTo(preRunTime);
}
private BulkDomainTransferAction createAction(String... domains) {
@Test
void testSuccess_withoutLosingRegistrarId() {
BulkDomainTransferAction action =
createActionWithOptionalLosingRegistrar(
Optional.empty(), "active.tld", "alreadytransferred.tld");
fakeClock.advanceOneMilli();
Instant now = fakeClock.now();
action.run();
assertThat(response.getStatus()).isEqualTo(200);
activeDomain = loadByEntity(activeDomain);
assertThat(activeDomain.cloneProjectedAtTime(now).getCurrentSponsorRegistrarId())
.isEqualTo("NewRegistrar");
}
private BulkDomainTransferAction createActionWithOptionalLosingRegistrar(
Optional<String> losingRegistrarId, String... domains) {
EppController eppController =
DaggerEppTestComponent.builder()
.fakesAndMocksModule(FakesAndMocksModule.create(new FakeClock()))
@@ -140,9 +156,13 @@ public class BulkDomainTransferActionTest {
rateLimiter,
ImmutableList.copyOf(domains),
"NewRegistrar",
"TheRegistrar",
losingRegistrarId,
true,
"reason",
response);
}
private BulkDomainTransferAction createAction(String... domains) {
return createActionWithOptionalLosingRegistrar(Optional.of("TheRegistrar"), domains);
}
}
@@ -193,4 +193,27 @@ public class BulkDomainTransferCommandTest extends CommandTestCase<BulkDomainTra
MediaType.PLAIN_TEXT_UTF_8,
"[\"foo.tld\",\"bar.tld\"]".getBytes(UTF_8));
}
@Test
void testSuccess_noLosingRegistrarId() throws Exception {
runCommandForced(
"--gaining_registrar_id",
"NewRegistrar",
"--reason",
"someReason",
"--domains",
"foo.tld,bar.tld");
verify(connection)
.sendPostRequest(
"/_dr/task/bulkDomainTransfer",
ImmutableMap.of(
"gainingRegistrarId",
"NewRegistrar",
"requestedByRegistrar",
false,
"reason",
"someReason"),
MediaType.PLAIN_TEXT_UTF_8,
"[\"foo.tld\",\"bar.tld\"]".getBytes(UTF_8));
}
}