Implement GET API for System Feature Upgrades (#78642) (#78860)

* Implement GET API for System Feature Upgrades (#78642)

* Implement and test get feature upgrade status API
* Add integration test for feature upgrade endpoint
* Use constant enum for statuses
* Add unit tests for transport class methods
* Fix bwc tests for 7.x
This commit is contained in:
William Brafford 2021-10-12 15:44:45 -04:00 committed by GitHub
parent 3b45d0ab53
commit 1b5827da3e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 500 additions and 64 deletions

View file

@ -9,6 +9,7 @@
package org.elasticsearch.client; package org.elasticsearch.client;
import org.elasticsearch.jdk.JavaVersion; import org.elasticsearch.jdk.JavaVersion;
import org.elasticsearch.Version;
import org.elasticsearch.client.migration.DeprecationInfoRequest; import org.elasticsearch.client.migration.DeprecationInfoRequest;
import org.elasticsearch.client.migration.DeprecationInfoResponse; import org.elasticsearch.client.migration.DeprecationInfoResponse;
import org.elasticsearch.client.migration.GetFeatureUpgradeStatusRequest; import org.elasticsearch.client.migration.GetFeatureUpgradeStatusRequest;
@ -20,11 +21,14 @@ import org.elasticsearch.common.settings.Settings;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.hamcrest.Matchers.anEmptyMap; import static org.hamcrest.Matchers.anEmptyMap;
import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.nullValue;
public class MigrationIT extends ESRestHighLevelClientTestCase { public class MigrationIT extends ESRestHighLevelClientTestCase {
@ -51,19 +55,24 @@ public class MigrationIT extends ESRestHighLevelClientTestCase {
public void testGetFeatureUpgradeStatus() throws IOException { public void testGetFeatureUpgradeStatus() throws IOException {
GetFeatureUpgradeStatusRequest request = new GetFeatureUpgradeStatusRequest(); GetFeatureUpgradeStatusRequest request = new GetFeatureUpgradeStatusRequest();
GetFeatureUpgradeStatusResponse response = highLevelClient().migration().getFeatureUpgradeStatus(request, RequestOptions.DEFAULT); GetFeatureUpgradeStatusResponse response = highLevelClient().migration().getFeatureUpgradeStatus(request, RequestOptions.DEFAULT);
assertThat(response.getUpgradeStatus(), equalTo("UPGRADE_NEEDED")); assertThat(response.getUpgradeStatus(), equalTo("NO_UPGRADE_NEEDED"));
assertThat(response.getFeatureUpgradeStatuses().size(), equalTo(1)); assertThat(response.getFeatureUpgradeStatuses().size(), greaterThanOrEqualTo(1));
GetFeatureUpgradeStatusResponse.FeatureUpgradeStatus status = response.getFeatureUpgradeStatuses().get(0); Optional<GetFeatureUpgradeStatusResponse.FeatureUpgradeStatus> optionalTasksStatus = response.getFeatureUpgradeStatuses().stream()
assertThat(status.getUpgradeStatus(), equalTo("UPGRADE_NEEDED")); .filter(status -> "tasks".equals(status.getFeatureName()))
assertThat(status.getMinimumIndexVersion(), equalTo("7.1.1")); .findFirst();
assertThat(status.getFeatureName(), equalTo("security"));
assertThat(status.getIndexVersions().size(), equalTo(1)); assertThat(optionalTasksStatus.isPresent(), is(true));
GetFeatureUpgradeStatusResponse.FeatureUpgradeStatus tasksStatus = optionalTasksStatus.get();
assertThat(tasksStatus.getUpgradeStatus(), equalTo("NO_UPGRADE_NEEDED"));
assertThat(tasksStatus.getMinimumIndexVersion(), equalTo(Version.CURRENT.toString()));
assertThat(tasksStatus.getFeatureName(), equalTo("tasks"));
} }
public void testPostFeatureUpgradeStatus() throws IOException { public void testPostFeatureUpgradeStatus() throws IOException {
PostFeatureUpgradeRequest request = new PostFeatureUpgradeRequest(); PostFeatureUpgradeRequest request = new PostFeatureUpgradeRequest();
PostFeatureUpgradeResponse response = highLevelClient().migration().postFeatureUpgrade(request, RequestOptions.DEFAULT); PostFeatureUpgradeResponse response = highLevelClient().migration().postFeatureUpgrade(request, RequestOptions.DEFAULT);
// a test like this cannot test actual deprecations
assertThat(response.isAccepted(), equalTo(true)); assertThat(response.isAccepted(), equalTo(true));
assertThat(response.getFeatures().size(), equalTo(1)); assertThat(response.getFeatures().size(), equalTo(1));
PostFeatureUpgradeResponse.Feature feature = response.getFeatures().get(0); PostFeatureUpgradeResponse.Feature feature = response.getFeatures().get(0);

View file

@ -8,6 +8,7 @@
package org.elasticsearch.client.migration; package org.elasticsearch.client.migration;
import org.elasticsearch.Version;
import org.elasticsearch.client.AbstractResponseTestCase; import org.elasticsearch.client.AbstractResponseTestCase;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.XContentType;
@ -37,14 +38,14 @@ public class GetFeatureUpgradeStatusResponseTests extends AbstractResponseTestCa
randomList(5, randomList(5,
() -> new org.elasticsearch.action.admin.cluster.migration.GetFeatureUpgradeStatusResponse.FeatureUpgradeStatus( () -> new org.elasticsearch.action.admin.cluster.migration.GetFeatureUpgradeStatusResponse.FeatureUpgradeStatus(
randomAlphaOfLengthBetween(3, 20), randomAlphaOfLengthBetween(3, 20),
randomAlphaOfLengthBetween(5, 9), randomFrom(Version.CURRENT, Version.CURRENT.minimumCompatibilityVersion()),
randomAlphaOfLengthBetween(4, 16), randomFrom(org.elasticsearch.action.admin.cluster.migration.GetFeatureUpgradeStatusResponse.UpgradeStatus.values()),
randomList(4, randomList(4,
() -> new org.elasticsearch.action.admin.cluster.migration.GetFeatureUpgradeStatusResponse.IndexVersion( () -> new org.elasticsearch.action.admin.cluster.migration.GetFeatureUpgradeStatusResponse.IndexVersion(
randomAlphaOfLengthBetween(3, 20), randomAlphaOfLengthBetween(3, 20),
randomAlphaOfLengthBetween(5, 9))) randomFrom(Version.CURRENT, Version.CURRENT.minimumCompatibilityVersion())))
)), )),
randomAlphaOfLength(5) randomFrom(org.elasticsearch.action.admin.cluster.migration.GetFeatureUpgradeStatusResponse.UpgradeStatus.values())
); );
} }
@ -58,7 +59,7 @@ public class GetFeatureUpgradeStatusResponseTests extends AbstractResponseTestCa
org.elasticsearch.action.admin.cluster.migration.GetFeatureUpgradeStatusResponse serverTestInstance, org.elasticsearch.action.admin.cluster.migration.GetFeatureUpgradeStatusResponse serverTestInstance,
GetFeatureUpgradeStatusResponse clientInstance) { GetFeatureUpgradeStatusResponse clientInstance) {
assertThat(clientInstance.getUpgradeStatus(), equalTo(serverTestInstance.getUpgradeStatus())); assertThat(clientInstance.getUpgradeStatus(), equalTo(serverTestInstance.getUpgradeStatus().toString()));
assertNotNull(serverTestInstance.getFeatureUpgradeStatuses()); assertNotNull(serverTestInstance.getFeatureUpgradeStatuses());
assertNotNull(clientInstance.getFeatureUpgradeStatuses()); assertNotNull(clientInstance.getFeatureUpgradeStatuses());
@ -71,8 +72,8 @@ public class GetFeatureUpgradeStatusResponseTests extends AbstractResponseTestCa
GetFeatureUpgradeStatusResponse.FeatureUpgradeStatus clientStatus = clientInstance.getFeatureUpgradeStatuses().get(i); GetFeatureUpgradeStatusResponse.FeatureUpgradeStatus clientStatus = clientInstance.getFeatureUpgradeStatuses().get(i);
assertThat(clientStatus.getFeatureName(), equalTo(serverTestStatus.getFeatureName())); assertThat(clientStatus.getFeatureName(), equalTo(serverTestStatus.getFeatureName()));
assertThat(clientStatus.getMinimumIndexVersion(), equalTo(serverTestStatus.getMinimumIndexVersion())); assertThat(clientStatus.getMinimumIndexVersion(), equalTo(serverTestStatus.getMinimumIndexVersion().toString()));
assertThat(clientStatus.getUpgradeStatus(), equalTo(serverTestStatus.getUpgradeStatus())); assertThat(clientStatus.getUpgradeStatus(), equalTo(serverTestStatus.getUpgradeStatus().toString()));
assertThat(clientStatus.getIndexVersions(), hasSize(serverTestStatus.getIndexVersions().size())); assertThat(clientStatus.getIndexVersions(), hasSize(serverTestStatus.getIndexVersions().size()));
@ -82,7 +83,7 @@ public class GetFeatureUpgradeStatusResponseTests extends AbstractResponseTestCa
GetFeatureUpgradeStatusResponse.IndexVersion clientIndexVersion = clientStatus.getIndexVersions().get(j); GetFeatureUpgradeStatusResponse.IndexVersion clientIndexVersion = clientStatus.getIndexVersions().get(j);
assertThat(clientIndexVersion.getIndexName(), equalTo(serverIndexVersion.getIndexName())); assertThat(clientIndexVersion.getIndexName(), equalTo(serverIndexVersion.getIndexName()));
assertThat(clientIndexVersion.getVersion(), equalTo(serverIndexVersion.getVersion())); assertThat(clientIndexVersion.getVersion(), equalTo(serverIndexVersion.getVersion().toString()));
} }
} }
} }

