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.
This commit is contained in:
Tim Vernum 2020-06-30 16:42:28 +10:00 committed by GitHub
parent 8341ebc061
commit dcc5a06dec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 149 additions and 27 deletions

View File

@ -5,10 +5,12 @@
*/ */
package org.elasticsearch.protocol.xpack; package org.elasticsearch.protocol.xpack;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.ActionRequestValidationException;
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;
import org.elasticsearch.license.License;
import java.io.IOException; import java.io.IOException;
import java.util.EnumSet; import java.util.EnumSet;
@ -40,8 +42,10 @@ public class XPackInfoRequest extends ActionRequest {
private boolean verbose; private boolean verbose;
private EnumSet<Category> categories = EnumSet.noneOf(Category.class); private EnumSet<Category> categories = EnumSet.noneOf(Category.class);
private int licenseVersion = License.VERSION_CURRENT;
public XPackInfoRequest() {} public XPackInfoRequest() {
}
public XPackInfoRequest(StreamInput in) throws IOException { public XPackInfoRequest(StreamInput in) throws IOException {
// NOTE: this does *not* call super, THIS IS A BUG // 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())); categories.add(Category.valueOf(in.readString()));
} }
this.categories = categories; this.categories = categories;
if (in.getVersion().onOrAfter(Version.V_7_8_1)) {
this.licenseVersion = in.readVInt();
}
} }
public void setVerbose(boolean verbose) { public void setVerbose(boolean verbose) {
@ -70,6 +77,14 @@ public class XPackInfoRequest extends ActionRequest {
return categories; return categories;
} }
public int getLicenseVersion() {
return licenseVersion;
}
public void setLicenseVersion(int licenseVersion) {
this.licenseVersion = licenseVersion;
}
@Override @Override
public ActionRequestValidationException validate() { public ActionRequestValidationException validate() {
return null; return null;
@ -82,5 +97,8 @@ public class XPackInfoRequest extends ActionRequest {
for (Category category : categories) { for (Category category : categories) {
out.writeString(category.name()); out.writeString(category.name());
} }
if (out.getVersion().onOrAfter(Version.V_7_8_1)) {
out.writeVInt(this.licenseVersion);
}
} }
} }

View File

@ -49,8 +49,18 @@ public class TransportXPackInfoAction extends HandledTransportAction<XPackInfoRe
if (request.getCategories().contains(XPackInfoRequest.Category.LICENSE)) { if (request.getCategories().contains(XPackInfoRequest.Category.LICENSE)) {
License license = licenseService.getLicense(); License license = licenseService.getLicense();
if (license != null) { if (license != null) {
licenseInfo = new LicenseInfo(license.uid(), license.type(), license.operationMode().description(), String type = license.type();
license.status(), license.expiryDate()); License.OperationMode mode = license.operationMode();
if (request.getLicenseVersion() < License.VERSION_ENTERPRISE) {
if (License.LicenseType.ENTERPRISE.getTypeName().equals(type)) {
type = License.LicenseType.PLATINUM.getTypeName();
}
if (mode == License.OperationMode.ENTERPRISE) {
mode = License.OperationMode.PLATINUM;
}
}
licenseInfo = new LicenseInfo(license.uid(), type, mode.description(), license.status(),
license.expiryDate());
} }
} }

View File

@ -33,4 +33,8 @@ public class XPackInfoRequestBuilder extends ActionRequestBuilder<XPackInfoReque
return this; return this;
} }
public XPackInfoRequestBuilder setLicenseVersion(int licenseVersion) {
request.setLicenseVersion(licenseVersion);
return this;
}
} }

View File

@ -5,6 +5,7 @@
*/ */
package org.elasticsearch.xpack.core.rest.action; package org.elasticsearch.xpack.core.rest.action;
import org.elasticsearch.license.License;
import org.elasticsearch.protocol.xpack.XPackInfoRequest; import org.elasticsearch.protocol.xpack.XPackInfoRequest;
import org.elasticsearch.rest.RestRequest; import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.action.RestToXContentListener; import org.elasticsearch.rest.action.RestToXContentListener;
@ -36,16 +37,20 @@ public class RestXPackInfoAction extends XPackRestHandler {
@Override @Override
public RestChannelConsumer doPrepareRequest(RestRequest request, XPackClient client) throws IOException { public RestChannelConsumer doPrepareRequest(RestRequest request, XPackClient client) throws IOException {
// we piggyback verbosity on "human" output // we piggyback verbosity on "human" output
boolean verbose = request.paramAsBoolean("human", true); boolean verbose = request.paramAsBoolean("human", true);
// Hide enterprise licenses by default, there is an opt-in flag to show them
final boolean acceptEnterprise = request.paramAsBoolean("accept_enterprise", false);
final int licenseVersion = acceptEnterprise ? License.VERSION_CURRENT : License.VERSION_CRYPTO_ALGORITHMS;
EnumSet<XPackInfoRequest.Category> categories = XPackInfoRequest.Category EnumSet<XPackInfoRequest.Category> categories = XPackInfoRequest.Category
.toSet(request.paramAsStringArray("categories", new String[] { "_all" })); .toSet(request.paramAsStringArray("categories", new String[] { "_all" }));
return channel -> return channel ->
client.prepareInfo() client.prepareInfo()
.setVerbose(verbose) .setVerbose(verbose)
.setCategories(categories) .setCategories(categories)
.setLicenseVersion(licenseVersion)
.execute(new RestToXContentListener<>(channel)); .execute(new RestToXContentListener<>(channel));
} }
} }

