mirror of
https://github.com/google/nomulus.git
synced 2025-05-01 12:37:52 +02:00
ModulesService does not provide a great API. Specifically, it doesn't have a way to get the hostname for a specific service; you have to get the hostname for a specific version as well. This is very rarely what we want, as we publish new versions every week and don't expect old ones to hang around for very long, so a task should execute against whatever the live version is, not whatever the current version was back when the task was enqueued (especially because that version might be deleted by now). This new and improved wrapper API removes the confusion and plays better with dependency injection to boot. We can also fold in other methods having to do with App Engine services, whereas ModulesService was quite limited in scope. This also has the side effect of fixing ResaveEntityAction, which is currently broken because the tasks it's enqueuing to execute up to 30 days in the future have the version hard-coded into the hostname, and we typically delete old versions sooner than that. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=206173763
103 lines
4.5 KiB
Java
103 lines
4.5 KiB
Java
// 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.export;
|
|
|
|
import static com.google.appengine.api.datastore.DatastoreServiceFactory.getDatastoreService;
|
|
import static com.google.appengine.api.taskqueue.QueueFactory.getQueue;
|
|
import static com.google.common.base.Strings.nullToEmpty;
|
|
import static com.google.common.collect.ImmutableList.toImmutableList;
|
|
|
|
import com.google.appengine.api.datastore.Query;
|
|
import com.google.appengine.api.taskqueue.TaskHandle;
|
|
import com.google.appengine.api.taskqueue.TaskOptions;
|
|
import com.google.appengine.api.taskqueue.TaskOptions.Method;
|
|
import com.google.common.collect.ImmutableSet;
|
|
import com.google.common.collect.Iterables;
|
|
import com.google.common.collect.Streams;
|
|
import google.registry.util.AppEngineServiceUtils;
|
|
import java.util.NoSuchElementException;
|
|
import javax.inject.Inject;
|
|
|
|
/** An object providing methods for starting and querying Datastore backups. */
|
|
public class DatastoreBackupService {
|
|
|
|
/** The internal kind name used for entities storing information about Datastore backups. */
|
|
static final String BACKUP_INFO_KIND = "_AE_Backup_Information";
|
|
|
|
/** The name of the app version used for hosting the Datastore Admin functionality. */
|
|
static final String DATASTORE_ADMIN_VERSION_NAME = "ah-builtin-python-bundle";
|
|
|
|
private final AppEngineServiceUtils appEngineServiceUtils;
|
|
|
|
@Inject
|
|
public DatastoreBackupService(AppEngineServiceUtils appEngineServiceUtils) {
|
|
this.appEngineServiceUtils = appEngineServiceUtils;
|
|
}
|
|
|
|
/**
|
|
* Generates the TaskOptions needed to trigger an AppEngine Datastore backup job.
|
|
*
|
|
* @see <a href="https://developers.google.com/appengine/articles/scheduled_backups">Scheduled Backups</a>
|
|
*/
|
|
private TaskOptions makeTaskOptions(
|
|
String queue, String name, String gcsBucket, ImmutableSet<String> kinds) {
|
|
String hostname =
|
|
appEngineServiceUtils.getVersionHostname("default", DATASTORE_ADMIN_VERSION_NAME);
|
|
TaskOptions options = TaskOptions.Builder.withUrl("/_ah/datastore_admin/backup.create")
|
|
.header("Host", hostname)
|
|
.method(Method.GET)
|
|
.param("name", name + "_") // Add underscore since the name will be used as a prefix.
|
|
.param("filesystem", "gs")
|
|
.param("gs_bucket_name", gcsBucket)
|
|
.param("queue", queue);
|
|
for (String kind : kinds) {
|
|
options.param("kind", kind);
|
|
}
|
|
return options;
|
|
}
|
|
|
|
/**
|
|
* Launches a new Datastore backup with the given name, GCS bucket, and set of kinds by
|
|
* submitting a task to the given task queue, and returns a handle to that task.
|
|
*/
|
|
public TaskHandle launchNewBackup(
|
|
String queue, String name, String gcsBucket, ImmutableSet<String> kinds) {
|
|
return getQueue(queue).add(makeTaskOptions(queue, name, gcsBucket, kinds));
|
|
}
|
|
|
|
/** Return an iterable of all Datastore backups whose names have the given string prefix. */
|
|
public Iterable<DatastoreBackupInfo> findAllByNamePrefix(final String namePrefix) {
|
|
// Need the raw DatastoreService to access the internal _AE_Backup_Information entities.
|
|
// TODO(b/19081037): make an Objectify entity class for these raw Datastore entities instead.
|
|
return Streams.stream(getDatastoreService().prepare(new Query(BACKUP_INFO_KIND)).asIterable())
|
|
.filter(entity -> nullToEmpty((String) entity.getProperty("name")).startsWith(namePrefix))
|
|
.map(DatastoreBackupInfo::new)
|
|
.collect(toImmutableList());
|
|
}
|
|
|
|
/**
|
|
* Return a single DatastoreBackup that uniquely matches this name prefix. Throws an IAE
|
|
* if no backups match or if more than one backup matches.
|
|
*/
|
|
public DatastoreBackupInfo findByName(final String namePrefix) {
|
|
try {
|
|
return Iterables.getOnlyElement(findAllByNamePrefix(namePrefix));
|
|
} catch (IllegalArgumentException e) {
|
|
throw new IllegalArgumentException("More than one backup with name prefix " + namePrefix, e);
|
|
} catch (NoSuchElementException e) {
|
|
throw new IllegalArgumentException("No backup found with name prefix " + namePrefix, e);
|
|
}
|
|
}
|
|
}
|