Skip to content

Commit cc62555

Browse files
authored
Run GeoIp YAML tests in multi-project cluster and fix bug discovered by tests (#131521)
1 parent 49cf915 commit cc62555

File tree

8 files changed

+210
-117
lines changed

8 files changed

+210
-117
lines changed

modules/ingest-geoip/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import org.elasticsearch.gradle.OS
1212
apply plugin: 'elasticsearch.internal-yaml-rest-test'
1313
apply plugin: 'elasticsearch.yaml-rest-compat-test'
1414
apply plugin: 'elasticsearch.internal-cluster-test'
15+
apply plugin: 'elasticsearch.internal-test-artifact'
1516

1617
esplugin {
1718
description = 'Ingest processor that uses lookup geo data based on IP addresses using the MaxMind geo database'

modules/ingest-geoip/qa/multi-project/build.gradle

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,25 @@
88
*/
99

1010
apply plugin: 'elasticsearch.internal-java-rest-test'
11+
apply plugin: 'elasticsearch.internal-yaml-rest-test'
1112

1213
dependencies {
1314
javaRestTestImplementation project(':modules:ingest-geoip')
1415
javaRestTestImplementation project(':test:external-modules:test-multi-project')
1516
javaRestTestImplementation project(':test:fixtures:geoip-fixture')
1617

18+
yamlRestTestImplementation(testArtifact(project(":modules:ingest-geoip"), "yamlRestTest")) // includes yaml test code from ingest-geoip
19+
yamlRestTestImplementation project(':modules:ingest-geoip')
20+
yamlRestTestImplementation project(':test:external-modules:test-multi-project')
21+
yamlRestTestImplementation project(':test:fixtures:geoip-fixture')
22+
yamlRestTestImplementation project(':x-pack:qa:multi-project:yaml-test-framework')
23+
1724
clusterModules project(':modules:ingest-geoip')
1825
clusterModules project(':modules:reindex') // needed for database cleanup
1926
clusterModules project(':test:external-modules:test-multi-project')
27+
28+
// includes yaml rest test artifacts from ingest-geoip module
29+
restTestConfig project(path: ':modules:ingest-geoip', configuration: "restTests")
2030
}
2131

2232
tasks.withType(Test).configureEach {
@@ -27,3 +37,10 @@ tasks.withType(Test).configureEach {
2737
tasks.named { it == "javaRestTest" || it == "yamlRestTest" }.configureEach {
2838
it.onlyIf("snapshot build") { buildParams.snapshotBuild }
2939
}
40+
41+
restResources {
42+
restTests {
43+
// includes yaml rest test from ingest_geoip folder
44+
includeCore 'ingest_geoip'
45+
}
46+
}

modules/ingest-geoip/qa/multi-project/src/javaRestTest/java/geoip/GeoIpMultiProjectIT.java renamed to modules/ingest-geoip/qa/multi-project/src/javaRestTest/java/org/elasticsearch/ingest/geoip/GeoIpMultiProjectIT.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,13 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10-
package geoip;
10+
package org.elasticsearch.ingest.geoip;
1111

1212
import fixture.geoip.GeoIpHttpFixture;
1313

1414
import org.elasticsearch.client.Request;
1515
import org.elasticsearch.client.RequestOptions;
1616
import org.elasticsearch.core.Booleans;
17-
import org.elasticsearch.ingest.geoip.GeoIpDownloader;
18-
import org.elasticsearch.ingest.geoip.GeoIpDownloaderTaskExecutor;
1917
import org.elasticsearch.tasks.Task;
2018
import org.elasticsearch.test.cluster.ElasticsearchCluster;
2119
import org.elasticsearch.test.rest.ESRestTestCase;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.ingest.geoip;
11+
12+
import fixture.geoip.GeoIpHttpFixture;
13+
14+
import com.carrotsearch.randomizedtesting.annotations.Name;
15+
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
16+
17+
import org.elasticsearch.core.Booleans;
18+
import org.elasticsearch.core.FixForMultiProject;
19+
import org.elasticsearch.multiproject.test.MultipleProjectsClientYamlSuiteTestCase;
20+
import org.elasticsearch.test.cluster.ElasticsearchCluster;
21+
import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate;
22+
import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase;
23+
import org.junit.Before;
24+
import org.junit.ClassRule;
25+
import org.junit.rules.RuleChain;
26+
import org.junit.rules.TestRule;
27+
28+
import static org.elasticsearch.ingest.geoip.IngestGeoIpClientYamlTestSuiteIT.assertDatabasesLoaded;
29+
import static org.elasticsearch.ingest.geoip.IngestGeoIpClientYamlTestSuiteIT.putGeoipPipeline;
30+
31+
@FixForMultiProject(description = "Potentially remove this test after https://elasticco.atlassian.net/browse/ES-12094 is implemented")
32+
public class IngestGeoIpClientMultiProjectYamlTestSuiteIT extends MultipleProjectsClientYamlSuiteTestCase {
33+
34+
private static final boolean useFixture = Booleans.parseBoolean(System.getProperty("geoip_use_service", "false")) == false;
35+
36+
private static GeoIpHttpFixture fixture = new GeoIpHttpFixture(useFixture);
37+
38+
private static ElasticsearchCluster cluster = ElasticsearchCluster.local()
39+
.module("reindex")
40+
.module("ingest-geoip")
41+
.systemProperty("ingest.geoip.downloader.enabled.default", "true")
42+
// sets the plain (geoip.elastic.co) downloader endpoint, which is used in these tests
43+
.setting("ingest.geoip.downloader.endpoint", () -> fixture.getAddress(), s -> useFixture)
44+
// also sets the enterprise downloader maxmind endpoint, to make sure we do not accidentally hit the real endpoint from tests
45+
// note: it's not important that the downloading actually work at this point -- the rest tests (so far) don't exercise
46+
// the downloading code because of license reasons -- but if they did, then it would be important that we're hitting a fixture
47+
.systemProperty("ingest.geoip.downloader.maxmind.endpoint.default", () -> fixture.getAddress(), s -> useFixture)
48+
.setting("test.multi_project.enabled", "true")
49+
.setting("xpack.license.self_generated.type", "trial")
50+
.user(USER, PASS)
51+
.build();
52+
53+
@ClassRule
54+
public static TestRule ruleChain = RuleChain.outerRule(fixture).around(cluster);
55+
56+
@Override
57+
protected String getTestRestCluster() {
58+
return cluster.getHttpAddresses();
59+
}
60+
61+
public IngestGeoIpClientMultiProjectYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) {
62+
super(testCandidate);
63+
}
64+
65+
@ParametersFactory
66+
public static Iterable<Object[]> parameters() throws Exception {
67+
return ESClientYamlSuiteTestCase.createParameters();
68+
}
69+
70+
@Before
71+
public void waitForDatabases() throws Exception {
72+
putGeoipPipeline("pipeline-with-geoip");
73+
assertDatabasesLoaded();
74+
}
75+
}

modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/DatabaseNodeService.java

Lines changed: 85 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.elasticsearch.client.internal.Client;
1717
import org.elasticsearch.client.internal.OriginSettingClient;
1818
import org.elasticsearch.cluster.ClusterState;
19+
import org.elasticsearch.cluster.ProjectState;
1920
import org.elasticsearch.cluster.metadata.IndexAbstraction;
2021
import org.elasticsearch.cluster.metadata.ProjectId;
2122
import org.elasticsearch.cluster.metadata.ProjectMetadata;
@@ -288,104 +289,102 @@ void checkDatabases(ClusterState state) {
288289
}
289290

290291
// Optimization: only load the .geoip_databases for projects that are allocated to this node
291-
for (ProjectMetadata projectMetadata : state.metadata().projects().values()) {
292-
ProjectId projectId = projectMetadata.id();
292+
state.forEachProject(this::checkDatabases);
293+
}
293294

294-
PersistentTasksCustomMetadata persistentTasks = projectMetadata.custom(PersistentTasksCustomMetadata.TYPE);
295-
if (persistentTasks == null) {
296-
logger.trace("Not checking databases for project [{}] because persistent tasks are null", projectId);
297-
continue;
298-
}
295+
void checkDatabases(ProjectState projectState) {
296+
ProjectId projectId = projectState.projectId();
297+
ProjectMetadata projectMetadata = projectState.metadata();
298+
PersistentTasksCustomMetadata persistentTasks = projectMetadata.custom(PersistentTasksCustomMetadata.TYPE);
299+
if (persistentTasks == null) {
300+
logger.trace("Not checking databases for project [{}] because persistent tasks are null", projectId);
301+
return;
302+
}
299303

300-
IndexAbstraction databasesAbstraction = projectMetadata.getIndicesLookup().get(GeoIpDownloader.DATABASES_INDEX);
301-
if (databasesAbstraction == null) {
302-
logger.trace("Not checking databases because geoip databases index does not exist for project [{}]", projectId);
304+
IndexAbstraction databasesAbstraction = projectMetadata.getIndicesLookup().get(GeoIpDownloader.DATABASES_INDEX);
305+
if (databasesAbstraction == null) {
306+
logger.trace("Not checking databases because geoip databases index does not exist for project [{}]", projectId);
307+
return;
308+
} else {
309+
// regardless of whether DATABASES_INDEX is an alias, resolve it to a concrete index
310+
Index databasesIndex = databasesAbstraction.getWriteIndex();
311+
IndexRoutingTable databasesIndexRT = projectState.routingTable().index(databasesIndex);
312+
if (databasesIndexRT == null || databasesIndexRT.allPrimaryShardsActive() == false) {
313+
logger.trace(
314+
"Not checking databases because geoip databases index does not have all active primary shards for project [{}]",
315+
projectId
316+
);
303317
return;
304-
} else {
305-
// regardless of whether DATABASES_INDEX is an alias, resolve it to a concrete index
306-
Index databasesIndex = databasesAbstraction.getWriteIndex();
307-
IndexRoutingTable databasesIndexRT = state.routingTable(projectId).index(databasesIndex);
308-
if (databasesIndexRT == null || databasesIndexRT.allPrimaryShardsActive() == false) {
309-
logger.trace(
310-
"Not checking databases because geoip databases index does not have all active primary shards for"
311-
+ " project [{}]",
312-
projectId
313-
);
314-
return;
315-
}
316318
}
319+
}
317320

318-
// we'll consult each of the geoip downloaders to build up a list of database metadatas to work with
319-
List<Tuple<String, GeoIpTaskState.Metadata>> validMetadatas = new ArrayList<>();
321+
// we'll consult each of the geoip downloaders to build up a list of database metadatas to work with
322+
List<Tuple<String, GeoIpTaskState.Metadata>> validMetadatas = new ArrayList<>();
320323

321-
// process the geoip task state for the (ordinary) geoip downloader
322-
{
323-
GeoIpTaskState taskState = getGeoIpTaskState(
324-
projectMetadata,
325-
getTaskId(projectId, projectResolver.supportsMultipleProjects())
326-
);
327-
if (taskState == null) {
328-
// Note: an empty state will purge stale entries in databases map
329-
taskState = GeoIpTaskState.EMPTY;
330-
}
331-
validMetadatas.addAll(
332-
taskState.getDatabases()
333-
.entrySet()
334-
.stream()
335-
.filter(e -> e.getValue().isNewEnough(state.getMetadata().settings()))
336-
.map(entry -> Tuple.tuple(entry.getKey(), entry.getValue()))
337-
.toList()
338-
);
324+
// process the geoip task state for the (ordinary) geoip downloader
325+
{
326+
GeoIpTaskState taskState = getGeoIpTaskState(projectMetadata, getTaskId(projectId, projectResolver.supportsMultipleProjects()));
327+
if (taskState == null) {
328+
// Note: an empty state will purge stale entries in databases map
329+
taskState = GeoIpTaskState.EMPTY;
339330
}
331+
validMetadatas.addAll(
332+
taskState.getDatabases()
333+
.entrySet()
334+
.stream()
335+
.filter(e -> e.getValue().isNewEnough(projectState.cluster().metadata().settings()))
336+
.map(entry -> Tuple.tuple(entry.getKey(), entry.getValue()))
337+
.toList()
338+
);
339+
}
340340

341-
// process the geoip task state for the enterprise geoip downloader
342-
{
343-
EnterpriseGeoIpTaskState taskState = getEnterpriseGeoIpTaskState(state);
344-
if (taskState == null) {
345-
// Note: an empty state will purge stale entries in databases map
346-
taskState = EnterpriseGeoIpTaskState.EMPTY;
347-
}
348-
validMetadatas.addAll(
349-
taskState.getDatabases()
350-
.entrySet()
351-
.stream()
352-
.filter(e -> e.getValue().isNewEnough(state.getMetadata().settings()))
353-
.map(entry -> Tuple.tuple(entry.getKey(), entry.getValue()))
354-
.toList()
355-
);
341+
// process the geoip task state for the enterprise geoip downloader
342+
{
343+
EnterpriseGeoIpTaskState taskState = getEnterpriseGeoIpTaskState(projectState.metadata());
344+
if (taskState == null) {
345+
// Note: an empty state will purge stale entries in databases map
346+
taskState = EnterpriseGeoIpTaskState.EMPTY;
356347
}
348+
validMetadatas.addAll(
349+
taskState.getDatabases()
350+
.entrySet()
351+
.stream()
352+
.filter(e -> e.getValue().isNewEnough(projectState.cluster().metadata().settings()))
353+
.map(entry -> Tuple.tuple(entry.getKey(), entry.getValue()))
354+
.toList()
355+
);
356+
}
357357

358-
// run through all the valid metadatas, regardless of source, and retrieve them if the persistent downloader task
359-
// has downloaded a new version of the databases
360-
validMetadatas.forEach(e -> {
361-
String name = e.v1();
362-
GeoIpTaskState.Metadata metadata = e.v2();
363-
DatabaseReaderLazyLoader reference = getProjectLazyLoader(projectId, name);
364-
String remoteMd5 = metadata.md5();
365-
String localMd5 = reference != null ? reference.getMd5() : null;
366-
if (Objects.equals(localMd5, remoteMd5)) {
367-
logger.debug("[{}] is up to date [{}] with cluster state [{}]", name, localMd5, remoteMd5);
368-
return;
369-
}
358+
// run through all the valid metadatas, regardless of source, and retrieve them if the persistent downloader task
359+
// has downloaded a new version of the databases
360+
validMetadatas.forEach(e -> {
361+
String name = e.v1();
362+
GeoIpTaskState.Metadata metadata = e.v2();
363+
DatabaseReaderLazyLoader reference = getProjectLazyLoader(projectId, name);
364+
String remoteMd5 = metadata.md5();
365+
String localMd5 = reference != null ? reference.getMd5() : null;
366+
if (Objects.equals(localMd5, remoteMd5)) {
367+
logger.debug("[{}] is up to date [{}] with cluster state [{}]", name, localMd5, remoteMd5);
368+
return;
369+
}
370370

371-
try {
372-
retrieveAndUpdateDatabase(projectId, name, metadata);
373-
} catch (Exception ex) {
374-
logger.error(() -> "failed to retrieve database [" + name + "]", ex);
375-
}
376-
});
377-
378-
// TODO perhaps we need to handle the license flap persistent task state better than we do
379-
// i think the ideal end state is that we *do not* drop the files that the enterprise downloader
380-
// handled if they fall out -- which means we need to track that in the databases map itself
381-
382-
// start with the list of all databases we currently know about in this service,
383-
// then drop the ones that didn't check out as valid from the task states
384-
if (databases.containsKey(projectId)) {
385-
Set<String> staleDatabases = new HashSet<>(databases.get(projectId).keySet());
386-
staleDatabases.removeAll(validMetadatas.stream().map(Tuple::v1).collect(Collectors.toSet()));
387-
removeStaleEntries(projectId, staleDatabases);
371+
try {
372+
retrieveAndUpdateDatabase(projectId, name, metadata);
373+
} catch (Exception ex) {
374+
logger.error(() -> "failed to retrieve database [" + name + "]", ex);
388375
}
376+
});
377+
378+
// TODO perhaps we need to handle the license flap persistent task state better than we do
379+
// i think the ideal end state is that we *do not* drop the files that the enterprise downloader
380+
// handled if they fall out -- which means we need to track that in the databases map itself
381+
382+
// start with the list of all databases we currently know about in this service,
383+
// then drop the ones that didn't check out as valid from the task states
384+
if (databases.containsKey(projectId)) {
385+
Set<String> staleDatabases = new HashSet<>(databases.get(projectId).keySet());
386+
staleDatabases.removeAll(validMetadatas.stream().map(Tuple::v1).collect(Collectors.toSet()));
387+
removeStaleEntries(projectId, staleDatabases);
389388
}
390389
}
391390

modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/EnterpriseGeoIpTaskState.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,10 @@
1111

1212
import org.elasticsearch.TransportVersion;
1313
import org.elasticsearch.TransportVersions;
14-
import org.elasticsearch.cluster.ClusterState;
15-
import org.elasticsearch.cluster.metadata.ProjectId;
14+
import org.elasticsearch.cluster.metadata.ProjectMetadata;
1615
import org.elasticsearch.common.io.stream.StreamInput;
1716
import org.elasticsearch.common.io.stream.StreamOutput;
1817
import org.elasticsearch.common.io.stream.VersionedNamedWriteable;
19-
import org.elasticsearch.core.FixForMultiProject;
2018
import org.elasticsearch.core.Nullable;
2119
import org.elasticsearch.core.Tuple;
2220
import org.elasticsearch.ingest.EnterpriseGeoIpTask;
@@ -144,14 +142,13 @@ public void writeTo(StreamOutput out) throws IOException {
144142
* Retrieves the geoip downloader's task state from the cluster state. This may return null in some circumstances,
145143
* for example if the geoip downloader task hasn't been created yet (which it wouldn't be if it's disabled).
146144
*
147-
* @param state the cluster state to read the task state from
145+
* @param projectMetadata the project metatdata to read the task state from
148146
* @return the geoip downloader's task state or null if there is not a state to read
149147
*/
150148
@Nullable
151-
@FixForMultiProject(description = "Replace ProjectId.DEFAULT")
152-
static EnterpriseGeoIpTaskState getEnterpriseGeoIpTaskState(ClusterState state) {
149+
static EnterpriseGeoIpTaskState getEnterpriseGeoIpTaskState(ProjectMetadata projectMetadata) {
153150
PersistentTasksCustomMetadata.PersistentTask<?> task = getTaskWithId(
154-
state.metadata().getProject(ProjectId.DEFAULT),
151+
projectMetadata,
155152
EnterpriseGeoIpTask.ENTERPRISE_GEOIP_DOWNLOADER
156153
);
157154
return (task == null) ? null : (EnterpriseGeoIpTaskState) task.getState();

modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/GeoIpTaskState.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.elasticsearch.common.io.stream.StreamOutput;
1717
import org.elasticsearch.common.io.stream.VersionedNamedWriteable;
1818
import org.elasticsearch.common.settings.Settings;
19+
import org.elasticsearch.core.FixForMultiProject;
1920
import org.elasticsearch.core.Nullable;
2021
import org.elasticsearch.core.TimeValue;
2122
import org.elasticsearch.core.Tuple;
@@ -214,6 +215,7 @@ public boolean isCloseToExpiration() {
214215
private static final TimeValue THIRTY_DAYS = TimeValue.timeValueDays(30);
215216
private static final long THIRTY_DAYS_MILLIS = THIRTY_DAYS.millis();
216217

218+
@FixForMultiProject(description = "Replace caller from cluster settings to project settings")
217219
public boolean isNewEnough(Settings settings) {
218220
// micro optimization: this looks a little silly, but the expected case is that database_validity is only used in tests.
219221
// we run this code on every document, though, so the argument checking and other bits that getAsTime does is enough

0 commit comments

Comments
 (0)