View file

@ -44,20 +44,82 @@ Example response:
{ {
"features" : [ "features" : [
{ {
"feature_name" : "security", "feature_name" : "async_search",
"minimum_index_version" : "7.1.1", "minimum_index_version" : "7.0.0",
"upgrade_status" : "UPGRADE_NEEDED", "upgrade_status" : "NO_UPGRADE_NEEDED",
"indices" : [ "indices" : [ ]
},
{ {
"index" : ".security-7", "feature_name" : "enrich",
"version" : "7.1.1" "minimum_index_version" : "7.0.0",
} "upgrade_status" : "NO_UPGRADE_NEEDED",
] "indices" : [ ]
},
{
"feature_name" : "fleet",
"minimum_index_version" : "7.0.0",
"upgrade_status" : "NO_UPGRADE_NEEDED",
"indices" : [ ]
},
{
"feature_name" : "geoip",
"minimum_index_version" : "7.0.0",
"upgrade_status" : "NO_UPGRADE_NEEDED",
"indices" : [ ]
},
{
"feature_name" : "kibana",
"minimum_index_version" : "7.0.0",
"upgrade_status" : "NO_UPGRADE_NEEDED",
"indices" : [ ]
},
{
"feature_name" : "logstash_management",
"minimum_index_version" : "7.0.0",
"upgrade_status" : "NO_UPGRADE_NEEDED",
"indices" : [ ]
},
{
"feature_name" : "machine_learning",
"minimum_index_version" : "7.0.0",
"upgrade_status" : "NO_UPGRADE_NEEDED",
"indices" : [ ]
},
{
"feature_name" : "searchable_snapshots",
"minimum_index_version" : "7.0.0",
"upgrade_status" : "NO_UPGRADE_NEEDED",
"indices" : [ ]
},
{
"feature_name" : "security",
"minimum_index_version" : "7.0.0",
"upgrade_status" : "NO_UPGRADE_NEEDED",
"indices" : [ ]
},
{
"feature_name" : "tasks",
"minimum_index_version" : "7.0.0",
"upgrade_status" : "NO_UPGRADE_NEEDED",
"indices" : [ ]
},
{
"feature_name" : "transform",
"minimum_index_version" : "7.0.0",
"upgrade_status" : "NO_UPGRADE_NEEDED",
"indices" : [ ]
},
{
"feature_name" : "watcher",
"minimum_index_version" : "7.0.0",
"upgrade_status" : "NO_UPGRADE_NEEDED",
"indices" : [ ]
} }
], ],
"upgrade_status" : "UPGRADE_NEEDED" "upgrade_status" : "NO_UPGRADE_NEEDED"
} }
-------------------------------------------------- --------------------------------------------------
// TESTRESPONSE[s/"minimum_index_version" : "7.0.0"/"minimum_index_version" : $body.$_path/]
This response tells us that Elasticsearch security needs its internal This response tells us that Elasticsearch security needs its internal
indices upgraded before we can upgrade the cluster to 8.0. indices upgraded before we can upgrade the cluster to 8.0.

