Compare commits

..
Author SHA1 Message Date
Pavlo TkachandGitHub 3108e8a871 Use builder image as a base for schema-deployer and schema-verifier (#1955) 2023-03-13 15:37:02 -04:00
Pavlo TkachandGitHub ec142caf9c Expand ID Token Auth verifier to catch all exceptions (#1960) 2023-03-13 12:12:47 -04:00
Pavlo TkachandGitHub e60ad58098 Restore resaveAllEppResourcesPipeline as a cloud task (#1953) 2023-03-13 10:44:25 -04:00
sarahcaseybotandGitHub 83e9e7fb5c Add allowedEppActions field to AllocationToken (#1957) 2023-03-10 14:14:47 -05:00
Pavlo TkachandGitHub 438c523fcb Remove app engine deps from Lock (#1910) 2023-03-09 10:47:48 -05:00
Lai JiangandGitHub 025a2faff2 Drop the indexs and columns for dns_refresh_request_time (#1949) 2023-03-09 10:29:31 -05:00
gbrodmanandGitHub fd822dd333 Add create/delete/update commands for User objects (#1936)
This also includes the change of allowing the Java User object to have a
null GAIA ID (when creating user objects, we don't know what the GAIA ID
is).
2023-03-07 17:18:48 -05:00
Ben McIlwainandGitHub 9b93749d43 Double the number of frontend instances from 12 to 24 (#1954)
It seems like we're hitting App Engine capacity issues resulting in actual pages
now (for whatever reason, but likely one customer), and we obviously don't want
that.
2023-03-06 16:04:28 -05:00
36 changed files with 4212 additions and 4166 deletions
@@ -7,7 +7,7 @@
<sessions-enabled>true</sessions-enabled>
<instance-class>B4_1G</instance-class>
<manual-scaling>
<instances>12</instances>
<instances>24</instances>
</manual-scaling>
<system-properties>
@@ -90,7 +90,18 @@
<schedule>0 */1 * * *</schedule>
</task>
<!-- TODO: @ptkach add resaveAllEppResourcesPipelineAction https://cs.opensource.google/nomulus/nomulus/+/master:core/src/main/java/google/registry/env/production/default/WEB-INF/cron.xml;l=105 -->
<task>
<url><![CDATA[/_dr/task/resaveAllEppResourcesPipeline?fast=true]]></url>
<name>resaveAllEppResourcesPipeline</name>
<description>
This job resaves all our resources, projected in time to "now".
</description>
<!--
Deviation from cron tasks schedule: 1st monday of month 09:00 is replaced
with 1st of the month 09:00
-->
<schedule>0 9 1 * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/task/updateRegistrarRdapBaseUrls]]></url>
@@ -95,6 +95,19 @@
<schedule>0 3 * * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/task/resaveAllEppResourcesPipeline?fast=true]]></url>
<name>resaveAllEppResourcesPipeline</name>
<description>
This job resaves all our resources, projected in time to "now".
</description>
<!--
Deviation from cron tasks schedule: 1st monday of month 09:00 is replaced
with 1st of the month 09:00
-->
<schedule>0 9 1 * *</schedule>
</task>
<task>
<url><![CDATA[/_dr/task/deleteExpiredDomains]]></url>
<name>deleteExpiredDomains</name>
@@ -132,8 +145,6 @@
<schedule>0 5 * * *</schedule>
</task>
<!-- TODO: @ptkach add resaveAllEppResourcesPipelineAction https://cs.opensource.google/nomulus/nomulus/+/master:core/src/main/java/google/registry/env/sandbox/default/WEB-INF/cron.xml;l=89 -->
<task>
<url><![CDATA[/_dr/cron/readDnsQueue?jitterSeconds=45]]></url>
<name>readDnsQueue</name>
@@ -24,6 +24,7 @@ import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import google.registry.model.Buildable;
import google.registry.model.UpdateAutoTimestampEntity;
import google.registry.persistence.VKey;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
@@ -47,7 +48,6 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
private Long id;
/** GAIA ID associated with the user in question. */
@Column(nullable = false)
private String gaiaId;
/** Email address of the user in question. */
@@ -118,6 +118,11 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
return new Builder(clone(this));
}
@Override
public VKey<User> createVKey() {
return VKey.create(User.class, getId());
}
/** Builder for constructing immutable {@link User} objects. */
public static class Builder extends Buildable.Builder<User> {
@@ -129,7 +134,6 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
@Override
public User build() {
checkArgumentNotNull(getInstance().gaiaId, "Gaia ID cannot be null");
checkArgumentNotNull(getInstance().emailAddress, "Email address cannot be null");
checkArgumentNotNull(getInstance().userRoles, "User roles cannot be null");
return super.build();
@@ -25,8 +25,6 @@ import com.google.common.base.Strings;
import com.google.common.flogger.FluentLogger;
import google.registry.model.ImmutableObject;
import google.registry.persistence.VKey;
import google.registry.util.RequestStatusChecker;
import google.registry.util.RequestStatusCheckerImpl;
import java.io.Serializable;
import java.util.Optional;
import java.util.function.Supplier;
@@ -70,8 +68,7 @@ public class Lock extends ImmutableObject implements Serializable {
enum LockState {
IN_USE,
FREE,
TIMED_OUT,
OWNER_DIED
TIMED_OUT
}
@VisibleForTesting static LockMetrics lockMetrics = new LockMetrics();
@@ -79,17 +76,6 @@ public class Lock extends ImmutableObject implements Serializable {
/** The name of the locked resource. */
@Transient @Id String lockId;
/**
* Unique log ID of the request that owns this lock.
*
* <p>When that request is no longer running (is finished), the lock can be considered implicitly
* released.
*
* <p>See {@link RequestStatusCheckerImpl#getLogId} for details about how it's created in
* practice.
*/
@Column String requestLogId;
/** When the lock can be considered implicitly released. */
@Column(nullable = false)
DateTime expirationTime;
@@ -124,7 +110,6 @@ public class Lock extends ImmutableObject implements Serializable {
private static Lock create(
String resourceName,
String scope,
String requestLogId,
DateTime acquiredTime,
Duration leaseLength) {
checkArgument(!Strings.isNullOrEmpty(resourceName), "resourceName cannot be null or empty");
@@ -132,7 +117,6 @@ public class Lock extends ImmutableObject implements Serializable {
// Add the scope to the Lock's id so that it is unique for locks acquiring the same resource
// across different TLDs.
instance.lockId = makeLockId(resourceName, scope);
instance.requestLogId = requestLogId;
instance.expirationTime = acquiredTime.plus(leaseLength);
instance.acquiredTime = acquiredTime;
instance.resourceName = resourceName;
@@ -172,18 +156,13 @@ public class Lock extends ImmutableObject implements Serializable {
switch (acquireResult.lockState()) {
case IN_USE:
logger.atInfo().log(
"Existing lock by request %s is still valid now %s (until %s) lock: %s",
lock.requestLogId, now, lock.expirationTime, lock.lockId);
"Existing lock by request is still valid now %s (until %s) lock: %s",
now, lock.expirationTime, lock.lockId);
break;
case TIMED_OUT:
logger.atInfo().log(
"Existing lock by request %s is timed out now %s (was valid until %s) lock: %s",
lock.requestLogId, now, lock.expirationTime, lock.lockId);
break;
case OWNER_DIED:
logger.atInfo().log(
"Existing lock is valid now %s (until %s), but owner (%s) isn't running lock: %s",
now, lock.expirationTime, lock.requestLogId, lock.lockId);
"Existing lock by request is timed out now %s (was valid until %s) lock: %s",
now, lock.expirationTime, lock.lockId);
break;
case FREE:
// There was no existing lock
@@ -203,11 +182,7 @@ public class Lock extends ImmutableObject implements Serializable {
/** Try to acquire a lock. Returns absent if it can't be acquired. */
public static Optional<Lock> acquire(
String resourceName,
@Nullable String tld,
Duration leaseLength,
RequestStatusChecker requestStatusChecker,
boolean checkThreadRunning) {
String resourceName, @Nullable String tld, Duration leaseLength) {
String scope = tld != null ? tld : GLOBAL;
Supplier<AcquireResult> lockAcquirer =
() -> {
@@ -219,22 +194,19 @@ public class Lock extends ImmutableObject implements Serializable {
.orElse(null);
if (lock != null) {
logger.atInfo().log(
"Loaded existing lock: %s for request: %s", lock.lockId, lock.requestLogId);
"Loaded existing lock: %s for resource: %s", lock.lockId, lock.resourceName);
}
LockState lockState;
if (lock == null) {
lockState = LockState.FREE;
} else if (isAtOrAfter(now, lock.expirationTime)) {
lockState = LockState.TIMED_OUT;
} else if (checkThreadRunning && !requestStatusChecker.isRunning(lock.requestLogId)) {
lockState = LockState.OWNER_DIED;
} else {
lockState = LockState.IN_USE;
return AcquireResult.create(now, lock, null, lockState);
}
Lock newLock =
create(resourceName, scope, requestStatusChecker.getLogId(), now, leaseLength);
Lock newLock = create(resourceName, scope, now, leaseLength);
tm().put(newLock);
return AcquireResult.create(now, lock, newLock, lockState);
@@ -39,8 +39,6 @@ import google.registry.request.HttpException.UnsupportedMediaTypeException;
import google.registry.request.auth.AuthResult;
import google.registry.request.lock.LockHandler;
import google.registry.request.lock.LockHandlerImpl;
import google.registry.util.RequestStatusChecker;
import google.registry.util.RequestStatusCheckerImpl;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
@@ -192,18 +190,6 @@ public final class RequestModule {
return lockHandler;
}
@Provides
static RequestStatusChecker provideRequestStatusChecker(
RequestStatusCheckerImpl requestStatusChecker) {
return requestStatusChecker;
}
@Provides
@RequestLogId
static String provideRequestLogId(RequestStatusChecker requestStatusChecker) {
return requestStatusChecker.getLogId();
}
@Provides
@JsonPayload
@SuppressWarnings("unchecked")
@@ -55,7 +55,7 @@ public abstract class IdTokenAuthenticationBase implements AuthenticationMechani
JsonWebSignature token;
try {
token = tokenVerifier.verify(rawIdToken);
} catch (TokenVerifier.VerificationException e) {
} catch (Exception e) {
logger.atInfo().withCause(e).log("Error when verifying access token");
return AuthResult.NOT_AUTHENTICATED;
}
@@ -26,7 +26,6 @@ import com.google.common.util.concurrent.UncheckedExecutionException;
import google.registry.model.server.Lock;
import google.registry.util.AppEngineTimeLimiter;
import google.registry.util.Clock;
import google.registry.util.RequestStatusChecker;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
@@ -48,12 +47,10 @@ public class LockHandlerImpl implements LockHandler {
/** Fudge factor to make sure we kill threads before a lock actually expires. */
private static final Duration LOCK_TIMEOUT_FUDGE = Duration.standardSeconds(5);
private final RequestStatusChecker requestStatusChecker;
private final Clock clock;
@Inject
public LockHandlerImpl(RequestStatusChecker requestStatusChecker, Clock clock) {
this.requestStatusChecker = requestStatusChecker;
public LockHandlerImpl(Clock clock) {
this.clock = clock;
}
@@ -114,7 +111,7 @@ public class LockHandlerImpl implements LockHandler {
/** Allows injection of mock Lock in tests. */
@VisibleForTesting
Optional<Lock> acquire(String lockName, @Nullable String tld, Duration leaseLength) {
return Lock.acquire(lockName, tld, leaseLength, requestStatusChecker, true);
return Lock.acquire(lockName, tld, leaseLength);
}
private interface LockAcquirer {
@@ -0,0 +1,85 @@
// Copyright 2023 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;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.beust.jcommander.Parameter;
import com.google.common.collect.ImmutableMap;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.model.console.UserRoles;
import google.registry.tools.params.KeyValueMapParameter.StringToRegistrarRoleMap;
import java.util.Optional;
import javax.annotation.Nullable;
/** Shared base class for commands that create or modify a {@link User}. */
public abstract class CreateOrUpdateUserCommand extends ConfirmingCommand {
@Nullable
@Parameter(names = "--email", description = "Email address of the user", required = true)
String email;
@Nullable
@Parameter(
names = "--admin",
description = "Whether or not the user in question is an admin",
arity = 1)
private Boolean isAdmin;
@Nullable
@Parameter(
names = "--global_role",
description = "Global role, e.g. SUPPORT_LEAD, to apply to the user")
private GlobalRole globalRole;
@Nullable
@Parameter(
names = "--registrar_roles",
converter = StringToRegistrarRoleMap.class,
validateWith = StringToRegistrarRoleMap.class,
description =
"Comma-delimited mapping of registrar name to role that the user has on that registrar")
private ImmutableMap<String, RegistrarRole> registrarRolesMap;
@Nullable
abstract User getExistingUser(String email);
@Override
protected final String execute() throws Exception {
checkArgumentNotNull(email, "Email must be provided");
tm().transact(this::executeInTransaction);
return String.format("Saved user with email %s", email);
}
private void executeInTransaction() {
User user = getExistingUser(email);
UserRoles.Builder userRolesBuilder =
(user == null) ? new UserRoles.Builder() : user.getUserRoles().asBuilder();
Optional.ofNullable(globalRole).ifPresent(userRolesBuilder::setGlobalRole);
Optional.ofNullable(registrarRolesMap).ifPresent(userRolesBuilder::setRegistrarRoles);
Optional.ofNullable(isAdmin).ifPresent(userRolesBuilder::setIsAdmin);
User.Builder builder =
(user == null) ? new User.Builder().setEmailAddress(email) : user.asBuilder();
builder.setUserRoles(userRolesBuilder.build());
User newUser = builder.build();
UserDao.saveUser(newUser);
}
}
@@ -0,0 +1,35 @@
// Copyright 2023 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;
import static com.google.common.base.Preconditions.checkArgument;
import com.beust.jcommander.Parameters;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import javax.annotation.Nullable;
/** Command to create a new User. */
@Parameters(separators = " =", commandDescription = "Update a user account")
public class CreateUserCommand extends CreateOrUpdateUserCommand {
@Nullable
@Override
User getExistingUser(String email) {
checkArgument(
!UserDao.loadUser(email).isPresent(), "A user with email %s already exists", email);
return null;
}
}
@@ -0,0 +1,53 @@
// Copyright 2023 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;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import java.util.Optional;
import javax.annotation.Nullable;
/** Deletes a {@link User}. */
@Parameters(separators = " =", commandDescription = "Delete a user account")
public class DeleteUserCommand extends ConfirmingCommand {
@Nullable
@Parameter(names = "--email", description = "Email address of the user", required = true)
String email;
@Override
protected String prompt() {
checkArgumentNotNull(email, "Email must be provided");
checkArgumentPresent(UserDao.loadUser(email), "Email does not correspond to a valid user");
return String.format("Delete user with email %s?", email);
}
@Override
protected String execute() throws Exception {
tm().transact(
() -> {
Optional<User> optionalUser = UserDao.loadUser(email);
checkArgumentPresent(optionalUser, "Email no longer corresponds to a valid user");
tm().delete(optionalUser.get());
});
return String.format("Deleted user with email %s", email);
}
}
@@ -46,6 +46,7 @@ public final class RegistryTool {
.put("create_registrar_groups", CreateRegistrarGroupsCommand.class)
.put("create_reserved_list", CreateReservedListCommand.class)
.put("create_tld", CreateTldCommand.class)
.put("create_user", CreateUserCommand.class)
.put("curl", CurlCommand.class)
.put("delete_allocation_tokens", DeleteAllocationTokensCommand.class)
.put("delete_domain", DeleteDomainCommand.class)
@@ -53,6 +54,7 @@ public final class RegistryTool {
.put("delete_premium_list", DeletePremiumListCommand.class)
.put("delete_reserved_list", DeleteReservedListCommand.class)
.put("delete_tld", DeleteTldCommand.class)
.put("delete_user", DeleteUserCommand.class)
.put("encrypt_escrow_deposit", EncryptEscrowDepositCommand.class)
.put("enqueue_poll_message", EnqueuePollMessageCommand.class)
.put("execute_epp", ExecuteEppCommand.class)
@@ -110,6 +112,7 @@ public final class RegistryTool {
.put("update_reserved_list", UpdateReservedListCommand.class)
.put("update_server_locks", UpdateServerLocksCommand.class)
.put("update_tld", UpdateTldCommand.class)
.put("update_user", UpdateUserCommand.class)
.put("upload_claims_list", UploadClaimsListCommand.class)
.put("validate_escrow_deposit", ValidateEscrowDepositCommand.class)
.put("validate_login_credentials", ValidateLoginCredentialsCommand.class)
@@ -0,0 +1,33 @@
// Copyright 2023 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;
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
import com.beust.jcommander.Parameters;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import javax.annotation.Nullable;
/** Updates a user, assuming that the user in question already exists. */
@Parameters(separators = " =", commandDescription = "Update a user account")
public class UpdateUserCommand extends CreateOrUpdateUserCommand {
@Nullable
@Override
User getExistingUser(String email) {
return checkArgumentPresent(UserDao.loadUser(email), "User %s not found", email);
}
}
@@ -17,6 +17,7 @@ package google.registry.tools.params;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import google.registry.model.console.RegistrarRole;
import java.util.Map;
import org.joda.money.CurrencyUnit;
@@ -107,4 +108,18 @@ public abstract class KeyValueMapParameter<K, V>
return value;
}
}
/** Combined converter/validator class for maps of registrar names to registrar roles. */
public static class StringToRegistrarRoleMap extends KeyValueMapParameter<String, RegistrarRole> {
@Override
protected String parseKey(String rawKey) {
return rawKey;
}
@Override
protected RegistrarRole parseValue(String rawValue) {
return RegistrarRole.valueOf(rawValue);
}
}
}
@@ -72,11 +72,7 @@ public class UserTest extends EntityTestCase {
assertThat(assertThrows(IllegalArgumentException.class, () -> builder.setUserRoles(null)))
.hasMessageThat()
.isEqualTo("User roles cannot be null");
assertThat(assertThrows(IllegalArgumentException.class, builder::build))
.hasMessageThat()
.isEqualTo("Gaia ID cannot be null");
builder.setGaiaId("gaiaId");
assertThat(assertThrows(IllegalArgumentException.class, builder::build))
.hasMessageThat()
.isEqualTo("Email address cannot be null");
@@ -18,17 +18,14 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static google.registry.model.server.Lock.LockState.FREE;
import static google.registry.model.server.Lock.LockState.IN_USE;
import static google.registry.model.server.Lock.LockState.OWNER_DIED;
import static google.registry.model.server.Lock.LockState.TIMED_OUT;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import google.registry.model.EntityTestCase;
import google.registry.model.server.Lock.LockState;
import google.registry.util.RequestStatusChecker;
import java.util.Optional;
import org.joda.time.Duration;
import org.junit.jupiter.api.AfterEach;
@@ -41,7 +38,6 @@ public class LockTest extends EntityTestCase {
private static final String RESOURCE_NAME = "foo";
private static final Duration ONE_DAY = Duration.standardDays(1);
private static final Duration TWO_MILLIS = Duration.millis(2);
private static final RequestStatusChecker requestStatusChecker = mock(RequestStatusChecker.class);
private LockMetrics origLockMetrics;
@@ -52,7 +48,7 @@ public class LockTest extends EntityTestCase {
private static Optional<Lock> acquire(
String tld, Duration leaseLength, LockState expectedLockState) {
Lock.lockMetrics = mock(LockMetrics.class);
Optional<Lock> lock = Lock.acquire(RESOURCE_NAME, tld, leaseLength, requestStatusChecker, true);
Optional<Lock> lock = Lock.acquire(RESOURCE_NAME, tld, leaseLength);
verify(Lock.lockMetrics).recordAcquire(RESOURCE_NAME, tld, expectedLockState);
verifyNoMoreInteractions(Lock.lockMetrics);
Lock.lockMetrics = null;
@@ -72,8 +68,6 @@ public class LockTest extends EntityTestCase {
void beforeEach() {
origLockMetrics = Lock.lockMetrics;
Lock.lockMetrics = null;
when(requestStatusChecker.getLogId()).thenReturn("current-request-id");
when(requestStatusChecker.isRunning("current-request-id")).thenReturn(true);
}
@AfterEach
@@ -111,9 +105,6 @@ public class LockTest extends EntityTestCase {
assertThat(acquire("", ONE_DAY, FREE)).isPresent();
// We can't get it again while request is active
assertThat(acquire("", ONE_DAY, IN_USE)).isEmpty();
// But if request is finished, we can get it.
when(requestStatusChecker.isRunning("current-request-id")).thenReturn(false);
assertThat(acquire("", ONE_DAY, OWNER_DIED)).isPresent();
}
@Test
@@ -134,9 +125,7 @@ public class LockTest extends EntityTestCase {
@Test
void testFailure_emptyResourceName() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> Lock.acquire("", "", TWO_MILLIS, requestStatusChecker, true));
assertThrows(IllegalArgumentException.class, () -> Lock.acquire("", "", TWO_MILLIS));
assertThat(thrown).hasMessageThat().contains("resourceName cannot be null or empty");
}
}
@@ -23,7 +23,6 @@ import static org.mockito.Mockito.verify;
import google.registry.model.server.Lock;
import google.registry.testing.FakeClock;
import google.registry.testing.UserServiceExtension;
import google.registry.util.RequestStatusCheckerImpl;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeoutException;
@@ -134,7 +133,7 @@ final class LockHandlerImplTest {
}
private LockHandler createTestLockHandler(@Nullable Lock acquiredLock) {
return new LockHandlerImpl(new RequestStatusCheckerImpl(), clock) {
return new LockHandlerImpl(clock) {
private static final long serialVersionUID = 0L;
@Override
@@ -0,0 +1,81 @@
// Copyright 2023 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;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.testing.DatabaseHelper;
import org.junit.jupiter.api.Test;
/** Tests for {@link CreateUserCommand}. */
public class CreateUserCommandTest extends CommandTestCase<CreateUserCommand> {
@Test
void testSuccess() throws Exception {
runCommandForced("--email", "user@example.test");
User onlyUser = Iterables.getOnlyElement(DatabaseHelper.loadAllOf(User.class));
assertThat(onlyUser.getEmailAddress()).isEqualTo("user@example.test");
assertThat(onlyUser.getUserRoles().isAdmin()).isFalse();
assertThat(onlyUser.getUserRoles().getGlobalRole()).isEqualTo(GlobalRole.NONE);
assertThat(onlyUser.getUserRoles().getRegistrarRoles()).isEmpty();
}
@Test
void testSuccess_admin() throws Exception {
runCommandForced("--email", "user@example.test", "--admin", "true");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().isAdmin()).isTrue();
}
@Test
void testSuccess_globalRole() throws Exception {
runCommandForced("--email", "user@example.test", "--global_role", "FTE");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getGlobalRole())
.isEqualTo(GlobalRole.FTE);
}
@Test
void testSuccess_registrarRoles() throws Exception {
runCommandForced(
"--email",
"user@example.test",
"--registrar_roles",
"TheRegistrar=ACCOUNT_MANAGER,NewRegistrar=PRIMARY_CONTACT");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getRegistrarRoles())
.isEqualTo(
ImmutableMap.of(
"TheRegistrar",
RegistrarRole.ACCOUNT_MANAGER,
"NewRegistrar",
RegistrarRole.PRIMARY_CONTACT));
}
@Test
void testFailure_alreadyExists() throws Exception {
runCommandForced("--email", "user@example.test");
assertThat(
assertThrows(
IllegalArgumentException.class,
() -> runCommandForced("--email", "user@example.test")))
.hasMessageThat()
.isEqualTo("A user with email user@example.test already exists");
}
}
@@ -0,0 +1,53 @@
// Copyright 2023 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;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static org.junit.Assert.assertThrows;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.model.console.UserRoles;
import org.junit.jupiter.api.Test;
/** Tests for {@link DeleteUserCommand}. */
public class DeleteUserCommandTest extends CommandTestCase<DeleteUserCommand> {
@Test
void testSuccess_deletesUser() throws Exception {
User user =
new User.Builder()
.setEmailAddress("email@example.test")
.setUserRoles(
new UserRoles.Builder().setGlobalRole(GlobalRole.FTE).setIsAdmin(true).build())
.build();
UserDao.saveUser(user);
assertThat(UserDao.loadUser("email@example.test")).isPresent();
runCommandForced("--email", "email@example.test");
assertThat(UserDao.loadUser("email@example.test")).isEmpty();
}
@Test
void testFailure_nonexistent() {
assertThat(
assertThrows(
IllegalArgumentException.class,
() -> runCommandForced("--email", "nonexistent@example.test")))
.hasMessageThat()
.isEqualTo("Email does not correspond to a valid user");
}
}
@@ -0,0 +1,89 @@
// Copyright 2023 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;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableMap;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserDao;
import google.registry.model.console.UserRoles;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** Tests for {@link UpdateUserCommand}. */
public class UpdateUserCommandTest extends CommandTestCase<UpdateUserCommand> {
@BeforeEach
void beforeEach() throws Exception {
UserDao.saveUser(
new User.Builder()
.setEmailAddress("user@example.test")
.setUserRoles(new UserRoles.Builder().build())
.build());
}
@Test
void testSuccess_admin() throws Exception {
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().isAdmin()).isFalse();
runCommandForced("--email", "user@example.test", "--admin", "true");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().isAdmin()).isTrue();
runCommandForced("--email", "user@example.test", "--admin", "false");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().isAdmin()).isFalse();
}
@Test
void testSuccess_registrarRoles() throws Exception {
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getRegistrarRoles())
.isEmpty();
runCommandForced(
"--email",
"user@example.test",
"--registrar_roles",
"TheRegistrar=ACCOUNT_MANAGER,NewRegistrar=PRIMARY_CONTACT");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getRegistrarRoles())
.isEqualTo(
ImmutableMap.of(
"TheRegistrar",
RegistrarRole.ACCOUNT_MANAGER,
"NewRegistrar",
RegistrarRole.PRIMARY_CONTACT));
runCommandForced("--email", "user@example.test", "--registrar_roles", "");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getRegistrarRoles())
.isEmpty();
}
@Test
void testSuccess_globalRole() throws Exception {
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getGlobalRole())
.isEqualTo(GlobalRole.NONE);
runCommandForced("--email", "user@example.test", "--global_role", "FTE");
assertThat(UserDao.loadUser("user@example.test").get().getUserRoles().getGlobalRole())
.isEqualTo(GlobalRole.FTE);
}
@Test
void testFailure_doesntExist() {
assertThat(
assertThrows(
IllegalArgumentException.class,
() -> runCommandForced("--email", "nonexistent@example.test")))
.hasMessageThat()
.isEqualTo("User nonexistent@example.test not found");
}
}
@@ -1,129 +0,0 @@
// 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.util;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.LogsSubject.assertAboutLogs;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.google.appengine.api.log.LogQuery;
import com.google.appengine.api.log.LogService;
import com.google.appengine.api.log.RequestLogs;
import com.google.apphosting.api.ApiProxy;
import com.google.common.collect.ImmutableList;
import com.google.common.testing.TestLogHandler;
import google.registry.testing.UserServiceExtension;
import java.util.logging.Level;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link RequestStatusCheckerImpl}. */
final class RequestStatusCheckerImplTest {
private static final TestLogHandler logHandler = new TestLogHandler();
private static final RequestStatusChecker requestStatusChecker = new RequestStatusCheckerImpl();
/**
* Matcher for the expected LogQuery in {@link RequestStatusCheckerImpl#isRunning}.
*
* Because LogQuery doesn't have a .equals function, we have to create an actual matcher to make
* sure we have the right argument in our mocks.
*/
private static LogQuery expectedLogQuery(final String requestLogId) {
return argThat(
object -> {
assertThat(object).isInstanceOf(LogQuery.class);
assertThat(object.getRequestIds()).containsExactly(requestLogId);
assertThat(object.getIncludeAppLogs()).isFalse();
assertThat(object.getIncludeIncomplete()).isTrue();
return true;
});
}
// We do not actually need to set up user service, rather, we just need this extension to set up
// App Engine environment so the status checker can make an App Engine API call.
@RegisterExtension UserServiceExtension userService = new UserServiceExtension("");
@BeforeEach
void beforeEach() {
JdkLoggerConfig.getConfig(RequestStatusCheckerImpl.class).addHandler(logHandler);
RequestStatusCheckerImpl.logService = mock(LogService.class);
}
@AfterEach
void afterEach() {
JdkLoggerConfig.getConfig(RequestStatusCheckerImpl.class).removeHandler(logHandler);
}
// If a logId is unrecognized, it could be that the log hasn't been uploaded yet - so we assume
// it's a request that has just started running recently.
@Test
void testIsRunning_unrecognized() {
when(RequestStatusCheckerImpl.logService.fetch(expectedLogQuery("12345678")))
.thenReturn(ImmutableList.of());
assertThat(requestStatusChecker.isRunning("12345678")).isTrue();
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(Level.INFO, "Queried an unrecognized requestLogId");
}
@Test
void testIsRunning_notFinished() {
RequestLogs requestLogs = new RequestLogs();
requestLogs.setFinished(false);
when(RequestStatusCheckerImpl.logService.fetch(expectedLogQuery("12345678")))
.thenReturn(ImmutableList.of(requestLogs));
assertThat(requestStatusChecker.isRunning("12345678")).isTrue();
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(Level.INFO, "isFinished: false");
}
@Test
void testIsRunning_finished() {
RequestLogs requestLogs = new RequestLogs();
requestLogs.setFinished(true);
when(RequestStatusCheckerImpl.logService.fetch(expectedLogQuery("12345678")))
.thenReturn(ImmutableList.of(requestLogs));
assertThat(requestStatusChecker.isRunning("12345678")).isFalse();
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(Level.INFO, "isFinished: true");
}
@Test
void testGetLogId_returnsRequestLogId() {
String expectedLogId = ApiProxy.getCurrentEnvironment().getAttributes().get(
"com.google.appengine.runtime.request_log_id").toString();
assertThat(requestStatusChecker.getLogId()).isEqualTo(expectedLogId);
}
@Test
void testGetLogId_createsLog() {
requestStatusChecker.getLogId();
assertAboutLogs()
.that(logHandler)
.hasLogAtLevelWithMessage(Level.INFO, "Current requestLogId: ");
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2
View File
@@ -135,3 +135,5 @@ V134__drop_not_null_request_id_lock_table.sql
V135__null_gaia_id_user.sql
V136__add_dns_refresh_request_table.sql
V137__add_process_time_column.sql
V138__drop_dns_refresh_request_time_column.sql
V139__add_allowed_epp_actions_column.sql
@@ -0,0 +1,19 @@
-- Copyright 2023 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 "Domain" DROP COLUMN IF EXISTS dns_refresh_request_time;
ALTER TABLE "DomainHistory" DROP COLUMN IF EXISTS dns_refresh_request_time;
ALTER TABLE "Host" DROP COLUMN IF EXISTS dns_refresh_request_time;
ALTER TABLE "HostHistory" DROP COLUMN IF EXISTS dns_refresh_request_time;
@@ -0,0 +1,15 @@
-- Copyright 2023 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 "AllocationToken" ADD COLUMN allowed_epp_actions text[];
@@ -488,7 +488,6 @@
scope text not null,
acquired_time timestamptz not null,
expiration_time timestamptz not null,
request_log_id text,
primary key (resource_name, scope)
);
@@ -747,7 +746,7 @@
id bigserial not null,
update_timestamp timestamptz,
email_address text not null,
gaia_id text not null,
gaia_id text,
registry_lock_password_hash text,
registry_lock_password_salt text,
global_role text not null,
@@ -53,7 +53,8 @@ CREATE TABLE public."AllocationToken" (
token_type text,
redemption_domain_history_id bigint,
renewal_price_behavior text DEFAULT 'DEFAULT'::text NOT NULL,
registration_behavior text DEFAULT 'DEFAULT'::text NOT NULL
registration_behavior text DEFAULT 'DEFAULT'::text NOT NULL,
allowed_epp_actions text[]
);
@@ -428,7 +429,6 @@ CREATE TABLE public."Domain" (
transfer_history_entry_id bigint,
transfer_repo_id text,
transfer_poll_message_id_3 bigint,
dns_refresh_request_time timestamp with time zone,
current_package_token text,
lordn_phase text DEFAULT 'NONE'::text NOT NULL
);
@@ -518,7 +518,6 @@ CREATE TABLE public."DomainHistory" (
transfer_history_entry_id bigint,
transfer_repo_id text,
transfer_poll_message_id_3 bigint,
dns_refresh_request_time timestamp with time zone,
current_package_token text,
lordn_phase text DEFAULT 'NONE'::text NOT NULL
);
@@ -630,8 +629,7 @@ CREATE TABLE public."Host" (
superordinate_domain text,
inet_addresses text[],
update_timestamp timestamp with time zone,
transfer_poll_message_id_3 bigint,
dns_refresh_request_time timestamp with time zone
transfer_poll_message_id_3 bigint
);
@@ -664,8 +662,7 @@ CREATE TABLE public."HostHistory" (
statuses text[],
host_repo_id text NOT NULL,
update_timestamp timestamp with time zone,
transfer_poll_message_id_3 bigint,
dns_refresh_request_time timestamp with time zone
transfer_poll_message_id_3 bigint
);
@@ -1579,13 +1576,6 @@ CREATE INDEX allocation_token_domain_name_idx ON public."AllocationToken" USING
CREATE UNIQUE INDEX database_migration_state_schedule_singleton ON public."DatabaseMigrationStateSchedule" USING btree ((true));
--
-- Name: domain_dns_refresh_request_time_idx; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX domain_dns_refresh_request_time_idx ON public."Domain" USING btree (dns_refresh_request_time);
--
-- Name: domain_history_to_ds_data_history_idx; Type: INDEX; Schema: public; Owner: -
--
@@ -1719,13 +1709,6 @@ CREATE INDEX idx6w3qbtgce93cal2orjg1tw7b7 ON public."DomainHistory" USING btree
CREATE INDEX idx73l103vc5900ig3p4odf0cngt ON public."BillingEvent" USING btree (registrar_id);
--
-- Name: idx7wg0yn3wdux3xsc4pfaljqf08; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX idx7wg0yn3wdux3xsc4pfaljqf08 ON public."Host" USING btree (dns_refresh_request_time) WHERE (dns_refresh_request_time IS NOT NULL);
--
-- Name: idx8gtvnbk64yskcvrdp61f5ied3; Type: INDEX; Schema: public; Owner: -
--
+7
View File
@@ -29,6 +29,13 @@ RUN go build -o /cloudSchedulerDeployer
FROM marketplace.gcr.io/google/debian10
ENV DEBIAN_FRONTEND=noninteractive LANG=en_US.UTF-8
# Add script for cloud scheduler deployer
COPY --from=cloudSchedulerBuilder /cloudSchedulerDeployer /usr/local/bin/cloudSchedulerDeployer
# Add Cloud sql connector
ADD https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64 \
/usr/local/bin/cloud_sql_proxy
RUN chmod +x /usr/local/bin/cloud_sql_proxy
ADD ./build.sh .
RUN ["bash", "./build.sh"]
+13 -2
View File
@@ -40,9 +40,16 @@ apt-get install lsb-release -y
export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)"
echo "deb http://packages.cloud.google.com/apt $CLOUD_SDK_REPO main" \
| tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg \
| apt-key add -
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
# Install pg_dump v11 (same as current server version). This needs to be
# downloaded from postgresql's own repo, because ubuntu1804 is too old. With a
# newer image 'apt-get install postgresql-client-11' may be sufficient.
curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" \
> /etc/apt/sources.list.d/pgdg.list'
apt-get update -y
apt-get install postgresql-client-11 procps -y
apt-get install google-cloud-sdk-app-engine-java -y
# Install git
apt-get install git -y
@@ -54,6 +61,10 @@ wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
apt install ./google-chrome-stable_current_amd64.deb -y
# Install libxss1 (needed by Karma)
apt install libxss1
# Use unzip to extract files from jars.
apt-get install zip -y
# Get netstat, used for checking Cloud SQL proxy readiness.
apt-get install net-tools
apt-get remove apt-utils locales -y
apt-get autoclean -y
apt-get autoremove -y
+2 -2
View File
@@ -107,7 +107,7 @@ steps:
- -c
- |
set -e
docker build -t gcr.io/${PROJECT_ID}/schema_deployer:${TAG_NAME} .
docker build -t gcr.io/${PROJECT_ID}/schema_deployer:${TAG_NAME} --build-arg TAG_NAME=${TAG_NAME} --build-arg PROJECT_ID=${PROJECT_ID} .
docker tag gcr.io/${PROJECT_ID}/schema_deployer:${TAG_NAME} \
gcr.io/${PROJECT_ID}/schema_deployer:latest
docker push gcr.io/${PROJECT_ID}/schema_deployer:latest
@@ -120,7 +120,7 @@ steps:
- -c
- |
set -e
docker build -t gcr.io/${PROJECT_ID}/schema_verifier:${TAG_NAME} .
docker build -t gcr.io/${PROJECT_ID}/schema_verifier:${TAG_NAME} --build-arg TAG_NAME=${TAG_NAME} --build-arg PROJECT_ID=${PROJECT_ID} .
docker tag gcr.io/${PROJECT_ID}/schema_verifier:${TAG_NAME} \
gcr.io/${PROJECT_ID}/schema_verifier:latest
docker push gcr.io/${PROJECT_ID}/schema_verifier:latest
+3 -16
View File
@@ -23,24 +23,11 @@
# Although any Linux-based Java image with bash would work (e.g., openjdk:11),
# as a GCP application we prefer to start with a GCP-approved base image.
FROM marketplace.gcr.io/google/debian10
ENV DEBIAN_FRONTEND=noninteractive LANG=en_US.UTF-8
# Install openjdk-11
RUN apt-get update -y \
&& apt-get install locales -y \
&& locale-gen en_US.UTF-8 \
&& apt-get install apt-utils -y \
&& apt-get upgrade -y \
&& apt-get install openjdk-11-jdk-headless curl procps -y
# Get netstat, used for checking Cloud SQL proxy readiness.
RUN apt-get install net-tools
ARG PROJECT_ID
ARG TAG_NAME
FROM gcr.io/${PROJECT_ID}/builder:${TAG_NAME}
COPY deploy_sql_schema.sh /usr/local/bin/
ADD https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64 \
/usr/local/bin/cloud_sql_proxy
RUN chmod +x /usr/local/bin/cloud_sql_proxy
# Adapted from https://github.com/flyway/flyway-docker/blob/master/Dockerfile
RUN \
FLYWAY_MAVEN=https://repo1.maven.org/maven2/org/flywaydb/flyway-commandline \
&& FLYWAY_VERSION=$(curl ${FLYWAY_MAVEN}/maven-metadata.xml \
+3 -26
View File
@@ -21,34 +21,11 @@
#
# Please refer to verify_deployed_sql_schema.sh for expected volumes and
# arguments.
FROM marketplace.gcr.io/google/debian10
ENV DEBIAN_FRONTEND=noninteractive LANG=en_US.UTF-8
# Install pg_dump v11 (same as current server version). This needs to be
# downloaded from postgresql's own repo, because ubuntu1804 is too old. With a
# newer image 'apt-get install postgresql-client-11' may be sufficient.
RUN apt-get update -y \
&& apt-get install locales -y \
&& locale-gen en_US.UTF-8 \
&& apt-get install curl gnupg lsb-release -y \
&& curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \
&& sh -c \
'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" \
> /etc/apt/sources.list.d/pgdg.list' \
&& apt-get update -y \
&& apt install postgresql-client-11 procps -y
# Use unzip to extract files from jars.
RUN apt-get install zip -y
# Get netstat, used for checking Cloud SQL proxy readiness.
RUN apt-get install net-tools
ARG PROJECT_ID
ARG TAG_NAME
FROM gcr.io/${PROJECT_ID}/builder:${TAG_NAME}
COPY verify_deployed_sql_schema.sh /usr/local/bin/
COPY allowed_diffs.txt /
ADD https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64 \
/usr/local/bin/cloud_sql_proxy
RUN chmod +x /usr/local/bin/cloud_sql_proxy
ENTRYPOINT [ "verify_deployed_sql_schema.sh" ]
@@ -14,39 +14,20 @@
package google.registry.util;
import static com.google.appengine.api.ThreadManager.currentRequestThreadFactory;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SimpleTimeLimiter;
import com.google.common.util.concurrent.TimeLimiter;
import java.util.List;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.TimeUnit;
/**
* A factory for {@link TimeLimiter} instances that use request threads, which carry the namespace
* and live only as long as the request that spawned them.
*
* <p>It is safe to reuse instances of this class, but there is no benefit in doing so over creating
* a fresh instance each time.
*/
public class AppEngineTimeLimiter {
/**
* An {@code ExecutorService} that uses a new thread for every task.
*
* <p>We need to use fresh threads for each request so that we can use App Engine's request
* threads. If we cached these threads in a thread pool (and if we were executing on a backend,
* where there is no time limit on requests) the caching would cause the thread to keep the task
* that opened it alive even after returning an http response, and would also cause the namespace
* that the original thread was created in to leak out to later reuses of the thread.
*
* <p>Since there are no cached resources, this class doesn't have to support being shutdown.
*/
private static class NewRequestThreadExecutorService extends AbstractExecutorService {
@Override
public void execute(Runnable command) {
currentRequestThreadFactory().newThread(command).start();
MoreExecutors.platformThreadFactory().newThread(command).start();
}
@Override
@@ -1,34 +0,0 @@
// 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.util;
import java.io.Serializable;
/** Used to query whether requests are still running. */
public interface RequestStatusChecker extends Serializable {
/**
* Returns the unique log identifier of the current request.
*
* <p>Multiple calls must return the same value during the same Request.
*/
String getLogId();
/**
* Returns true if the given request is currently running.
*/
boolean isRunning(String requestLogId);
}
@@ -1,95 +0,0 @@
// 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.util;
import com.google.appengine.api.log.LogQuery;
import com.google.appengine.api.log.LogService;
import com.google.appengine.api.log.LogServiceFactory;
import com.google.appengine.api.log.RequestLogs;
import com.google.apphosting.api.ApiProxy;
import com.google.apphosting.api.ApiProxy.Environment;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
import java.util.Collections;
import javax.inject.Inject;
/** Implementation of the {@link RequestStatusChecker} interface. */
public class RequestStatusCheckerImpl implements RequestStatusChecker {
private static final long serialVersionUID = -8161977032130865437L;
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@VisibleForTesting
static LogService logService = LogServiceFactory.getLogService();
/**
* The key to {@link Environment#getAttributes}'s request_log_id value.
*/
private static final String REQUEST_LOG_ID_KEY = "com.google.appengine.runtime.request_log_id";
@Inject public RequestStatusCheckerImpl() {}
/**
* Returns the unique log identifier of the current request.
*
* <p>May be safely called multiple times, will always return the same result (within the same
* request).
*
* @see <a href="https://cloud.google.com/appengine/docs/standard/java/how-requests-are-handled#request-ids">appengine documentation</a>
*/
@Override
public String getLogId() {
String requestLogId =
ApiProxy.getCurrentEnvironment().getAttributes().get(REQUEST_LOG_ID_KEY).toString();
logger.atInfo().log("Current requestLogId: %s.", requestLogId);
// We want to make sure there actually is a log to query for this request, even if the request
// dies right after this call.
//
// flushLogs() is synchronous, so once the function returns, no matter what happens next, the
// returned requestLogId will point to existing logs.
ApiProxy.flushLogs();
return requestLogId;
}
/**
* Returns true if the given request is currently running.
*
* @see <a href="https://cloud.google.com/appengine/docs/standard/java/javadoc/com/google/appengine/api/log/LogQuery">appengine documentation</a>
*/
@Override
public boolean isRunning(String requestLogId) {
RequestLogs requestLogs =
Iterables.getOnlyElement(
logService.fetch(
LogQuery.Builder
.withRequestIds(Collections.singletonList(requestLogId))
.includeAppLogs(false)
.includeIncomplete(true)),
null);
// requestLogs will be null if that requestLogId isn't found at all, which can happen if the
// request is too new (it can take several seconds until the logs are available for "fetch").
// So we have to assume it's "running" in that case.
if (requestLogs == null) {
logger.atInfo().log(
"Queried an unrecognized requestLogId %s - assume it's running.", requestLogId);
return true;
}
logger.atInfo().log(
"Found logs for requestLogId %s - isFinished: %b", requestLogId, requestLogs.isFinished());
return !requestLogs.isFinished();
}
}