diff --git a/modules/ingest-geoip/src/internalClusterTest/java/org/elasticsearch/ingest/geoip/DatabaseNodeServiceIT.java b/modules/ingest-geoip/src/internalClusterTest/java/org/elasticsearch/ingest/geoip/DatabaseNodeServiceIT.java index 57d7a58b67972..811da47b9eb59 100644 --- a/modules/ingest-geoip/src/internalClusterTest/java/org/elasticsearch/ingest/geoip/DatabaseNodeServiceIT.java +++ b/modules/ingest-geoip/src/internalClusterTest/java/org/elasticsearch/ingest/geoip/DatabaseNodeServiceIT.java @@ -9,9 +9,6 @@ package org.elasticsearch.ingest.geoip; -import com.maxmind.geoip2.model.CountryResponse; -import com.maxmind.geoip2.record.Country; - import org.elasticsearch.action.admin.indices.flush.FlushRequest; import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; import org.elasticsearch.action.index.IndexRequest; @@ -102,11 +99,9 @@ private void assertValidDatabase(DatabaseNodeService databaseNodeService, String IpDatabase database = databaseNodeService.getDatabase(projectId, databaseFileName); assertNotNull(database); assertThat(database.getDatabaseType(), equalTo(databaseType)); - CountryResponse countryResponse = database.getResponse("89.160.20.128", GeoIpTestUtils::getCountry); + GeoIpTestUtils.SimpleCountry countryResponse = database.getResponse("89.160.20.128", GeoIpTestUtils::getCountry); assertNotNull(countryResponse); - Country country = countryResponse.getCountry(); - assertNotNull(country); - assertThat(country.getName(), equalTo("Sweden")); + assertThat(countryResponse.countryName(), equalTo("Sweden")); } /* diff --git a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/DatabaseReaderLazyLoader.java b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/DatabaseReaderLazyLoader.java index db4dc1a9dab8c..23383edc93897 100644 --- a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/DatabaseReaderLazyLoader.java +++ b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/DatabaseReaderLazyLoader.java @@ -115,7 +115,10 @@ int current() { @Override @Nullable - public RESPONSE getResponse(String ipAddress, CheckedBiFunction responseProvider) { + public RESPONSE getResponse( + String ipAddress, + CheckedBiFunction responseProvider + ) { return cache.putIfAbsent(projectId, ipAddress, cachedDatabasePathToString, ip -> { try { return responseProvider.apply(get(), ipAddress); diff --git a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/GeoIpCache.java b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/GeoIpCache.java index 0122499583799..9ff369bd488b6 100644 --- a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/GeoIpCache.java +++ b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/GeoIpCache.java @@ -10,9 +10,11 @@ import com.maxmind.db.NodeCache; +import org.apache.lucene.util.RamUsageEstimator; import org.elasticsearch.cluster.metadata.ProjectId; import org.elasticsearch.common.cache.Cache; import org.elasticsearch.common.cache.CacheBuilder; +import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.core.TimeValue; import org.elasticsearch.ingest.geoip.stats.CacheStats; @@ -29,44 +31,69 @@ */ public final class GeoIpCache { + static GeoIpCache createGeoIpCacheWithMaxCount(long maxSize) { + if (maxSize < 0) { + throw new IllegalArgumentException("geoip max cache size must be 0 or greater"); + } + return new GeoIpCache(System::nanoTime, CacheBuilder.builder().setMaximumWeight(maxSize).build()); + } + + static GeoIpCache createGeoIpCacheWithMaxBytes(ByteSizeValue maxByteSize) { + if (maxByteSize.getBytes() < 0) { + throw new IllegalArgumentException("geoip max cache size in bytes must be 0 or greater"); + } + return new GeoIpCache( + System::nanoTime, + CacheBuilder.builder() + .setMaximumWeight(maxByteSize.getBytes()) + .weigher((key, value) -> key.sizeInBytes() + value.sizeInBytes()) + .build() + ); + } + + // package private for testing + static GeoIpCache createGeoIpCacheWithMaxCountAndCustomTimeProvider(long maxSize, LongSupplier relativeNanoTimeProvider) { + return new GeoIpCache( + relativeNanoTimeProvider, + CacheBuilder.builder().setMaximumWeight(maxSize).build() + ); + } + /** * Internal-only sentinel object for recording that a result from the geoip database was null (i.e. there was no result). By caching * this no-result we can distinguish between something not being in the cache because we haven't searched for that data yet, versus * something not being in the cache because the data doesn't exist in the database. */ // visible for testing - static final Object NO_RESULT = new Object() { + static final IpDatabase.Response NO_RESULT = new IpDatabase.Response() { @Override public String toString() { return "NO_RESULT"; } }; - private final Cache cache; + private final Cache cache; private final LongSupplier relativeNanoTimeProvider; private final LongAdder hitsTimeInNanos = new LongAdder(); private final LongAdder missesTimeInNanos = new LongAdder(); - // package private for testing - GeoIpCache(long maxSize, LongSupplier relativeNanoTimeProvider) { - if (maxSize < 0) { - throw new IllegalArgumentException("geoip max cache size must be 0 or greater"); - } + private GeoIpCache(LongSupplier relativeNanoTimeProvider, Cache cache) { this.relativeNanoTimeProvider = relativeNanoTimeProvider; - this.cache = CacheBuilder.builder().setMaximumWeight(maxSize).build(); - } - - GeoIpCache(long maxSize) { - this(maxSize, System::nanoTime); + this.cache = cache; } @SuppressWarnings("unchecked") - RESPONSE putIfAbsent(ProjectId projectId, String ip, String databasePath, Function retrieveFunction) { + RESPONSE putIfAbsent( + ProjectId projectId, + String ip, + String databasePath, + Function retrieveFunction + ) { // can't use cache.computeIfAbsent due to the elevated permissions for the jackson (run via the cache loader) CacheKey cacheKey = new CacheKey(projectId, ip, databasePath); long cacheStart = relativeNanoTimeProvider.getAsLong(); // intentionally non-locking for simplicity...it's OK if we re-put the same key/value in the cache during a race condition. - Object response = cache.get(cacheKey); + IpDatabase.Response response = cache.get(cacheKey); long cacheRequestTime = relativeNanoTimeProvider.getAsLong() - cacheStart; // populate the cache for this key, if necessary @@ -136,5 +163,21 @@ public CacheStats getCacheStats() { * path is needed to be included in the cache key. For example, if we only used the IP address as the key the City and ASN the same * IP may be in both with different values and we need to cache both. */ - private record CacheKey(ProjectId projectId, String ip, String databasePath) {} + private record CacheKey(ProjectId projectId, String ip, String databasePath) { + + private static final long BASE_BYTES = RamUsageEstimator.shallowSizeOfInstance(CacheKey.class); + private static final long PROJECT_ID_BASE_BYTES = RamUsageEstimator.shallowSizeOfInstance(ProjectId.class); + + private long sizeInBytes() { + return keySizeInBytes(projectId, ip, databasePath); + } + } + + // visible for testing + static long keySizeInBytes(ProjectId projectId, String ip, String databasePath) { + // TODO: Check this size computation before merging: + return CacheKey.BASE_BYTES + CacheKey.PROJECT_ID_BASE_BYTES + RamUsageEstimator.sizeOf(projectId.id()) + RamUsageEstimator.sizeOf( + ip + ) + RamUsageEstimator.sizeOf(databasePath); + } } diff --git a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/IngestGeoIpPlugin.java b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/IngestGeoIpPlugin.java index 7996faa684862..541bfcf187bac 100644 --- a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/IngestGeoIpPlugin.java +++ b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/IngestGeoIpPlugin.java @@ -24,6 +24,8 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.SettingsFilter; import org.elasticsearch.common.settings.SettingsModule; +import org.elasticsearch.common.unit.ByteSizeValue; +import org.elasticsearch.core.Strings; import org.elasticsearch.features.NodeFeature; import org.elasticsearch.indices.SystemIndexDescriptor; import org.elasticsearch.ingest.EnterpriseGeoIpTask.EnterpriseGeoIpTaskParams; @@ -43,6 +45,7 @@ import org.elasticsearch.ingest.geoip.stats.GeoIpStatsAction; import org.elasticsearch.ingest.geoip.stats.GeoIpStatsTransportAction; import org.elasticsearch.ingest.geoip.stats.RestGeoIpStatsAction; +import org.elasticsearch.monitor.jvm.JvmInfo; import org.elasticsearch.persistent.PersistentTaskParams; import org.elasticsearch.persistent.PersistentTaskState; import org.elasticsearch.persistent.PersistentTasksExecutor; @@ -86,7 +89,20 @@ public class IngestGeoIpPlugin extends Plugin PersistentTaskPlugin, ActionPlugin, ReloadablePlugin { - public static final Setting CACHE_SIZE = Setting.longSetting("ingest.geoip.cache_size", 1000, 0, Setting.Property.NodeScope); + + private static final Setting CACHE_SIZE_COUNT = Setting.longSetting( + "ingest.geoip.cache_size", + 0, // default is never used, we use CACHE_SIZE_BYTES if this is not set + 0, + Setting.Property.NodeScope + ); + + private static final Setting CACHE_SIZE_BYTES = Setting.byteSizeSetting( + "ingest.geoip.cache_memory_size", + // TODO: Think more carefully about this default before merging: + ByteSizeValue.ofBytes((long) (0.01 * JvmInfo.jvmInfo().getConfiguredMaxHeapSize())), + Setting.Property.NodeScope + ); private static final int GEOIP_INDEX_MAPPINGS_VERSION = 1; /** * No longer used for determining the age of mappings, but system index descriptor @@ -105,7 +121,8 @@ public class IngestGeoIpPlugin extends Plugin @Override public List> getSettings() { return List.of( - CACHE_SIZE, + CACHE_SIZE_COUNT, + CACHE_SIZE_BYTES, GeoIpDownloaderTaskExecutor.EAGER_DOWNLOAD_SETTING, GeoIpDownloaderTaskExecutor.ENABLED_SETTING, GeoIpDownloader.ENDPOINT_SETTING, @@ -119,8 +136,7 @@ public List> getSettings() { public Map getProcessors(Processor.Parameters parameters) { ingestService.set(parameters.ingestService); - long cacheSize = CACHE_SIZE.get(parameters.env.settings()); - GeoIpCache geoIpCache = new GeoIpCache(cacheSize); + GeoIpCache geoIpCache = createGeoIpCache(parameters.env.settings()); DatabaseNodeService registry = new DatabaseNodeService( parameters.env, parameters.client, @@ -137,6 +153,30 @@ public Map getProcessors(Processor.Parameters paramet ); } + private static GeoIpCache createGeoIpCache(Settings settings) { + if (settings.hasValue(CACHE_SIZE_COUNT.getKey())) { + if (settings.hasValue(CACHE_SIZE_BYTES.getKey())) { + // Both CACHE_SIZE_COUNT and CACHE_SIZE_BYTES are set, which is an error: + throw new IllegalArgumentException( + Strings.format( + "Both %s and %s are set: " + + "please use either %s to set a size based on count or %s to set a size based on bytes of memory", + CACHE_SIZE_COUNT.getKey(), + CACHE_SIZE_BYTES.getKey(), + CACHE_SIZE_COUNT.getKey(), + CACHE_SIZE_BYTES.getKey() + ) + ); + } else { + // Only CACHE_SIZE_COUNT is set, so use that: + return GeoIpCache.createGeoIpCacheWithMaxCount(CACHE_SIZE_COUNT.get(settings)); + } + } else { + // CACHE_SIZE_COUNT is not set, so use either the explicit or default value of CACHE_SIZE_BYTES: + return GeoIpCache.createGeoIpCacheWithMaxBytes(CACHE_SIZE_BYTES.get(settings)); + } + } + @Override public Collection createComponents(PluginServices services) { try { diff --git a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/IpDataLookup.java b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/IpDataLookup.java index 1f1e04274ac25..1b9457eb366b0 100644 --- a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/IpDataLookup.java +++ b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/IpDataLookup.java @@ -34,5 +34,5 @@ interface IpDataLookup { * as a network for which the record applies. Having a helper record prevents each individual response record from needing to * track these bits of information. */ - record Result(T result, String ip, String network) {} + record Result(T result, String ip, String network) implements IpDatabase.Response {} } diff --git a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/IpDatabase.java b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/IpDatabase.java index db1ffc1c682b8..acfb88d0ed13b 100644 --- a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/IpDatabase.java +++ b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/IpDatabase.java @@ -36,7 +36,13 @@ public interface IpDatabase extends AutoCloseable { * @param the type of response that will be returned */ @Nullable - RESPONSE getResponse(String ipAddress, CheckedBiFunction responseProvider); + // TODO: This change requires a one-line change to an implementation in a logstash filter. Coordinate with that team before merging. + // See repo https://github.com/elastic/logstash-filter-elastic_integration, + // class co.elastic.logstash.filters.elasticintegration.geoip.IpDatabaseAdapter. + RESPONSE getResponse( + String ipAddress, + CheckedBiFunction responseProvider + ); /** * Releases the current database object. Called after processing a single document. Databases should be closed or returned to a @@ -46,4 +52,12 @@ public interface IpDatabase extends AutoCloseable { */ @Override void close() throws IOException; + + interface Response { + + // TODO: Remove this default implementation and implement in all implementing classes instead before merging: + default long sizeInBytes() { + return 0; + } + } } diff --git a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/IpinfoIpDataLookups.java b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/IpinfoIpDataLookups.java index d548fa9bc98ea..52fd345ce28b7 100644 --- a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/IpinfoIpDataLookups.java +++ b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/IpinfoIpDataLookups.java @@ -191,7 +191,7 @@ public record AsnResult( String domain, String name, @Nullable String type // not present in the free asn database - ) { + ) implements IpDatabase.Response { @SuppressWarnings("checkstyle:RedundantModifier") @MaxMindDbConstructor public AsnResult( @@ -210,20 +210,14 @@ public record CountryResult( @MaxMindDbParameter(name = "continent_name") String continentName, @MaxMindDbParameter(name = "country") String country, @MaxMindDbParameter(name = "country_name") String countryName - ) { + ) implements IpDatabase.Response { @MaxMindDbConstructor public CountryResult {} } - public record GeolocationResult( - String city, - String country, - Double lat, - Double lng, - String postalCode, - String region, - String timezone - ) { + public record GeolocationResult(String city, String country, Double lat, Double lng, String postalCode, String region, String timezone) + implements + IpDatabase.Response { @SuppressWarnings("checkstyle:RedundantModifier") @MaxMindDbConstructor public GeolocationResult( @@ -241,7 +235,9 @@ public GeolocationResult( } } - public record PrivacyDetectionResult(Boolean hosting, Boolean proxy, Boolean relay, String service, Boolean tor, Boolean vpn) { + public record PrivacyDetectionResult(Boolean hosting, Boolean proxy, Boolean relay, String service, Boolean tor, Boolean vpn) + implements + IpDatabase.Response { @SuppressWarnings("checkstyle:RedundantModifier") @MaxMindDbConstructor public PrivacyDetectionResult( @@ -466,7 +462,7 @@ protected Map transform(final Result res * * @param the record type that will be wrapped and returned */ - private abstract static class AbstractBase implements IpDataLookup { + private abstract static class AbstractBase implements IpDataLookup { protected final Set properties; protected final Class clazz; diff --git a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/MaxmindIpDataLookups.java b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/MaxmindIpDataLookups.java index 196137ce99c4c..812c4892f803e 100644 --- a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/MaxmindIpDataLookups.java +++ b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/MaxmindIpDataLookups.java @@ -109,7 +109,23 @@ static Function, IpDataLookup> getMaxmindLookup(final Dat }; } - static class AnonymousIp extends AbstractBase { + static class CacheableAnonymousIpResponse extends AnonymousIpResponse implements IpDatabase.Response { + + CacheableAnonymousIpResponse(AnonymousIpResponse response) { + super( + response.getIpAddress(), + response.isAnonymous(), + response.isAnonymousVpn(), + response.isHostingProvider(), + response.isPublicProxy(), + response.isResidentialProxy(), + response.isTorExitNode(), + response.getNetwork() + ); + } + } + + static class AnonymousIp extends AbstractBase { AnonymousIp(final Set properties) { super( properties, @@ -119,12 +135,12 @@ static class AnonymousIp extends AbstractBase transform(final AnonymousIpResponse response) { + protected Map transform(final CacheableAnonymousIpResponse response) { boolean isHostingProvider = response.isHostingProvider(); boolean isTorExitNode = response.isTorExitNode(); boolean isAnonymousVpn = response.isAnonymousVpn(); @@ -160,18 +176,30 @@ protected Map transform(final AnonymousIpResponse response) { } } - static class Asn extends AbstractBase { + static class CacheableAsnResponse extends AsnResponse implements IpDatabase.Response { + + CacheableAsnResponse(AsnResponse response) { + super( + response.getAutonomousSystemNumber(), + response.getAutonomousSystemOrganization(), + response.getIpAddress(), + response.getNetwork() + ); + } + } + + static class Asn extends AbstractBase { Asn(Set properties) { super(properties, AsnResponse.class, (response, ipAddress, network, locales) -> new AsnResponse(response, ipAddress, network)); } @Override - protected AsnResponse cacheableRecord(AsnResponse response) { - return response; + protected CacheableAsnResponse cacheableRecord(AsnResponse response) { + return new CacheableAsnResponse(response); } @Override - protected Map transform(final AsnResponse response) { + protected Map transform(final CacheableAsnResponse response) { Long asn = response.getAutonomousSystemNumber(); String organizationName = response.getAutonomousSystemOrganization(); Network network = response.getNetwork(); @@ -218,7 +246,7 @@ record CacheableCityResponse( Boolean registeredCountryIsInEuropeanUnion, String registeredCountryIsoCode, String registeredCountryName - ) {} + ) implements IpDatabase.Response {} static class City extends AbstractBase> { City(final Set properties) { @@ -354,7 +382,14 @@ protected Map transform(final Result resu } } - static class ConnectionType extends AbstractBase { + static class CacheableConnectionTypeResponse extends ConnectionTypeResponse implements IpDatabase.Response { + + CacheableConnectionTypeResponse(ConnectionTypeResponse response) { + super(response.getConnectionType(), response.getIpAddress(), response.getNetwork()); + } + } + + static class ConnectionType extends AbstractBase { ConnectionType(final Set properties) { super( properties, @@ -364,12 +399,12 @@ static class ConnectionType extends AbstractBase transform(final ConnectionTypeResponse response) { + protected Map transform(final CacheableConnectionTypeResponse response) { ConnectionTypeResponse.ConnectionType connectionType = response.getConnectionType(); Map data = new HashMap<>(); @@ -396,7 +431,7 @@ record CacheableCountryResponse( Boolean registeredCountryIsInEuropeanUnion, String registeredCountryIsoCode, String registeredCountryName - ) {} + ) implements IpDatabase.Response {} static class Country extends AbstractBase> { Country(final Set properties) { @@ -479,7 +514,14 @@ protected Map transform(final Result r } } - static class Domain extends AbstractBase { + static class CacheableDomainResponse extends DomainResponse implements IpDatabase.Response { + + CacheableDomainResponse(DomainResponse response) { + super(response.getDomain(), response.getIpAddress(), response.getNetwork()); + } + } + + static class Domain extends AbstractBase { Domain(final Set properties) { super( properties, @@ -489,12 +531,12 @@ static class Domain extends AbstractBase { } @Override - protected DomainResponse cacheableRecord(DomainResponse response) { - return response; + protected CacheableDomainResponse cacheableRecord(DomainResponse response) { + return new CacheableDomainResponse(response); } @Override - protected Map transform(final DomainResponse response) { + protected Map transform(final CacheableDomainResponse response) { String domain = response.getDomain(); Map data = new HashMap<>(); @@ -547,7 +589,7 @@ record CacheableEnterpriseResponse( Boolean registeredCountryIsInEuropeanUnion, String registeredCountryIsoCode, String registeredCountryName - ) {} + ) implements IpDatabase.Response {} static class Enterprise extends AbstractBase> { Enterprise(final Set properties) { @@ -784,18 +826,25 @@ protected Map transform(final Result { + static class CacheableIspResponse extends IspResponse implements IpDatabase.Response { + + CacheableIspResponse(IspResponse response) { + super(response, response.getIpAddress(), response.getNetwork()); + } + } + + static class Isp extends AbstractBase { Isp(final Set properties) { super(properties, IspResponse.class, (response, ipAddress, network, locales) -> new IspResponse(response, ipAddress, network)); } @Override - protected IspResponse cacheableRecord(IspResponse response) { - return response; + protected CacheableIspResponse cacheableRecord(IspResponse response) { + return new CacheableIspResponse(response); } @Override - protected Map transform(final IspResponse response) { + protected Map transform(final CacheableIspResponse response) { String isp = response.getIsp(); String ispOrganization = response.getOrganization(); String mobileNetworkCode = response.getMobileNetworkCode(); @@ -867,7 +916,9 @@ private interface ResponseBuilder { * * @param the intermediate type of {@link AbstractResponse} */ - private abstract static class AbstractBase implements IpDataLookup { + private abstract static class AbstractBase + implements + IpDataLookup { protected final Set properties; protected final Class clazz; diff --git a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/ConfigDatabasesTests.java b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/ConfigDatabasesTests.java index 7f38a37b43edf..38a910c88cc99 100644 --- a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/ConfigDatabasesTests.java +++ b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/ConfigDatabasesTests.java @@ -9,8 +9,6 @@ package org.elasticsearch.ingest.geoip; -import com.maxmind.geoip2.model.CityResponse; - import org.elasticsearch.common.settings.Settings; import org.elasticsearch.core.TimeValue; import org.elasticsearch.test.ESTestCase; @@ -50,7 +48,7 @@ public void cleanup() { public void testLocalDatabasesEmptyConfig() throws Exception { Path configDir = createTempDir(); - ConfigDatabases configDatabases = new ConfigDatabases(configDir, new GeoIpCache(0)); + ConfigDatabases configDatabases = new ConfigDatabases(configDir, GeoIpCache.createGeoIpCacheWithMaxCount(0)); configDatabases.initialize(resourceWatcherService); assertThat(configDatabases.getConfigDatabases(), anEmptyMap()); @@ -64,7 +62,7 @@ public void testDatabasesConfigDir() throws Exception { copyDatabase("GeoIP2-City-Test.mmdb", configDir.resolve("GeoIP2-City.mmdb")); copyDatabase("GeoLite2-City-Test.mmdb", configDir.resolve("GeoLite2-City.mmdb")); - ConfigDatabases configDatabases = new ConfigDatabases(configDir, new GeoIpCache(0)); + ConfigDatabases configDatabases = new ConfigDatabases(configDir, GeoIpCache.createGeoIpCacheWithMaxCount(0)); configDatabases.initialize(resourceWatcherService); assertThat(configDatabases.getConfigDatabases().size(), equalTo(2)); @@ -77,7 +75,7 @@ public void testDatabasesConfigDir() throws Exception { public void testDatabasesDynamicUpdateConfigDir() throws Exception { Path configDir = prepareConfigDir(); - ConfigDatabases configDatabases = new ConfigDatabases(configDir, new GeoIpCache(0)); + ConfigDatabases configDatabases = new ConfigDatabases(configDir, GeoIpCache.createGeoIpCacheWithMaxCount(0)); configDatabases.initialize(resourceWatcherService); { assertThat(configDatabases.getConfigDatabases().size(), equalTo(3)); @@ -117,7 +115,7 @@ public void testDatabasesUpdateExistingConfDatabase() throws Exception { Path configDir = createTempDir(); copyDatabase("GeoLite2-City.mmdb", configDir); - GeoIpCache cache = new GeoIpCache(1000); // real cache to test purging of entries upon a reload + GeoIpCache cache = GeoIpCache.createGeoIpCacheWithMaxCount(1000); // real cache to test purging of entries upon a reload ConfigDatabases configDatabases = new ConfigDatabases(configDir, cache); configDatabases.initialize(resourceWatcherService); { @@ -126,8 +124,8 @@ public void testDatabasesUpdateExistingConfDatabase() throws Exception { DatabaseReaderLazyLoader loader = configDatabases.getDatabase("GeoLite2-City.mmdb"); assertThat(loader.getDatabaseType(), equalTo("GeoLite2-City")); - CityResponse cityResponse = loader.getResponse("89.160.20.128", GeoIpTestUtils::getCity); - assertThat(cityResponse.getCity().getName(), equalTo("Tumba")); + GeoIpTestUtils.SimpleCity cityResponse = loader.getResponse("89.160.20.128", GeoIpTestUtils::getCity); + assertThat(cityResponse.cityName(), equalTo("Tumba")); assertThat(cache.count(), equalTo(1)); } @@ -138,8 +136,8 @@ public void testDatabasesUpdateExistingConfDatabase() throws Exception { DatabaseReaderLazyLoader loader = configDatabases.getDatabase("GeoLite2-City.mmdb"); assertThat(loader.getDatabaseType(), equalTo("GeoLite2-City")); - CityResponse cityResponse = loader.getResponse("89.160.20.128", GeoIpTestUtils::getCity); - assertThat(cityResponse.getCity().getName(), equalTo("Linköping")); + GeoIpTestUtils.SimpleCity cityResponse = loader.getResponse("89.160.20.128", GeoIpTestUtils::getCity); + assertThat(cityResponse.cityName(), equalTo("Linköping")); assertThat(cache.count(), equalTo(1)); }); diff --git a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/DatabaseNodeServiceTests.java b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/DatabaseNodeServiceTests.java index 031fc91403cfb..adf52466101f4 100644 --- a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/DatabaseNodeServiceTests.java +++ b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/DatabaseNodeServiceTests.java @@ -133,7 +133,7 @@ public void setup() throws IOException { projectId = multiProject ? randomProjectIdOrDefault() : ProjectId.DEFAULT; projectResolver = multiProject ? TestProjectResolvers.singleProject(projectId) : TestProjectResolvers.DEFAULT_PROJECT_ONLY; final Path geoIpConfigDir = createTempDir(); - GeoIpCache cache = new GeoIpCache(1000); + GeoIpCache cache = GeoIpCache.createGeoIpCacheWithMaxCount(1000); ConfigDatabases configDatabases = new ConfigDatabases(geoIpConfigDir, cache); copyDefaultDatabases(geoIpConfigDir, configDatabases); diff --git a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/GeoIpCacheTests.java b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/GeoIpCacheTests.java index 491d603854af1..7b3ff0e2865b5 100644 --- a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/GeoIpCacheTests.java +++ b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/GeoIpCacheTests.java @@ -9,9 +9,8 @@ package org.elasticsearch.ingest.geoip; -import com.maxmind.geoip2.model.AbstractResponse; - import org.elasticsearch.cluster.metadata.ProjectId; +import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.core.PathUtils; import org.elasticsearch.core.TimeValue; import org.elasticsearch.ingest.geoip.stats.CacheStats; @@ -23,18 +22,19 @@ import java.util.function.Function; import static org.hamcrest.Matchers.equalTo; -import static org.mockito.Mockito.mock; public class GeoIpCacheTests extends ESTestCase { - public void testCachesAndEvictsResults() { - GeoIpCache cache = new GeoIpCache(1); + private record FakeResponse(long sizeInBytes) implements IpDatabase.Response {} + + public void testCachesAndEvictsResults_maxCount() { + GeoIpCache cache = GeoIpCache.createGeoIpCacheWithMaxCount(1); ProjectId projectId = randomProjectIdOrDefault(); - AbstractResponse response1 = mock(AbstractResponse.class); - AbstractResponse response2 = mock(AbstractResponse.class); + FakeResponse response1 = new FakeResponse(0); + FakeResponse response2 = new FakeResponse(0); // add a key - AbstractResponse cachedResponse = cache.putIfAbsent(projectId, "127.0.0.1", "path/to/db", ip -> response1); + FakeResponse cachedResponse = cache.putIfAbsent(projectId, "127.0.0.1", "path/to/db", ip -> response1); assertSame(cachedResponse, response1); assertSame(cachedResponse, cache.putIfAbsent(projectId, "127.0.0.1", "path/to/db", ip -> response1)); assertSame(cachedResponse, cache.get(projectId, "127.0.0.1", "path/to/db")); @@ -47,16 +47,42 @@ public void testCachesAndEvictsResults() { assertNotSame(response1, cache.get(projectId, "127.0.0.1", "path/to/db")); } + public void testCachesAndEvictsResults_maxBytes() { + ProjectId projectId = randomProjectIdOrDefault(); + String ip1 = "127.0.0.1"; + String databasePath1 = "path/to/db"; + FakeResponse response1 = new FakeResponse(111); + String ip2 = "127.0.0.2"; + String databasePath2 = "path/to/db"; + FakeResponse response2 = new FakeResponse(222); + long totalSize = GeoIpCache.keySizeInBytes(projectId, ip1, databasePath1) + GeoIpCache.keySizeInBytes(projectId, ip2, databasePath2) + + response1.sizeInBytes() + response2.sizeInBytes(); + + GeoIpCache justBigEnoughCache = GeoIpCache.createGeoIpCacheWithMaxBytes(ByteSizeValue.ofBytes(totalSize)); + justBigEnoughCache.putIfAbsent(projectId, ip1, databasePath1, ip -> response1); + justBigEnoughCache.putIfAbsent(projectId, ip2, databasePath2, ip -> response2); + // Cache is just big enough for both values: + assertSame(response2, justBigEnoughCache.get(projectId, ip2, databasePath2)); + assertSame(response1, justBigEnoughCache.get(projectId, ip1, databasePath1)); + + GeoIpCache justTooSmallCache = GeoIpCache.createGeoIpCacheWithMaxBytes(ByteSizeValue.ofBytes(totalSize - 1L)); + justTooSmallCache.putIfAbsent(projectId, ip1, databasePath1, ip -> response1); + justTooSmallCache.putIfAbsent(projectId, ip2, databasePath2, ip -> response2); + // Cache is just too small for both values, so the older one should have been evicted: + assertSame(response2, justTooSmallCache.get(projectId, ip2, databasePath2)); + assertNull(justTooSmallCache.get(projectId, ip1, databasePath1)); + } + public void testCachesNoResult() { - GeoIpCache cache = new GeoIpCache(1); + GeoIpCache cache = GeoIpCache.createGeoIpCacheWithMaxCount(1); final AtomicInteger count = new AtomicInteger(0); - Function countAndReturnNull = (ip) -> { + Function countAndReturnNull = (ip) -> { count.incrementAndGet(); return null; }; ProjectId projectId = randomProjectIdOrDefault(); - AbstractResponse response = cache.putIfAbsent(projectId, "127.0.0.1", "path/to/db", countAndReturnNull); + FakeResponse response = cache.putIfAbsent(projectId, "127.0.0.1", "path/to/db", countAndReturnNull); assertNull(response); assertNull(cache.putIfAbsent(projectId, "127.0.0.1", "path/to/db", countAndReturnNull)); assertEquals(1, count.get()); @@ -66,9 +92,9 @@ public void testCachesNoResult() { } public void testCacheDoesNotCollideForDifferentDatabases() { - GeoIpCache cache = new GeoIpCache(2); - AbstractResponse response1 = mock(AbstractResponse.class); - AbstractResponse response2 = mock(AbstractResponse.class); + GeoIpCache cache = GeoIpCache.createGeoIpCacheWithMaxCount(2); + FakeResponse response1 = new FakeResponse(111); + FakeResponse response2 = new FakeResponse(222); ProjectId projectId = randomProjectIdOrDefault(); assertSame(response1, cache.putIfAbsent(projectId, "127.0.0.1", "path/to/db1", ip -> response1)); @@ -78,9 +104,9 @@ public void testCacheDoesNotCollideForDifferentDatabases() { } public void testCacheDoesNotCollideForDifferentProjects() { - GeoIpCache cache = new GeoIpCache(2); - AbstractResponse response1 = mock(AbstractResponse.class); - AbstractResponse response2 = mock(AbstractResponse.class); + GeoIpCache cache = GeoIpCache.createGeoIpCacheWithMaxCount(2); + FakeResponse response1 = new FakeResponse(111); + FakeResponse response2 = new FakeResponse(222); ProjectId projectId1 = randomUniqueProjectId(); ProjectId projectId2 = randomUniqueProjectId(); @@ -91,7 +117,7 @@ public void testCacheDoesNotCollideForDifferentProjects() { } public void testThrowsFunctionsException() { - GeoIpCache cache = new GeoIpCache(1); + GeoIpCache cache = GeoIpCache.createGeoIpCacheWithMaxCount(1); ProjectId projectId = randomProjectIdOrDefault(); IllegalArgumentException ex = expectThrows( IllegalArgumentException.class, @@ -102,18 +128,29 @@ public void testThrowsFunctionsException() { assertEquals("bad", ex.getMessage()); } - public void testInvalidInit() { - IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> new GeoIpCache(-1)); + public void testInvalidInit_maxCount() { + IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> GeoIpCache.createGeoIpCacheWithMaxCount(-1)); assertEquals("geoip max cache size must be 0 or greater", ex.getMessage()); } + public void testInvalidInit_maxBytes() { + IllegalArgumentException ex = expectThrows( + IllegalArgumentException.class, + () -> GeoIpCache.createGeoIpCacheWithMaxBytes(ByteSizeValue.MINUS_ONE) + ); + assertEquals("geoip max cache size in bytes must be 0 or greater", ex.getMessage()); + } + public void testGetCacheStats() { final long maxCacheSize = 2; final AtomicLong testNanoTime = new AtomicLong(0); // We use a relative time provider that increments 1ms every time it is called. So each operation appears to take 1ms - GeoIpCache cache = new GeoIpCache(maxCacheSize, () -> testNanoTime.addAndGet(TimeValue.timeValueMillis(1).getNanos())); + GeoIpCache cache = GeoIpCache.createGeoIpCacheWithMaxCountAndCustomTimeProvider( + maxCacheSize, + () -> testNanoTime.addAndGet(TimeValue.timeValueMillis(1).getNanos()) + ); ProjectId projectId = randomProjectIdOrDefault(); - AbstractResponse response = mock(AbstractResponse.class); + FakeResponse response = new FakeResponse(0); String databasePath = "path/to/db1"; String key1 = "127.0.0.1"; String key2 = "127.0.0.2"; @@ -138,7 +175,7 @@ public void testGetCacheStats() { } public void testPurgeCacheEntriesForDatabase() { - GeoIpCache cache = new GeoIpCache(100); + GeoIpCache cache = GeoIpCache.createGeoIpCacheWithMaxCount(100); ProjectId projectId1 = randomUniqueProjectId(); ProjectId projectId2 = randomUniqueProjectId(); // Turn the path strings into Paths to ensure that we always use the canonical string representation (this string literal does not @@ -148,7 +185,7 @@ public void testPurgeCacheEntriesForDatabase() { String ip1 = "127.0.0.1"; String ip2 = "127.0.0.2"; - AbstractResponse response = mock(AbstractResponse.class); + FakeResponse response = new FakeResponse(111); cache.putIfAbsent(projectId1, ip1, databasePath1.toString(), ip -> response); // cache miss cache.putIfAbsent(projectId1, ip2, databasePath1.toString(), ip -> response); // cache miss cache.putIfAbsent(projectId2, ip1, databasePath1.toString(), ip -> response); // cache miss diff --git a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/GeoIpProcessorFactoryTests.java b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/GeoIpProcessorFactoryTests.java index ff3ea6d6bfd98..6da02a2a4afde 100644 --- a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/GeoIpProcessorFactoryTests.java +++ b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/GeoIpProcessorFactoryTests.java @@ -84,8 +84,8 @@ public void loadDatabaseReaders() throws IOException { Files.createDirectories(geoIpConfigDir); Client client = mock(Client.class); - GeoIpCache cache = new GeoIpCache(1000); - configDatabases = new ConfigDatabases(geoIpConfigDir, new GeoIpCache(1000)); + GeoIpCache cache = GeoIpCache.createGeoIpCacheWithMaxCount(1000); + configDatabases = new ConfigDatabases(geoIpConfigDir, GeoIpCache.createGeoIpCacheWithMaxCount(1000)); copyDefaultDatabases(geoIpConfigDir, configDatabases); geoipTmpDir = createTempDir(); clusterService = mock(ClusterService.class); @@ -391,7 +391,7 @@ public void testLazyLoading() throws Exception { final Path configDir = createTempDir(); final Path geoIpConfigDir = configDir.resolve("ingest-geoip"); Files.createDirectories(geoIpConfigDir); - GeoIpCache cache = new GeoIpCache(1000); + GeoIpCache cache = GeoIpCache.createGeoIpCacheWithMaxCount(1000); ConfigDatabases configDatabases = new ConfigDatabases(geoIpConfigDir, cache); copyDefaultDatabases(geoIpConfigDir, configDatabases); @@ -460,7 +460,7 @@ public void testLoadingCustomDatabase() throws IOException { final Path configDir = createTempDir(); final Path geoIpConfigDir = configDir.resolve("ingest-geoip"); Files.createDirectories(geoIpConfigDir); - ConfigDatabases configDatabases = new ConfigDatabases(geoIpConfigDir, new GeoIpCache(1000)); + ConfigDatabases configDatabases = new ConfigDatabases(geoIpConfigDir, GeoIpCache.createGeoIpCacheWithMaxCount(1000)); copyDefaultDatabases(geoIpConfigDir, configDatabases); // fake the GeoIP2-City database copyDatabase("GeoLite2-City.mmdb", geoIpConfigDir); @@ -473,7 +473,7 @@ public void testLoadingCustomDatabase() throws IOException { ThreadPool threadPool = new TestThreadPool("test"); ResourceWatcherService resourceWatcherService = new ResourceWatcherService(Settings.EMPTY, threadPool); Client client = mock(Client.class); - GeoIpCache cache = new GeoIpCache(1000); + GeoIpCache cache = GeoIpCache.createGeoIpCacheWithMaxCount(1000); DatabaseNodeService databaseNodeService = new DatabaseNodeService( createTempDir(), client, diff --git a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/GeoIpProcessorTests.java b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/GeoIpProcessorTests.java index 8e34bff465395..1ce092ff060af 100644 --- a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/GeoIpProcessorTests.java +++ b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/GeoIpProcessorTests.java @@ -492,7 +492,7 @@ private DatabaseReaderLazyLoader loader(final String databaseName, final AtomicB final Path path = tmpDir.resolve(last == -1 ? databaseName : databaseName.substring(last + 1)); copyDatabase(databaseName, path); - final GeoIpCache cache = new GeoIpCache(1000); + final GeoIpCache cache = GeoIpCache.createGeoIpCacheWithMaxCount(1000); return new DatabaseReaderLazyLoader(ProjectId.DEFAULT, cache, path, null) { @Override protected void doShutdown() throws IOException { diff --git a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/GeoIpTestUtils.java b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/GeoIpTestUtils.java index 160671fd39001..288703972ab1d 100644 --- a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/GeoIpTestUtils.java +++ b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/GeoIpTestUtils.java @@ -42,7 +42,7 @@ private static boolean isDirectory(final Path path) { return path.toFile().isDirectory(); } - public static void copyDatabase(final String databaseName, final Path destination) { + static void copyDatabase(final String databaseName, final Path destination) { try (InputStream is = GeoIpTestUtils.class.getResourceAsStream("/" + databaseName)) { if (is == null) { throw new FileNotFoundException("Resource [" + databaseName + "] not found in classpath"); @@ -54,40 +54,46 @@ public static void copyDatabase(final String databaseName, final Path destinatio } } - public static void copyDefaultDatabases(final Path directory) { + static void copyDefaultDatabases(final Path directory) { for (final String database : DEFAULT_DATABASES) { copyDatabase(database, directory); } } - public static void copyDefaultDatabases(final Path directory, ConfigDatabases configDatabases) { + static void copyDefaultDatabases(final Path directory, ConfigDatabases configDatabases) { for (final String database : DEFAULT_DATABASES) { copyDatabase(database, directory); configDatabases.updateDatabase(directory.resolve(database), true); } } + public record SimpleCity(String cityName) implements IpDatabase.Response {} + /** * A static city-specific responseProvider for use with {@link IpDatabase#getResponse(String, CheckedBiFunction)} in * tests. *

- * Like this: {@code CityResponse city = loader.getResponse("some.ip.address", GeoIpTestUtils::getCity);} + * Like this: {@code SimpleCity city = loader.getResponse("some.ip.address", GeoIpTestUtils::getCity);} */ - public static CityResponse getCity(Reader reader, String ip) throws IOException { + public static SimpleCity getCity(Reader reader, String ip) throws IOException { DatabaseRecord record = reader.getRecord(InetAddresses.forString(ip), CityResponse.class); CityResponse data = record.getData(); - return data == null ? null : new CityResponse(data, ip, record.getNetwork(), List.of("en")); + return data == null ? null : new SimpleCity(new CityResponse(data, ip, record.getNetwork(), List.of("en")).getCity().getName()); } + public record SimpleCountry(String countryName) implements IpDatabase.Response {} + /** * A static country-specific responseProvider for use with {@link IpDatabase#getResponse(String, CheckedBiFunction)} in * tests. *

- * Like this: {@code CountryResponse country = loader.getResponse("some.ip.address", GeoIpTestUtils::getCountry);} + * Like this: {@code SimpleCountry country = loader.getResponse("some.ip.address", GeoIpTestUtils::getCountry);} */ - public static CountryResponse getCountry(Reader reader, String ip) throws IOException { + public static SimpleCountry getCountry(Reader reader, String ip) throws IOException { DatabaseRecord record = reader.getRecord(InetAddresses.forString(ip), CountryResponse.class); CountryResponse data = record.getData(); - return data == null ? null : new CountryResponse(data, ip, record.getNetwork(), List.of("en")); + return data == null + ? null + : new SimpleCountry(new CountryResponse(data, ip, record.getNetwork(), List.of("en")).getCountry().getName()); } } diff --git a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/IpinfoIpDataLookupsTests.java b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/IpinfoIpDataLookupsTests.java index ffcaa39604762..9d8f9a7706590 100644 --- a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/IpinfoIpDataLookupsTests.java +++ b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/IpinfoIpDataLookupsTests.java @@ -561,7 +561,7 @@ private void assertActualResultsMatchReader( private DatabaseReaderLazyLoader loader(final String databaseName) { Path path = tmpDir.resolve(databaseName); copyDatabase("ipinfo/" + databaseName, path); // the ipinfo databases are prefixed on the test classpath - final GeoIpCache cache = new GeoIpCache(1000); + final GeoIpCache cache = GeoIpCache.createGeoIpCacheWithMaxCount(1000); return new DatabaseReaderLazyLoader(ProjectId.DEFAULT, cache, path, null); } } diff --git a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/MaxmindIpDataLookupsTests.java b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/MaxmindIpDataLookupsTests.java index 7da621c1dacc7..b4f7111098f3c 100644 --- a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/MaxmindIpDataLookupsTests.java +++ b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/MaxmindIpDataLookupsTests.java @@ -336,7 +336,7 @@ private void assertExpectedLookupResults(String databaseName, String ip, IpDataL private DatabaseReaderLazyLoader loader(final String databaseName) { Path path = tmpDir.resolve(databaseName); copyDatabase(databaseName, path); - final GeoIpCache cache = new GeoIpCache(1000); + final GeoIpCache cache = GeoIpCache.createGeoIpCacheWithMaxCount(1000); return new DatabaseReaderLazyLoader(ProjectId.DEFAULT, cache, path, null); } } diff --git a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/ReloadingDatabasesWhilePerformingGeoLookupsTests.java b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/ReloadingDatabasesWhilePerformingGeoLookupsTests.java index 7f298038141df..c34924e545e90 100644 --- a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/ReloadingDatabasesWhilePerformingGeoLookupsTests.java +++ b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/ReloadingDatabasesWhilePerformingGeoLookupsTests.java @@ -222,7 +222,7 @@ private static DatabaseNodeService createRegistry( ClusterService clusterService, ProjectResolver projectResolver ) throws IOException { - GeoIpCache cache = new GeoIpCache(0); + GeoIpCache cache = GeoIpCache.createGeoIpCacheWithMaxCount(0); ConfigDatabases configDatabases = new ConfigDatabases(geoIpConfigDir, cache); copyDefaultDatabases(geoIpConfigDir, configDatabases); DatabaseNodeService databaseNodeService = new DatabaseNodeService( @@ -242,10 +242,12 @@ private static DatabaseNodeService createRegistry( private static void lazyLoadReaders(ProjectId projectId, DatabaseNodeService databaseNodeService) throws IOException { if (databaseNodeService.get(projectId, "GeoLite2-City.mmdb") != null) { databaseNodeService.get(projectId, "GeoLite2-City.mmdb").getDatabaseType(); - databaseNodeService.get(projectId, "GeoLite2-City.mmdb").getResponse("2.125.160.216", GeoIpTestUtils::getCity); + DatabaseReaderLazyLoader databaseReaderLazyLoader = databaseNodeService.get(projectId, "GeoLite2-City.mmdb"); + databaseReaderLazyLoader.getResponse("2.125.160.216", GeoIpTestUtils::getCity); } databaseNodeService.get(projectId, "GeoLite2-City-Test.mmdb").getDatabaseType(); - databaseNodeService.get(projectId, "GeoLite2-City-Test.mmdb").getResponse("2.125.160.216", GeoIpTestUtils::getCity); + DatabaseReaderLazyLoader databaseReaderLazyLoader = databaseNodeService.get(projectId, "GeoLite2-City-Test.mmdb"); + databaseReaderLazyLoader.getResponse("2.125.160.216", GeoIpTestUtils::getCity); } }