View file

@ -0,0 +1,115 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.upgrades;
import org.elasticsearch.Version;
import org.elasticsearch.action.admin.cluster.migration.TransportGetFeatureUpgradeStatusAction;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.ResponseException;
import org.elasticsearch.test.XContentTestUtils;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
public class FeatureUpgradeIT extends AbstractRollingTestCase {
@SuppressWarnings("unchecked")
public void testGetFeatureUpgradeStatus() throws Exception {
final String systemIndexWarning = "this request accesses system indices: [.tasks], but in a future major version, direct " +
"access to system indices will be prevented by default";
if (CLUSTER_TYPE == ClusterType.OLD) {
// setup - put something in the tasks index
// create index
Request createTestIndex = new Request("PUT", "/feature_test_index_old");
createTestIndex.setJsonEntity("{\"settings\": {" +
"\"index.number_of_replicas\": 0," +
"\"index.number_of_shards\": 1" +
"}}");
client().performRequest(createTestIndex);
Request bulk = new Request("POST", "/_bulk");
bulk.addParameter("refresh", "true");
if (UPGRADE_FROM_VERSION.before(Version.V_7_0_0)) {
bulk.setJsonEntity("{\"index\": {\"_index\": \"feature_test_index_old\", \"_type\" : \"_doc\"}}\n" +
"{\"f1\": \"v1\", \"f2\": \"v2\"}\n");
} else {
bulk.setJsonEntity("{\"index\": {\"_index\": \"feature_test_index_old\"}\n" +
"{\"f1\": \"v1\", \"f2\": \"v2\"}\n");
}
client().performRequest(bulk);
// start a async reindex job
Request reindex = new Request("POST", "/_reindex");
reindex.setJsonEntity(
"{\n" +
" \"source\":{\n" +
" \"index\":\"feature_test_index_old\"\n" +
" },\n" +
" \"dest\":{\n" +
" \"index\":\"feature_test_index_reindex\"\n" +
" }\n" +
"}");
reindex.addParameter("wait_for_completion", "false");
Map<String, Object> response = entityAsMap(client().performRequest(reindex));
String taskId = (String) response.get("task");
// wait for task
Request getTask = new Request("GET", "/_tasks/" + taskId);
getTask.addParameter("wait_for_completion", "true");
client().performRequest(getTask);
// make sure .tasks index exists
Request getTasksIndex = new Request("GET", "/.tasks");
getTasksIndex.addParameter("allow_no_indices", "false");
if (UPGRADE_FROM_VERSION.before(Version.V_7_0_0)) {
getTasksIndex.addParameter("include_type_name", "false");
}
getTasksIndex.setOptions(expectVersionSpecificWarnings(v -> {
v.current(systemIndexWarning);
v.compatible(systemIndexWarning);
}));
assertBusy(() -> {
try {
assertThat(client().performRequest(getTasksIndex).getStatusLine().getStatusCode(), is(200));
} catch (ResponseException e) {
throw new AssertionError(".tasks index does not exist yet");
}
});
} else if (CLUSTER_TYPE == ClusterType.UPGRADED) {
// check results
assertBusy(() -> {
Request clusterStateRequest = new Request("GET", "/_migration/system_features");
XContentTestUtils.JsonMapView view = new XContentTestUtils.JsonMapView(
entityAsMap(client().performRequest(clusterStateRequest)));
List<Map<String, Object>> features = view.get("features");
Map<String, Object> feature = features.stream()
.filter(e -> "tasks".equals(e.get("feature_name")))
.findFirst()
.orElse(Collections.emptyMap());
assertThat(feature.size(), equalTo(4));
assertThat(feature.get("minimum_index_version"), equalTo(UPGRADE_FROM_VERSION.toString()));
if (UPGRADE_FROM_VERSION.before(TransportGetFeatureUpgradeStatusAction.NO_UPGRADE_REQUIRED_VERSION)) {
assertThat(feature.get("upgrade_status"), equalTo("UPGRADE_NEEDED"));
} else {
assertThat(feature.get("upgrade_status"), equalTo("NO_UPGRADE_NEEDED"));
}
});
}
}
}

