mirror of
https://github.com/google/nomulus.git
synced 2025-04-30 12:07:51 +02:00
The dark lord Gosling designed the Java package naming system so that ownership flows from the DNS system. Since we own the domain name registry.google, it seems only appropriate that we should use google.registry as our package name.
166 lines
7.1 KiB
Java
166 lines
7.1 KiB
Java
// Copyright 2016 The Domain Registry 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.tmch;
|
|
|
|
import static com.google.appengine.api.taskqueue.QueueFactory.getQueue;
|
|
import static com.google.appengine.api.taskqueue.TaskOptions.Builder.withUrl;
|
|
import static com.google.appengine.api.urlfetch.FetchOptions.Builder.validateCertificate;
|
|
import static com.google.appengine.api.urlfetch.HTTPMethod.POST;
|
|
import static com.google.common.base.Preconditions.checkArgument;
|
|
import static com.google.common.net.HttpHeaders.LOCATION;
|
|
import static com.google.common.net.MediaType.CSV_UTF_8;
|
|
import static google.registry.tmch.LordnTask.COLUMNS_CLAIMS;
|
|
import static google.registry.tmch.LordnTask.COLUMNS_SUNRISE;
|
|
import static google.registry.tmch.LordnTask.convertTasksToCsv;
|
|
import static google.registry.util.UrlFetchUtils.getHeaderFirst;
|
|
import static google.registry.util.UrlFetchUtils.setPayloadMultipart;
|
|
import static javax.servlet.http.HttpServletResponse.SC_ACCEPTED;
|
|
|
|
import com.google.appengine.api.taskqueue.Queue;
|
|
import com.google.appengine.api.taskqueue.TaskHandle;
|
|
import com.google.appengine.api.taskqueue.TaskOptions;
|
|
import com.google.appengine.api.urlfetch.HTTPRequest;
|
|
import com.google.appengine.api.urlfetch.HTTPResponse;
|
|
import com.google.appengine.api.urlfetch.URLFetchService;
|
|
import com.google.common.base.Optional;
|
|
|
|
import google.registry.config.ConfigModule.Config;
|
|
import google.registry.request.Action;
|
|
import google.registry.request.Parameter;
|
|
import google.registry.request.RequestParameters;
|
|
import google.registry.util.Clock;
|
|
import google.registry.util.FormattingLogger;
|
|
import google.registry.util.UrlFetchException;
|
|
|
|
import org.joda.time.DateTime;
|
|
import org.joda.time.Duration;
|
|
|
|
import java.io.IOException;
|
|
import java.net.URL;
|
|
import java.util.List;
|
|
import java.util.Random;
|
|
|
|
import javax.inject.Inject;
|
|
|
|
/**
|
|
* Action that reads the NORDN pull queues, uploads claims and sunrise marks data to TMCH, and
|
|
* enqueues subsequent upload verification tasks. A unique actionLogId is generated and passed
|
|
* along to the verify action so that connected verify tasks can be identified by looking at logs.
|
|
*
|
|
* @see NordnVerifyAction
|
|
*/
|
|
@Action(path = NordnUploadAction.PATH, method = Action.Method.POST, automaticallyPrintOk = true)
|
|
public final class NordnUploadAction implements Runnable {
|
|
|
|
static final String PATH = "/_dr/task/nordnUpload";
|
|
static final String LORDN_PHASE_PARAM = "lordn-phase";
|
|
|
|
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
|
|
|
/**
|
|
* A unique (enough) id that is outputted in log lines to make it clear which log lines are
|
|
* associated with a given invocation of the NordnUploadAction in the event that multiple
|
|
* instances execute simultaneously.
|
|
*/
|
|
private final String actionLogId = String.valueOf(1000000000 + new Random().nextInt(1000000000));
|
|
|
|
@Inject Clock clock;
|
|
@Inject LordnRequestInitializer lordnRequestInitializer;
|
|
@Inject URLFetchService fetchService;
|
|
@Inject @Config("tmchMarksdbUrl") String tmchMarksdbUrl;
|
|
@Inject @Parameter(LORDN_PHASE_PARAM) String phase;
|
|
@Inject @Parameter(RequestParameters.PARAM_TLD) String tld;
|
|
@Inject NordnUploadAction() {}
|
|
|
|
/**
|
|
* These LORDN parameter names correspond to the relative paths in LORDN URLs and cannot be
|
|
* changed on our end.
|
|
*/
|
|
private static final String PARAM_LORDN_PHASE_SUNRISE = "sunrise";
|
|
private static final String PARAM_LORDN_PHASE_CLAIMS = "claims";
|
|
|
|
/** How long to wait before attempting to verify an upload by fetching the log. */
|
|
private static final Duration VERIFY_DELAY = Duration.standardMinutes(30);
|
|
|
|
@Override
|
|
public void run() {
|
|
try {
|
|
processLordnTasks();
|
|
} catch (IOException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
private void processLordnTasks() throws IOException {
|
|
checkArgument(phase.equals(PARAM_LORDN_PHASE_SUNRISE)
|
|
|| phase.equals(PARAM_LORDN_PHASE_CLAIMS),
|
|
"Invalid phase specified to Nordn servlet: %s.", phase);
|
|
DateTime now = clock.nowUtc();
|
|
Queue queue = getQueue(
|
|
phase.equals(PARAM_LORDN_PHASE_SUNRISE) ? LordnTask.QUEUE_SUNRISE : LordnTask.QUEUE_CLAIMS);
|
|
String columns = phase.equals(PARAM_LORDN_PHASE_SUNRISE) ? COLUMNS_SUNRISE : COLUMNS_CLAIMS;
|
|
List<TaskHandle> tasks = LordnTask.loadAllTasks(queue, tld);
|
|
if (!tasks.isEmpty()) {
|
|
String csvData = convertTasksToCsv(tasks, now, columns);
|
|
uploadCsvToLordn(String.format("/LORDN/%s/%s", tld, phase), csvData);
|
|
queue.deleteTask(tasks);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Upload LORDN file to MarksDB.
|
|
*
|
|
* <p>Idempotency: If the exact same LORDN report is uploaded twice, the MarksDB server will
|
|
* return the same confirmation number.
|
|
*
|
|
* @see "http://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-6.3"
|
|
*/
|
|
private void uploadCsvToLordn(String urlPath, String csvData) throws IOException {
|
|
String url = tmchMarksdbUrl + urlPath;
|
|
logger.infofmt("LORDN upload task %s: Sending to URL: %s ; data: %s",
|
|
actionLogId, url, csvData);
|
|
HTTPRequest req = new HTTPRequest(new URL(url), POST, validateCertificate().setDeadline(60d));
|
|
lordnRequestInitializer.initialize(req, tld);
|
|
setPayloadMultipart(req, "file", "claims.csv", CSV_UTF_8, csvData);
|
|
HTTPResponse rsp = fetchService.fetch(req);
|
|
logger.infofmt("LORDN upload task %s response: HTTP response code %d, response data: %s",
|
|
actionLogId, rsp.getResponseCode(), rsp.getContent());
|
|
if (rsp.getResponseCode() != SC_ACCEPTED) {
|
|
throw new UrlFetchException(
|
|
String.format("LORDN upload task %s error: Failed to upload LORDN claims to MarksDB",
|
|
actionLogId),
|
|
req, rsp);
|
|
}
|
|
Optional<String> location = getHeaderFirst(rsp, LOCATION);
|
|
if (!location.isPresent()) {
|
|
throw new UrlFetchException(
|
|
String.format("LORDN upload task %s error: MarksDB failed to provide a Location header",
|
|
actionLogId),
|
|
req, rsp);
|
|
}
|
|
getQueue(NordnVerifyAction.QUEUE).add(makeVerifyTask(new URL(location.get()), csvData));
|
|
}
|
|
|
|
private TaskOptions makeVerifyTask(URL url, String csvData) {
|
|
// This task doesn't technically need csvData. The only reason it's passed along is in case the
|
|
// upload is rejected, in which case csvData will be logged so that it may be uploaded manually.
|
|
return withUrl(NordnVerifyAction.PATH)
|
|
.header(NordnVerifyAction.URL_HEADER, url.toString())
|
|
.header(NordnVerifyAction.HEADER_ACTION_LOG_ID, actionLogId)
|
|
.param(RequestParameters.PARAM_TLD, tld)
|
|
.param(NordnVerifyAction.PARAM_CSV_DATA, csvData)
|
|
.countdownMillis(VERIFY_DELAY.getMillis());
|
|
}
|
|
}
|