From dcc5a06decca4cc60bb7c69bb9902eedb7e26316 Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Tue, 30 Jun 2020 16:42:28 +1000 Subject: [PATCH] Display enterprise license as platinum in /_xpack (#58217) The GET /_license endpoint displays "enterprise" licenses as "platinum" by default so that old clients (including beats, kibana and logstash) know to interpret this new license type as if it were a platinum license. However, this compatibility layer was not applied to the GET /_xpack/ endpoint which also displays a license type & mode. This commit causes the _xpack API to mimic the _license API and treat enterprise as platinum by default, with a new accept_enterprise parameter that will cause the API to return the correct "enterprise" value. This BWC layer exists only for the 7.x branch. This is a breaking change because, since 7.6, the _xpack API has returned "enterprise" for enterprise licenses, but this has been found to break old versions of beats and logstash so needs to be corrected. --- .../protocol/xpack/XPackInfoRequest.java | 20 ++++- .../core/action/TransportXPackInfoAction.java | 14 ++- .../core/action/XPackInfoRequestBuilder.java | 4 + .../core/rest/action/RestXPackInfoAction.java | 7 +- .../action/TransportXPackInfoActionTests.java | 90 ++++++++++++++----- .../rest-api-spec/api/xpack.info.json | 4 + .../test/license/30_enterprise_license.yml | 22 +++++ .../rest-api-spec/test/xpack/20_info.yml | 9 ++ .../upgrades/CcrRollingUpgradeIT.java | 6 ++ 9 files changed, 149 insertions(+), 27 deletions(-) create mode 100644 x-pack/plugin/src/test/resources/rest-api-spec/test/xpack/20_info.yml diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/protocol/xpack/XPackInfoRequest.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/protocol/xpack/XPackInfoRequest.java index 4deb63c060d..e45f00d4d01 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/protocol/xpack/XPackInfoRequest.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/protocol/xpack/XPackInfoRequest.java @@ -5,10 +5,12 @@ */ package org.elasticsearch.protocol.xpack; +import org.elasticsearch.Version; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.license.License; import java.io.IOException; import java.util.EnumSet; @@ -40,8 +42,10 @@ public class XPackInfoRequest extends ActionRequest { private boolean verbose; private EnumSet categories = EnumSet.noneOf(Category.class); + private int licenseVersion = License.VERSION_CURRENT; - public XPackInfoRequest() {} + public XPackInfoRequest() { + } public XPackInfoRequest(StreamInput in) throws IOException { // NOTE: this does *not* call super, THIS IS A BUG @@ -52,6 +56,9 @@ public class XPackInfoRequest extends ActionRequest { categories.add(Category.valueOf(in.readString())); } this.categories = categories; + if (in.getVersion().onOrAfter(Version.V_7_8_1)) { + this.licenseVersion = in.readVInt(); + } } public void setVerbose(boolean verbose) { @@ -70,6 +77,14 @@ public class XPackInfoRequest extends ActionRequest { return categories; } + public int getLicenseVersion() { + return licenseVersion; + } + + public void setLicenseVersion(int licenseVersion) { + this.licenseVersion = licenseVersion; + } + @Override public ActionRequestValidationException validate() { return null; @@ -82,5 +97,8 @@ public class XPackInfoRequest extends ActionRequest { for (Category category : categories) { out.writeString(category.name()); } + if (out.getVersion().onOrAfter(Version.V_7_8_1)) { + out.writeVInt(this.licenseVersion); + } } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/TransportXPackInfoAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/TransportXPackInfoAction.java index e0938ad7d97..5a54fbf34f0 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/TransportXPackInfoAction.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/TransportXPackInfoAction.java @@ -49,8 +49,18 @@ public class TransportXPackInfoAction extends HandledTransportAction categories = XPackInfoRequest.Category .toSet(request.paramAsStringArray("categories", new String[] { "_all" })); return channel -> client.prepareInfo() .setVerbose(verbose) .setCategories(categories) + .setLicenseVersion(licenseVersion) .execute(new RestToXContentListener<>(channel)); } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/action/TransportXPackInfoActionTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/action/TransportXPackInfoActionTests.java index 33500271de9..f6fd0cecfd0 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/action/TransportXPackInfoActionTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/action/TransportXPackInfoActionTests.java @@ -29,6 +29,7 @@ import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasKey; @@ -41,7 +42,69 @@ import static org.mockito.Mockito.when; public class TransportXPackInfoActionTests extends ESTestCase { public void testDoExecute() throws Exception { + EnumSet categories = EnumSet.noneOf(XPackInfoRequest.Category.class); + int maxCategoryCount = randomIntBetween(0, XPackInfoRequest.Category.values().length); + for (int i = 0; i < maxCategoryCount; i++) { + categories.add(randomFrom(XPackInfoRequest.Category.values())); + } + License license = mock(License.class); + long expiryDate = randomLong(); + when(license.expiryDate()).thenReturn(expiryDate); + LicenseStatus status = randomFrom(LicenseStatus.values()); + when(license.status()).thenReturn(status); + String licenseType = randomAlphaOfLength(10); + when(license.type()).thenReturn(licenseType); + License.OperationMode licenseMode = randomFrom(License.OperationMode.values()); + when(license.operationMode()).thenReturn(licenseMode); + String uid = randomAlphaOfLength(30); + when(license.uid()).thenReturn(uid); + + checkAction(categories, -1, license, (XPackInfoResponse.LicenseInfo licenseInfo) -> { + assertThat(licenseInfo.getExpiryDate(), is(expiryDate)); + assertThat(licenseInfo.getStatus(), is(status)); + assertThat(licenseInfo.getType(), is(licenseType)); + assertThat(licenseInfo.getMode(), is(licenseMode.name().toLowerCase(Locale.ROOT))); + assertThat(licenseInfo.getUid(), is(uid)); + }); + } + + public void testDoExecuteWithEnterpriseLicenseWithoutBackwardsCompat() throws Exception { + EnumSet categories = EnumSet.allOf(XPackInfoRequest.Category.class); + + License license = mock(License.class); + when(license.expiryDate()).thenReturn(randomLong()); + when(license.status()).thenReturn(LicenseStatus.ACTIVE); + when(license.type()).thenReturn("enterprise"); + when(license.operationMode()).thenReturn(License.OperationMode.ENTERPRISE); + when(license.uid()).thenReturn(randomAlphaOfLength(30)); + + checkAction(categories, randomFrom(License.VERSION_ENTERPRISE, License.VERSION_CURRENT, -1), license, + licenseInfo -> { + assertThat(licenseInfo.getType(), is("enterprise")); + assertThat(licenseInfo.getMode(), is("enterprise")); + }); + } + + public void testDoExecuteWithEnterpriseLicenseWithBackwardsCompat() throws Exception { + EnumSet categories = EnumSet.allOf(XPackInfoRequest.Category.class); + + License license = mock(License.class); + when(license.expiryDate()).thenReturn(randomLong()); + when(license.status()).thenReturn(LicenseStatus.ACTIVE); + when(license.type()).thenReturn("enterprise"); + when(license.operationMode()).thenReturn(License.OperationMode.ENTERPRISE); + when(license.uid()).thenReturn(randomAlphaOfLength(30)); + + checkAction(categories, randomFrom(License.VERSION_START_DATE, License.VERSION_CRYPTO_ALGORITHMS), license, + licenseInfo -> { + assertThat(licenseInfo.getType(), is("platinum")); + assertThat(licenseInfo.getMode(), is("platinum")); + }); + } + + private void checkAction(EnumSet categories, int licenseVersion, License license, + Consumer licenseVerifier) throws InterruptedException { LicenseService licenseService = mock(LicenseService.class); final Set featureSets = new HashSet<>(); @@ -59,28 +122,14 @@ public class TransportXPackInfoActionTests extends ESTestCase { TransportXPackInfoAction action = new TransportXPackInfoAction(transportService, mock(ActionFilters.class), licenseService, featureSets); - License license = mock(License.class); - long expiryDate = randomLong(); - when(license.expiryDate()).thenReturn(expiryDate); - LicenseStatus status = randomFrom(LicenseStatus.values()); - when(license.status()).thenReturn(status); - String type = randomAlphaOfLength(10); - when(license.type()).thenReturn(type); - License.OperationMode mode = randomFrom(License.OperationMode.values()); - when(license.operationMode()).thenReturn(mode); - String uid = randomAlphaOfLength(30); - when(license.uid()).thenReturn(uid); when(licenseService.getLicense()).thenReturn(license); XPackInfoRequest request = new XPackInfoRequest(); request.setVerbose(randomBoolean()); - - EnumSet categories = EnumSet.noneOf(XPackInfoRequest.Category.class); - int maxCategoryCount = randomIntBetween(0, XPackInfoRequest.Category.values().length); - for (int i = 0; i < maxCategoryCount; i++) { - categories.add(randomFrom(XPackInfoRequest.Category.values())); - } request.setCategories(categories); + if (licenseVersion != -1) { + request.setLicenseVersion(licenseVersion); + } final CountDownLatch latch = new CountDownLatch(1); final AtomicReference response = new AtomicReference<>(); @@ -114,11 +163,7 @@ public class TransportXPackInfoActionTests extends ESTestCase { if (request.getCategories().contains(XPackInfoRequest.Category.LICENSE)) { assertThat(response.get().getLicenseInfo(), notNullValue()); - assertThat(response.get().getLicenseInfo().getExpiryDate(), is(expiryDate)); - assertThat(response.get().getLicenseInfo().getStatus(), is(status)); - assertThat(response.get().getLicenseInfo().getType(), is(type)); - assertThat(response.get().getLicenseInfo().getMode(), is(mode.name().toLowerCase(Locale.ROOT))); - assertThat(response.get().getLicenseInfo().getUid(), is(uid)); + licenseVerifier.accept(response.get().getLicenseInfo()); } else { assertThat(response.get().getLicenseInfo(), nullValue()); } @@ -136,7 +181,6 @@ public class TransportXPackInfoActionTests extends ESTestCase { } else { assertThat(response.get().getFeatureSetsInfo(), nullValue()); } - } } diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/api/xpack.info.json b/x-pack/plugin/src/test/resources/rest-api-spec/api/xpack.info.json index 2ccb1beda7d..b9fcd27aa26 100644 --- a/x-pack/plugin/src/test/resources/rest-api-spec/api/xpack.info.json +++ b/x-pack/plugin/src/test/resources/rest-api-spec/api/xpack.info.json @@ -19,6 +19,10 @@ "categories":{ "type":"list", "description":"Comma-separated list of info categories. Can be any of: build, license, features" + }, + "accept_enterprise":{ + "type":"boolean", + "description":"If an enterprise license is installed, return the type and mode as 'enterprise' (default: false)" } } } diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/license/30_enterprise_license.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/license/30_enterprise_license.yml index 1d2470b5c4b..d275c2d1bf8 100644 --- a/x-pack/plugin/src/test/resources/rest-api-spec/test/license/30_enterprise_license.yml +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/license/30_enterprise_license.yml @@ -27,6 +27,13 @@ teardown: - match: { license.type: "platinum" } - match: { license.max_nodes: 50 } + ## Check the xpack info API as well + - do: + xpack.info: {} + - match: { license.type: "platinum" } + - match: { license.mode: "platinum" } + + ## Check the opt-in v5 license type - do: license.get: accept_enterprise: true @@ -39,6 +46,13 @@ teardown: - match: { license.max_resource_units: 50 } - match: { license.max_nodes: null } + ## Check the xpack info API as well + - do: + xpack.info: + accept_enterprise: true + - match: { license.type: "enterprise" } + - match: { license.mode: "enterprise" } + - do: license.get: accept_enterprise: false @@ -49,3 +63,11 @@ teardown: ## opt-out of real enterprise type - match: { license.type: "platinum" } - match: { license.max_nodes: 50 } + + ## Check the xpack info API as well + - do: + xpack.info: + accept_enterprise: false + - match: { license.type: "platinum" } + - match: { license.mode: "platinum" } + diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/xpack/20_info.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/xpack/20_info.yml new file mode 100644 index 00000000000..1af19aed9a2 --- /dev/null +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/xpack/20_info.yml @@ -0,0 +1,9 @@ +"XPack Info API": + - do: + xpack.info: {} + + - match: { license.type: "trial" } + - match: { license.mode: "trial" } + - match: { license.status: "active" } + + diff --git a/x-pack/qa/rolling-upgrade-multi-cluster/src/test/java/org/elasticsearch/upgrades/CcrRollingUpgradeIT.java b/x-pack/qa/rolling-upgrade-multi-cluster/src/test/java/org/elasticsearch/upgrades/CcrRollingUpgradeIT.java index 3c8fff8a9b3..172f0474ed7 100644 --- a/x-pack/qa/rolling-upgrade-multi-cluster/src/test/java/org/elasticsearch/upgrades/CcrRollingUpgradeIT.java +++ b/x-pack/qa/rolling-upgrade-multi-cluster/src/test/java/org/elasticsearch/upgrades/CcrRollingUpgradeIT.java @@ -13,6 +13,7 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.ObjectPath; import org.elasticsearch.common.xcontent.support.XContentMapValues; +import org.junit.Before; import java.io.IOException; import java.util.Map; @@ -23,6 +24,11 @@ import static org.hamcrest.Matchers.equalTo; public class CcrRollingUpgradeIT extends AbstractMultiClusterUpgradeTestCase { + @Before + public void skipForBackportOf58217() { + assumeFalse("Skip while back-porting #58217", UPGRADE_FROM_VERSION.equals(Version.V_7_8_1)); + } + public void testUniDirectionalIndexFollowing() throws Exception { logger.info("clusterName={}, upgradeState={}", clusterName, upgradeState);