View file

@ -0,0 +1,70 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.system.indices;
import org.elasticsearch.Version;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.Response;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.test.XContentTestUtils;
import org.elasticsearch.test.rest.ESRestTestCase;
import org.junit.After;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
public class FeatureUpgradeApiIT extends ESRestTestCase {
static final String BASIC_AUTH_VALUE = basicAuthHeaderValue("rest_user", new SecureString("rest-user-password".toCharArray()));
@After
public void resetFeatures() throws Exception {
client().performRequest(new Request("POST", "/_features/_reset"));
}
@Override
protected Settings restClientSettings() {
return Settings.builder().put(ThreadContext.PREFIX + ".Authorization", BASIC_AUTH_VALUE).build();
}
public void testCreatingSystemIndex() throws Exception {
Response response = client().performRequest(new Request("PUT", "/_net_new_sys_index/_create"));
assertThat(response.getStatusLine().getStatusCode(), is(200));
}
@SuppressWarnings("unchecked")
public void testGetFeatureUpgradedStatuses() throws Exception {
client().performRequest(new Request("PUT", "/_net_new_sys_index/_create"));
Response response = client().performRequest(new Request("GET", "/_migration/system_features"));
assertThat(response.getStatusLine().getStatusCode(), is(200));
XContentTestUtils.JsonMapView view = XContentTestUtils.createJsonMapView(response.getEntity().getContent());
String upgradeStatus = view.get("upgrade_status");
assertThat(upgradeStatus, equalTo("NO_UPGRADE_NEEDED"));
List<Map<String, Object>> features = view.get("features");
Map<String, Object> testFeature = features.stream()
.filter(feature -> "system indices qa".equals(feature.get("feature_name")))
.findFirst()
.orElse(Collections.emptyMap());
assertThat(testFeature.size(), equalTo(4));
assertThat(testFeature.get("minimum_index_version"), equalTo(Version.CURRENT.toString()));
assertThat(testFeature.get("upgrade_status"), equalTo("NO_UPGRADE_NEEDED"));
assertThat(testFeature.get("indices"), instanceOf(List.class));
assertThat((List<Object>) testFeature.get("indices"), hasSize(1));
}
}

View file