View File

@ -29,6 +29,7 @@ import java.util.Set;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasKey; import static org.hamcrest.Matchers.hasKey;
@ -41,7 +42,69 @@ import static org.mockito.Mockito.when;
public class TransportXPackInfoActionTests extends ESTestCase { public class TransportXPackInfoActionTests extends ESTestCase {
public void testDoExecute() throws Exception { public void testDoExecute() throws Exception {
EnumSet<XPackInfoRequest.Category> 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<XPackInfoRequest.Category> 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<XPackInfoRequest.Category> 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<XPackInfoRequest.Category> categories, int licenseVersion, License license,
Consumer<XPackInfoResponse.LicenseInfo> licenseVerifier) throws InterruptedException {
LicenseService licenseService = mock(LicenseService.class); LicenseService licenseService = mock(LicenseService.class);
final Set<XPackFeatureSet> featureSets = new HashSet<>(); final Set<XPackFeatureSet> featureSets = new HashSet<>();
@ -59,28 +122,14 @@ public class TransportXPackInfoActionTests extends ESTestCase {
TransportXPackInfoAction action = new TransportXPackInfoAction(transportService, mock(ActionFilters.class), TransportXPackInfoAction action = new TransportXPackInfoAction(transportService, mock(ActionFilters.class),
licenseService, featureSets); 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); when(licenseService.getLicense()).thenReturn(license);
XPackInfoRequest request = new XPackInfoRequest(); XPackInfoRequest request = new XPackInfoRequest();
request.setVerbose(randomBoolean()); request.setVerbose(randomBoolean());
EnumSet<XPackInfoRequest.Category> 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); request.setCategories(categories);
if (licenseVersion != -1) {
request.setLicenseVersion(licenseVersion);
}
final CountDownLatch latch = new CountDownLatch(1); final CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<XPackInfoResponse> response = new AtomicReference<>(); final AtomicReference<XPackInfoResponse> response = new AtomicReference<>();
@ -114,11 +163,7 @@ public class TransportXPackInfoActionTests extends ESTestCase {
if (request.getCategories().contains(XPackInfoRequest.Category.LICENSE)) { if (request.getCategories().contains(XPackInfoRequest.Category.LICENSE)) {
assertThat(response.get().getLicenseInfo(), notNullValue()); assertThat(response.get().getLicenseInfo(), notNullValue());
assertThat(response.get().getLicenseInfo().getExpiryDate(), is(expiryDate)); licenseVerifier.accept(response.get().getLicenseInfo());
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));
} else { } else {
assertThat(response.get().getLicenseInfo(), nullValue()); assertThat(response.get().getLicenseInfo(), nullValue());
} }
@ -136,7 +181,6 @@ public class TransportXPackInfoActionTests extends ESTestCase {
} else { } else {
assertThat(response.get().getFeatureSetsInfo(), nullValue()); assertThat(response.get().getFeatureSetsInfo(), nullValue());
} }
} }
} }

View File

@ -19,6 +19,10 @@
"categories":{ "categories":{
"type":"list", "type":"list",
"description":"Comma-separated list of info categories. Can be any of: build, license, features" "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)"
} }
} }
} }

View File

@ -27,6 +27,13 @@ teardown:
- match: { license.type: "platinum" } - match: { license.type: "platinum" }
- match: { license.max_nodes: 50 } - 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: - do:
license.get: license.get:
accept_enterprise: true accept_enterprise: true
@ -39,6 +46,13 @@ teardown:
- match: { license.max_resource_units: 50 } - match: { license.max_resource_units: 50 }
- match: { license.max_nodes: null } - 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: - do:
license.get: license.get:
accept_enterprise: false accept_enterprise: false
@ -49,3 +63,11 @@ teardown:
## opt-out of real enterprise type ## opt-out of real enterprise type
- match: { license.type: "platinum" } - match: { license.type: "platinum" }
- match: { license.max_nodes: 50 } - 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" }

View File

@ -0,0 +1,9 @@
"XPack Info API":
- do:
xpack.info: {}
- match: { license.type: "trial" }
- match: { license.mode: "trial" }
- match: { license.status: "active" }

View File

@ -13,6 +13,7 @@ import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ObjectPath; import org.elasticsearch.common.xcontent.ObjectPath;
import org.elasticsearch.common.xcontent.support.XContentMapValues; import org.elasticsearch.common.xcontent.support.XContentMapValues;
import org.junit.Before;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
@ -23,6 +24,11 @@ import static org.hamcrest.Matchers.equalTo;
public class CcrRollingUpgradeIT extends AbstractMultiClusterUpgradeTestCase { 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 { public void testUniDirectionalIndexFollowing() throws Exception {
logger.info("clusterName={}, upgradeState={}", clusterName, upgradeState); logger.info("clusterName={}, upgradeState={}", clusterName, upgradeState);