Add a relockDuration to the RegistryLock SQL object (#514)

* Add a relockDuration to the RegistryLock SQL object

This is the length of time after an unlock that we will re-lock the
domain in question.

* Sort by domain name for stability

Note: this is likely not the best solution for the UI but we can iterate
on this.

* Add nullable

* Add a converter for Duration
This commit is contained in:
gbrodman 2020-03-16 17:44:25 -04:00 committed by GitHub
parent 4a1242a299
commit bdaa598ae1
12 changed files with 174 additions and 18 deletions

View file

@ -52,8 +52,10 @@ public final class RegistryLockDao {
jpaTm() jpaTm()
.getEntityManager() .getEntityManager()
.createQuery( .createQuery(
"SELECT lock FROM RegistryLock lock WHERE lock.registrarId = :registrarId" "SELECT lock FROM RegistryLock lock"
+ " AND lock.unlockCompletionTimestamp IS NULL", + " WHERE lock.registrarId = :registrarId"
+ " AND lock.unlockCompletionTimestamp IS NULL"
+ " ORDER BY lock.domainName ASC",
RegistryLock.class) RegistryLock.class)
.setParameter("registrarId", registrarId) .setParameter("registrarId", registrarId)
.getResultList()); .getResultList());

View file

@ -0,0 +1,37 @@
// Copyright 2020 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.persistence.converter;
import javax.annotation.Nullable;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import org.joda.time.Duration;
/** JPA converter to for storing/retrieving {@link org.joda.time.DateTime} objects. */
@Converter(autoApply = true)
public class DurationConverter implements AttributeConverter<Duration, Long> {
@Override
@Nullable
public Long convertToDatabaseColumn(@Nullable Duration duration) {
return duration == null ? null : duration.getMillis();
}
@Override
@Nullable
public Duration convertToEntityAttribute(@Nullable Long dbData) {
return dbData == null ? null : new Duration(dbData);
}
}

View file

@ -26,6 +26,7 @@ import google.registry.model.UpdateAutoTimestamp;
import google.registry.util.DateTimeUtils; import google.registry.util.DateTimeUtils;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.Optional; import java.util.Optional;
import javax.annotation.Nullable;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.FetchType; import javax.persistence.FetchType;
@ -37,6 +38,7 @@ import javax.persistence.JoinColumn;
import javax.persistence.OneToOne; import javax.persistence.OneToOne;
import javax.persistence.Table; import javax.persistence.Table;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.Duration;
/** /**
* Represents a registry lock/unlock object, meaning that the domain is locked on the registry * Represents a registry lock/unlock object, meaning that the domain is locked on the registry
@ -131,6 +133,9 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
@JoinColumn(name = "relockRevisionId", referencedColumnName = "revisionId") @JoinColumn(name = "relockRevisionId", referencedColumnName = "revisionId")
private RegistryLock relock; private RegistryLock relock;
/** The duration after which we will re-lock this domain after it is unlocked. */
private Duration relockDuration;
/** Time that this entity was last updated. */ /** Time that this entity was last updated. */
private UpdateAutoTimestamp lastUpdateTimestamp; private UpdateAutoTimestamp lastUpdateTimestamp;
@ -197,6 +202,11 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
return relock; return relock;
} }
/** The duration after which we will re-lock this domain after it is unlocked. */
public Optional<Duration> getRelockDuration() {
return Optional.ofNullable(relockDuration);
}
public boolean isLocked() { public boolean isLocked() {
return lockCompletionTimestamp != null && unlockCompletionTimestamp == null; return lockCompletionTimestamp != null && unlockCompletionTimestamp == null;
} }
@ -289,5 +299,10 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
getInstance().relock = relock; getInstance().relock = relock;
return this; return this;
} }
public Builder setRelockDuration(@Nullable Duration relockDuration) {
getInstance().relockDuration = relockDuration;
return this;
}
} }
} }

View file