@ -8,6 +8,7 @@
package org.elasticsearch.action.admin.cluster.migration; package org.elasticsearch.action.admin.cluster.migration;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionResponse; import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
@ -27,13 +28,13 @@ import java.util.Objects;
public class GetFeatureUpgradeStatusResponse extends ActionResponse implements ToXContentObject { public class GetFeatureUpgradeStatusResponse extends ActionResponse implements ToXContentObject {
private final List<FeatureUpgradeStatus> featureUpgradeStatuses; private final List<FeatureUpgradeStatus> featureUpgradeStatuses;
private final String upgradeStatus; private final UpgradeStatus upgradeStatus;
/** /**
* @param statuses A list of feature statuses * @param statuses A list of feature statuses
* @param upgradeStatus Whether system features need to be upgraded * @param upgradeStatus Whether system features need to be upgraded
*/ */
public GetFeatureUpgradeStatusResponse(List<FeatureUpgradeStatus> statuses, String upgradeStatus) { public GetFeatureUpgradeStatusResponse(List<FeatureUpgradeStatus> statuses, UpgradeStatus upgradeStatus) {
this.featureUpgradeStatuses = Objects.nonNull(statuses) ? statuses : Collections.emptyList(); this.featureUpgradeStatuses = Objects.nonNull(statuses) ? statuses : Collections.emptyList();
this.upgradeStatus = upgradeStatus; this.upgradeStatus = upgradeStatus;
} }
@ -45,7 +46,7 @@ public class GetFeatureUpgradeStatusResponse extends ActionResponse implements T
public GetFeatureUpgradeStatusResponse(StreamInput in) throws IOException { public GetFeatureUpgradeStatusResponse(StreamInput in) throws IOException {
super(in); super(in);
this.featureUpgradeStatuses = in.readList(FeatureUpgradeStatus::new); this.featureUpgradeStatuses = in.readList(FeatureUpgradeStatus::new);
this.upgradeStatus = in.readString(); this.upgradeStatus = in.readEnum(UpgradeStatus.class);
} }
@Override @Override
@ -64,14 +65,14 @@ public class GetFeatureUpgradeStatusResponse extends ActionResponse implements T
@Override @Override
public void writeTo(StreamOutput out) throws IOException { public void writeTo(StreamOutput out) throws IOException {
out.writeList(this.featureUpgradeStatuses); out.writeList(this.featureUpgradeStatuses);
out.writeString(upgradeStatus); out.writeEnum(upgradeStatus);
} }
public List<FeatureUpgradeStatus> getFeatureUpgradeStatuses() { public List<FeatureUpgradeStatus> getFeatureUpgradeStatuses() {
return featureUpgradeStatuses; return featureUpgradeStatuses;
} }
public String getUpgradeStatus() { public UpgradeStatus getUpgradeStatus() {
return upgradeStatus; return upgradeStatus;
} }
@ -88,14 +89,28 @@ public class GetFeatureUpgradeStatusResponse extends ActionResponse implements T
return Objects.hash(featureUpgradeStatuses, upgradeStatus); return Objects.hash(featureUpgradeStatuses, upgradeStatus);
} }
@Override
public String toString() {
return "GetFeatureUpgradeStatusResponse{" +
"featureUpgradeStatuses=" + featureUpgradeStatuses +
", upgradeStatus='" + upgradeStatus + '\'' +
'}';
}
public enum UpgradeStatus {
UPGRADE_NEEDED,
NO_UPGRADE_NEEDED,
IN_PROGRESS
}
/** /**
* A class for a particular feature, showing whether it needs to be upgraded and the earliest * A class for a particular feature, showing whether it needs to be upgraded and the earliest
* Elasticsearch version used to create one of this feature's system indices. * Elasticsearch version used to create one of this feature's system indices.
*/ */
public static class FeatureUpgradeStatus implements Writeable, ToXContentObject { public static class FeatureUpgradeStatus implements Writeable, ToXContentObject {
private final String featureName; private final String featureName;
private final String minimumIndexVersion; private final Version minimumIndexVersion;
private final String upgradeStatus; private final UpgradeStatus upgradeStatus;
private final List<IndexVersion> indexVersions; private final List<IndexVersion> indexVersions;
/** /**
@ -104,8 +119,8 @@ public class GetFeatureUpgradeStatusResponse extends ActionResponse implements T
* @param upgradeStatus Whether the feature needs to be upgraded * @param upgradeStatus Whether the feature needs to be upgraded
* @param indexVersions A list of this feature's concrete indices and the Elasticsearch version that created them * @param indexVersions A list of this feature's concrete indices and the Elasticsearch version that created them
*/ */
public FeatureUpgradeStatus(String featureName, String minimumIndexVersion, public FeatureUpgradeStatus(String featureName, Version minimumIndexVersion,
String upgradeStatus, List<IndexVersion> indexVersions) { UpgradeStatus upgradeStatus, List<IndexVersion> indexVersions) {
this.featureName = featureName; this.featureName = featureName;
this.minimumIndexVersion = minimumIndexVersion; this.minimumIndexVersion = minimumIndexVersion;
this.upgradeStatus = upgradeStatus; this.upgradeStatus = upgradeStatus;
@ -118,8 +133,8 @@ public class GetFeatureUpgradeStatusResponse extends ActionResponse implements T
*/ */
public FeatureUpgradeStatus(StreamInput in) throws IOException { public FeatureUpgradeStatus(StreamInput in) throws IOException {
this.featureName = in.readString(); this.featureName = in.readString();
this.minimumIndexVersion = in.readString(); this.minimumIndexVersion = Version.readVersion(in);
this.upgradeStatus = in.readString(); this.upgradeStatus = in.readEnum(UpgradeStatus.class);
this.indexVersions = in.readList(IndexVersion::new); this.indexVersions = in.readList(IndexVersion::new);
} }
@ -127,11 +142,11 @@ public class GetFeatureUpgradeStatusResponse extends ActionResponse implements T
return this.featureName; return this.featureName;
} }
public String getMinimumIndexVersion() { public Version getMinimumIndexVersion() {
return this.minimumIndexVersion; return this.minimumIndexVersion;
} }
public String getUpgradeStatus() { public UpgradeStatus getUpgradeStatus() {
return this.upgradeStatus; return this.upgradeStatus;
} }
@ -142,8 +157,8 @@ public class GetFeatureUpgradeStatusResponse extends ActionResponse implements T
@Override @Override
public void writeTo(StreamOutput out) throws IOException { public void writeTo(StreamOutput out) throws IOException {
out.writeString(this.featureName); out.writeString(this.featureName);
out.writeString(this.minimumIndexVersion); Version.writeVersion(this.minimumIndexVersion, out);
out.writeString(this.upgradeStatus); out.writeEnum(this.upgradeStatus);
out.writeList(this.indexVersions); out.writeList(this.indexVersions);
} }
@ -151,7 +166,7 @@ public class GetFeatureUpgradeStatusResponse extends ActionResponse implements T
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(); builder.startObject();
builder.field("feature_name", this.featureName); builder.field("feature_name", this.featureName);
builder.field("minimum_index_version", this.minimumIndexVersion); builder.field("minimum_index_version", this.minimumIndexVersion.toString());
builder.field("upgrade_status", this.upgradeStatus); builder.field("upgrade_status", this.upgradeStatus);
builder.startArray("indices"); builder.startArray("indices");
for (IndexVersion version : this.indexVersions) { for (IndexVersion version : this.indexVersions) {
@ -177,6 +192,16 @@ public class GetFeatureUpgradeStatusResponse extends ActionResponse implements T
public int hashCode() { public int hashCode() {
return Objects.hash(featureName, minimumIndexVersion, upgradeStatus, indexVersions); return Objects.hash(featureName, minimumIndexVersion, upgradeStatus, indexVersions);
} }
@Override
public String toString() {
return "FeatureUpgradeStatus{" +
"featureName='" + featureName + '\'' +
", minimumIndexVersion='" + minimumIndexVersion + '\'' +
", upgradeStatus='" + upgradeStatus + '\'' +
", indexVersions=" + indexVersions +
'}';
}
} }
/** /**
@ -184,13 +209,13 @@ public class GetFeatureUpgradeStatusResponse extends ActionResponse implements T
*/ */
public static class IndexVersion implements Writeable, ToXContentObject { public static class IndexVersion implements Writeable, ToXContentObject {
private final String indexName; private final String indexName;
private final String version; private final Version version;
/** /**
* @param indexName Name of the index * @param indexName Name of the index
* @param version Version of Elasticsearch that created the index * @param version Version of Elasticsearch that created the index
*/ */
public IndexVersion(String indexName, String version) { public IndexVersion(String indexName, Version version) {
this.indexName = indexName; this.indexName = indexName;
this.version = version; this.version = version;
} }
@ -201,28 +226,28 @@ public class GetFeatureUpgradeStatusResponse extends ActionResponse implements T
*/ */
public IndexVersion(StreamInput in) throws IOException { public IndexVersion(StreamInput in) throws IOException {
this.indexName = in.readString(); this.indexName = in.readString();
this.version = in.readString(); this.version = Version.readVersion(in);
} }
public String getIndexName() { public String getIndexName() {
return this.indexName; return this.indexName;
} }
public String getVersion() { public Version getVersion() {
return this.version; return this.version;
} }
@Override @Override
public void writeTo(StreamOutput out) throws IOException { public void writeTo(StreamOutput out) throws IOException {
out.writeString(this.indexName); out.writeString(this.indexName);
out.writeString(this.version); Version.writeVersion(this.version, out);
} }
@Override @Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(); builder.startObject();
builder.field("index", this.indexName); builder.field("index", this.indexName);
builder.field("version", this.version); builder.field("version", this.version.toString());
builder.endObject(); builder.endObject();
return builder; return builder;
} }
@ -239,5 +264,13 @@ public class GetFeatureUpgradeStatusResponse extends ActionResponse implements T
public int hashCode() { public int hashCode() {
return Objects.hash(indexName, version); return Objects.hash(indexName, version);
} }
@Override
public String toString() {
return "IndexVersion{" +
"indexName='" + indexName + '\'' +
", version='" + version + '\'' +
'}';
}
} }
} }

