mirror of
https://github.com/google/nomulus.git
synced 2025-07-04 10:13:30 +02:00
Add SQL schema and DAO for SignedMarkRevocationList (#850)
* Add SQL schema and DAO for SignedMarkRevocationList This gets saved every day so we're not concerned about history, meaning we can dual-write and/or dual-read without concern. The structure here is somewhat similar to the ClaimsListDao and related classes. * Update the DB files
This commit is contained in:
parent
8bd5eb4eca
commit
40eef2a06c
12 changed files with 2548 additions and 1735 deletions
|
@ -28,8 +28,12 @@ import static google.registry.util.DateTimeUtils.isBeforeOrAt;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.Supplier;
|
import com.google.common.base.Supplier;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.collect.MapDifference;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.common.flogger.FluentLogger;
|
||||||
import com.googlecode.objectify.Key;
|
import com.googlecode.objectify.Key;
|
||||||
import com.googlecode.objectify.annotation.EmbedMap;
|
import com.googlecode.objectify.annotation.EmbedMap;
|
||||||
import com.googlecode.objectify.annotation.Entity;
|
import com.googlecode.objectify.annotation.Entity;
|
||||||
|
@ -41,8 +45,19 @@ import google.registry.model.ImmutableObject;
|
||||||
import google.registry.model.annotations.NotBackedUp;
|
import google.registry.model.annotations.NotBackedUp;
|
||||||
import google.registry.model.annotations.NotBackedUp.Reason;
|
import google.registry.model.annotations.NotBackedUp.Reason;
|
||||||
import google.registry.model.common.EntityGroupRoot;
|
import google.registry.model.common.EntityGroupRoot;
|
||||||
|
import google.registry.schema.replay.DatastoreEntity;
|
||||||
|
import google.registry.schema.replay.SqlEntity;
|
||||||
import google.registry.util.CollectionUtils;
|
import google.registry.util.CollectionUtils;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import javax.persistence.CollectionTable;
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.ElementCollection;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.GenerationType;
|
||||||
|
import javax.persistence.JoinColumn;
|
||||||
|
import javax.persistence.MapKeyColumn;
|
||||||
|
import javax.persistence.Transient;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,35 +71,50 @@ import org.joda.time.DateTime;
|
||||||
* order to avoid exceeding the one megabyte max entity size limit, we'll also be sharding that
|
* order to avoid exceeding the one megabyte max entity size limit, we'll also be sharding that
|
||||||
* entity into multiple entities, each entity containing {@value #SHARD_SIZE} rows.
|
* entity into multiple entities, each entity containing {@value #SHARD_SIZE} rows.
|
||||||
*
|
*
|
||||||
|
* <p>TODO: We can remove the sharding once we have converted entirely to Cloud SQL storage during
|
||||||
|
* the Registry 3.0 migration. Then, the entire table will be stored conceptually as one entity (in
|
||||||
|
* fact in SignedMarkRevocationList and SignedMarkRevocationEntry tables).
|
||||||
|
*
|
||||||
* @see google.registry.tmch.SmdrlCsvParser
|
* @see google.registry.tmch.SmdrlCsvParser
|
||||||
* @see <a href="http://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-6.2">
|
* @see <a href="http://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-6.2">TMCH
|
||||||
* TMCH functional specifications - SMD Revocation List</a>
|
* functional specifications - SMD Revocation List</a>
|
||||||
*/
|
*/
|
||||||
@Entity
|
@Entity
|
||||||
|
@javax.persistence.Entity
|
||||||
@NotBackedUp(reason = Reason.EXTERNALLY_SOURCED)
|
@NotBackedUp(reason = Reason.EXTERNALLY_SOURCED)
|
||||||
public class SignedMarkRevocationList extends ImmutableObject {
|
public class SignedMarkRevocationList extends ImmutableObject
|
||||||
|
implements DatastoreEntity, SqlEntity {
|
||||||
|
|
||||||
@VisibleForTesting
|
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||||
static final int SHARD_SIZE = 10000;
|
|
||||||
|
@VisibleForTesting static final int SHARD_SIZE = 10000;
|
||||||
|
|
||||||
/** Common ancestor for queries. */
|
/** Common ancestor for queries. */
|
||||||
@Parent
|
@Parent @Transient Key<EntityGroupRoot> parent = getCrossTldKey();
|
||||||
Key<EntityGroupRoot> parent = getCrossTldKey();
|
|
||||||
|
|
||||||
/** ID for the sharded entity. */
|
/** ID for the sharded entity. */
|
||||||
@Id
|
@Id @Transient long id;
|
||||||
long id;
|
|
||||||
|
@Ignore
|
||||||
|
@javax.persistence.Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
Long revisionId;
|
||||||
|
|
||||||
/** Time when this list was last updated, as specified in the first line of the CSV file. */
|
/** Time when this list was last updated, as specified in the first line of the CSV file. */
|
||||||
DateTime creationTime;
|
DateTime creationTime;
|
||||||
|
|
||||||
/** A map from SMD IDs to revocation time. */
|
/** A map from SMD IDs to revocation time. */
|
||||||
@EmbedMap
|
@EmbedMap
|
||||||
|
@ElementCollection
|
||||||
|
@CollectionTable(
|
||||||
|
name = "SignedMarkRevocationEntry",
|
||||||
|
joinColumns = @JoinColumn(name = "revisionId", referencedColumnName = "revisionId"))
|
||||||
|
@MapKeyColumn(name = "smdId")
|
||||||
|
@Column(name = "revocationTime", nullable = false)
|
||||||
Map</*@MatchesPattern("[0-9]+-[0-9]+")*/ String, DateTime> revokes;
|
Map</*@MatchesPattern("[0-9]+-[0-9]+")*/ String, DateTime> revokes;
|
||||||
|
|
||||||
/** Indicates that this is a shard rather than a "full" list. */
|
/** Indicates that this is a shard rather than a "full" list. */
|
||||||
@Ignore
|
@Ignore @Transient boolean isShard;
|
||||||
boolean isShard;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A cached supplier that fetches the SMDRL shards from Datastore and recombines them into a
|
* A cached supplier that fetches the SMDRL shards from Datastore and recombines them into a
|
||||||
|
@ -92,32 +122,16 @@ public class SignedMarkRevocationList extends ImmutableObject {
|
||||||
*/
|
*/
|
||||||
private static final Supplier<SignedMarkRevocationList> CACHE =
|
private static final Supplier<SignedMarkRevocationList> CACHE =
|
||||||
memoizeWithShortExpiration(
|
memoizeWithShortExpiration(
|
||||||
() ->
|
() -> {
|
||||||
tm()
|
SignedMarkRevocationList datastoreList = loadFromDatastore();
|
||||||
.transactNewReadOnly(
|
// Also load the list from Cloud SQL, compare the two lists, and log if different.
|
||||||
() -> {
|
try {
|
||||||
Iterable<SignedMarkRevocationList> shards =
|
loadAndCompareCloudSqlList(datastoreList);
|
||||||
ofy()
|
} catch (Throwable t) {
|
||||||
.load()
|
logger.atSevere().withCause(t).log("Error comparing signed mark revocation lists.");
|
||||||
.type(SignedMarkRevocationList.class)
|
}
|
||||||
.ancestor(getCrossTldKey());
|
return datastoreList;
|
||||||
DateTime creationTime =
|
});
|
||||||
isEmpty(shards)
|
|
||||||
? START_OF_TIME
|
|
||||||
: checkNotNull(
|
|
||||||
Iterables.get(shards, 0).creationTime, "creationTime");
|
|
||||||
ImmutableMap.Builder<String, DateTime> revokes =
|
|
||||||
new ImmutableMap.Builder<>();
|
|
||||||
for (SignedMarkRevocationList shard : shards) {
|
|
||||||
revokes.putAll(shard.revokes);
|
|
||||||
checkState(
|
|
||||||
creationTime.equals(shard.creationTime),
|
|
||||||
"Inconsistent creation times: %s vs. %s",
|
|
||||||
creationTime,
|
|
||||||
shard.creationTime);
|
|
||||||
}
|
|
||||||
return create(creationTime, revokes.build());
|
|
||||||
}));
|
|
||||||
|
|
||||||
/** Return a single logical instance that combines all Datastore shards. */
|
/** Return a single logical instance that combines all Datastore shards. */
|
||||||
public static SignedMarkRevocationList get() {
|
public static SignedMarkRevocationList get() {
|
||||||
|
@ -149,10 +163,39 @@ public class SignedMarkRevocationList extends ImmutableObject {
|
||||||
return revokes.size();
|
return revokes.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Save this list to Datastore in sharded form. Returns {@code this}. */
|
/** Save this list to Datastore in sharded form and to Cloud SQL. Returns {@code this}. */
|
||||||
public SignedMarkRevocationList save() {
|
public SignedMarkRevocationList save() {
|
||||||
tm()
|
saveToDatastore();
|
||||||
.transact(
|
SignedMarkRevocationListDao.trySave(this);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Loads the shards from Datastore and combines them into one list. */
|
||||||
|
private static SignedMarkRevocationList loadFromDatastore() {
|
||||||
|
return tm().transactNewReadOnly(
|
||||||
|
() -> {
|
||||||
|
Iterable<SignedMarkRevocationList> shards =
|
||||||
|
ofy().load().type(SignedMarkRevocationList.class).ancestor(getCrossTldKey());
|
||||||
|
DateTime creationTime =
|
||||||
|
isEmpty(shards)
|
||||||
|
? START_OF_TIME
|
||||||
|
: checkNotNull(Iterables.get(shards, 0).creationTime, "creationTime");
|
||||||
|
ImmutableMap.Builder<String, DateTime> revokes = new ImmutableMap.Builder<>();
|
||||||
|
for (SignedMarkRevocationList shard : shards) {
|
||||||
|
revokes.putAll(shard.revokes);
|
||||||
|
checkState(
|
||||||
|
creationTime.equals(shard.creationTime),
|
||||||
|
"Inconsistent creation times: %s vs. %s",
|
||||||
|
creationTime,
|
||||||
|
shard.creationTime);
|
||||||
|
}
|
||||||
|
return create(creationTime, revokes.build());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Save this list to Datastore in sharded form. */
|
||||||
|
private SignedMarkRevocationList saveToDatastore() {
|
||||||
|
tm().transact(
|
||||||
() -> {
|
() -> {
|
||||||
ofy()
|
ofy()
|
||||||
.deleteWithoutBackup()
|
.deleteWithoutBackup()
|
||||||
|
@ -165,8 +208,7 @@ public class SignedMarkRevocationList extends ImmutableObject {
|
||||||
ofy()
|
ofy()
|
||||||
.saveWithoutBackup()
|
.saveWithoutBackup()
|
||||||
.entities(
|
.entities(
|
||||||
CollectionUtils.partitionMap(revokes, SHARD_SIZE)
|
CollectionUtils.partitionMap(revokes, SHARD_SIZE).stream()
|
||||||
.stream()
|
|
||||||
.map(
|
.map(
|
||||||
shardRevokes -> {
|
shardRevokes -> {
|
||||||
SignedMarkRevocationList shard = create(creationTime, shardRevokes);
|
SignedMarkRevocationList shard = create(creationTime, shardRevokes);
|
||||||
|
@ -180,6 +222,38 @@ public class SignedMarkRevocationList extends ImmutableObject {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void loadAndCompareCloudSqlList(SignedMarkRevocationList datastoreList) {
|
||||||
|
// Lifted with some modifications from ClaimsListShard
|
||||||
|
Optional<SignedMarkRevocationList> maybeCloudSqlList =
|
||||||
|
SignedMarkRevocationListDao.getLatestRevision();
|
||||||
|
if (maybeCloudSqlList.isPresent()) {
|
||||||
|
SignedMarkRevocationList cloudSqlList = maybeCloudSqlList.get();
|
||||||
|
MapDifference<String, DateTime> diff =
|
||||||
|
Maps.difference(datastoreList.revokes, cloudSqlList.revokes);
|
||||||
|
if (!diff.areEqual()) {
|
||||||
|
if (diff.entriesDiffering().size() > 10) {
|
||||||
|
logger.atWarning().log(
|
||||||
|
String.format(
|
||||||
|
"Unequal SM revocation lists detected, Cloud SQL list with revision id %d has %d"
|
||||||
|
+ " different records than the current Datastore list.",
|
||||||
|
cloudSqlList.revisionId, diff.entriesDiffering().size()));
|
||||||
|
} else {
|
||||||
|
StringBuilder diffMessage = new StringBuilder("Unequal SM revocation lists detected:\n");
|
||||||
|
diff.entriesDiffering()
|
||||||
|
.forEach(
|
||||||
|
(label, valueDiff) ->
|
||||||
|
diffMessage.append(
|
||||||
|
String.format(
|
||||||
|
"SMD %s has key %s in Datastore and key %s in Cloud SQL.\n",
|
||||||
|
label, valueDiff.leftValue(), valueDiff.rightValue())));
|
||||||
|
logger.atWarning().log(diffMessage.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.atWarning().log("Signed mark revocation list in Cloud SQL is empty.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** As a safety mechanism, fail if someone tries to save this class directly. */
|
/** As a safety mechanism, fail if someone tries to save this class directly. */
|
||||||
@OnSave
|
@OnSave
|
||||||
void disallowUnshardedSaves() {
|
void disallowUnshardedSaves() {
|
||||||
|
@ -188,6 +262,16 @@ public class SignedMarkRevocationList extends ImmutableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableList<SqlEntity> toSqlEntities() {
|
||||||
|
return ImmutableList.of(); // Dually-written every day
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableList<DatastoreEntity> toDatastoreEntities() {
|
||||||
|
return ImmutableList.of(); // Dually-written every day
|
||||||
|
}
|
||||||
|
|
||||||
/** Exception when trying to directly save a {@link SignedMarkRevocationList} without sharding. */
|
/** Exception when trying to directly save a {@link SignedMarkRevocationList} without sharding. */
|
||||||
public static class UnshardedSaveException extends RuntimeException {}
|
public static class UnshardedSaveException extends RuntimeException {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
// 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.model.smd;
|
||||||
|
|
||||||
|
import static google.registry.model.CacheUtils.memoizeWithShortExpiration;
|
||||||
|
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||||
|
|
||||||
|
import com.google.common.base.Supplier;
|
||||||
|
import com.google.common.flogger.FluentLogger;
|
||||||
|
import java.util.Optional;
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
|
||||||
|
public class SignedMarkRevocationListDao {
|
||||||
|
|
||||||
|
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||||
|
|
||||||
|
private static final Supplier<Optional<SignedMarkRevocationList>> CACHE =
|
||||||
|
memoizeWithShortExpiration(SignedMarkRevocationListDao::getLatestRevision);
|
||||||
|
|
||||||
|
/** Returns the most recent revision of the {@link SignedMarkRevocationList}, from cache. */
|
||||||
|
public static Optional<SignedMarkRevocationList> getLatestRevisionCached() {
|
||||||
|
return CACHE.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<SignedMarkRevocationList> getLatestRevision() {
|
||||||
|
return jpaTm()
|
||||||
|
.transact(
|
||||||
|
() -> {
|
||||||
|
EntityManager em = jpaTm().getEntityManager();
|
||||||
|
Long revisionId =
|
||||||
|
em.createQuery("SELECT MAX(revisionId) FROM SignedMarkRevocationList", Long.class)
|
||||||
|
.getSingleResult();
|
||||||
|
return em.createQuery(
|
||||||
|
"FROM SignedMarkRevocationList smrl LEFT JOIN FETCH smrl.revokes "
|
||||||
|
+ "WHERE smrl.revisionId = :revisionId",
|
||||||
|
SignedMarkRevocationList.class)
|
||||||
|
.setParameter("revisionId", revisionId)
|
||||||
|
.getResultStream()
|
||||||
|
.findFirst();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to save the given {@link SignedMarkRevocationList} into Cloud SQL. If the save fails, the
|
||||||
|
* error will be logged but no exception will be thrown.
|
||||||
|
*
|
||||||
|
* <p>This method is used during the dual-write phase of database migration as Datastore is still
|
||||||
|
* the authoritative database.
|
||||||
|
*/
|
||||||
|
static void trySave(SignedMarkRevocationList signedMarkRevocationList) {
|
||||||
|
try {
|
||||||
|
SignedMarkRevocationListDao.save(signedMarkRevocationList);
|
||||||
|
logger.atInfo().log(
|
||||||
|
"Inserted %,d signed mark revocations into Cloud SQL",
|
||||||
|
signedMarkRevocationList.revokes.size());
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.atSevere().withCause(e).log("Error inserting signed mark revocations into Cloud SQL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void save(SignedMarkRevocationList signedMarkRevocationList) {
|
||||||
|
jpaTm().transact(() -> jpaTm().getEntityManager().persist(signedMarkRevocationList));
|
||||||
|
}
|
||||||
|
}
|
|
@ -62,6 +62,7 @@
|
||||||
<class>google.registry.model.registry.Registry</class>
|
<class>google.registry.model.registry.Registry</class>
|
||||||
<class>google.registry.model.reporting.DomainTransactionRecord</class>
|
<class>google.registry.model.reporting.DomainTransactionRecord</class>
|
||||||
<class>google.registry.model.reporting.Spec11ThreatMatch</class>
|
<class>google.registry.model.reporting.Spec11ThreatMatch</class>
|
||||||
|
<class>google.registry.model.smd.SignedMarkRevocationList</class>
|
||||||
<class>google.registry.model.tmch.ClaimsListShard</class>
|
<class>google.registry.model.tmch.ClaimsListShard</class>
|
||||||
<class>google.registry.persistence.transaction.TransactionEntity</class>
|
<class>google.registry.persistence.transaction.TransactionEntity</class>
|
||||||
<class>google.registry.schema.cursor.Cursor</class>
|
<class>google.registry.schema.cursor.Cursor</class>
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
// 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.model.smd;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import google.registry.persistence.transaction.JpaTestRules;
|
||||||
|
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension;
|
||||||
|
import google.registry.testing.DatastoreEntityExtension;
|
||||||
|
import google.registry.testing.DualDatabaseTest;
|
||||||
|
import google.registry.testing.FakeClock;
|
||||||
|
import org.junit.jupiter.api.Order;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
|
||||||
|
@DualDatabaseTest
|
||||||
|
public class SignedMarkRevocationListDaoTest {
|
||||||
|
|
||||||
|
private final FakeClock fakeClock = new FakeClock();
|
||||||
|
|
||||||
|
@RegisterExtension
|
||||||
|
final JpaIntegrationWithCoverageExtension jpa =
|
||||||
|
new JpaTestRules.Builder().withClock(fakeClock).buildIntegrationWithCoverageExtension();
|
||||||
|
|
||||||
|
@RegisterExtension
|
||||||
|
@Order(value = 1)
|
||||||
|
final DatastoreEntityExtension datastoreEntityExtension = new DatastoreEntityExtension();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSave_success() {
|
||||||
|
SignedMarkRevocationList list =
|
||||||
|
SignedMarkRevocationList.create(
|
||||||
|
fakeClock.nowUtc(), ImmutableMap.of("mark", fakeClock.nowUtc().minusHours(1)));
|
||||||
|
SignedMarkRevocationListDao.trySave(list);
|
||||||
|
SignedMarkRevocationList fromDb = SignedMarkRevocationListDao.getLatestRevision().get();
|
||||||
|
assertAboutImmutableObjects().that(fromDb).isEqualExceptFields(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void trySave_failureIsSwallowed() {
|
||||||
|
SignedMarkRevocationList list =
|
||||||
|
SignedMarkRevocationList.create(
|
||||||
|
fakeClock.nowUtc(), ImmutableMap.of("mark", fakeClock.nowUtc().minusHours(1)));
|
||||||
|
SignedMarkRevocationListDao.trySave(list);
|
||||||
|
SignedMarkRevocationList fromDb = SignedMarkRevocationListDao.getLatestRevision().get();
|
||||||
|
assertAboutImmutableObjects().that(fromDb).isEqualExceptFields(list);
|
||||||
|
|
||||||
|
// This should throw an exception, which is swallowed and nothing changed
|
||||||
|
SignedMarkRevocationListDao.trySave(list);
|
||||||
|
SignedMarkRevocationList secondFromDb = SignedMarkRevocationListDao.getLatestRevision().get();
|
||||||
|
assertAboutImmutableObjects().that(secondFromDb).isEqualExceptFields(fromDb);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRetrieval_notPresent() {
|
||||||
|
assertThat(SignedMarkRevocationListDao.getLatestRevision().isPresent()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSaveAndRetrieval_emptyList() {
|
||||||
|
SignedMarkRevocationList list =
|
||||||
|
SignedMarkRevocationList.create(fakeClock.nowUtc(), ImmutableMap.of());
|
||||||
|
SignedMarkRevocationListDao.trySave(list);
|
||||||
|
SignedMarkRevocationList fromDb = SignedMarkRevocationListDao.getLatestRevision().get();
|
||||||
|
assertAboutImmutableObjects().that(fromDb).isEqualExceptFields(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSave_multipleVersions() {
|
||||||
|
SignedMarkRevocationList list =
|
||||||
|
SignedMarkRevocationList.create(
|
||||||
|
fakeClock.nowUtc(), ImmutableMap.of("mark", fakeClock.nowUtc().minusHours(1)));
|
||||||
|
SignedMarkRevocationListDao.trySave(list);
|
||||||
|
assertThat(
|
||||||
|
SignedMarkRevocationListDao.getLatestRevision()
|
||||||
|
.get()
|
||||||
|
.isSmdRevoked("mark", fakeClock.nowUtc()))
|
||||||
|
.isTrue();
|
||||||
|
|
||||||
|
// Now remove the revocation
|
||||||
|
SignedMarkRevocationList secondList =
|
||||||
|
SignedMarkRevocationList.create(fakeClock.nowUtc(), ImmutableMap.of());
|
||||||
|
SignedMarkRevocationListDao.trySave(secondList);
|
||||||
|
assertThat(
|
||||||
|
SignedMarkRevocationListDao.getLatestRevision()
|
||||||
|
.get()
|
||||||
|
.isSmdRevoked("mark", fakeClock.nowUtc()))
|
||||||
|
.isFalse();
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,7 @@
|
||||||
package google.registry.model.smd;
|
package google.registry.model.smd;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
|
||||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||||
import static google.registry.model.smd.SignedMarkRevocationList.SHARD_SIZE;
|
import static google.registry.model.smd.SignedMarkRevocationList.SHARD_SIZE;
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||||
|
@ -72,10 +73,11 @@ public class SignedMarkRevocationListTest {
|
||||||
revokes.put(Integer.toString(i), clock.nowUtc());
|
revokes.put(Integer.toString(i), clock.nowUtc());
|
||||||
}
|
}
|
||||||
// Save it with sharding, and make sure that reloading it works.
|
// Save it with sharding, and make sure that reloading it works.
|
||||||
SignedMarkRevocationList unsharded = SignedMarkRevocationList
|
SignedMarkRevocationList unsharded =
|
||||||
.create(clock.nowUtc(), revokes.build())
|
SignedMarkRevocationList.create(clock.nowUtc(), revokes.build()).save();
|
||||||
.save();
|
assertAboutImmutableObjects()
|
||||||
assertThat(SignedMarkRevocationList.get()).isEqualTo(unsharded);
|
.that(SignedMarkRevocationList.get())
|
||||||
|
.isEqualExceptFields(unsharded, "revisionId");
|
||||||
assertThat(ofy().load().type(SignedMarkRevocationList.class).count()).isEqualTo(2);
|
assertThat(ofy().load().type(SignedMarkRevocationList.class).count()).isEqualTo(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +93,9 @@ public class SignedMarkRevocationListTest {
|
||||||
SignedMarkRevocationList unsharded = SignedMarkRevocationList
|
SignedMarkRevocationList unsharded = SignedMarkRevocationList
|
||||||
.create(clock.nowUtc(), revokes.build())
|
.create(clock.nowUtc(), revokes.build())
|
||||||
.save();
|
.save();
|
||||||
assertThat(SignedMarkRevocationList.get()).isEqualTo(unsharded);
|
assertAboutImmutableObjects()
|
||||||
|
.that(SignedMarkRevocationList.get())
|
||||||
|
.isEqualExceptFields(unsharded, "revisionId");
|
||||||
assertThat(ofy().load().type(SignedMarkRevocationList.class).count()).isEqualTo(4);
|
assertThat(ofy().load().type(SignedMarkRevocationList.class).count()).isEqualTo(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ import google.registry.model.registry.RegistryLockDaoTest;
|
||||||
import google.registry.model.registry.RegistryTest;
|
import google.registry.model.registry.RegistryTest;
|
||||||
import google.registry.model.registry.label.ReservedListSqlDaoTest;
|
import google.registry.model.registry.label.ReservedListSqlDaoTest;
|
||||||
import google.registry.model.reporting.Spec11ThreatMatchTest;
|
import google.registry.model.reporting.Spec11ThreatMatchTest;
|
||||||
|
import google.registry.model.smd.SignedMarkRevocationListDaoTest;
|
||||||
import google.registry.model.tmch.ClaimsListDaoTest;
|
import google.registry.model.tmch.ClaimsListDaoTest;
|
||||||
import google.registry.persistence.transaction.JpaEntityCoverageExtension;
|
import google.registry.persistence.transaction.JpaEntityCoverageExtension;
|
||||||
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension;
|
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension;
|
||||||
|
@ -92,6 +93,7 @@ import org.junit.runner.RunWith;
|
||||||
RegistryTest.class,
|
RegistryTest.class,
|
||||||
ReservedListSqlDaoTest.class,
|
ReservedListSqlDaoTest.class,
|
||||||
RegistryLockDaoTest.class,
|
RegistryLockDaoTest.class,
|
||||||
|
SignedMarkRevocationListDaoTest.class,
|
||||||
Spec11ThreatMatchTest.class,
|
Spec11ThreatMatchTest.class,
|
||||||
// AfterSuiteTest must be the last entry. See class javadoc for details.
|
// AfterSuiteTest must be the last entry. See class javadoc for details.
|
||||||
AfterSuiteTest.class
|
AfterSuiteTest.class
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -67,3 +67,4 @@ V66__create_rde_revision.sql
|
||||||
V67__grace_period_history_ids.sql
|
V67__grace_period_history_ids.sql
|
||||||
V68__make_reserved_list_nullable_in_registry.sql
|
V68__make_reserved_list_nullable_in_registry.sql
|
||||||
V69__change_primary_key_and_add_history_table_for_delegation_signer.sql
|
V69__change_primary_key_and_add_history_table_for_delegation_signer.sql
|
||||||
|
V70__signed_mark_revocation_list.sql
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
-- 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.
|
||||||
|
|
||||||
|
create table "SignedMarkRevocationEntry" (
|
||||||
|
revision_id int8 not null,
|
||||||
|
revocation_time timestamptz not null,
|
||||||
|
smd_id text not null,
|
||||||
|
primary key (revision_id, smd_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table "SignedMarkRevocationList" (
|
||||||
|
revision_id bigserial not null,
|
||||||
|
creation_time timestamptz,
|
||||||
|
primary key (revision_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
alter table if exists "SignedMarkRevocationEntry"
|
||||||
|
add constraint FK5ivlhvs3121yx2li5tqh54u4
|
||||||
|
foreign key (revision_id)
|
||||||
|
references "SignedMarkRevocationList";
|
|
@ -617,6 +617,19 @@
|
||||||
primary key (revision_id)
|
primary key (revision_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create table "SignedMarkRevocationEntry" (
|
||||||
|
revision_id int8 not null,
|
||||||
|
revocation_time timestamptz not null,
|
||||||
|
smd_id text not null,
|
||||||
|
primary key (revision_id, smd_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table "SignedMarkRevocationList" (
|
||||||
|
revision_id bigserial not null,
|
||||||
|
creation_time timestamptz,
|
||||||
|
primary key (revision_id)
|
||||||
|
);
|
||||||
|
|
||||||
create table "Spec11ThreatMatch" (
|
create table "Spec11ThreatMatch" (
|
||||||
id bigserial not null,
|
id bigserial not null,
|
||||||
check_date date not null,
|
check_date date not null,
|
||||||
|
@ -779,3 +792,8 @@ create index spec11threatmatch_check_date_idx on "Spec11ThreatMatch" (check_date
|
||||||
add constraint FKgq03rk0bt1hb915dnyvd3vnfc
|
add constraint FKgq03rk0bt1hb915dnyvd3vnfc
|
||||||
foreign key (revision_id)
|
foreign key (revision_id)
|
||||||
references "ReservedList";
|
references "ReservedList";
|
||||||
|
|
||||||
|
alter table if exists "SignedMarkRevocationEntry"
|
||||||
|
add constraint FK5ivlhvs3121yx2li5tqh54u4
|
||||||
|
foreign key (revision_id)
|
||||||
|
references "SignedMarkRevocationList";
|
||||||
|
|
|
@ -886,6 +886,46 @@ CREATE SEQUENCE public."SafeBrowsingThreat_id_seq"
|
||||||
ALTER SEQUENCE public."SafeBrowsingThreat_id_seq" OWNED BY public."Spec11ThreatMatch".id;
|
ALTER SEQUENCE public."SafeBrowsingThreat_id_seq" OWNED BY public."Spec11ThreatMatch".id;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: SignedMarkRevocationEntry; Type: TABLE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public."SignedMarkRevocationEntry" (
|
||||||
|
revision_id bigint NOT NULL,
|
||||||
|
revocation_time timestamp with time zone NOT NULL,
|
||||||
|
smd_id text NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: SignedMarkRevocationList; Type: TABLE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public."SignedMarkRevocationList" (
|
||||||
|
revision_id bigint NOT NULL,
|
||||||
|
creation_time timestamp with time zone
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: SignedMarkRevocationList_revision_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE SEQUENCE public."SignedMarkRevocationList_revision_id_seq"
|
||||||
|
START WITH 1
|
||||||
|
INCREMENT BY 1
|
||||||
|
NO MINVALUE
|
||||||
|
NO MAXVALUE
|
||||||
|
CACHE 1;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: SignedMarkRevocationList_revision_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER SEQUENCE public."SignedMarkRevocationList_revision_id_seq" OWNED BY public."SignedMarkRevocationList".revision_id;
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: Tld; Type: TABLE; Schema: public; Owner: -
|
-- Name: Tld; Type: TABLE; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -996,6 +1036,13 @@ ALTER TABLE ONLY public."RegistryLock" ALTER COLUMN revision_id SET DEFAULT next
|
||||||
ALTER TABLE ONLY public."ReservedList" ALTER COLUMN revision_id SET DEFAULT nextval('public."ReservedList_revision_id_seq"'::regclass);
|
ALTER TABLE ONLY public."ReservedList" ALTER COLUMN revision_id SET DEFAULT nextval('public."ReservedList_revision_id_seq"'::regclass);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: SignedMarkRevocationList revision_id; Type: DEFAULT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public."SignedMarkRevocationList" ALTER COLUMN revision_id SET DEFAULT nextval('public."SignedMarkRevocationList_revision_id_seq"'::regclass);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: Spec11ThreatMatch id; Type: DEFAULT; Schema: public; Owner: -
|
-- Name: Spec11ThreatMatch id; Type: DEFAULT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -1234,6 +1281,22 @@ ALTER TABLE ONLY public."Spec11ThreatMatch"
|
||||||
ADD CONSTRAINT "SafeBrowsingThreat_pkey" PRIMARY KEY (id);
|
ADD CONSTRAINT "SafeBrowsingThreat_pkey" PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: SignedMarkRevocationEntry SignedMarkRevocationEntry_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public."SignedMarkRevocationEntry"
|
||||||
|
ADD CONSTRAINT "SignedMarkRevocationEntry_pkey" PRIMARY KEY (revision_id, smd_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: SignedMarkRevocationList SignedMarkRevocationList_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public."SignedMarkRevocationList"
|
||||||
|
ADD CONSTRAINT "SignedMarkRevocationList_pkey" PRIMARY KEY (revision_id);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: Tld Tld_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
-- Name: Tld Tld_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -1655,6 +1718,14 @@ ALTER TABLE ONLY public."HostHistory"
|
||||||
ADD CONSTRAINT fk3d09knnmxrt6iniwnp8j2ykga FOREIGN KEY (history_registrar_id) REFERENCES public."Registrar"(registrar_id);
|
ADD CONSTRAINT fk3d09knnmxrt6iniwnp8j2ykga FOREIGN KEY (history_registrar_id) REFERENCES public."Registrar"(registrar_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: SignedMarkRevocationEntry fk5ivlhvs3121yx2li5tqh54u4; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public."SignedMarkRevocationEntry"
|
||||||
|
ADD CONSTRAINT fk5ivlhvs3121yx2li5tqh54u4 FOREIGN KEY (revision_id) REFERENCES public."SignedMarkRevocationList"(revision_id);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: ClaimsEntry fk6sc6at5hedffc0nhdcab6ivuq; Type: FK CONSTRAINT; Schema: public; Owner: -
|
-- Name: ClaimsEntry fk6sc6at5hedffc0nhdcab6ivuq; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue