diff --git a/java/google/registry/rdap/RdapActionBase.java b/java/google/registry/rdap/RdapActionBase.java index 25cf11b44..68abe65a8 100644 --- a/java/google/registry/rdap/RdapActionBase.java +++ b/java/google/registry/rdap/RdapActionBase.java @@ -34,6 +34,7 @@ import google.registry.config.RegistryConfig.Config; import google.registry.model.EppResource; import google.registry.model.registrar.Registrar; import google.registry.rdap.RdapMetrics.EndpointType; +import google.registry.rdap.RdapMetrics.WildcardType; import google.registry.rdap.RdapSearchResults.IncompletenessWarningType; import google.registry.request.Action; import google.registry.request.FullServletPath; @@ -73,6 +74,7 @@ public abstract class RdapActionBase implements Runnable { * hyphens. In this case, allow the wildcard asterisk as well. */ static final Pattern LDH_PATTERN = Pattern.compile("[-.a-zA-Z0-9*]+"); + private static final int RESULT_SET_SIZE_SCALING_FACTOR = 30; private static final MediaType RESPONSE_MEDIA_TYPE = MediaType.create("application", "rdap+json").withCharset(UTF_8); @@ -89,6 +91,11 @@ public abstract class RdapActionBase implements Runnable { @Inject @Parameter("includeDeleted") Optional includeDeletedParam; @Inject @Config("rdapWhoisServer") @Nullable String rdapWhoisServer; @Inject @Config("rdapResultSetMaxSize") int rdapResultSetMaxSize; + @Inject RdapMetrics rdapMetrics; + + /** Builder for metric recording. */ + final RdapMetrics.RdapMetricInformation.Builder metricInformationBuilder = + RdapMetrics.RdapMetricInformation.builder(); /** Returns a string like "domain name" or "nameserver", used for error strings. */ abstract String getHumanReadableObjectTypeName(); @@ -115,6 +122,11 @@ public abstract class RdapActionBase implements Runnable { @Override public void run() { + metricInformationBuilder.setIncludeDeleted(includeDeletedParam.orElse(false)); + metricInformationBuilder.setRegistrarSpecified(registrarParam.isPresent()); + metricInformationBuilder.setRole(getAuthorization().role()); + metricInformationBuilder.setRequestMethod(requestMethod); + metricInformationBuilder.setEndpointType(getEndpointType()); try { // Extract what we're searching for from the request path. Some RDAP commands use trailing // data in the path itself (e.g. /rdap/domain/mydomain.com), and some use the query string @@ -134,6 +146,7 @@ public abstract class RdapActionBase implements Runnable { if (requestMethod != Action.Method.HEAD) { response.setPayload(JSONValue.toJSONString(rdapJson)); } + metricInformationBuilder.setStatusCode(SC_OK); } catch (HttpException e) { setError(e.getResponseCode(), e.getResponseCodeString(), e.getMessage()); } catch (URISyntaxException | IllegalArgumentException e) { @@ -142,9 +155,11 @@ public abstract class RdapActionBase implements Runnable { setError(SC_INTERNAL_SERVER_ERROR, "Internal Server Error", "An error was encountered"); logger.severe(e, "Exception encountered while processing RDAP command"); } + rdapMetrics.updateMetrics(metricInformationBuilder.build()); } void setError(int status, String title, String description) { + metricInformationBuilder.setStatusCode(status); response.setStatus(status); response.setContentType(RESPONSE_MEDIA_TYPE); try { @@ -264,6 +279,12 @@ public abstract class RdapActionBase implements Runnable { return name; } + int getStandardQuerySizeLimit() { + return shouldIncludeDeleted() + ? (RESULT_SET_SIZE_SCALING_FACTOR * (rdapResultSetMaxSize + 1)) + : (rdapResultSetMaxSize + 1); + } + /** * Handles prefix searches in cases where, if we need to filter out deleted items, there are no * pending deletes. In such cases, it is sufficient to check whether {@code deletionTime} is equal @@ -385,14 +406,16 @@ public abstract class RdapActionBase implements Runnable { * the query could not check deletion status (due to Datastore limitations such as the * limit of one field queried for inequality, for instance), it may need to be set to true * even when not including deleted records + * @param querySizeLimit the maximum number of items the query is expected to return, usually + * because the limit has been set * @return an {@link RdapResultSet} object containing the list of * resources and an incompleteness warning flag, which is set to MIGHT_BE_INCOMPLETE iff * any resources were excluded due to lack of visibility, and the resulting list of - * resources is less than the maximum allowable, which indicates that we may not have - * fetched enough resources + * resources is less than the maximum allowable, and the number of items returned by the + * query is greater than or equal to the maximum number we might have expected */ RdapResultSet getMatchingResources( - Query query, boolean checkForVisibility, DateTime now) { + Query query, boolean checkForVisibility, DateTime now, int querySizeLimit) { Optional desiredRegistrar = getDesiredRegistrar(); if (desiredRegistrar.isPresent()) { query = query.filter("currentSponsorClientId", desiredRegistrar.get()); @@ -415,11 +438,40 @@ public abstract class RdapActionBase implements Runnable { break; } } + // The incompleteness problem comes about because we don't know how many items to fetch. We want + // to return rdapResultSetMaxSize worth of items, but some might be excluded, so we fetch more + // just in case. But how many more? That's the potential problem, addressed with the three way + // AND statement: + // 1. If we didn't exclude any items, then we can't have the incompleteness problem. + // 2. If have a full result set batch (rdapResultSetMaxSize items), we must by definition be + // giving the user a complete result set. + // 3. If we started with fewer than querySizeLimit items, then there weren't any more items that + // we missed. Even if we return fewer than rdapResultSetMaxSize items, it isn't because we + // didn't fetch enough to start. + // Only if all three conditions are true might things be incomplete. In other words, we fetched + // as many as our limit allowed, but then excluded so many that we wound up with less than a + // full result set's worth of results. return RdapResultSet.create( resources, - (someExcluded && (resources.size() < rdapResultSetMaxSize + 1)) + (someExcluded + && (resources.size() < rdapResultSetMaxSize) + && (numResourcesQueried >= querySizeLimit)) ? IncompletenessWarningType.MIGHT_BE_INCOMPLETE : IncompletenessWarningType.COMPLETE, numResourcesQueried); } + + RdapSearchPattern recordWildcardType(RdapSearchPattern partialStringQuery) { + if (!partialStringQuery.getHasWildcard()) { + metricInformationBuilder.setWildcardType(WildcardType.NO_WILDCARD); + } else if (partialStringQuery.getSuffix() == null) { + metricInformationBuilder.setWildcardType(WildcardType.PREFIX); + } else if (partialStringQuery.getInitialString().isEmpty()) { + metricInformationBuilder.setWildcardType(WildcardType.SUFFIX); + } else { + metricInformationBuilder.setWildcardType(WildcardType.PREFIX_AND_SUFFIX); + } + metricInformationBuilder.setPrefixLength(partialStringQuery.getInitialString().length()); + return partialStringQuery; + } } diff --git a/java/google/registry/rdap/RdapDomainSearchAction.java b/java/google/registry/rdap/RdapDomainSearchAction.java index e5ff5a16d..7debff863 100644 --- a/java/google/registry/rdap/RdapDomainSearchAction.java +++ b/java/google/registry/rdap/RdapDomainSearchAction.java @@ -239,7 +239,10 @@ public class RdapDomainSearchAction extends RdapActionBase { query = query.filter("tld", partialStringQuery.getSuffix()); } // Always check for visibility, because we couldn't look at the deletionTime in the query. - return makeSearchResults(getMatchingResources(query, true, now), now); + return makeSearchResults( + getMatchingResources( + query, true, now, RESULT_SET_SIZE_SCALING_FACTOR * rdapResultSetMaxSize), + now); } /** Searches for domains by domain name with a TLD suffix. */ @@ -255,7 +258,10 @@ public class RdapDomainSearchAction extends RdapActionBase { .filter("tld", tld) .order("fullyQualifiedDomainName") .limit(RESULT_SET_SIZE_SCALING_FACTOR * rdapResultSetMaxSize); - return makeSearchResults(getMatchingResources(query, true, now), now); + return makeSearchResults( + getMatchingResources( + query, true, now, RESULT_SET_SIZE_SCALING_FACTOR * rdapResultSetMaxSize), + now); } /** diff --git a/java/google/registry/rdap/RdapEntitySearchAction.java b/java/google/registry/rdap/RdapEntitySearchAction.java index 96ecb24b6..64ecaa8ad 100644 --- a/java/google/registry/rdap/RdapEntitySearchAction.java +++ b/java/google/registry/rdap/RdapEntitySearchAction.java @@ -64,8 +64,6 @@ public class RdapEntitySearchAction extends RdapActionBase { public static final String PATH = "/rdap/entities"; - private static final int RESULT_SET_SIZE_SCALING_FACTOR = 30; - @Inject Clock clock; @Inject @Parameter("fn") Optional fnParam; @Inject @Parameter("handle") Optional handleParam; @@ -189,7 +187,7 @@ public class RdapEntitySearchAction extends RdapActionBase { if (authorization.role() != RdapAuthorization.Role.ADMINISTRATOR) { query = query.filter("currentSponsorClientId in", authorization.clientIds()); } - resultSet = getMatchingResources(query, false, now); + resultSet = getMatchingResources(query, false, now, rdapResultSetMaxSize + 1); } return makeSearchResults(resultSet, registrars, now); } @@ -234,16 +232,17 @@ public class RdapEntitySearchAction extends RdapActionBase { // Get the contact matches and return the results, fetching an additional contact to detect // truncation. If we are including deleted entries, we must fetch more entries, in case some // get excluded due to permissioning. + int querySizeLimit = getStandardQuerySizeLimit(); Query query = queryItemsByKey( ContactResource.class, partialStringQuery, shouldIncludeDeleted(), - shouldIncludeDeleted() - ? (RESULT_SET_SIZE_SCALING_FACTOR * (rdapResultSetMaxSize + 1)) - : (rdapResultSetMaxSize + 1)); + querySizeLimit); return makeSearchResults( - getMatchingResources(query, shouldIncludeDeleted(), now), registrars, now); + getMatchingResources(query, shouldIncludeDeleted(), now, querySizeLimit), + registrars, + now); } } diff --git a/java/google/registry/rdap/RdapMetrics.java b/java/google/registry/rdap/RdapMetrics.java index 02e6fbdf0..40998608d 100644 --- a/java/google/registry/rdap/RdapMetrics.java +++ b/java/google/registry/rdap/RdapMetrics.java @@ -294,7 +294,11 @@ public class RdapMetrics { } static Builder builder() { - return new AutoValue_RdapMetrics_RdapMetricInformation.Builder(); + return new AutoValue_RdapMetrics_RdapMetricInformation.Builder() + .setSearchType(SearchType.NONE) + .setWildcardType(WildcardType.INVALID) + .setPrefixLength(0) + .setIncompletenessWarningType(IncompletenessWarningType.COMPLETE); } } } diff --git a/java/google/registry/rdap/RdapNameserverSearchAction.java b/java/google/registry/rdap/RdapNameserverSearchAction.java index e1324669f..dc0887feb 100644 --- a/java/google/registry/rdap/RdapNameserverSearchAction.java +++ b/java/google/registry/rdap/RdapNameserverSearchAction.java @@ -64,7 +64,6 @@ import org.joda.time.DateTime; public class RdapNameserverSearchAction extends RdapActionBase { public static final String PATH = "/rdap/nameservers"; - private static final int RESULT_SET_SIZE_SCALING_FACTOR = 30; @Inject Clock clock; @Inject @Parameter("name") Optional nameParam; @@ -223,31 +222,31 @@ public class RdapNameserverSearchAction extends RdapActionBase { private RdapSearchResults searchByNameUsingPrefix( final RdapSearchPattern partialStringQuery, final DateTime now) { // Add 1 so we can detect truncation. + int querySizeLimit = getStandardQuerySizeLimit(); Query query = queryItems( HostResource.class, "fullyQualifiedHostName", partialStringQuery, shouldIncludeDeleted(), - shouldIncludeDeleted() - ? (RESULT_SET_SIZE_SCALING_FACTOR * (rdapResultSetMaxSize + 1)) - : (rdapResultSetMaxSize + 1)); - return makeSearchResults(getMatchingResources(query, shouldIncludeDeleted(), now), now); + querySizeLimit); + return makeSearchResults( + getMatchingResources(query, shouldIncludeDeleted(), now, querySizeLimit), now); } /** Searches for nameservers by IP address, returning a JSON array of nameserver info maps. */ private RdapSearchResults searchByIp(final InetAddress inetAddress, DateTime now) { // Add 1 so we can detect truncation. + int querySizeLimit = getStandardQuerySizeLimit(); Query query = queryItems( HostResource.class, "inetAddresses", inetAddress.getHostAddress(), shouldIncludeDeleted(), - shouldIncludeDeleted() - ? (RESULT_SET_SIZE_SCALING_FACTOR * (rdapResultSetMaxSize + 1)) - : (rdapResultSetMaxSize + 1)); - return makeSearchResults(getMatchingResources(query, shouldIncludeDeleted(), now), now); + querySizeLimit); + return makeSearchResults( + getMatchingResources(query, shouldIncludeDeleted(), now, querySizeLimit), now); } /** Output JSON for a lists of hosts contained in an {@link RdapResultSet}. */ diff --git a/javatests/google/registry/rdap/RdapActionBaseTest.java b/javatests/google/registry/rdap/RdapActionBaseTest.java index 7d1e9d206..89d20905d 100644 --- a/javatests/google/registry/rdap/RdapActionBaseTest.java +++ b/javatests/google/registry/rdap/RdapActionBaseTest.java @@ -20,16 +20,28 @@ import static google.registry.request.Action.Method.GET; import static google.registry.request.Action.Method.HEAD; import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.testing.TestDataHelper.loadFileWithSubstitutions; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import com.google.appengine.api.users.User; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import google.registry.model.ofy.Ofy; import google.registry.rdap.RdapJsonFormatter.BoilerplateType; import google.registry.rdap.RdapMetrics.EndpointType; +import google.registry.rdap.RdapMetrics.SearchType; +import google.registry.rdap.RdapMetrics.WildcardType; +import google.registry.rdap.RdapSearchResults.IncompletenessWarningType; +import google.registry.request.Action; +import google.registry.request.auth.AuthLevel; +import google.registry.request.auth.AuthResult; +import google.registry.request.auth.UserAuthInfo; import google.registry.testing.AppEngineRule; import google.registry.testing.FakeClock; import google.registry.testing.FakeResponse; import google.registry.testing.InjectRule; +import google.registry.ui.server.registrar.SessionUtils; +import java.util.Optional; import org.joda.time.DateTime; import org.json.simple.JSONValue; import org.junit.Before; @@ -52,6 +64,10 @@ public class RdapActionBaseTest { private final FakeResponse response = new FakeResponse(); private final FakeClock clock = new FakeClock(DateTime.parse("2000-01-01TZ")); + private final SessionUtils sessionUtils = mock(SessionUtils.class); + private final User user = new User("rdap.user@example.com", "gmail.com", "12345"); + private final UserAuthInfo userAuthInfo = UserAuthInfo.create(user, false); + private final RdapMetrics rdapMetrics = mock(RdapMetrics.class); /** * Dummy RdapActionBase subclass used for testing. @@ -103,8 +119,13 @@ public class RdapActionBaseTest { createTld("thing"); inject.setStaticField(Ofy.class, "clock", clock); action = new RdapTestAction(); + action.sessionUtils = sessionUtils; + action.authResult = AuthResult.create(AuthLevel.USER, userAuthInfo); + action.includeDeletedParam = Optional.empty(); + action.registrarParam = Optional.empty(); action.response = response; action.rdapJsonFormatter = RdapTestHelper.getTestRdapJsonFormatter(); + action.rdapMetrics = rdapMetrics; } private Object generateActualJson(String domainName) { @@ -172,4 +193,42 @@ public class RdapActionBaseTest { generateActualJson("no.thing"); assertThat(response.getHeaders().get(ACCESS_CONTROL_ALLOW_ORIGIN)).isEqualTo("*"); } + + @Test + public void testMetrics_onSuccess() throws Exception { + generateActualJson("no.thing"); + verify(rdapMetrics) + .updateMetrics( + RdapMetrics.RdapMetricInformation.builder() + .setEndpointType(EndpointType.HELP) + .setSearchType(SearchType.NONE) + .setWildcardType(WildcardType.INVALID) + .setPrefixLength(0) + .setIncludeDeleted(false) + .setRegistrarSpecified(false) + .setRole(RdapAuthorization.Role.PUBLIC) + .setRequestMethod(Action.Method.GET) + .setStatusCode(200) + .setIncompletenessWarningType(IncompletenessWarningType.COMPLETE) + .build()); + } + + @Test + public void testMetrics_onError() throws Exception { + generateActualJson("IllegalArgumentException"); + verify(rdapMetrics) + .updateMetrics( + RdapMetrics.RdapMetricInformation.builder() + .setEndpointType(EndpointType.HELP) + .setSearchType(SearchType.NONE) + .setWildcardType(WildcardType.INVALID) + .setPrefixLength(0) + .setIncludeDeleted(false) + .setRegistrarSpecified(false) + .setRole(RdapAuthorization.Role.PUBLIC) + .setRequestMethod(Action.Method.GET) + .setStatusCode(400) + .setIncompletenessWarningType(IncompletenessWarningType.COMPLETE) + .build()); + } } diff --git a/javatests/google/registry/rdap/RdapDomainActionTest.java b/javatests/google/registry/rdap/RdapDomainActionTest.java index 5489aaf64..538b48ccd 100644 --- a/javatests/google/registry/rdap/RdapDomainActionTest.java +++ b/javatests/google/registry/rdap/RdapDomainActionTest.java @@ -26,6 +26,7 @@ import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar; import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrarContacts; import static google.registry.testing.TestDataHelper.loadFileWithSubstitutions; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.google.appengine.api.users.User; @@ -40,6 +41,11 @@ import google.registry.model.ofy.Ofy; import google.registry.model.registrar.Registrar; import google.registry.model.registry.Registry; import google.registry.model.reporting.HistoryEntry; +import google.registry.rdap.RdapMetrics.EndpointType; +import google.registry.rdap.RdapMetrics.SearchType; +import google.registry.rdap.RdapMetrics.WildcardType; +import google.registry.rdap.RdapSearchResults.IncompletenessWarningType; +import google.registry.request.Action; import google.registry.request.auth.AuthLevel; import google.registry.request.auth.AuthResult; import google.registry.request.auth.UserAuthInfo; @@ -82,6 +88,7 @@ public class RdapDomainActionTest { private final User user = new User("rdap.user@example.com", "gmail.com", "12345"); private final UserAuthInfo userAuthInfo = UserAuthInfo.create(user, false); private final UserAuthInfo adminUserAuthInfo = UserAuthInfo.create(user, true); + private final RdapMetrics rdapMetrics = mock(RdapMetrics.class); private RdapDomainAction action; @@ -256,6 +263,7 @@ public class RdapDomainActionTest { action = new RdapDomainAction(); action.clock = clock; action.request = request; + action.requestMethod = Action.Method.GET; action.fullServletPath = "https://example.com/rdap"; action.response = response; action.registrarParam = Optional.empty(); @@ -264,6 +272,7 @@ public class RdapDomainActionTest { action.rdapWhoisServer = null; action.sessionUtils = sessionUtils; action.authResult = AuthResult.create(AuthLevel.USER, userAuthInfo); + action.rdapMetrics = rdapMetrics; } private void login(String clientId) { @@ -617,4 +626,23 @@ public class RdapDomainActionTest { "rdap_domain_deleted.json")); assertThat(response.getStatus()).isEqualTo(200); } + + @Test + public void testMetrics() throws Exception { + generateActualJson("cat.lol"); + verify(rdapMetrics) + .updateMetrics( + RdapMetrics.RdapMetricInformation.builder() + .setEndpointType(EndpointType.DOMAIN) + .setSearchType(SearchType.NONE) + .setWildcardType(WildcardType.INVALID) + .setPrefixLength(0) + .setIncludeDeleted(false) + .setRegistrarSpecified(false) + .setRole(RdapAuthorization.Role.PUBLIC) + .setRequestMethod(Action.Method.GET) + .setStatusCode(200) + .setIncompletenessWarningType(IncompletenessWarningType.COMPLETE) + .build()); + } } diff --git a/javatests/google/registry/rdap/RdapDomainSearchActionTest.java b/javatests/google/registry/rdap/RdapDomainSearchActionTest.java index d6fc338d0..15614b07a 100644 --- a/javatests/google/registry/rdap/RdapDomainSearchActionTest.java +++ b/javatests/google/registry/rdap/RdapDomainSearchActionTest.java @@ -46,6 +46,7 @@ import google.registry.model.ofy.Ofy; import google.registry.model.registrar.Registrar; import google.registry.model.registry.Registry; import google.registry.model.reporting.HistoryEntry; +import google.registry.request.Action; import google.registry.request.auth.AuthLevel; import google.registry.request.auth.AuthResult; import google.registry.request.auth.UserAuthInfo; @@ -91,8 +92,8 @@ public class RdapDomainSearchActionTest { private final User user = new User("rdap.user@example.com", "gmail.com", "12345"); private final UserAuthInfo userAuthInfo = UserAuthInfo.create(user, false); private final UserAuthInfo adminUserAuthInfo = UserAuthInfo.create(user, true); - private final RdapDomainSearchAction action = new RdapDomainSearchAction(); + private final RdapMetrics rdapMetrics = mock(RdapMetrics.class); private Registrar registrar; private DomainResource domainCatLol; @@ -362,6 +363,7 @@ public class RdapDomainSearchActionTest { action.clock = clock; action.request = request; + action.requestMethod = Action.Method.GET; action.fullServletPath = "https://example.com/rdap"; action.response = response; action.registrarParam = Optional.empty(); @@ -370,6 +372,7 @@ public class RdapDomainSearchActionTest { action.rdapWhoisServer = null; action.sessionUtils = sessionUtils; action.authResult = AuthResult.create(AuthLevel.USER, userAuthInfo); + action.rdapMetrics = rdapMetrics; } private void login(String clientId) { diff --git a/javatests/google/registry/rdap/RdapEntityActionTest.java b/javatests/google/registry/rdap/RdapEntityActionTest.java index cba098be1..a8e7450cd 100644 --- a/javatests/google/registry/rdap/RdapEntityActionTest.java +++ b/javatests/google/registry/rdap/RdapEntityActionTest.java @@ -26,6 +26,7 @@ import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar; import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrarContacts; import static google.registry.testing.TestDataHelper.loadFileWithSubstitutions; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.google.appengine.api.users.User; @@ -36,6 +37,11 @@ import google.registry.model.contact.ContactResource; import google.registry.model.host.HostResource; import google.registry.model.ofy.Ofy; import google.registry.model.registrar.Registrar; +import google.registry.rdap.RdapMetrics.EndpointType; +import google.registry.rdap.RdapMetrics.SearchType; +import google.registry.rdap.RdapMetrics.WildcardType; +import google.registry.rdap.RdapSearchResults.IncompletenessWarningType; +import google.registry.request.Action; import google.registry.request.auth.AuthLevel; import google.registry.request.auth.AuthResult; import google.registry.request.auth.UserAuthInfo; @@ -73,8 +79,9 @@ public class RdapEntityActionTest { private final FakeClock clock = new FakeClock(DateTime.parse("2000-01-01TZ")); private final SessionUtils sessionUtils = mock(SessionUtils.class); private final User user = new User("rdap.user@example.com", "gmail.com", "12345"); - UserAuthInfo userAuthInfo = UserAuthInfo.create(user, false); - UserAuthInfo adminUserAuthInfo = UserAuthInfo.create(user, true); + private final UserAuthInfo userAuthInfo = UserAuthInfo.create(user, false); + private final UserAuthInfo adminUserAuthInfo = UserAuthInfo.create(user, true); + private final RdapMetrics rdapMetrics = mock(RdapMetrics.class); private RdapEntityAction action; @@ -157,6 +164,7 @@ public class RdapEntityActionTest { action = new RdapEntityAction(); action.clock = clock; action.request = request; + action.requestMethod = Action.Method.GET; action.fullServletPath = "https://example.com/rdap"; action.response = response; action.registrarParam = Optional.empty(); @@ -165,6 +173,7 @@ public class RdapEntityActionTest { action.rdapWhoisServer = null; action.sessionUtils = sessionUtils; action.authResult = AuthResult.create(AuthLevel.USER, userAuthInfo); + action.rdapMetrics = rdapMetrics; } private void login(String registrar) { @@ -492,4 +501,23 @@ public class RdapEntityActionTest { techContact.getRepoId(), "rdap_associated_contact.json")); assertThat(response.getStatus()).isEqualTo(200); } + + @Test + public void testMetrics() throws Exception { + generateActualJson(registrant.getRepoId()); + verify(rdapMetrics) + .updateMetrics( + RdapMetrics.RdapMetricInformation.builder() + .setEndpointType(EndpointType.ENTITY) + .setSearchType(SearchType.NONE) + .setWildcardType(WildcardType.INVALID) + .setPrefixLength(0) + .setIncludeDeleted(false) + .setRegistrarSpecified(false) + .setRole(RdapAuthorization.Role.PUBLIC) + .setRequestMethod(Action.Method.GET) + .setStatusCode(200) + .setIncompletenessWarningType(IncompletenessWarningType.COMPLETE) + .build()); + } } diff --git a/javatests/google/registry/rdap/RdapEntitySearchActionTest.java b/javatests/google/registry/rdap/RdapEntitySearchActionTest.java index 84af14b98..9dd4059f1 100644 --- a/javatests/google/registry/rdap/RdapEntitySearchActionTest.java +++ b/javatests/google/registry/rdap/RdapEntitySearchActionTest.java @@ -35,6 +35,7 @@ import com.google.common.collect.ImmutableMap; import google.registry.model.ImmutableObject; import google.registry.model.ofy.Ofy; import google.registry.model.registrar.Registrar; +import google.registry.request.Action; import google.registry.request.auth.AuthLevel; import google.registry.request.auth.AuthResult; import google.registry.request.auth.UserAuthInfo; @@ -71,6 +72,7 @@ public class RdapEntitySearchActionTest { private final User user = new User("rdap.user@example.com", "gmail.com", "12345"); private final UserAuthInfo userAuthInfo = UserAuthInfo.create(user, false); private final UserAuthInfo adminUserAuthInfo = UserAuthInfo.create(user, true); + private final RdapMetrics rdapMetrics = mock(RdapMetrics.class); private final RdapEntitySearchAction action = new RdapEntitySearchAction(); private Registrar registrarDeleted; @@ -140,6 +142,7 @@ public class RdapEntitySearchActionTest { action.clock = clock; action.request = request; + action.requestMethod = Action.Method.GET; action.fullServletPath = "https://example.com/rdap"; action.requestPath = RdapEntitySearchAction.PATH; action.response = response; @@ -152,6 +155,7 @@ public class RdapEntitySearchActionTest { action.includeDeletedParam = Optional.empty(); action.sessionUtils = sessionUtils; action.authResult = AuthResult.create(AuthLevel.USER, userAuthInfo); + action.rdapMetrics = rdapMetrics; } private void login(String registrar) { diff --git a/javatests/google/registry/rdap/RdapHelpActionTest.java b/javatests/google/registry/rdap/RdapHelpActionTest.java index a6573880d..45d5547b5 100644 --- a/javatests/google/registry/rdap/RdapHelpActionTest.java +++ b/javatests/google/registry/rdap/RdapHelpActionTest.java @@ -16,12 +16,25 @@ package google.registry.rdap; import static com.google.common.truth.Truth.assertThat; import static google.registry.testing.TestDataHelper.loadFileWithSubstitutions; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import com.google.appengine.api.users.User; import com.google.common.collect.ImmutableMap; import google.registry.model.ofy.Ofy; +import google.registry.rdap.RdapMetrics.EndpointType; +import google.registry.rdap.RdapMetrics.SearchType; +import google.registry.rdap.RdapMetrics.WildcardType; +import google.registry.rdap.RdapSearchResults.IncompletenessWarningType; +import google.registry.request.Action; +import google.registry.request.auth.AuthLevel; +import google.registry.request.auth.AuthResult; +import google.registry.request.auth.UserAuthInfo; import google.registry.testing.FakeClock; import google.registry.testing.FakeResponse; import google.registry.testing.InjectRule; +import google.registry.ui.server.registrar.SessionUtils; +import java.util.Optional; import org.joda.time.DateTime; import org.json.simple.JSONValue; import org.junit.Before; @@ -39,6 +52,10 @@ public class RdapHelpActionTest { private final FakeResponse response = new FakeResponse(); private final FakeClock clock = new FakeClock(DateTime.parse("2000-01-01TZ")); + private final SessionUtils sessionUtils = mock(SessionUtils.class); + private final User user = new User("rdap.user@example.com", "gmail.com", "12345"); + private final UserAuthInfo userAuthInfo = UserAuthInfo.create(user, false); + private final RdapMetrics rdapMetrics = mock(RdapMetrics.class); private RdapHelpAction action; @@ -49,9 +66,15 @@ public class RdapHelpActionTest { action = new RdapHelpAction(); action.clock = clock; action.fullServletPath = "https://example.tld/rdap"; + action.requestMethod = Action.Method.GET; + action.sessionUtils = sessionUtils; + action.authResult = AuthResult.create(AuthLevel.USER, userAuthInfo); + action.includeDeletedParam = Optional.empty(); + action.registrarParam = Optional.empty(); action.response = response; action.rdapJsonFormatter = RdapTestHelper.getTestRdapJsonFormatter(); action.rdapWhoisServer = null; + action.rdapMetrics = rdapMetrics; } private Object generateActualJson(String helpPath) { @@ -113,4 +136,23 @@ public class RdapHelpActionTest { .isEqualTo(generateExpectedJson("", "rdap_help_tos.json")); assertThat(response.getStatus()).isEqualTo(200); } + + @Test + public void testHelpActionMetrics() throws Exception { + generateActualJson("/tos"); + verify(rdapMetrics) + .updateMetrics( + RdapMetrics.RdapMetricInformation.builder() + .setEndpointType(EndpointType.HELP) + .setSearchType(SearchType.NONE) + .setWildcardType(WildcardType.INVALID) + .setPrefixLength(0) + .setIncludeDeleted(false) + .setRegistrarSpecified(false) + .setRole(RdapAuthorization.Role.PUBLIC) + .setRequestMethod(Action.Method.GET) + .setStatusCode(200) + .setIncompletenessWarningType(IncompletenessWarningType.COMPLETE) + .build()); + } } diff --git a/javatests/google/registry/rdap/RdapMetricsTest.java b/javatests/google/registry/rdap/RdapMetricsTest.java index 712305de2..daee457db 100644 --- a/javatests/google/registry/rdap/RdapMetricsTest.java +++ b/javatests/google/registry/rdap/RdapMetricsTest.java @@ -47,7 +47,7 @@ public class RdapMetricsTest { return RdapMetrics.RdapMetricInformation.builder() .setEndpointType(EndpointType.DOMAINS) .setSearchType(SearchType.NONE) - .setWildcardType(WildcardType.NO_WILDCARD) + .setWildcardType(WildcardType.INVALID) .setPrefixLength(0) .setIncludeDeleted(false) .setRegistrarSpecified(false) @@ -80,7 +80,7 @@ public class RdapMetricsTest { rdapMetrics.updateMetrics( getBuilder().setPrefixLength(6).setNumDomainsRetrieved(1).build()); assertThat(RdapMetrics.numberOfDomainsRetrieved) - .hasDataSetForLabels(ImmutableSet.of(1), "DOMAINS", "NONE", "NO_WILDCARD", "5+", "NO") + .hasDataSetForLabels(ImmutableSet.of(1), "DOMAINS", "NONE", "INVALID", "5+", "NO") .and() .hasNoOtherValues(); } @@ -110,7 +110,7 @@ public class RdapMetricsTest { .setIncompletenessWarningType(IncompletenessWarningType.COMPLETE) .build()); assertThat(RdapMetrics.responses) - .hasValueForLabels(1, "DOMAINS", "NONE", "NO_WILDCARD", "200", "COMPLETE") + .hasValueForLabels(1, "DOMAINS", "NONE", "INVALID", "200", "COMPLETE") .and() .hasNoOtherValues(); } @@ -122,7 +122,7 @@ public class RdapMetricsTest { .setIncompletenessWarningType(IncompletenessWarningType.TRUNCATED) .build()); assertThat(RdapMetrics.responses) - .hasValueForLabels(1, "DOMAINS", "NONE", "NO_WILDCARD", "200", "TRUNCATED") + .hasValueForLabels(1, "DOMAINS", "NONE", "INVALID", "200", "TRUNCATED") .and() .hasNoOtherValues(); } @@ -134,7 +134,7 @@ public class RdapMetricsTest { .setIncompletenessWarningType(IncompletenessWarningType.MIGHT_BE_INCOMPLETE) .build()); assertThat(RdapMetrics.responses) - .hasValueForLabels(1, "DOMAINS", "NONE", "NO_WILDCARD", "200", "MIGHT_BE_INCOMPLETE") + .hasValueForLabels(1, "DOMAINS", "NONE", "INVALID", "200", "MIGHT_BE_INCOMPLETE") .and() .hasNoOtherValues(); } @@ -172,6 +172,7 @@ public class RdapMetricsTest { rdapMetrics.updateMetrics( getBuilder() .setSearchType(SearchType.BY_DOMAIN_NAME) + .setWildcardType(WildcardType.NO_WILDCARD) .setPrefixLength(7) .setNumDomainsRetrieved(1) .build()); diff --git a/javatests/google/registry/rdap/RdapNameserverActionTest.java b/javatests/google/registry/rdap/RdapNameserverActionTest.java index c811dca66..d0c1d9694 100644 --- a/javatests/google/registry/rdap/RdapNameserverActionTest.java +++ b/javatests/google/registry/rdap/RdapNameserverActionTest.java @@ -21,6 +21,7 @@ import static google.registry.testing.FullFieldsTestEntityHelper.makeAndPersistH import static google.registry.testing.FullFieldsTestEntityHelper.makeRegistrar; import static google.registry.testing.TestDataHelper.loadFileWithSubstitutions; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.google.appengine.api.users.User; @@ -29,6 +30,11 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import google.registry.model.ofy.Ofy; import google.registry.model.registrar.Registrar; +import google.registry.rdap.RdapMetrics.EndpointType; +import google.registry.rdap.RdapMetrics.SearchType; +import google.registry.rdap.RdapMetrics.WildcardType; +import google.registry.rdap.RdapSearchResults.IncompletenessWarningType; +import google.registry.request.Action; import google.registry.request.auth.AuthLevel; import google.registry.request.auth.AuthResult; import google.registry.request.auth.UserAuthInfo; @@ -68,6 +74,7 @@ public class RdapNameserverActionTest { private final User user = new User("rdap.user@example.com", "gmail.com", "12345"); private final UserAuthInfo userAuthInfo = UserAuthInfo.create(user, false); private final UserAuthInfo adminUserAuthInfo = UserAuthInfo.create(user, true); + private final RdapMetrics rdapMetrics = mock(RdapMetrics.class); @Before public void setUp() throws Exception { @@ -111,6 +118,7 @@ public class RdapNameserverActionTest { RdapNameserverAction action = new RdapNameserverAction(); action.clock = clock; action.request = request; + action.requestMethod = Action.Method.GET; action.fullServletPath = "https://example.tld/rdap"; action.response = response; action.requestPath = RdapNameserverAction.PATH.concat(input); @@ -120,6 +128,7 @@ public class RdapNameserverActionTest { action.rdapWhoisServer = null; action.authResult = authResult; action.sessionUtils = sessionUtils; + action.rdapMetrics = rdapMetrics; return action; } @@ -427,4 +436,23 @@ public class RdapNameserverActionTest { generateActualJson("ns1.cat.lol", Optional.of("otherregistrar"), Optional.of(false)); assertThat(response.getStatus()).isEqualTo(404); } + + @Test + public void testMetrics() throws Exception { + generateActualJson("ns1.cat.lol"); + verify(rdapMetrics) + .updateMetrics( + RdapMetrics.RdapMetricInformation.builder() + .setEndpointType(EndpointType.NAMESERVER) + .setSearchType(SearchType.NONE) + .setWildcardType(WildcardType.INVALID) + .setPrefixLength(0) + .setIncludeDeleted(false) + .setRegistrarSpecified(false) + .setRole(RdapAuthorization.Role.PUBLIC) + .setRequestMethod(Action.Method.GET) + .setStatusCode(200) + .setIncompletenessWarningType(IncompletenessWarningType.COMPLETE) + .build()); + } } diff --git a/javatests/google/registry/rdap/RdapNameserverSearchActionTest.java b/javatests/google/registry/rdap/RdapNameserverSearchActionTest.java index 2c3fb0caa..eec00b1e0 100644 --- a/javatests/google/registry/rdap/RdapNameserverSearchActionTest.java +++ b/javatests/google/registry/rdap/RdapNameserverSearchActionTest.java @@ -38,6 +38,7 @@ import google.registry.model.domain.DomainResource; import google.registry.model.host.HostResource; import google.registry.model.ofy.Ofy; import google.registry.model.registrar.Registrar; +import google.registry.request.Action; import google.registry.request.auth.AuthLevel; import google.registry.request.auth.AuthResult; import google.registry.request.auth.UserAuthInfo; @@ -73,6 +74,7 @@ public class RdapNameserverSearchActionTest { private final UserAuthInfo userAuthInfo = UserAuthInfo.create(user, false); private final UserAuthInfo adminUserAuthInfo = UserAuthInfo.create(user, true); private final RdapNameserverSearchAction action = new RdapNameserverSearchAction(); + private final RdapMetrics rdapMetrics = mock(RdapMetrics.class); private DomainResource domainCatLol; private HostResource hostNs1CatLol; @@ -144,6 +146,7 @@ public class RdapNameserverSearchActionTest { action.fullServletPath = "https://example.tld/rdap"; action.requestPath = RdapNameserverSearchAction.PATH; action.request = request; + action.requestMethod = Action.Method.GET; action.response = response; action.rdapJsonFormatter = RdapTestHelper.getTestRdapJsonFormatter(); action.rdapResultSetMaxSize = 4; @@ -154,6 +157,7 @@ public class RdapNameserverSearchActionTest { action.includeDeletedParam = Optional.empty(); action.authResult = AuthResult.create(AuthLevel.USER, userAuthInfo); action.sessionUtils = sessionUtils; + action.rdapMetrics = rdapMetrics; } private Object generateExpectedJson(String expectedOutputFile) {