View file

@ -8,6 +8,7 @@
package org.elasticsearch.action.admin.cluster.migration; package org.elasticsearch.action.admin.cluster.migration;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.master.TransportMasterNodeAction; import org.elasticsearch.action.support.master.TransportMasterNodeAction;
@ -21,8 +22,14 @@ import org.elasticsearch.indices.SystemIndices;
import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService; import org.elasticsearch.transport.TransportService;
import java.util.ArrayList; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.elasticsearch.action.admin.cluster.migration.GetFeatureUpgradeStatusResponse.UpgradeStatus.NO_UPGRADE_NEEDED;
import static org.elasticsearch.action.admin.cluster.migration.GetFeatureUpgradeStatusResponse.UpgradeStatus.UPGRADE_NEEDED;
/** /**
* Transport class for the get feature upgrade status action * Transport class for the get feature upgrade status action
@ -31,6 +38,8 @@ public class TransportGetFeatureUpgradeStatusAction extends TransportMasterNodeA
GetFeatureUpgradeStatusRequest, GetFeatureUpgradeStatusRequest,
GetFeatureUpgradeStatusResponse> { GetFeatureUpgradeStatusResponse> {
public static final Version NO_UPGRADE_REQUIRED_VERSION = Version.V_7_0_0;
private final SystemIndices systemIndices; private final SystemIndices systemIndices;
@Inject @Inject
@ -59,16 +68,51 @@ public class TransportGetFeatureUpgradeStatusAction extends TransportMasterNodeA
@Override @Override
protected void masterOperation(GetFeatureUpgradeStatusRequest request, ClusterState state, protected void masterOperation(GetFeatureUpgradeStatusRequest request, ClusterState state,
ActionListener<GetFeatureUpgradeStatusResponse> listener) throws Exception { ActionListener<GetFeatureUpgradeStatusResponse> listener) throws Exception {
List<GetFeatureUpgradeStatusResponse.IndexVersion> indexVersions = new ArrayList<>();
indexVersions.add(new GetFeatureUpgradeStatusResponse.IndexVersion(".security-7", "7.1.1")); List<GetFeatureUpgradeStatusResponse.FeatureUpgradeStatus> features = systemIndices.getFeatures().entrySet().stream()
List<GetFeatureUpgradeStatusResponse.FeatureUpgradeStatus> features = new ArrayList<>(); .sorted(Map.Entry.comparingByKey())
features.add(new GetFeatureUpgradeStatusResponse.FeatureUpgradeStatus( .map(entry -> getFeatureUpgradeStatus(state, entry.getKey(), entry.getValue()))
"security", .collect(Collectors.toList());
"7.1.1",
"UPGRADE_NEEDED", boolean isUpgradeNeeded = features.stream()
.map(GetFeatureUpgradeStatusResponse.FeatureUpgradeStatus::getMinimumIndexVersion)
.min(Version::compareTo)
.orElse(Version.CURRENT)
.before(Version.V_7_0_0);
listener.onResponse(new GetFeatureUpgradeStatusResponse(features, isUpgradeNeeded ? UPGRADE_NEEDED : NO_UPGRADE_NEEDED));
}
// visible for testing
static GetFeatureUpgradeStatusResponse.FeatureUpgradeStatus getFeatureUpgradeStatus(
ClusterState state, String featureName, SystemIndices.Feature feature) {
List<GetFeatureUpgradeStatusResponse.IndexVersion> indexVersions = getIndexVersions(state, feature);
Version minimumVersion = indexVersions.stream()
.map(GetFeatureUpgradeStatusResponse.IndexVersion::getVersion)
.min(Version::compareTo)
.orElse(Version.CURRENT);
return new GetFeatureUpgradeStatusResponse.FeatureUpgradeStatus(
featureName,
minimumVersion,
minimumVersion.before(Version.V_7_0_0) ? UPGRADE_NEEDED : NO_UPGRADE_NEEDED,
indexVersions indexVersions
)); );
listener.onResponse(new GetFeatureUpgradeStatusResponse(features, "UPGRADE_NEEDED")); }
// visible for testing
static List<GetFeatureUpgradeStatusResponse.IndexVersion> getIndexVersions(ClusterState state, SystemIndices.Feature feature) {
return Stream.of(feature.getIndexDescriptors(), feature.getAssociatedIndexDescriptors())
.flatMap(Collection::stream)
.flatMap(descriptor -> descriptor.getMatchingIndices(state.metadata()).stream())
.sorted(String::compareTo)
.map(index -> state.metadata().index(index))
.map(indexMetadata -> new GetFeatureUpgradeStatusResponse.IndexVersion(
indexMetadata.getIndex().getName(),
indexMetadata.getCreationVersion()))
.collect(Collectors.toList());
} }
@Override @Override

View file

@ -8,12 +8,14 @@
package org.elasticsearch.action.admin.cluster.migration; package org.elasticsearch.action.admin.cluster.migration;
import org.elasticsearch.Version;
import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.test.AbstractWireSerializingTestCase; import org.elasticsearch.test.AbstractWireSerializingTestCase;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import static org.elasticsearch.action.admin.cluster.migration.GetFeatureUpgradeStatusResponse.UpgradeStatus.UPGRADE_NEEDED;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.notNullValue;
@ -31,7 +33,7 @@ public class GetFeatureUpgradeStatusResponseTests extends AbstractWireSerializin
protected GetFeatureUpgradeStatusResponse createTestInstance() { protected GetFeatureUpgradeStatusResponse createTestInstance() {
return new GetFeatureUpgradeStatusResponse( return new GetFeatureUpgradeStatusResponse(
randomList(8, GetFeatureUpgradeStatusResponseTests::createFeatureStatus), randomList(8, GetFeatureUpgradeStatusResponseTests::createFeatureStatus),
randomAlphaOfLengthBetween(4, 16) randomFrom(org.elasticsearch.action.admin.cluster.migration.GetFeatureUpgradeStatusResponse.UpgradeStatus.values())
); );
} }
@ -41,13 +43,14 @@ public class GetFeatureUpgradeStatusResponseTests extends AbstractWireSerializin
randomList(8, randomList(8,
() -> randomValueOtherThanMany(instance.getFeatureUpgradeStatuses()::contains, () -> randomValueOtherThanMany(instance.getFeatureUpgradeStatuses()::contains,
GetFeatureUpgradeStatusResponseTests::createFeatureStatus)), GetFeatureUpgradeStatusResponseTests::createFeatureStatus)),
randomValueOtherThan(instance.getUpgradeStatus(), () -> randomAlphaOfLengthBetween(4, 16)) randomValueOtherThan(instance.getUpgradeStatus(), () ->
); randomFrom(org.elasticsearch.action.admin.cluster.migration.GetFeatureUpgradeStatusResponse.UpgradeStatus.values())));
} }
/** If constructor is called with null for a list, we just use an empty list */ /** If constructor is called with null for a list, we just use an empty list */
public void testConstructorHandlesNullLists() { public void testConstructorHandlesNullLists() {
GetFeatureUpgradeStatusResponse response = new GetFeatureUpgradeStatusResponse(null, "status"); GetFeatureUpgradeStatusResponse response = new GetFeatureUpgradeStatusResponse(null, UPGRADE_NEEDED);
assertThat(response.getFeatureUpgradeStatuses(), notNullValue()); assertThat(response.getFeatureUpgradeStatuses(), notNullValue());
assertThat(response.getFeatureUpgradeStatuses(), equalTo(Collections.emptyList())); assertThat(response.getFeatureUpgradeStatuses(), equalTo(Collections.emptyList()));
} }
@ -55,8 +58,8 @@ public class GetFeatureUpgradeStatusResponseTests extends AbstractWireSerializin
private static GetFeatureUpgradeStatusResponse.FeatureUpgradeStatus createFeatureStatus() { private static GetFeatureUpgradeStatusResponse.FeatureUpgradeStatus createFeatureStatus() {
return new GetFeatureUpgradeStatusResponse.FeatureUpgradeStatus( return new GetFeatureUpgradeStatusResponse.FeatureUpgradeStatus(
randomAlphaOfLengthBetween(3, 20), randomAlphaOfLengthBetween(3, 20),
randomAlphaOfLengthBetween(5, 9), randomFrom(Version.CURRENT, Version.CURRENT.minimumCompatibilityVersion()),
randomAlphaOfLengthBetween(4, 16), randomFrom(org.elasticsearch.action.admin.cluster.migration.GetFeatureUpgradeStatusResponse.UpgradeStatus.values()),
randomList(4, GetFeatureUpgradeStatusResponseTests::getIndexVersion) randomList(4, GetFeatureUpgradeStatusResponseTests::getIndexVersion)
); );
} }
@ -64,7 +67,7 @@ public class GetFeatureUpgradeStatusResponseTests extends AbstractWireSerializin
private static GetFeatureUpgradeStatusResponse.IndexVersion getIndexVersion() { private static GetFeatureUpgradeStatusResponse.IndexVersion getIndexVersion() {
return new GetFeatureUpgradeStatusResponse.IndexVersion( return new GetFeatureUpgradeStatusResponse.IndexVersion(
randomAlphaOfLengthBetween(3, 20), randomAlphaOfLengthBetween(3, 20),
randomAlphaOfLengthBetween(5, 9) randomFrom(Version.CURRENT, Version.CURRENT.minimumCompatibilityVersion())
); );
} }
} }

