Add MR to expand billing events into OneTimes

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=124837292
This commit is contained in:
Chris Tingue 2016-06-14 07:52:13 -07:00 committed by Justine Tunney
parent 7cf4ddce97
commit 2a83d122ef
6 changed files with 69 additions and 8 deletions

View file

@ -265,6 +265,12 @@
<url-pattern>/_dr/task/dnsRefreshForHostRename</url-pattern> <url-pattern>/_dr/task/dnsRefreshForHostRename</url-pattern>
</servlet-mapping> </servlet-mapping>
<!-- Mapreduce to expand recurring billing events into OneTimes. -->
<servlet-mapping>
<servlet-name>backend-servlet</servlet-name>
<url-pattern>/_dr/task/expandRecurringBillingEvents</url-pattern>
</servlet-mapping>
<!-- Security config --> <!-- Security config -->
<security-constraint> <security-constraint>
<web-resource-collection> <web-resource-collection>

View file

@ -20,8 +20,6 @@ import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.util.DateTimeUtils.START_OF_TIME; import static google.registry.util.DateTimeUtils.START_OF_TIME;
import com.google.common.annotations.VisibleForTesting;
import com.googlecode.objectify.Key; import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity; import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id; import com.googlecode.objectify.annotation.Id;
@ -69,7 +67,12 @@ public class Cursor extends ImmutableObject {
*/ */
RDE_UPLOAD_SFTP(Registry.class), RDE_UPLOAD_SFTP(Registry.class),
/** Cursor for ensuring rolling transactional isolation of recurring billing expansion. */ /**
* Cursor for ensuring rolling transactional isolation of recurring billing expansion. The
* value of this cursor represents the exclusive upper bound on the range of billing times
* for which Recurring billing events have been expanded (i.e. the inclusive first billing time
* for the next expansion job).
*/
RECURRING_BILLING(EntityGroupRoot.class); RECURRING_BILLING(EntityGroupRoot.class);
/** See the definition of scope on {@link #getScopeClass}. */ /** See the definition of scope on {@link #getScopeClass}. */
@ -120,16 +123,14 @@ public class Cursor extends ImmutableObject {
} }
/** Creates a unique key for a given scope and cursor type. */ /** Creates a unique key for a given scope and cursor type. */
@VisibleForTesting public static Key<Cursor> createKey(CursorType cursorType, ImmutableObject scope) {
static Key<Cursor> createKey(CursorType cursorType, ImmutableObject scope) {
Key<? extends ImmutableObject> scopeKey = Key.create(scope); Key<? extends ImmutableObject> scopeKey = Key.create(scope);
checkValidCursorTypeForScope(cursorType, scopeKey); checkValidCursorTypeForScope(cursorType, scopeKey);
return Key.create(getCrossTldKey(), Cursor.class, generateId(cursorType, scopeKey)); return Key.create(getCrossTldKey(), Cursor.class, generateId(cursorType, scopeKey));
} }
/** Creates a unique key for a given global cursor type. */ /** Creates a unique key for a given global cursor type. */
@VisibleForTesting public static Key<Cursor> createGlobalKey(CursorType cursorType) {
static Key<Cursor> createGlobalKey(CursorType cursorType) {
checkArgument( checkArgument(
cursorType.getScopeClass().equals(EntityGroupRoot.class), cursorType.getScopeClass().equals(EntityGroupRoot.class),
"Cursor type is not a global cursor."); "Cursor type is not a global cursor.");

View file

@ -14,11 +14,11 @@ java_library(
"//java/com/google/common/net", "//java/com/google/common/net",
"//java/google/registry/backup", "//java/google/registry/backup",
"//java/google/registry/bigquery", "//java/google/registry/bigquery",
"//java/google/registry/billing",
"//java/google/registry/config", "//java/google/registry/config",
"//java/google/registry/cron", "//java/google/registry/cron",
"//java/google/registry/dns", "//java/google/registry/dns",
"//java/google/registry/dns/writer/api", "//java/google/registry/dns/writer/api",
"//java/google/registry/dns/writer/dnsupdate",
"//java/google/registry/export", "//java/google/registry/export",
"//java/google/registry/export/sheet", "//java/google/registry/export/sheet",
"//java/google/registry/flows", "//java/google/registry/flows",
@ -35,6 +35,7 @@ java_library(
"//java/google/registry/util", "//java/google/registry/util",
"//third_party/java/bouncycastle", "//third_party/java/bouncycastle",
"//third_party/java/dagger", "//third_party/java/dagger",
"//third_party/java/joda_time",
"//third_party/java/jsr305_annotations", "//third_party/java/jsr305_annotations",
"//third_party/java/jsr330_inject", "//third_party/java/jsr330_inject",
"//third_party/java/servlet/servlet_api", "//third_party/java/servlet/servlet_api",

View file

@ -15,14 +15,20 @@
package google.registry.module.backend; package google.registry.module.backend;
import static google.registry.model.registry.Registries.assertTldExists; import static google.registry.model.registry.Registries.assertTldExists;
import static google.registry.request.RequestParameters.extractOptionalDatetimeParameter;
import static google.registry.request.RequestParameters.extractRequiredParameter; import static google.registry.request.RequestParameters.extractRequiredParameter;
import com.google.common.base.Optional;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import google.registry.billing.ExpandRecurringBillingEventsAction;
import google.registry.request.Parameter; import google.registry.request.Parameter;
import google.registry.request.RequestParameters; import google.registry.request.RequestParameters;
import org.joda.time.DateTime;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
/** /**
@ -36,4 +42,11 @@ public class BackendModule {
static String provideTld(HttpServletRequest req) { static String provideTld(HttpServletRequest req) {
return assertTldExists(extractRequiredParameter(req, RequestParameters.PARAM_TLD)); return assertTldExists(extractRequiredParameter(req, RequestParameters.PARAM_TLD));
} }
@Provides
@Parameter("cursorTime")
static Optional<DateTime> provideCursorTime(HttpServletRequest req) {
return extractOptionalDatetimeParameter(
req, ExpandRecurringBillingEventsAction.PARAM_CURSOR_TIME);
}
} }

View file

@ -160,6 +160,25 @@ public final class RequestParameters {
} }
} }
/**
* Returns first request parameter associated with {@code name} parsed as an
* <a href="https://goo.gl/pk5Q2k">ISO 8601</a> timestamp, e.g. {@code 1984-12-18TZ},
* {@code 2000-01-01T16:20:00Z}.
*
* @throws BadRequestException if request parameter is present but not a valid {@link DateTime}.
*/
public static Optional<DateTime> extractOptionalDatetimeParameter(
HttpServletRequest req, String name) {
String stringParam = req.getParameter(name);
try {
return isNullOrEmpty(stringParam)
? Optional.<DateTime>absent()
: Optional.of(DateTime.parse(stringParam));
} catch (IllegalArgumentException e) {
throw new BadRequestException("Bad ISO 8601 timestamp: " + name);
}
}
/** /**
* Returns first request parameter associated with {@code name} parsed as an optional * Returns first request parameter associated with {@code name} parsed as an optional
* {@link InetAddress} (which might be IPv6). * {@link InetAddress} (which might be IPv6).

View file

@ -17,6 +17,7 @@ package google.registry.request;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.request.RequestParameters.extractBooleanParameter; import static google.registry.request.RequestParameters.extractBooleanParameter;
import static google.registry.request.RequestParameters.extractEnumParameter; import static google.registry.request.RequestParameters.extractEnumParameter;
import static google.registry.request.RequestParameters.extractOptionalDatetimeParameter;
import static google.registry.request.RequestParameters.extractOptionalParameter; import static google.registry.request.RequestParameters.extractOptionalParameter;
import static google.registry.request.RequestParameters.extractRequiredDatetimeParameter; import static google.registry.request.RequestParameters.extractRequiredDatetimeParameter;
import static google.registry.request.RequestParameters.extractRequiredMaybeEmptyParameter; import static google.registry.request.RequestParameters.extractRequiredMaybeEmptyParameter;
@ -184,6 +185,26 @@ public class RequestParametersTest {
extractRequiredDatetimeParameter(req, "timeParam"); extractRequiredDatetimeParameter(req, "timeParam");
} }
@Test
public void testExtractOptionalDatetimeParameter_correctValue_works() throws Exception {
when(req.getParameter("timeParam")).thenReturn("2015-08-27T13:25:34.123Z");
assertThat(extractOptionalDatetimeParameter(req, "timeParam"))
.hasValue(DateTime.parse("2015-08-27T13:25:34.123Z"));
}
@Test
public void testExtractOptionalDatetimeParameter_badValue_throwsBadRequest() throws Exception {
when(req.getParameter("timeParam")).thenReturn("Tuesday at three o'clock");
thrown.expect(BadRequestException.class, "timeParam");
extractOptionalDatetimeParameter(req, "timeParam");
}
@Test
public void testExtractOptionalDatetimeParameter_empty_returnsAbsent() throws Exception {
when(req.getParameter("timeParam")).thenReturn("");
assertThat(extractOptionalDatetimeParameter(req, "timeParam")).isAbsent();
}
@Test @Test
public void testExtractRequiredDatetimeParameter_noValue_throwsBadRequest() throws Exception { public void testExtractRequiredDatetimeParameter_noValue_throwsBadRequest() throws Exception {
thrown.expect(BadRequestException.class, "timeParam"); thrown.expect(BadRequestException.class, "timeParam");