diff --git a/java/google/registry/config/ConfigModule.java b/java/google/registry/config/ConfigModule.java index c53e39e35..74091525b 100644 --- a/java/google/registry/config/ConfigModule.java +++ b/java/google/registry/config/ConfigModule.java @@ -648,7 +648,7 @@ public final class ConfigModule { } /** - * The reporting interval, for Metrics to be sent to a {@link + * The reporting interval, for BigQueryMetricsEnqueuer to be sent to a {@link * google.registry.monitoring.metrics.MetricWriter}. * * @see google.registry.monitoring.metrics.MetricReporter diff --git a/java/google/registry/flows/EppController.java b/java/google/registry/flows/EppController.java index 5c33fac65..c19a4f2cd 100644 --- a/java/google/registry/flows/EppController.java +++ b/java/google/registry/flows/EppController.java @@ -25,7 +25,8 @@ import google.registry.model.eppoutput.EppOutput; import google.registry.model.eppoutput.EppResponse; import google.registry.model.eppoutput.Result; import google.registry.model.eppoutput.Result.Code; -import google.registry.monitoring.whitebox.EppMetrics; +import google.registry.monitoring.whitebox.BigQueryMetricsEnqueuer; +import google.registry.monitoring.whitebox.EppMetric; import google.registry.util.Clock; import google.registry.util.FormattingLogger; import javax.inject.Inject; @@ -41,7 +42,8 @@ public final class EppController { @Inject Clock clock; @Inject FlowComponent.Builder flowComponentBuilder; - @Inject EppMetrics metrics; + @Inject EppMetric.Builder metric; + @Inject BigQueryMetricsEnqueuer bigQueryMetricsEnqueuer; @Inject EppController() {} /** Read EPP XML, execute the matching flow, and return an {@link EppOutput}. */ @@ -52,20 +54,20 @@ public final class EppController { boolean isDryRun, boolean isSuperuser, byte[] inputXmlBytes) { - metrics.setClientId(sessionMetadata.getClientId()); - metrics.setPrivilegeLevel(isSuperuser ? "SUPERUSER" : "NORMAL"); + metric.setClientId(sessionMetadata.getClientId()); + metric.setPrivilegeLevel(isSuperuser ? "SUPERUSER" : "NORMAL"); try { EppInput eppInput; try { eppInput = unmarshal(EppInput.class, inputXmlBytes); } catch (EppException e) { // Send the client an error message, with no clTRID since we couldn't unmarshal it. - metrics.setEppStatus(e.getResult().getCode()); + metric.setStatus(e.getResult().getCode()); return getErrorResponse(clock, e.getResult(), Trid.create(null)); } - metrics.setCommandName(eppInput.getCommandName()); + metric.setCommandName(eppInput.getCommandName()); if (!eppInput.getTargetIds().isEmpty()) { - metrics.setEppTarget(Joiner.on(',').join(eppInput.getTargetIds())); + metric.setEppTarget(Joiner.on(',').join(eppInput.getTargetIds())); } EppOutput output = runFlowConvertEppErrors(flowComponentBuilder .flowModule(new FlowModule.Builder() @@ -79,11 +81,11 @@ public final class EppController { .build()) .build()); if (output.isResponse()) { - metrics.setEppStatus(output.getResponse().getResult().getCode()); + metric.setStatus(output.getResponse().getResult().getCode()); } return output; } finally { - metrics.export(); + bigQueryMetricsEnqueuer.export(metric.build()); } } diff --git a/java/google/registry/flows/FlowRunner.java b/java/google/registry/flows/FlowRunner.java index 2306fe8f0..bf94c110d 100644 --- a/java/google/registry/flows/FlowRunner.java +++ b/java/google/registry/flows/FlowRunner.java @@ -31,7 +31,7 @@ import google.registry.flows.FlowModule.Transactional; import google.registry.model.eppcommon.Trid; import google.registry.model.eppinput.EppInput; import google.registry.model.eppoutput.EppOutput; -import google.registry.monitoring.whitebox.EppMetrics; +import google.registry.monitoring.whitebox.EppMetric; import google.registry.util.Clock; import google.registry.util.FormattingLogger; import javax.annotation.Nullable; @@ -67,7 +67,7 @@ public class FlowRunner { @Inject @DryRun boolean isDryRun; @Inject @Superuser boolean isSuperuser; @Inject @Transactional boolean isTransactional; - @Inject EppMetrics metrics; + @Inject EppMetric.Builder metric; @Inject SessionMetadata sessionMetadata; @Inject Trid trid; @Inject FlowRunner() {} @@ -100,7 +100,7 @@ public class FlowRunner { "xml", prettyXml, "xmlBytes", xmlBase64))); if (!isTransactional) { - metrics.incrementAttempts(); + metric.incrementAttempts(); return createAndInitFlow(clock.nowUtc()).run(); } // We log the command in a structured format. Note that we do this before the transaction; @@ -111,20 +111,24 @@ public class FlowRunner { .add("privileges", isSuperuser ? "SUPERUSER" : "NORMAL") .add("xmlBytes", xmlBase64)); try { - EppOutput flowResult = ofy().transact(new Work() { - @Override - public EppOutput run() { - metrics.incrementAttempts(); - try { - EppOutput output = createAndInitFlow(ofy().getTransactionTime()).run(); - if (isDryRun) { - throw new DryRunException(output); - } - return output; - } catch (EppException e) { - throw new RuntimeException(e); - } - }}); + EppOutput flowResult = + ofy() + .transact( + new Work() { + @Override + public EppOutput run() { + metric.incrementAttempts(); + try { + EppOutput output = createAndInitFlow(ofy().getTransactionTime()).run(); + if (isDryRun) { + throw new DryRunException(output); + } + return output; + } catch (EppException e) { + throw new RuntimeException(e); + } + } + }); logger.info("EPP_Mutation_Committed " + new JsonLogStatement(trid) .add("createdRepoId", flowResult.getResponse().getCreatedRepoId()) .add("executionTime", flowResult.getResponse().getExecutionTime().getMillis())); diff --git a/java/google/registry/module/tools/ToolsComponent.java b/java/google/registry/module/tools/ToolsComponent.java index fb9492f21..6a6c0753e 100644 --- a/java/google/registry/module/tools/ToolsComponent.java +++ b/java/google/registry/module/tools/ToolsComponent.java @@ -27,6 +27,7 @@ import google.registry.request.Modules.AppIdentityCredentialModule; import google.registry.request.Modules.DatastoreServiceModule; import google.registry.request.Modules.GoogleCredentialModule; import google.registry.request.Modules.Jackson2Module; +import google.registry.request.Modules.ModulesServiceModule; import google.registry.request.Modules.UrlFetchTransportModule; import google.registry.request.Modules.UseAppIdentityCredentialForGoogleApisModule; import google.registry.request.RequestModule; @@ -49,6 +50,7 @@ import javax.inject.Singleton; GroupssettingsModule.class, Jackson2Module.class, KeyModule.class, + ModulesServiceModule.class, UrlFetchTransportModule.class, UseAppIdentityCredentialForGoogleApisModule.class, SystemClockModule.class, diff --git a/java/google/registry/monitoring/whitebox/BUILD b/java/google/registry/monitoring/whitebox/BUILD index ad772b627..4c62b40e8 100644 --- a/java/google/registry/monitoring/whitebox/BUILD +++ b/java/google/registry/monitoring/whitebox/BUILD @@ -22,6 +22,7 @@ java_library( "//third_party/java/appengine:appengine-api", "//third_party/java/appengine_mapreduce2:appengine_mapreduce", "//third_party/java/auto:auto_factory", + "//third_party/java/auto:auto_value", "//third_party/java/dagger", "//third_party/java/joda_time", "//third_party/java/jsr305_annotations", diff --git a/java/google/registry/monitoring/whitebox/BigQueryMetric.java b/java/google/registry/monitoring/whitebox/BigQueryMetric.java new file mode 100644 index 000000000..67fd59da7 --- /dev/null +++ b/java/google/registry/monitoring/whitebox/BigQueryMetric.java @@ -0,0 +1,36 @@ +// 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.monitoring.whitebox; + +import com.google.api.services.bigquery.model.TableFieldSchema; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +/** + * A metric which can be encoded into a BigQuery row. + * + * @see BigQueryMetricsEnqueuer + */ +public interface BigQueryMetric { + + /** Get the BigQuery table name for this metric. */ + String getTableId(); + + /** Get the schema description for the BigQuery table. */ + ImmutableList getSchemaFields(); + + /** Get a map of the row values for this metric instance. */ + ImmutableMap getBigQueryRowEncoding(); +} diff --git a/java/google/registry/monitoring/whitebox/Metrics.java b/java/google/registry/monitoring/whitebox/BigQueryMetricsEnqueuer.java similarity index 50% rename from java/google/registry/monitoring/whitebox/Metrics.java rename to java/google/registry/monitoring/whitebox/BigQueryMetricsEnqueuer.java index b0d8e28a1..5bbb9077a 100644 --- a/java/google/registry/monitoring/whitebox/Metrics.java +++ b/java/google/registry/monitoring/whitebox/BigQueryMetricsEnqueuer.java @@ -16,67 +16,54 @@ package google.registry.monitoring.whitebox; import static com.google.appengine.api.taskqueue.QueueFactory.getQueue; import static com.google.appengine.api.taskqueue.TaskOptions.Builder.withUrl; -import static google.registry.bigquery.BigqueryUtils.toBigqueryTimestamp; import com.google.appengine.api.modules.ModulesService; -import com.google.appengine.api.modules.ModulesServiceFactory; import com.google.appengine.api.taskqueue.TaskOptions; import com.google.appengine.api.taskqueue.TransientFailureException; -import com.google.common.base.Supplier; -import google.registry.util.Clock; +import com.google.common.annotations.VisibleForTesting; import google.registry.util.FormattingLogger; -import google.registry.util.NonFinalForTesting; -import google.registry.util.SystemClock; -import java.util.HashMap; -import java.util.Map; import java.util.Map.Entry; import java.util.UUID; -import java.util.concurrent.TimeUnit; +import javax.inject.Inject; -/** A collector of metric information. */ -public abstract class Metrics { +/** + * A collector of metric information. Enqueues collected metrics to a task queue to be written to + * BigQuery asynchronously. + * + * @see MetricsExportAction + */ +public class BigQueryMetricsEnqueuer { private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass(); public static final String QUEUE = "bigquery-streaming-metrics"; - @NonFinalForTesting - private static ModulesService modulesService = ModulesServiceFactory.getModulesService(); + @Inject ModulesService modulesService; - @NonFinalForTesting - private static Clock clock = new SystemClock(); + @Inject + BigQueryMetricsEnqueuer() {} - @NonFinalForTesting - private static Supplier idGenerator = - new Supplier() { - @Override - public String get() { - return UUID.randomUUID().toString(); - }}; - - protected final Map fields = new HashMap<>(); - - private final long startTimeMillis = clock.nowUtc().getMillis(); - - public void setTableId(String tableId) { - fields.put("tableId", tableId); - } - - public void export() { + @VisibleForTesting + void export(BigQueryMetric metric, String insertId) { try { String hostname = modulesService.getVersionHostname("backend", null); - TaskOptions opts = withUrl(MetricsExportAction.PATH) - .header("Host", hostname) - .param("insertId", idGenerator.get()) - .param("startTime", toBigqueryTimestamp(startTimeMillis, TimeUnit.MILLISECONDS)) - .param("endTime", toBigqueryTimestamp(clock.nowUtc().getMillis(), TimeUnit.MILLISECONDS)); - for (Entry entry : fields.entrySet()) { - opts.param(entry.getKey(), String.valueOf(entry.getValue())); + TaskOptions opts = + withUrl(MetricsExportAction.PATH) + .header("Host", hostname) + .param("insertId", insertId); + for (Entry entry : metric.getBigQueryRowEncoding().entrySet()) { + opts.param(entry.getKey(), entry.getValue()); } + opts.param("tableId", metric.getTableId()); getQueue(QUEUE).add(opts); } catch (TransientFailureException e) { // Log and swallow. We may drop some metrics here but this should be rare. logger.info(e, e.getMessage()); } } + + /** Enqueue a metric to be exported to BigQuery. */ + public void export(BigQueryMetric metric) { + export(metric, UUID.randomUUID().toString()); + } } diff --git a/java/google/registry/monitoring/whitebox/EppMetric.java b/java/google/registry/monitoring/whitebox/EppMetric.java new file mode 100644 index 000000000..832d6f9f6 --- /dev/null +++ b/java/google/registry/monitoring/whitebox/EppMetric.java @@ -0,0 +1,228 @@ +// 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.monitoring.whitebox; + +import static com.google.apphosting.api.ApiProxy.getCurrentEnvironment; +import static google.registry.bigquery.BigqueryUtils.toBigqueryTimestamp; +import static org.joda.time.DateTimeZone.UTC; + +import com.google.api.services.bigquery.model.TableFieldSchema; +import com.google.auto.value.AutoValue; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import google.registry.bigquery.BigqueryUtils.FieldType; +import google.registry.model.eppoutput.Result.Code; +import google.registry.request.RequestScope; +import java.util.concurrent.TimeUnit; +import javax.inject.Inject; +import org.joda.time.DateTime; + +/** + * A value class for recording attributes of an EPP metric. + * + * @see BigQueryMetricsEnqueuer + */ +@AutoValue +@RequestScope +public abstract class EppMetric implements BigQueryMetric { + + static final String TABLE_ID = "eppMetrics"; + static final ImmutableList SCHEMA_FIELDS = + ImmutableList.of( + new TableFieldSchema().setName("requestId").setType(FieldType.STRING.name()), + new TableFieldSchema().setName("startTime").setType(FieldType.TIMESTAMP.name()), + new TableFieldSchema().setName("endTime").setType(FieldType.TIMESTAMP.name()), + new TableFieldSchema().setName("commandName").setType(FieldType.STRING.name()), + new TableFieldSchema().setName("clientId").setType(FieldType.STRING.name()), + new TableFieldSchema().setName("privilegeLevel").setType(FieldType.STRING.name()), + new TableFieldSchema().setName("eppTarget").setType(FieldType.STRING.name()), + new TableFieldSchema().setName("eppStatus").setType(FieldType.INTEGER.name()), + new TableFieldSchema().setName("attempts").setType(FieldType.INTEGER.name())); + + private static final String REQUEST_LOG_ID = "com.google.appengine.runtime.request_log_id"; + + private static EppMetric create( + String requestId, + DateTime startTimestamp, + DateTime endTimestamp, + String commandName, + String clientId, + String privilegeLevel, + String eppTarget, + Code status, + int attempts) { + return new AutoValue_EppMetric( + requestId, + startTimestamp, + endTimestamp, + Optional.ofNullable(commandName), + Optional.ofNullable(clientId), + Optional.ofNullable(privilegeLevel), + Optional.ofNullable(eppTarget), + Optional.ofNullable(status), + attempts); + } + + public abstract String getRequestId(); + + public abstract DateTime getStartTimestamp(); + + public abstract DateTime getEndTimestamp(); + + public abstract Optional getCommandName(); + + public abstract Optional getClientId(); + + public abstract Optional getPrivilegeLevel(); + + public abstract Optional getEppTarget(); + + public abstract Optional getStatus(); + + public abstract Integer getAttempts(); + + @Override + public String getTableId() { + return TABLE_ID; + } + + @Override + public ImmutableList getSchemaFields() { + return SCHEMA_FIELDS; + } + + @Override + public ImmutableMap getBigQueryRowEncoding() { + // Create map builder, start with required values + ImmutableMap.Builder map = + ImmutableMap.builder() + .put("requestId", getRequestId()) + .put( + "startTime", + toBigqueryTimestamp(getStartTimestamp().getMillis(), TimeUnit.MILLISECONDS)) + .put( + "endTime", + toBigqueryTimestamp(getEndTimestamp().getMillis(), TimeUnit.MILLISECONDS)) + .put("attempts", getAttempts().toString()); + // Populate optional values, if present + addOptional("commandName", getCommandName(), map); + addOptional("clientId", getClientId(), map); + addOptional("privilegeLevel", getPrivilegeLevel(), map); + addOptional("eppTarget", getEppTarget(), map); + addOptional("status", getStatus(), map); + + return map.build(); + } + + /** + * Helper method to populate an {@link com.google.common.collect.ImmutableMap.Builder} with an + * {@link Optional} value if the value is {@link Optional#isPresent()}. + */ + private static void addOptional( + String key, Optional value, ImmutableMap.Builder map) { + if (value.isPresent()) { + map.put(key, value.get().toString()); + } + } + + /** A builder to create instances of {@link EppMetric}. */ + public static class Builder { + + // Required values + private final String requestId; + private final DateTime startTimestamp; + private int attempts = 0; + + // Optional values + private String commandName; + private String clientId; + private String privilegeLevel; + private String eppTarget; + private Code status; + + /** + * Create an {@link EppMetric.Builder}. + * + *

The start timestamp of metrics created via this instance's {@link Builder#build()} will be + * the time that this builder was created. + */ + @Inject + public Builder() { + this(DateTime.now(UTC)); + } + + @VisibleForTesting + Builder(DateTime startTimestamp) { + this.requestId = getCurrentEnvironment().getAttributes().get(REQUEST_LOG_ID).toString(); + this.startTimestamp = startTimestamp; + this.attempts = 0; + } + + public Builder setCommandName(String value) { + commandName = value; + return this; + } + + public Builder setClientId(String value) { + clientId = value; + return this; + } + + public Builder setPrivilegeLevel(String value) { + privilegeLevel = value; + return this; + } + + public Builder setEppTarget(String value) { + eppTarget = value; + return this; + } + + public Builder setStatus(Code value) { + status = value; + return this; + } + + public Builder incrementAttempts() { + attempts++; + return this; + } + + @VisibleForTesting + EppMetric build(DateTime endTimestamp) { + return EppMetric.create( + requestId, + startTimestamp, + endTimestamp, + commandName, + clientId, + privilegeLevel, + eppTarget, + status, + attempts); + } + + /** + * Build an instance of {@link EppMetric} using this builder. + * + *

The end timestamp of the metric will be the current time. + */ + public EppMetric build() { + return build(DateTime.now(UTC)); + } + } +} diff --git a/java/google/registry/monitoring/whitebox/EppMetrics.java b/java/google/registry/monitoring/whitebox/EppMetrics.java deleted file mode 100644 index d2a192b6a..000000000 --- a/java/google/registry/monitoring/whitebox/EppMetrics.java +++ /dev/null @@ -1,76 +0,0 @@ -// 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.monitoring.whitebox; - -import static com.google.apphosting.api.ApiProxy.getCurrentEnvironment; - -import com.google.api.services.bigquery.model.TableFieldSchema; -import com.google.common.collect.ImmutableList; -import google.registry.bigquery.BigqueryUtils.FieldType; -import google.registry.model.eppoutput.Result.Code; -import google.registry.request.RequestScope; -import javax.inject.Inject; - -/** The EPP Metrics collector. See {@link Metrics}. */ -@RequestScope -public class EppMetrics extends Metrics { - - private static final String REQUEST_LOG_ID = "com.google.appengine.runtime.request_log_id"; - - static final String TABLE_ID = "eppMetrics"; - - static final ImmutableList SCHEMA_FIELDS = - ImmutableList.of( - new TableFieldSchema().setName("requestId").setType(FieldType.STRING.name()), - new TableFieldSchema().setName("startTime").setType(FieldType.TIMESTAMP.name()), - new TableFieldSchema().setName("endTime").setType(FieldType.TIMESTAMP.name()), - new TableFieldSchema().setName("commandName").setType(FieldType.STRING.name()), - new TableFieldSchema().setName("clientId").setType(FieldType.STRING.name()), - new TableFieldSchema().setName("privilegeLevel").setType(FieldType.STRING.name()), - new TableFieldSchema().setName("eppTarget").setType(FieldType.STRING.name()), - new TableFieldSchema().setName("eppStatus").setType(FieldType.INTEGER.name()), - new TableFieldSchema().setName("attempts").setType(FieldType.INTEGER.name())); - - @Inject - public EppMetrics() { - setTableId(TABLE_ID); - fields.put("attempts", 0); - fields.put("requestId", getCurrentEnvironment().getAttributes().get(REQUEST_LOG_ID).toString()); - } - - public void setCommandName(String name) { - fields.put("commandName", name); - } - - public void setClientId(String clientId) { - fields.put("clientId", clientId); - } - - public void setPrivilegeLevel(String level) { - fields.put("privilegeLevel", level); - } - - public void setEppTarget(String eppTarget) { - fields.put("eppTarget", eppTarget); - } - - public void setEppStatus(Code status) { - fields.put("eppStatus", String.valueOf(status.code)); - } - - public void incrementAttempts() { - fields.put("attempts", ((int) fields.get("attempts")) + 1); - } -} diff --git a/java/google/registry/monitoring/whitebox/WhiteboxModule.java b/java/google/registry/monitoring/whitebox/WhiteboxModule.java index 1bed0da5c..b8cff04d8 100644 --- a/java/google/registry/monitoring/whitebox/WhiteboxModule.java +++ b/java/google/registry/monitoring/whitebox/WhiteboxModule.java @@ -35,9 +35,9 @@ public class WhiteboxModule { @Provides @IntoMap - @StringKey(EppMetrics.TABLE_ID) + @StringKey(EppMetric.TABLE_ID) static ImmutableList provideEppMetricsSchema() { - return EppMetrics.SCHEMA_FIELDS; + return EppMetric.SCHEMA_FIELDS; } @Provides diff --git a/javatests/google/registry/flows/EppControllerTest.java b/javatests/google/registry/flows/EppControllerTest.java index 0551b3bde..cf8a8ea05 100644 --- a/javatests/google/registry/flows/EppControllerTest.java +++ b/javatests/google/registry/flows/EppControllerTest.java @@ -26,7 +26,8 @@ import google.registry.model.eppoutput.EppOutput; import google.registry.model.eppoutput.EppResponse; import google.registry.model.eppoutput.Result; import google.registry.model.eppoutput.Result.Code; -import google.registry.monitoring.whitebox.EppMetrics; +import google.registry.monitoring.whitebox.BigQueryMetricsEnqueuer; +import google.registry.monitoring.whitebox.EppMetric; import google.registry.testing.AppEngineRule; import google.registry.testing.FakeClock; import google.registry.testing.ShardableTestCase; @@ -49,7 +50,9 @@ public class EppControllerTest extends ShardableTestCase { @Mock SessionMetadata sessionMetadata; @Mock TransportCredentials transportCredentials; - @Mock EppMetrics eppMetrics; + @Mock EppMetric.Builder eppMetricBuilder; + @Mock EppMetric eppMetric; + @Mock BigQueryMetricsEnqueuer metricsEnqueuer; @Mock FlowComponent.Builder flowComponentBuilder; @Mock FlowComponent flowComponent; @Mock FlowRunner flowRunner; @@ -71,9 +74,11 @@ public class EppControllerTest extends ShardableTestCase { when(eppOutput.getResponse()).thenReturn(eppResponse); when(eppResponse.getResult()).thenReturn(result); when(result.getCode()).thenReturn(Code.SuccessWithNoMessages); + when(eppMetricBuilder.build()).thenReturn(eppMetric); eppController = new EppController(); - eppController.metrics = eppMetrics; + eppController.metric = eppMetricBuilder; + eppController.bigQueryMetricsEnqueuer = metricsEnqueuer; eppController.clock = new FakeClock(); eppController.flowComponentBuilder = flowComponentBuilder; } @@ -96,10 +101,11 @@ public class EppControllerTest extends ShardableTestCase { false, new byte[0]); - verify(eppMetrics).setClientId("foo"); - verify(eppMetrics).setPrivilegeLevel("NORMAL"); - verify(eppMetrics).setEppStatus(Code.SyntaxError); - verify(eppMetrics).export(); + verify(eppMetricBuilder).setClientId("foo"); + verify(eppMetricBuilder).setPrivilegeLevel("NORMAL"); + verify(eppMetricBuilder).setStatus(Code.SyntaxError); + verify(eppMetricBuilder).build(); + verify(metricsEnqueuer).export(eppMetric); } @Test @@ -115,11 +121,12 @@ public class EppControllerTest extends ShardableTestCase { true, domainCreateXml.getBytes(UTF_8)); - verify(eppMetrics).setClientId("foo"); - verify(eppMetrics).setPrivilegeLevel("SUPERUSER"); - verify(eppMetrics).setEppStatus(Code.SuccessWithNoMessages); - verify(eppMetrics).setCommandName("Create"); - verify(eppMetrics).setEppTarget("example.tld"); - verify(eppMetrics).export(); + verify(eppMetricBuilder).setClientId("foo"); + verify(eppMetricBuilder).setPrivilegeLevel("SUPERUSER"); + verify(eppMetricBuilder).setStatus(Code.SuccessWithNoMessages); + verify(eppMetricBuilder).setCommandName("Create"); + verify(eppMetricBuilder).setEppTarget("example.tld"); + verify(eppMetricBuilder).build(); + verify(metricsEnqueuer).export(eppMetric); } } diff --git a/javatests/google/registry/flows/EppTestComponent.java b/javatests/google/registry/flows/EppTestComponent.java index 5641955c2..5b8cd1698 100644 --- a/javatests/google/registry/flows/EppTestComponent.java +++ b/javatests/google/registry/flows/EppTestComponent.java @@ -16,11 +16,13 @@ package google.registry.flows; import static org.mockito.Mockito.mock; +import com.google.appengine.api.modules.ModulesService; import dagger.Component; import dagger.Module; import dagger.Provides; import dagger.Subcomponent; -import google.registry.monitoring.whitebox.EppMetrics; +import google.registry.monitoring.whitebox.BigQueryMetricsEnqueuer; +import google.registry.monitoring.whitebox.EppMetric; import google.registry.request.RequestScope; import google.registry.testing.FakeClock; import google.registry.util.Clock; @@ -40,11 +42,15 @@ interface EppTestComponent { @Module static class FakesAndMocksModule { final FakeClock clock; - final EppMetrics metrics; + final EppMetric.Builder metrics; + final BigQueryMetricsEnqueuer metricsEnqueuer; + final ModulesService modulesService; FakesAndMocksModule(FakeClock clock) { this.clock = clock; - this.metrics = mock(EppMetrics.class); + this.metrics = mock(EppMetric.Builder.class); + this.modulesService = mock(ModulesService.class); + this.metricsEnqueuer = mock(BigQueryMetricsEnqueuer.class); } @Provides @@ -53,9 +59,19 @@ interface EppTestComponent { } @Provides - EppMetrics provideMetrics() { + EppMetric.Builder provideMetrics() { return metrics; } + + @Provides + ModulesService provideModulesService() { + return modulesService; + } + + @Provides + BigQueryMetricsEnqueuer provideBigQueryMetricsEnqueuer() { + return metricsEnqueuer; + } } /** Subcomponent for request scoped injections. */ diff --git a/javatests/google/registry/flows/FlowRunnerTest.java b/javatests/google/registry/flows/FlowRunnerTest.java index 11fe5bea1..eeadabc7c 100644 --- a/javatests/google/registry/flows/FlowRunnerTest.java +++ b/javatests/google/registry/flows/FlowRunnerTest.java @@ -35,7 +35,7 @@ import google.registry.model.eppcommon.Trid; import google.registry.model.eppinput.EppInput; import google.registry.model.eppoutput.EppOutput; import google.registry.model.eppoutput.EppResponse; -import google.registry.monitoring.whitebox.EppMetrics; +import google.registry.monitoring.whitebox.EppMetric; import google.registry.testing.AppEngineRule; import google.registry.testing.FakeClock; import google.registry.testing.FakeHttpSession; @@ -91,7 +91,7 @@ public class FlowRunnerTest extends ShardableTestCase { flowRunner.isDryRun = false; flowRunner.isSuperuser = false; flowRunner.isTransactional = false; - flowRunner.metrics = mock(EppMetrics.class); + flowRunner.metric = mock(EppMetric.Builder.class); flowRunner.sessionMetadata = new StatelessRequestSessionMetadata("TheRegistrar", ImmutableSet.of()); flowRunner.trid = Trid.create("client-123", "server-456"); @@ -113,7 +113,7 @@ public class FlowRunnerTest extends ShardableTestCase { public void testRun_notIsTransactional_callsMetricIncrementAttempts() throws Exception { flowRunner.run(); - verify(flowRunner.metrics).incrementAttempts(); + verify(flowRunner.metric).incrementAttempts(); } @Test @@ -121,7 +121,7 @@ public class FlowRunnerTest extends ShardableTestCase { flowRunner.isTransactional = true; flowRunner.run(); - verify(flowRunner.metrics).incrementAttempts(); + verify(flowRunner.metric).incrementAttempts(); } @Test diff --git a/javatests/google/registry/monitoring/whitebox/BUILD b/javatests/google/registry/monitoring/whitebox/BUILD index c8e4563e4..b6ca6d59c 100644 --- a/javatests/google/registry/monitoring/whitebox/BUILD +++ b/javatests/google/registry/monitoring/whitebox/BUILD @@ -23,6 +23,7 @@ java_library( "//third_party/java/appengine:appengine-api-testonly", "//third_party/java/appengine:appengine-stubs", "//third_party/java/appengine:appengine-testing", + "//third_party/java/auto:auto_value", "//third_party/java/joda_money", "//third_party/java/joda_time", "//third_party/java/junit", diff --git a/javatests/google/registry/monitoring/whitebox/BigQueryMetricsEnqueuerTest.java b/javatests/google/registry/monitoring/whitebox/BigQueryMetricsEnqueuerTest.java new file mode 100644 index 000000000..46129d44a --- /dev/null +++ b/javatests/google/registry/monitoring/whitebox/BigQueryMetricsEnqueuerTest.java @@ -0,0 +1,111 @@ +// 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.monitoring.whitebox; + +import static google.registry.bigquery.BigqueryUtils.toBigqueryTimestamp; +import static google.registry.testing.TaskQueueHelper.assertTasksEnqueued; +import static org.mockito.Mockito.when; + +import com.google.api.services.bigquery.model.TableFieldSchema; +import com.google.appengine.api.modules.ModulesService; +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import google.registry.testing.AppEngineRule; +import google.registry.testing.InjectRule; +import google.registry.testing.TaskQueueHelper.TaskMatcher; +import org.joda.time.DateTime; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +/** Unit tests for {@link BigQueryMetricsEnqueuer}. */ +@RunWith(MockitoJUnitRunner.class) +public class BigQueryMetricsEnqueuerTest { + + @Rule + public final InjectRule inject = new InjectRule(); + + @Rule + public final AppEngineRule appEngine = AppEngineRule.builder() + .withDatastore() + .withLocalModules() + .withTaskQueue() + .build(); + + @Mock ModulesService modulesService; + + private BigQueryMetricsEnqueuer enqueuer; + + @Before + public void setUp() { + enqueuer = new BigQueryMetricsEnqueuer(); + enqueuer.modulesService = modulesService; + when(modulesService.getVersionHostname(Matchers.anyString(), Matchers.anyString())) + .thenReturn("1.backend.test.localhost"); + } + + @Test + public void testExport() throws Exception { + TestMetric metric = + TestMetric.create( + DateTime.parse("1984-12-18TZ"), DateTime.parse("1984-12-18TZ").plusMillis(1)); + + enqueuer.export(metric, "laffo"); + + assertTasksEnqueued("bigquery-streaming-metrics", + new TaskMatcher() + .url("/_dr/task/metrics") + .header("Host", "1.backend.test.localhost") + .param("tableId", "test") + .param("startTime", "472176000.000000") + .param("endTime", "472176000.001000") + .param("insertId", "laffo")); + } + + /** A stub implementation of {@link BigQueryMetric}. */ + @AutoValue + abstract static class TestMetric implements BigQueryMetric { + + static TestMetric create(DateTime startTimestamp, DateTime endTimestamp) { + return new AutoValue_BigQueryMetricsEnqueuerTest_TestMetric(startTimestamp, endTimestamp); + } + + @Override + public String getTableId() { + return "test"; + } + + @Override + public ImmutableList getSchemaFields() { + return null; + } + + @Override + public ImmutableMap getBigQueryRowEncoding() { + return ImmutableMap.of( + "startTime", toBigqueryTimestamp(getStartTimestamp()), + "endTime", toBigqueryTimestamp(getEndTimestamp())); + } + + abstract DateTime getStartTimestamp(); + + abstract DateTime getEndTimestamp(); + } +} diff --git a/javatests/google/registry/monitoring/whitebox/MetricsTest.java b/javatests/google/registry/monitoring/whitebox/MetricsTest.java deleted file mode 100644 index cf0086575..000000000 --- a/javatests/google/registry/monitoring/whitebox/MetricsTest.java +++ /dev/null @@ -1,69 +0,0 @@ -// 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.monitoring.whitebox; - -import static google.registry.testing.TaskQueueHelper.assertTasksEnqueued; - -import com.google.common.base.Suppliers; -import google.registry.testing.AppEngineRule; -import google.registry.testing.FakeClock; -import google.registry.testing.InjectRule; -import google.registry.testing.TaskQueueHelper.TaskMatcher; -import org.joda.time.DateTime; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; - -/** Unit tests for {@link Metrics}. */ -@RunWith(MockitoJUnitRunner.class) -public class MetricsTest { - - @Rule - public final InjectRule inject = new InjectRule(); - - @Rule - public final AppEngineRule appEngine = AppEngineRule.builder() - .withDatastore() - .withLocalModules() - .withTaskQueue() - .build(); - - private final FakeClock clock = new FakeClock(DateTime.parse("1984-12-18TZ")); - - @Before - public void before() throws Exception { - inject.setStaticField(Metrics.class, "clock", clock); - inject.setStaticField(Metrics.class, "idGenerator", Suppliers.ofInstance("laffo")); - } - - @Test - public void testExport() throws Exception { - class TestMetric extends Metrics {} - Metrics metrics = new TestMetric(); - clock.advanceOneMilli(); - metrics.setTableId("test"); - metrics.export(); - assertTasksEnqueued("bigquery-streaming-metrics", - new TaskMatcher() - .url("/_dr/task/metrics") - .header("Host", "1.backend.test.localhost") - .param("tableId", "test") - .param("startTime", "472176000.000000") - .param("endTime", "472176000.001000") - .param("insertId", "laffo")); - } -}