View file

@ -0,0 +1,99 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.action.admin.cluster.migration;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.IndexMetadata;
import org.elasticsearch.cluster.metadata.Metadata;
import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.indices.SystemIndexDescriptor;
import org.elasticsearch.indices.SystemIndices;
import org.elasticsearch.test.ESTestCase;
import java.util.ArrayList;
import java.util.List;
import static org.elasticsearch.action.admin.cluster.migration.GetFeatureUpgradeStatusResponse.UpgradeStatus.NO_UPGRADE_NEEDED;
import static org.hamcrest.Matchers.equalTo;
public class TransportGetFeatureUpgradeStatusActionTests extends ESTestCase {
public static String TEST_SYSTEM_INDEX_PATTERN = ".test*";
private static final ClusterState CLUSTER_STATE = getClusterState();
private static final SystemIndices.Feature FEATURE = getFeature();
public void testGetFeatureStatus() {
GetFeatureUpgradeStatusResponse.FeatureUpgradeStatus status =
TransportGetFeatureUpgradeStatusAction.getFeatureUpgradeStatus(
CLUSTER_STATE, "test-feature", FEATURE);
assertThat(status.getUpgradeStatus(), equalTo(NO_UPGRADE_NEEDED));
assertThat(status.getFeatureName(), equalTo("test-feature"));
assertThat(status.getMinimumIndexVersion(), equalTo(Version.V_7_0_0));
assertThat(status.getIndexVersions().size(), equalTo(2)); // additional testing below
}
public void testGetIndexVersion() {
List<GetFeatureUpgradeStatusResponse.IndexVersion> versions =
TransportGetFeatureUpgradeStatusAction.getIndexVersions(CLUSTER_STATE, FEATURE);
assertThat(versions.size(), equalTo(2));
{
GetFeatureUpgradeStatusResponse.IndexVersion version = versions.get(0);
assertThat(version.getVersion(), equalTo(Version.CURRENT));
assertThat(version.getIndexName(), equalTo(".test-index-1"));
}
{
GetFeatureUpgradeStatusResponse.IndexVersion version = versions.get(1);
assertThat(version.getVersion(), equalTo(Version.V_7_0_0));
assertThat(version.getIndexName(), equalTo(".test-index-2"));
}
}
private static SystemIndices.Feature getFeature() {
SystemIndexDescriptor descriptor = new SystemIndexDescriptor(TEST_SYSTEM_INDEX_PATTERN, "descriptor for tests");
List<SystemIndexDescriptor> descriptors = new ArrayList<>();
descriptors.add(descriptor);
// system indices feature object
SystemIndices.Feature feature = new SystemIndices.Feature(
"test-feature",
"feature for tests",
descriptors);
return feature;
}
private static ClusterState getClusterState() {
IndexMetadata indexMetadata1 = IndexMetadata.builder(".test-index-1")
.settings(Settings.builder().put("index.version.created", Version.CURRENT).build())
.numberOfShards(1)
.numberOfReplicas(0)
.build();
IndexMetadata indexMetadata2 = IndexMetadata.builder(".test-index-2")
.settings(Settings.builder().put("index.version.created", Version.V_7_0_0).build())
.numberOfShards(1)
.numberOfReplicas(0)
.build();
ClusterState clusterState = new ClusterState.Builder(ClusterState.EMPTY_STATE)
.metadata(new Metadata.Builder()
.indices(ImmutableOpenMap.<String, IndexMetadata>builder()
.fPut(".test-index-1", indexMetadata1)
.fPut(".test-index-2", indexMetadata2)
.build())
.build())
.build();
return clusterState;
}
}