@ -38,6 +38,7 @@
<class>google.registry.persistence.converter.CreateAutoTimestampConverter</class> <class>google.registry.persistence.converter.CreateAutoTimestampConverter</class>
<class>google.registry.persistence.converter.CurrencyUnitConverter</class> <class>google.registry.persistence.converter.CurrencyUnitConverter</class>
<class>google.registry.persistence.converter.DateTimeConverter</class> <class>google.registry.persistence.converter.DateTimeConverter</class>
<class>google.registry.persistence.converter.DurationConverter</class>
<class>google.registry.persistence.converter.RegistrarPocSetConverter</class> <class>google.registry.persistence.converter.RegistrarPocSetConverter</class>
<class>google.registry.persistence.converter.StatusValueSetConverter</class> <class>google.registry.persistence.converter.StatusValueSetConverter</class>
<class>google.registry.persistence.converter.StringListConverter</class> <class>google.registry.persistence.converter.StringListConverter</class>

View file

@ -30,6 +30,7 @@ import google.registry.schema.domain.RegistryLock;
import google.registry.testing.AppEngineRule; import google.registry.testing.AppEngineRule;
import google.registry.testing.FakeClock; import google.registry.testing.FakeClock;
import java.util.Optional; import java.util.Optional;
import org.joda.time.Duration;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -88,12 +89,14 @@ public final class RegistryLockDaoTest {
.setLockCompletionTimestamp(fakeClock.nowUtc()) .setLockCompletionTimestamp(fakeClock.nowUtc())
.setUnlockRequestTimestamp(fakeClock.nowUtc()) .setUnlockRequestTimestamp(fakeClock.nowUtc())
.setUnlockCompletionTimestamp(fakeClock.nowUtc()) .setUnlockCompletionTimestamp(fakeClock.nowUtc())
.setRelockDuration(Duration.standardHours(6))
.build()); .build());
RegistryLock fromDatabase = getRegistryLockByVerificationCode(lock.getVerificationCode()).get(); RegistryLock fromDatabase = getRegistryLockByVerificationCode(lock.getVerificationCode()).get();
assertThat(fromDatabase.getUnlockRequestTimestamp()).isEqualTo(Optional.of(fakeClock.nowUtc())); assertThat(fromDatabase.getUnlockRequestTimestamp()).isEqualTo(Optional.of(fakeClock.nowUtc()));
assertThat(fromDatabase.getUnlockCompletionTimestamp()) assertThat(fromDatabase.getUnlockCompletionTimestamp())
.isEqualTo(Optional.of(fakeClock.nowUtc())); .isEqualTo(Optional.of(fakeClock.nowUtc()));
assertThat(fromDatabase.isLocked()).isFalse(); assertThat(fromDatabase.isLocked()).isFalse();
assertThat(fromDatabase.getRelockDuration().get()).isEqualTo(Duration.standardHours(6));
} }
@Test @Test

View file

@ -11,6 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package google.registry.persistence.converter; package google.registry.persistence.converter;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;

View file

@ -0,0 +1,80 @@
// Copyright 2020 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.persistence.converter;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import google.registry.model.ImmutableObject;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaUnitTestRule;
import java.math.BigInteger;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.joda.time.Duration;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link DurationConverter}. */
@RunWith(JUnit4.class)
public class DurationConverterTest {
@Rule
public final JpaUnitTestRule jpaRule =
new JpaTestRules.Builder().withEntityClass(TestEntity.class).buildUnitTestRule();
private final DurationConverter converter = new DurationConverter();
@Test
public void testNulls() {
assertThat(converter.convertToDatabaseColumn(null)).isNull();
assertThat(converter.convertToEntityAttribute(null)).isNull();
}
@Test
public void testRoundTrip() {
TestEntity entity = new TestEntity(Duration.standardDays(6));
jpaTm().transact(() -> jpaTm().getEntityManager().persist(entity));
assertThat(
jpaTm()
.transact(
() ->
jpaTm()
.getEntityManager()
.createNativeQuery(
"SELECT duration FROM \"TestEntity\" WHERE name = 'id'")
.getResultList()))
.containsExactly(BigInteger.valueOf(Duration.standardDays(6).getMillis()));
TestEntity persisted =
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.duration).isEqualTo(Duration.standardDays(6));
}
@Entity(name = "TestEntity") // Override entity name to avoid the nested class reference.
public static class TestEntity extends ImmutableObject {
@Id String name = "id";
Duration duration;
public TestEntity() {}
TestEntity(Duration duration) {
this.duration = duration;
}
}
}

