Isolate customizable code in activity reporting

Modularize the code for DNS count reporting to allow it to be customized for
more flexible systems.
Tested:
  Uploaded to alpha with hacks to allow admin initiating and logging from the
  DnsCountQueryCoordinatorModule, verified that the provider function is invoked and
  that the action runs successfully.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=225225587
This commit is contained in:
mmuller 2018-12-12 11:48:08 -08:00 committed by Michael Muller
parent 6966151bed
commit c396957d15
13 changed files with 208 additions and 12 deletions

View file

@ -1135,6 +1135,12 @@ public final class RegistryConfig {
return config.registryPolicy.allocationTokenCustomLogicClass;
}
@Provides
@Config("dnsCountQueryCoordinatorClass")
public static String dnsCountQueryCoordinatorClass(RegistryConfigSettings config) {
return config.registryPolicy.dnsCountQueryCoordinatorClass;
}
/** Returns the disclaimer text for the exported premium terms. */
@Provides
@Config("premiumTermsExportDisclaimer")

View file

@ -77,6 +77,7 @@ public class RegistryConfigSettings {
public String customLogicFactoryClass;
public String whoisCommandFactoryClass;
public String allocationTokenCustomLogicClass;
public String dnsCountQueryCoordinatorClass;
public int contactAutomaticTransferDays;
public String greetingServerId;
public List<String> registrarChangesNotificationEmailAddresses;

View file

@ -55,6 +55,10 @@ registryPolicy:
# See flows/domain/token/AllocationTokenCustomLogic.java
allocationTokenCustomLogicClass: google.registry.flows.domain.token.AllocationTokenCustomLogic
# Custom logic class for handling DNS query count reporting for ICANN.
# See reporting/icann/DnsCountQueryCoordinator.java
dnsCountQueryCoordinatorClass: google.registry.reporting.icann.BasicDnsCountQueryCoordinator
# Length of time after which contact transfers automatically conclude.
contactAutomaticTransferDays: 5

View file

@ -73,6 +73,7 @@ import google.registry.reporting.billing.BillingModule;
import google.registry.reporting.billing.CopyDetailReportsAction;
import google.registry.reporting.billing.GenerateInvoicesAction;
import google.registry.reporting.billing.PublishInvoicesAction;
import google.registry.reporting.icann.DnsCountQueryCoordinatorModule;
import google.registry.reporting.icann.IcannReportingModule;
import google.registry.reporting.icann.IcannReportingStagingAction;
import google.registry.reporting.icann.IcannReportingUploadAction;
@ -100,6 +101,7 @@ import google.registry.tmch.TmchSmdrlAction;
BillingModule.class,
CloudDnsWriterModule.class,
CronModule.class,
DnsCountQueryCoordinatorModule.class,
DnsModule.class,
DnsUpdateConfigModule.class,
DnsUpdateWriterModule.class,

View file

@ -29,9 +29,7 @@ import org.joda.time.YearMonth;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
/**
* Utility class that produces SQL queries used to generate activity reports from Bigquery.
*/
/** Utility class that produces SQL queries used to generate activity reports from Bigquery. */
public final class ActivityReportingQueryBuilder implements QueryBuilder {
// Names for intermediary tables for overall activity reporting query.
@ -42,20 +40,23 @@ public final class ActivityReportingQueryBuilder implements QueryBuilder {
static final String WHOIS_COUNTS = "whois_counts";
static final String ACTIVITY_REPORT_AGGREGATION = "activity_report_aggregation";
@Inject @Config("projectId") String projectId;
@Inject
@Config("projectId")
String projectId;
@Inject YearMonth yearMonth;
@Inject ActivityReportingQueryBuilder() {}
@Inject DnsCountQueryCoordinator dnsCountQueryCoordinator;
@Inject
ActivityReportingQueryBuilder() {}
/** Returns the aggregate query which generates the activity report from the saved view. */
@Override
public String getReportQuery() {
return String.format(
"#standardSQL\nSELECT * FROM `%s.%s.%s`",
projectId,
ICANN_REPORTING_DATA_SET,
getTableName(ACTIVITY_REPORT_AGGREGATION));
projectId, ICANN_REPORTING_DATA_SET, getTableName(ACTIVITY_REPORT_AGGREGATION));
}
/** Sets the month we're doing activity reporting for, and returns the view query map. */
@ -67,6 +68,10 @@ public final class ActivityReportingQueryBuilder implements QueryBuilder {
return createQueryMap(firstDayOfMonth, lastDayOfMonth);
}
public void prepareForQuery() throws Exception {
dnsCountQueryCoordinator.prepareForQuery();
}
/** Returns a map from view name to its associated SQL query. */
private ImmutableMap<String, String> createQueryMap(
LocalDate firstDayOfMonth, LocalDate lastDayOfMonth) {
@ -80,8 +85,7 @@ public final class ActivityReportingQueryBuilder implements QueryBuilder {
.build();
queriesBuilder.put(getTableName(REGISTRAR_OPERATING_STATUS), operationalRegistrarsQuery);
String dnsCountsQuery =
SqlTemplate.create(getQueryFromFile("dns_counts.sql")).build();
String dnsCountsQuery = dnsCountQueryCoordinator.createQuery();
queriesBuilder.put(getTableName(DNS_COUNTS), dnsCountsQuery);
// Convert reportingMonth into YYYYMMDD format for Bigquery table partition pattern-matching.
@ -133,7 +137,6 @@ public final class ActivityReportingQueryBuilder implements QueryBuilder {
return queriesBuilder.build();
}
/** Returns the table name of the query, suffixed with the yearMonth in _yyyyMM format. */
private String getTableName(String queryName) {
return String.format("%s_%s", queryName, DateTimeFormat.forPattern("yyyyMM").print(yearMonth));

View file

@ -0,0 +1,38 @@
// Copyright 2018 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.reporting.icann;
import com.google.common.io.Resources;
import google.registry.util.ResourceUtils;
import google.registry.util.SqlTemplate;
/**
* DNS Count query for the basic case.
*/
public class BasicDnsCountQueryCoordinator implements DnsCountQueryCoordinator {
BasicDnsCountQueryCoordinator(DnsCountQueryCoordinator.Params params) {}
@Override
public String createQuery() {
return SqlTemplate.create(
ResourceUtils.readResourceUtf8(
Resources.getResource(this.getClass(), "sql/" + "dns_counts.sql")))
.build();
}
@Override
public void prepareForQuery() throws Exception {}
}

View file

@ -0,0 +1,58 @@
// Copyright 2018 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.reporting.icann;
import google.registry.bigquery.BigqueryConnection;
import org.joda.time.YearMonth;
/**
* Methods for preparing and querying DNS statistics.
*
* <p>DNS systems may have different ways of providing this information, so it's useful to
* modularize this.
*
* <p>Derived classes must provide a constructor that accepts a
* {@link google.registry.reporting.icann.DnsCountQueryCoordinator.Params}. To override this,
* define dnsCountQueryCoordinatorClass in your config file.
*/
public interface DnsCountQueryCoordinator {
/**
* Class to carry parameters for a new coordinator.
*
* If your report query requires any additional parameters, add them here.
*/
public class Params {
public BigqueryConnection bigquery;
/** The year and month of the report. */
public YearMonth yearMonth;
/** The Google Cloud project id. */
public String projectId;
public Params(BigqueryConnection bigquery, YearMonth yearMonth, String projectId) {
this.bigquery = bigquery;
this.yearMonth = yearMonth;
this.projectId = projectId;
}
}
/** Creates the string used to query bigtable for DNS count information. */
String createQuery();
/** Do any necessry preparation for the DNS query. */
void prepareForQuery() throws Exception;
}

View file

@ -0,0 +1,42 @@
// Copyright 2018 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.reporting.icann;
import static google.registry.util.TypeUtils.getClassFromString;
import static google.registry.util.TypeUtils.instantiate;
import dagger.Module;
import dagger.Provides;
import google.registry.bigquery.BigqueryConnection;
import google.registry.config.RegistryConfig.Config;
import org.joda.time.YearMonth;
/** Dagger module to provide the DnsCountQueryCoordinator. */
@Module
public class DnsCountQueryCoordinatorModule {
@Provides
static DnsCountQueryCoordinator provideDnsCountQueryCoordinator(
@Config("dnsCountQueryCoordinatorClass") String customClass,
BigqueryConnection bigquery,
YearMonth yearMonth,
@Config("projectId") String projectId) {
DnsCountQueryCoordinator.Params params =
new DnsCountQueryCoordinator.Params(bigquery, yearMonth, projectId);
DnsCountQueryCoordinator result =
instantiate(getClassFromString(customClass, DnsCountQueryCoordinator.class), params);
return result;
}
}

View file

@ -83,6 +83,10 @@ public class IcannReportingStager {
QueryBuilder queryBuilder =
(reportType == ReportType.ACTIVITY) ? activityQueryBuilder : transactionsQueryBuilder;
if (reportType == ReportType.ACTIVITY) {
// Prepare for the DNS count query, which may have special needs.
activityQueryBuilder.prepareForQuery();
}
ImmutableMap<String, String> viewQueryMap = queryBuilder.getViewQueryMap();
// Generate intermediary views

View file

@ -56,6 +56,22 @@ public class TypeUtils {
}
}
/**
* Instantiate a class with the specified constructor argument.
*
* <p>Because we use arg1's type to lookup the constructor, this only works if arg1's class is
* exactly the same type as the constructor argument. Subtypes are not allowed.
*/
public static <T, U> T instantiate(Class<? extends T> clazz, U arg1) {
checkArgument(Modifier.isPublic(clazz.getModifiers()),
"AppEngine's custom security manager won't let us reflectively access non-public types");
try {
return clazz.getConstructor(arg1.getClass()).newInstance(arg1);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
/**
* Returns the class referred to by a fully qualified class name string.
*

View file

@ -31,6 +31,10 @@ public class ActivityReportingQueryBuilderTest {
ActivityReportingQueryBuilder queryBuilder = new ActivityReportingQueryBuilder();
queryBuilder.yearMonth = new YearMonth(2017, 9);
queryBuilder.projectId = "domain-registry-alpha";
queryBuilder.dnsCountQueryCoordinator =
new BasicDnsCountQueryCoordinator(
new BasicDnsCountQueryCoordinator.Params(null, queryBuilder.yearMonth,
queryBuilder.projectId));
return queryBuilder;
}
@ -63,5 +67,4 @@ public class ActivityReportingQueryBuilderTest {
.isEqualTo(ReportingTestData.loadFile(testFilename));
}
}
}

View file

@ -62,6 +62,7 @@ public class IcannReportingStagerTest {
ActivityReportingQueryBuilder activityBuilder = new ActivityReportingQueryBuilder();
activityBuilder.projectId = "test-project";
activityBuilder.yearMonth = new YearMonth(2017, 6);
activityBuilder.dnsCountQueryCoordinator = new BasicDnsCountQueryCoordinator(null);
action.activityQueryBuilder = activityBuilder;
TransactionsReportingQueryBuilder transactionsBuilder = new TransactionsReportingQueryBuilder();
transactionsBuilder.projectId = "test-project";

View file

@ -52,4 +52,22 @@ public class TypeUtilsTest {
.hasMessageThat()
.contains("Failed to load class com.fake.company.nonexistent.Class");
}
public static class ExampleClass {
String val;
public ExampleClass(String val) {
this.val = val;
}
}
@Test
public void test_instantiateWithArg() {
Class<ExampleClass> clazz =
TypeUtils.getClassFromString(
"google.registry.util.TypeUtilsTest$ExampleClass", ExampleClass.class);
ExampleClass result = TypeUtils.instantiate(clazz, "test");
assertThat(result.val).isEqualTo("test");
}
}