mirror of
https://github.com/google/nomulus
synced 2026-06-09 08:22:59 +00:00
Add initial support to write/update reserved lists to Cloud SQL (#388)
* Add initial support to write reserved list to Cloud SQL * Add support to update reserved list in Cloud SQL * Fix wrong check when override is enabled in create command * Add sql dependent tests to the suite * Address comment * Make invocation of super.execute more readable * Add test to check upper case and non-puny code label * Move ReservedListDao related classes to schema package * Simplify a function name
This commit is contained in:
@@ -121,7 +121,7 @@ public abstract class BaseDomainLabelList<T extends Comparable<?>, R extends Dom
|
||||
* (sans comment) and the comment (in that order). If the line was blank or empty, then this
|
||||
* method returns an empty list.
|
||||
*/
|
||||
protected static List<String> splitOnComment(String line) {
|
||||
public static List<String> splitOnComment(String line) {
|
||||
String comment = "";
|
||||
int index = line.indexOf('#');
|
||||
if (index != -1) {
|
||||
|
||||
@@ -14,13 +14,20 @@
|
||||
|
||||
package google.registry.schema.tld;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.collect.ImmutableList.sortedCopyOf;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.model.CreateAutoTimestamp;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.registry.label.ReservationType;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.CollectionTable;
|
||||
import javax.persistence.Column;
|
||||
@@ -114,6 +121,22 @@ public class ReservedList extends ImmutableObject {
|
||||
/** Constructs a {@link ReservedList} object. */
|
||||
public static ReservedList create(
|
||||
String name, Boolean shouldPublish, Map<String, ReservedEntry> labelsToReservations) {
|
||||
ImmutableList<String> invalidLabels =
|
||||
labelsToReservations.entrySet().parallelStream()
|
||||
.flatMap(
|
||||
entry -> {
|
||||
String label = entry.getKey();
|
||||
if (label.equals(canonicalizeDomainName(label))) {
|
||||
return Stream.empty();
|
||||
} else {
|
||||
return Stream.of(label);
|
||||
}
|
||||
})
|
||||
.collect(toImmutableList());
|
||||
checkArgument(
|
||||
invalidLabels.isEmpty(),
|
||||
"Label(s) [%s] must be in puny-coded, lower-case form",
|
||||
Joiner.on(",").join(sortedCopyOf(invalidLabels)));
|
||||
return new ReservedList(name, shouldPublish, labelsToReservations);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2019 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.schema.tld;
|
||||
|
||||
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
|
||||
|
||||
/** Data access object class for {@link ReservedList} */
|
||||
public class ReservedListDao {
|
||||
|
||||
/** Persist a new reserved list to Cloud SQL. */
|
||||
public static void save(ReservedList reservedList) {
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().persist(reservedList));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the reserved list of the given name exists.
|
||||
*
|
||||
* <p>This means that at least one reserved list revision must exist for the given name.
|
||||
*/
|
||||
public static boolean checkExists(String reservedListName) {
|
||||
return jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
jpaTm()
|
||||
.getEntityManager()
|
||||
.createQuery("SELECT 1 FROM ReservedList WHERE name = :name", Integer.class)
|
||||
.setParameter("name", reservedListName)
|
||||
.setMaxResults(1)
|
||||
.getResultList()
|
||||
.size()
|
||||
> 0);
|
||||
}
|
||||
|
||||
private ReservedListDao() {}
|
||||
}
|
||||
@@ -14,9 +14,23 @@
|
||||
|
||||
package google.registry.tools;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.model.registry.label.BaseDomainLabelList.splitOnComment;
|
||||
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.HashMultiset;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multiset;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.model.registry.label.ReservationType;
|
||||
import google.registry.schema.tld.ReservedList.ReservedEntry;
|
||||
import google.registry.tools.params.PathParameter;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
@@ -25,6 +39,8 @@ import javax.annotation.Nullable;
|
||||
*/
|
||||
public abstract class CreateOrUpdateReservedListCommand extends MutatingCommand {
|
||||
|
||||
static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
@Nullable
|
||||
@Parameter(
|
||||
names = {"-n", "--name"},
|
||||
@@ -45,4 +61,81 @@ public abstract class CreateOrUpdateReservedListCommand extends MutatingCommand
|
||||
"Whether the list is published to the concatenated list on Drive (defaults to true).",
|
||||
arity = 1)
|
||||
Boolean shouldPublish;
|
||||
|
||||
@Parameter(
|
||||
names = {"--also_cloud_sql"},
|
||||
description =
|
||||
"Persist reserved list to Cloud SQL in addition to Datastore; defaults to false.")
|
||||
boolean alsoCloudSql;
|
||||
|
||||
google.registry.schema.tld.ReservedList cloudSqlReservedList;
|
||||
|
||||
abstract void saveToCloudSql();
|
||||
|
||||
@Override
|
||||
protected String execute() throws Exception {
|
||||
// Save the list to Datastore and output its response.
|
||||
String output = super.execute();
|
||||
logger.atInfo().log(output);
|
||||
|
||||
String cloudSqlMessage;
|
||||
if (alsoCloudSql) {
|
||||
cloudSqlMessage =
|
||||
String.format(
|
||||
"Saved reserved list %s with %d entries",
|
||||
name, cloudSqlReservedList.getLabelsToReservations().size());
|
||||
try {
|
||||
logger.atInfo().log("Saving reserved list to Cloud SQL for TLD %s", name);
|
||||
saveToCloudSql();
|
||||
logger.atInfo().log(cloudSqlMessage);
|
||||
} catch (Throwable e) {
|
||||
cloudSqlMessage =
|
||||
"Unexpected error saving reserved list to Cloud SQL from nomulus tool command";
|
||||
logger.atSevere().withCause(e).log(cloudSqlMessage);
|
||||
}
|
||||
} else {
|
||||
cloudSqlMessage = "Persisting reserved list to Cloud SQL is not enabled";
|
||||
}
|
||||
return cloudSqlMessage;
|
||||
}
|
||||
|
||||
/** Turns the list CSV data into a map of labels to {@link ReservedEntry}. */
|
||||
static ImmutableMap<String, ReservedEntry> parseToReservationsByLabels(Iterable<String> lines) {
|
||||
Map<String, ReservedEntry> labelsToEntries = Maps.newHashMap();
|
||||
Multiset<String> duplicateLabels = HashMultiset.create();
|
||||
for (String originalLine : lines) {
|
||||
List<String> lineAndComment = splitOnComment(originalLine);
|
||||
if (lineAndComment.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
String line = lineAndComment.get(0);
|
||||
String comment = lineAndComment.get(1);
|
||||
List<String> parts = Splitter.on(',').trimResults().splitToList(line);
|
||||
checkArgument(
|
||||
parts.size() == 2 || parts.size() == 3,
|
||||
"Could not parse line in reserved list: %s",
|
||||
originalLine);
|
||||
String label = parts.get(0);
|
||||
checkArgument(
|
||||
label.equals(canonicalizeDomainName(label)),
|
||||
"Label '%s' must be in puny-coded, lower-case form",
|
||||
label);
|
||||
ReservationType reservationType = ReservationType.valueOf(parts.get(1));
|
||||
ReservedEntry reservedEntry = ReservedEntry.create(reservationType, comment);
|
||||
// Check if the label was already processed for this list (which is an error), and if so,
|
||||
// accumulate it so that a list of all duplicates can be thrown.
|
||||
if (labelsToEntries.containsKey(label)) {
|
||||
duplicateLabels.add(label, duplicateLabels.contains(label) ? 1 : 2);
|
||||
} else {
|
||||
labelsToEntries.put(label, reservedEntry);
|
||||
}
|
||||
}
|
||||
if (!duplicateLabels.isEmpty()) {
|
||||
throw new IllegalStateException(
|
||||
String.format(
|
||||
"Reserved list cannot contain duplicate labels. Dupes (with counts) were: %s",
|
||||
duplicateLabels));
|
||||
}
|
||||
return ImmutableMap.copyOf(labelsToEntries);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ package google.registry.tools;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.model.registry.Registries.assertTldExists;
|
||||
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.util.ListNamingUtils.convertFilePathToName;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.joda.time.DateTimeZone.UTC;
|
||||
@@ -26,6 +27,7 @@ import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.base.Strings;
|
||||
import google.registry.model.registry.label.ReservedList;
|
||||
import google.registry.schema.tld.ReservedListDao;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
import org.joda.time.DateTime;
|
||||
@@ -54,15 +56,35 @@ final class CreateReservedListCommand extends CreateOrUpdateReservedListCommand
|
||||
validateListName(name);
|
||||
}
|
||||
DateTime now = DateTime.now(UTC);
|
||||
List<String> allLines = Files.readAllLines(input, UTF_8);
|
||||
boolean shouldPublish = this.shouldPublish == null || this.shouldPublish;
|
||||
ReservedList reservedList =
|
||||
new ReservedList.Builder()
|
||||
.setName(name)
|
||||
.setReservedListMapFromLines(Files.readAllLines(input, UTF_8))
|
||||
.setShouldPublish(shouldPublish == null || shouldPublish)
|
||||
.setReservedListMapFromLines(allLines)
|
||||
.setShouldPublish(shouldPublish)
|
||||
.setCreationTime(now)
|
||||
.setLastUpdateTime(now)
|
||||
.build();
|
||||
stageEntityChange(null, reservedList);
|
||||
if (alsoCloudSql) {
|
||||
cloudSqlReservedList =
|
||||
google.registry.schema.tld.ReservedList.create(
|
||||
name, shouldPublish, parseToReservationsByLabels(allLines));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void saveToCloudSql() {
|
||||
jpaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
checkArgument(
|
||||
!ReservedListDao.checkExists(cloudSqlReservedList.getName()),
|
||||
"A reserved list of this name already exists: %s.",
|
||||
cloudSqlReservedList.getName());
|
||||
ReservedListDao.save(cloudSqlReservedList);
|
||||
});
|
||||
}
|
||||
|
||||
private static void validateListName(String name) {
|
||||
|
||||
@@ -15,14 +15,17 @@
|
||||
package google.registry.tools;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.util.ListNamingUtils.convertFilePathToName;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.beust.jcommander.Parameters;
|
||||
import com.google.common.base.Strings;
|
||||
import google.registry.model.registry.label.ReservedList;
|
||||
import google.registry.schema.tld.ReservedListDao;
|
||||
import google.registry.util.SystemClock;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/** Command to safely update {@link ReservedList} on Datastore. */
|
||||
@@ -32,18 +35,38 @@ final class UpdateReservedListCommand extends CreateOrUpdateReservedListCommand
|
||||
@Override
|
||||
protected void init() throws Exception {
|
||||
name = Strings.isNullOrEmpty(name) ? convertFilePathToName(input) : name;
|
||||
// TODO(shicong): Read existing entry from Cloud SQL
|
||||
Optional<ReservedList> existing = ReservedList.get(name);
|
||||
checkArgument(
|
||||
existing.isPresent(), "Could not update reserved list %s because it doesn't exist.", name);
|
||||
boolean shouldPublish =
|
||||
this.shouldPublish == null ? existing.get().getShouldPublish() : this.shouldPublish;
|
||||
List<String> allLines = Files.readAllLines(input, UTF_8);
|
||||
ReservedList.Builder updated =
|
||||
existing
|
||||
.get()
|
||||
.asBuilder()
|
||||
.setReservedListMapFromLines(Files.readAllLines(input, UTF_8))
|
||||
.setLastUpdateTime(new SystemClock().nowUtc());
|
||||
if (shouldPublish != null) {
|
||||
updated.setShouldPublish(shouldPublish);
|
||||
}
|
||||
.setReservedListMapFromLines(allLines)
|
||||
.setLastUpdateTime(new SystemClock().nowUtc())
|
||||
.setShouldPublish(shouldPublish);
|
||||
stageEntityChange(existing.get(), updated.build());
|
||||
if (alsoCloudSql) {
|
||||
cloudSqlReservedList =
|
||||
google.registry.schema.tld.ReservedList.create(
|
||||
name, shouldPublish, parseToReservationsByLabels(allLines));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void saveToCloudSql() {
|
||||
jpaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
checkArgument(
|
||||
ReservedListDao.checkExists(cloudSqlReservedList.getName()),
|
||||
"A reserved list of this name doesn't exist: %s.",
|
||||
cloudSqlReservedList.getName());
|
||||
ReservedListDao.save(cloudSqlReservedList);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user