View file

@ -188,10 +188,10 @@ public final class RegistryLockGetActionTest {
"locks", "locks",
ImmutableList.of( ImmutableList.of(
new ImmutableMap.Builder<>() new ImmutableMap.Builder<>()
.put("fullyQualifiedDomainName", "expiredunlock.test") .put("fullyQualifiedDomainName", "adminexample.test")
.put("lockedTime", "2000-06-08T22:00:00.000Z") .put("lockedTime", "2000-06-09T22:00:00.001Z")
.put("lockedBy", "johndoe@theregistrar.com") .put("lockedBy", "admin")
.put("userCanUnlock", true) .put("userCanUnlock", false)
.put("isLockPending", false) .put("isLockPending", false)
.put("isUnlockPending", false) .put("isUnlockPending", false)
.build(), .build(),
@ -204,19 +204,11 @@ public final class RegistryLockGetActionTest {
.put("isUnlockPending", false) .put("isUnlockPending", false)
.build(), .build(),
new ImmutableMap.Builder<>() new ImmutableMap.Builder<>()
.put("fullyQualifiedDomainName", "adminexample.test") .put("fullyQualifiedDomainName", "expiredunlock.test")
.put("lockedTime", "2000-06-09T22:00:00.001Z") .put("lockedTime", "2000-06-08T22:00:00.000Z")
.put("lockedBy", "admin")
.put("userCanUnlock", false)
.put("isLockPending", false)
.put("isUnlockPending", false)
.build(),
new ImmutableMap.Builder<>()
.put("fullyQualifiedDomainName", "pending.test")
.put("lockedTime", "")
.put("lockedBy", "johndoe@theregistrar.com") .put("lockedBy", "johndoe@theregistrar.com")
.put("userCanUnlock", true) .put("userCanUnlock", true)
.put("isLockPending", true) .put("isLockPending", false)
.put("isUnlockPending", false) .put("isUnlockPending", false)
.build(), .build(),
new ImmutableMap.Builder<>() new ImmutableMap.Builder<>()
@ -226,6 +218,14 @@ public final class RegistryLockGetActionTest {
.put("userCanUnlock", true) .put("userCanUnlock", true)
.put("isLockPending", false) .put("isLockPending", false)
.put("isUnlockPending", true) .put("isUnlockPending", true)
.build(),
new ImmutableMap.Builder<>()
.put("fullyQualifiedDomainName", "pending.test")
.put("lockedTime", "")
.put("lockedBy", "johndoe@theregistrar.com")
.put("userCanUnlock", true)
.put("isLockPending", true)
.put("isUnlockPending", false)
.build())))); .build()))));
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Before After
Before After

View file

@ -0,0 +1,15 @@
-- Copyright 2020 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 "RegistryLock" ADD COLUMN relock_duration bigint;

View file

@ -176,6 +176,7 @@
lock_request_timestamp timestamptz not null, lock_request_timestamp timestamptz not null,
registrar_id text not null, registrar_id text not null,
registrar_poc_id text, registrar_poc_id text,
relock_duration int8,
repo_id text not null, repo_id text not null,
unlock_completion_timestamp timestamptz, unlock_completion_timestamp timestamptz,
unlock_request_timestamp timestamptz, unlock_request_timestamp timestamptz,

View file

@ -262,7 +262,8 @@ CREATE TABLE public."RegistryLock" (
unlock_request_timestamp timestamp with time zone, unlock_request_timestamp timestamp with time zone,
unlock_completion_timestamp timestamp with time zone, unlock_completion_timestamp timestamp with time zone,
last_update_timestamp timestamp with time zone, last_update_timestamp timestamp with time zone,
relock_revision_id bigint relock_revision_id bigint,
relock_duration bigint
); );