Add max_resource_units to enterprise license (#50910)

The enterprise license type must have "max_resource_units" and may not
have "max_nodes".

This change adds support for this new field, validation that the field
is present if-and-only-if the license is enterprise and bumps the
license version number to reflect the new field.

Includes a BWC layer to return "max_nodes: ${max_resource_units}" in
the GET license API.

Backport of: #50735
This commit is contained in:
Tim Vernum 2020-01-14 12:37:05 +11:00 committed by GitHub
parent f0924e6d5b
commit c2acb8830a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 314 additions and 91 deletions

View File

@ -113,13 +113,19 @@ public class License implements ToXContentObject {
static boolean isTrial(String typeName) {
return TRIAL.getTypeName().equals(typeName);
}
static boolean isEnterprise(String typeName) {
return ENTERPRISE.getTypeName().equals(typeName);
}
}
public static final int VERSION_START = 1;
public static final int VERSION_NO_FEATURE_TYPE = 2;
public static final int VERSION_START_DATE = 3;
public static final int VERSION_CRYPTO_ALGORITHMS = 4;
public static final int VERSION_CURRENT = VERSION_CRYPTO_ALGORITHMS;
public static final int VERSION_ENTERPRISE = 5;
public static final int VERSION_CURRENT = VERSION_ENTERPRISE;
/**
* XContent param name to deserialize license(s) with
@ -159,13 +165,14 @@ public class License implements ToXContentObject {
private final long expiryDate;
private final long startDate;
private final int maxNodes;
private final int maxResourceUnits;
private final OperationMode operationMode;
/**
* Decouples operation mode of a license from the license type value.
* <p>
* Note: The mode indicates features that should be made available, but it does not indicate whether the license is active!
*
* <p>
* The id byte is used for ordering operation modes
*/
public enum OperationMode {
@ -182,13 +189,16 @@ public class License implements ToXContentObject {
this.id = id;
}
/** Returns non-zero positive number when <code>opMode1</code> is greater than <code>opMode2</code> */
/**
* Returns non-zero positive number when <code>opMode1</code> is greater than <code>opMode2</code>
*/
public static int compare(OperationMode opMode1, OperationMode opMode2) {
return Integer.compare(opMode1.id, opMode2.id);
}
/**
* Determine the operating mode for a license type
*
* @see LicenseType#resolve(License)
* @see #parse(String)
*/
@ -217,6 +227,7 @@ public class License implements ToXContentObject {
* Parses an {@code OperatingMode} from a String.
* The string must name an operating mode, and not a licensing level (that is, it cannot parse old style license levels
* such as "dev" or "silver").
*
* @see #description()
*/
public static OperationMode parse(String mode) {
@ -233,8 +244,8 @@ public class License implements ToXContentObject {
}
}
private License(int version, String uid, String issuer, String issuedTo, long issueDate, String type,
String subscriptionType, String feature, String signature, long expiryDate, int maxNodes, long startDate) {
private License(int version, String uid, String issuer, String issuedTo, long issueDate, String type, String subscriptionType,
String feature, String signature, long expiryDate, int maxNodes, int maxResourceUnits, long startDate) {
this.version = version;
this.uid = uid;
this.issuer = issuer;
@ -252,6 +263,7 @@ public class License implements ToXContentObject {
this.expiryDate = expiryDate;
}
this.maxNodes = maxNodes;
this.maxResourceUnits = maxResourceUnits;
this.startDate = startDate;
this.operationMode = OperationMode.resolve(LicenseType.resolve(this));
validate();
@ -300,12 +312,21 @@ public class License implements ToXContentObject {
}
/**
* @return the maximum number of nodes this license has been issued for
* @return the maximum number of nodes this license has been issued for, or {@code -1} if this license is not node based.
*/
public int maxNodes() {
return maxNodes;
}
/**
* @return the maximum number of "resource units" this license has been issued for, or {@code -1} if this license is not resource based.
* A "resource unit" is a measure of computing power (RAM/CPU), the definition of which is maintained outside of the license format,
* or this class.
*/
public int maxResourceUnits() {
return maxResourceUnits;
}
/**
* @return a string representing the entity this licenses has been issued to
*/
@ -392,20 +413,39 @@ public class License implements ToXContentObject {
throw new IllegalStateException("uid can not be null");
} else if (feature == null && version == VERSION_START) {
throw new IllegalStateException("feature can not be null");
} else if (maxNodes == -1) {
throw new IllegalStateException("maxNodes has to be set");
} else if (expiryDate == -1) {
throw new IllegalStateException("expiryDate has to be set");
} else if (expiryDate == LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS && LicenseType.isBasic(type) == false) {
throw new IllegalStateException("only basic licenses are allowed to have no expiration");
}
if (LicenseType.isEnterprise(type) && version < VERSION_ENTERPRISE) {
throw new IllegalStateException("license type [" + type + "] is not a valid for version [" + version + "] licenses");
}
validateLimits(type, maxNodes, maxResourceUnits);
}
private static void validateLimits(String type, int maxNodes, int maxResourceUnits) {
if (LicenseType.isEnterprise(type)) {
if (maxResourceUnits == -1) {
throw new IllegalStateException("maxResourceUnits must be set for enterprise licenses (type=[" + type + "])");
} else if (maxNodes != -1) {
throw new IllegalStateException("maxNodes may not be set for enterprise licenses (type=[" + type + "])");
}
} else {
if (maxNodes == -1) {
throw new IllegalStateException("maxNodes has to be set");
} else if (maxResourceUnits != -1) {
throw new IllegalStateException("maxResourceUnits may only be set for enterprise licenses (not permitted for type=[" +
type + "])");
}
}
}
public static License readLicense(StreamInput in) throws IOException {
int version = in.readVInt(); // Version for future extensibility
if (version > VERSION_CURRENT) {
throw new ElasticsearchException("Unknown license version found, please upgrade all nodes to the latest elasticsearch-license" +
" plugin");
throw new ElasticsearchException("Unknown license version found, please upgrade all nodes to the latest elasticsearch release");
}
Builder builder = builder();
builder.version(version);
@ -420,6 +460,9 @@ public class License implements ToXContentObject {
}
builder.expiryDate(in.readLong());
builder.maxNodes(in.readInt());
if (version >= VERSION_ENTERPRISE) {
builder.maxResourceUnits(in.readInt());
}
builder.issuedTo(in.readString());
builder.issuer(in.readString());
builder.signature(in.readOptionalString());
@ -442,6 +485,9 @@ public class License implements ToXContentObject {
}
out.writeLong(expiryDate);
out.writeInt(maxNodes);
if (version >= VERSION_ENTERPRISE) {
out.writeInt(maxResourceUnits);
}
out.writeString(issuedTo);
out.writeString(issuer);
out.writeOptionalString(signature);
@ -506,7 +552,16 @@ public class License implements ToXContentObject {
if (expiryDate != LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS) {
builder.timeField(Fields.EXPIRY_DATE_IN_MILLIS, Fields.EXPIRY_DATE, expiryDate);
}
builder.field(Fields.MAX_NODES, maxNodes);
if (version >= VERSION_ENTERPRISE) {
builder.field(Fields.MAX_NODES, maxNodes == -1 ? null : maxNodes);
builder.field(Fields.MAX_RESOURCE_UNITS, maxResourceUnits == -1 ? null : maxResourceUnits);
} else if (hideEnterprise && maxNodes == -1) {
builder.field(Fields.MAX_NODES, maxResourceUnits);
} else {
builder.field(Fields.MAX_NODES, maxNodes);
}
builder.field(Fields.ISSUED_TO, issuedTo);
builder.field(Fields.ISSUER, issuer);
if (!licenseSpecMode && !restViewMode && signature != null) {
@ -551,6 +606,8 @@ public class License implements ToXContentObject {
builder.startDate(parser.longValue());
} else if (Fields.MAX_NODES.equals(currentFieldName)) {
builder.maxNodes(parser.intValue());
} else if (Fields.MAX_RESOURCE_UNITS.equals(currentFieldName)) {
builder.maxResourceUnits(parser.intValue());
} else if (Fields.ISSUED_TO.equals(currentFieldName)) {
builder.issuedTo(parser.text());
} else if (Fields.ISSUER.equals(currentFieldName)) {
@ -593,7 +650,7 @@ public class License implements ToXContentObject {
throw new ElasticsearchException("malformed signature for license [" + builder.uid + "]");
} else if (version > VERSION_CURRENT) {
throw new ElasticsearchException("Unknown license version found, please upgrade all nodes to the latest " +
"elasticsearch-license plugin");
"elasticsearch-license plugin");
}
// signature version is the source of truth
builder.version(version);
@ -625,8 +682,7 @@ public class License implements ToXContentObject {
// EMPTY is safe here because we don't call namedObject
try (InputStream byteStream = bytes.streamInput();
XContentParser parser = xContentType.xContent()
.createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, byteStream))
{
.createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, byteStream)) {
License license = null;
if (parser.nextToken() == XContentParser.Token.START_OBJECT) {
if (parser.nextToken() == XContentParser.Token.FIELD_NAME) {
@ -675,7 +731,7 @@ public class License implements ToXContentObject {
if (issueDate != license.issueDate) return false;
if (expiryDate != license.expiryDate) return false;
if (startDate!= license.startDate) return false;
if (startDate != license.startDate) return false;
if (maxNodes != license.maxNodes) return false;
if (version != license.version) return false;
if (uid != null ? !uid.equals(license.uid) : license.uid != null) return false;
@ -700,7 +756,7 @@ public class License implements ToXContentObject {
result = 31 * result + (feature != null ? feature.hashCode() : 0);
result = 31 * result + (signature != null ? signature.hashCode() : 0);
result = 31 * result + (int) (expiryDate ^ (expiryDate >>> 32));
result = 31 * result + (int) (startDate ^ (startDate>>> 32));
result = 31 * result + (int) (startDate ^ (startDate >>> 32));
result = 31 * result + maxNodes;
result = 31 * result + version;
return result;
@ -719,6 +775,7 @@ public class License implements ToXContentObject {
public static final String START_DATE_IN_MILLIS = "start_date_in_millis";
public static final String START_DATE = "start_date";
public static final String MAX_NODES = "max_nodes";
public static final String MAX_RESOURCE_UNITS = "max_resource_units";
public static final String ISSUED_TO = "issued_to";
public static final String ISSUER = "issuer";
public static final String VERSION = "version";
@ -762,6 +819,7 @@ public class License implements ToXContentObject {
private long expiryDate = -1;
private long startDate = -1;
private int maxNodes = -1;
private int maxResourceUnits = -1;
public Builder uid(String uid) {
this.uid = uid;
@ -817,6 +875,11 @@ public class License implements ToXContentObject {
return this;
}
public Builder maxResourceUnits(int maxUnits) {
this.maxResourceUnits = maxUnits;
return this;
}
public Builder signature(String signature) {
if (signature != null) {
this.signature = signature;
@ -831,17 +894,18 @@ public class License implements ToXContentObject {
public Builder fromLicenseSpec(License license, String signature) {
return uid(license.uid())
.version(license.version())
.issuedTo(license.issuedTo())
.issueDate(license.issueDate())
.startDate(license.startDate())
.type(license.type())
.subscriptionType(license.subscriptionType)
.feature(license.feature)
.maxNodes(license.maxNodes())
.expiryDate(license.expiryDate())
.issuer(license.issuer())
.signature(signature);
.version(license.version())
.issuedTo(license.issuedTo())
.issueDate(license.issueDate())
.startDate(license.startDate())
.type(license.type())
.subscriptionType(license.subscriptionType)
.feature(license.feature)
.maxNodes(license.maxNodes())
.maxResourceUnits(license.maxResourceUnits())
.expiryDate(license.expiryDate())
.issuer(license.issuer())
.signature(signature);
}
/**
@ -850,15 +914,15 @@ public class License implements ToXContentObject {
*/
public Builder fromPre20LicenseSpec(License pre20License) {
return uid(pre20License.uid())
.issuedTo(pre20License.issuedTo())
.issueDate(pre20License.issueDate())
.maxNodes(pre20License.maxNodes())
.expiryDate(pre20License.expiryDate());
.issuedTo(pre20License.issuedTo())
.issueDate(pre20License.issueDate())
.maxNodes(pre20License.maxNodes())
.expiryDate(pre20License.expiryDate());
}
public License build() {
return new License(version, uid, issuer, issuedTo, issueDate, type,
subscriptionType, feature, signature, expiryDate, maxNodes, startDate);
subscriptionType, feature, signature, expiryDate, maxNodes, maxResourceUnits, startDate);
}
public Builder validate() {
@ -874,11 +938,10 @@ public class License implements ToXContentObject {
throw new IllegalStateException("uid can not be null");
} else if (signature == null) {
throw new IllegalStateException("signature can not be null");
} else if (maxNodes == -1) {
throw new IllegalStateException("maxNodes has to be set");
} else if (expiryDate == -1) {
throw new IllegalStateException("expiryDate has to be set");
}
validateLimits(type, maxNodes, maxResourceUnits);
return this;
}

View File

@ -122,6 +122,7 @@ public class LicenseService extends AbstractLifecycleComponent implements Cluste
* Max number of nodes licensed by generated trial license
*/
static final int SELF_GENERATED_LICENSE_MAX_NODES = 1000;
static final int SELF_GENERATED_LICENSE_MAX_RESOURCE_UNITS = SELF_GENERATED_LICENSE_MAX_NODES;
public static final String LICENSE_JOB = "licenseJob";
@ -292,11 +293,8 @@ public class LicenseService extends AbstractLifecycleComponent implements Cluste
}
private static boolean licenseIsCompatible(License license, Version version) {
if (License.LicenseType.ENTERPRISE.getTypeName().equalsIgnoreCase(license.type())) {
return version.onOrAfter(Version.V_7_6_0);
} else {
return true;
}
final int maxVersion = LicenseUtils.getMaxLicenseVersion(version);
return license.version() <= maxVersion;
}
private boolean isAllowedLicenseType(License.LicenseType type) {

View File

@ -11,8 +11,6 @@ import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.license.License.LicenseType;
import org.elasticsearch.rest.RestStatus;
import java.util.stream.StreamSupport;
public class LicenseUtils {
public static final String EXPIRED_FEATURE_METADATA = "es.license.expired.feature";
@ -49,25 +47,30 @@ public class LicenseUtils {
* recreated with the new key
*/
public static boolean signatureNeedsUpdate(License license, DiscoveryNodes currentNodes) {
assert License.VERSION_CRYPTO_ALGORITHMS == License.VERSION_CURRENT : "update this method when adding a new version";
assert License.VERSION_ENTERPRISE == License.VERSION_CURRENT : "update this method when adding a new version";
String typeName = license.type();
return (LicenseType.isBasic(typeName) || LicenseType.isTrial(typeName)) &&
// only upgrade signature when all nodes are ready to deserialize the new signature
(license.version() < License.VERSION_CRYPTO_ALGORITHMS &&
compatibleLicenseVersion(currentNodes) == License.VERSION_CRYPTO_ALGORITHMS
compatibleLicenseVersion(currentNodes) >= License.VERSION_CRYPTO_ALGORITHMS
);
}
public static int compatibleLicenseVersion(DiscoveryNodes currentNodes) {
assert License.VERSION_CRYPTO_ALGORITHMS == License.VERSION_CURRENT : "update this method when adding a new version";
if (StreamSupport.stream(currentNodes.spliterator(), false)
.allMatch(node -> node.getVersion().onOrAfter(Version.V_6_4_0))) {
// License.VERSION_CRYPTO_ALGORITHMS was introduced in 6.4.0
return License.VERSION_CRYPTO_ALGORITHMS;
} else {
return License.VERSION_START_DATE;
}
return getMaxLicenseVersion(currentNodes.getMinNodeVersion());
}
public static int getMaxLicenseVersion(Version version){
if (version != null) {
if (version.before(Version.V_6_4_0)) {
return License.VERSION_START_DATE;
}
if (version.before(Version.V_7_6_0)) {
return License.VERSION_CRYPTO_ALGORITHMS;
}
}
assert License.VERSION_ENTERPRISE == License.VERSION_CURRENT : "update this method when adding a new version";
return License.VERSION_ENTERPRISE;
}
}

View File

@ -51,12 +51,13 @@ public class RestGetLicenseAction extends XPackRestHandler {
*/
@Override
public RestChannelConsumer doPrepareRequest(final RestRequest request, final XPackClient client) throws IOException {
final Map<String, String> overrideParams = new HashMap<>(3);
overrideParams.put(License.REST_VIEW_MODE, "true");
overrideParams.put(License.LICENSE_VERSION_MODE, String.valueOf(License.VERSION_CURRENT));
// Hide enterprise licenses by default, there is an opt-in flag to show them
final boolean hideEnterprise = request.paramAsBoolean("accept_enterprise", false) == false;
final int licenseVersion = hideEnterprise ? License.VERSION_CRYPTO_ALGORITHMS : License.VERSION_CURRENT;
final Map<String, String> overrideParams = new HashMap<>(3);
overrideParams.put(License.REST_VIEW_MODE, "true");
overrideParams.put(License.LICENSE_VERSION_MODE, String.valueOf(licenseVersion));
overrideParams.put(License.XCONTENT_HIDE_ENTERPRISE, String.valueOf(hideEnterprise));
final ToXContent.Params params = new ToXContent.DelegatingMapParams(overrideParams, request);

View File

@ -75,10 +75,14 @@ public class StartTrialClusterTask extends ClusterStateUpdateTask {
License.Builder specBuilder = License.builder()
.uid(UUID.randomUUID().toString())
.issuedTo(clusterName)
.maxNodes(LicenseService.SELF_GENERATED_LICENSE_MAX_NODES)
.issueDate(issueDate)
.type(request.getType())
.expiryDate(expiryDate);
if (License.LicenseType.isEnterprise(request.getType())) {
specBuilder.maxResourceUnits(LicenseService.SELF_GENERATED_LICENSE_MAX_RESOURCE_UNITS);
} else {
specBuilder.maxNodes(LicenseService.SELF_GENERATED_LICENSE_MAX_NODES);
}
License selfGeneratedLicense = SelfGeneratedLicense.create(specBuilder, currentState.nodes());
LicensesMetaData newLicensesMetaData = new LicensesMetaData(selfGeneratedLicense, Version.CURRENT);
mdBuilder.putCustom(LicensesMetaData.TYPE, newLicensesMetaData);

View File

@ -70,6 +70,7 @@ public abstract class AbstractLicenseServiceTestCase extends ESTestCase {
when(discoveryNodes.getMasterNode()).thenReturn(mockNode);
when(discoveryNodes.spliterator()).thenReturn(Arrays.asList(mockNode).spliterator());
when(discoveryNodes.isLocalNodeElectedMaster()).thenReturn(false);
when(discoveryNodes.getMinNodeVersion()).thenReturn(mockNode.getVersion());
when(state.nodes()).thenReturn(discoveryNodes);
when(state.getNodes()).thenReturn(discoveryNodes); // it is really ridiculous we have nodes() and getNodes()...
when(clusterService.state()).thenReturn(state);

View File

@ -35,15 +35,19 @@ public class LicenseOperationModeUpdateTests extends ESTestCase {
public void testLicenseOperationModeUpdate() throws Exception {
License.LicenseType type = randomFrom(License.LicenseType.values());
License license = License.builder()
.uid("id")
.expiryDate(0)
.issueDate(0)
.issuedTo("elasticsearch")
.issuer("issuer")
.type(type)
.maxNodes(1)
.build();
final License.Builder licenseBuilder = License.builder()
.uid("id")
.expiryDate(0)
.issueDate(0)
.issuedTo("elasticsearch")
.issuer("issuer")
.type(type);
if (type == License.LicenseType.ENTERPRISE) {
licenseBuilder.maxResourceUnits(1);
} else {
licenseBuilder.maxNodes(1);
}
License license = licenseBuilder.build();
assertThat(license.operationMode(), equalTo(License.OperationMode.resolve(type)));
OperationModeFileWatcherTests.writeMode("gold", licenseModeFile);

View File

@ -190,7 +190,8 @@ public class LicenseServiceTests extends ESTestCase {
.issuer(randomAlphaOfLengthBetween(5, 60))
.issuedTo(randomAlphaOfLengthBetween(5, 60))
.issueDate(System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(randomLongBetween(1, 5000)))
.maxNodes(randomIntBetween(1, 500))
.maxNodes(type == License.LicenseType.ENTERPRISE ? -1 : randomIntBetween(1, 500))
.maxResourceUnits(type == License.LicenseType.ENTERPRISE ? randomIntBetween(10, 500) : -1)
.signature(null)
.build();
}

View File

@ -6,13 +6,18 @@
package org.elasticsearch.license;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.Version;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.UUIDs;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.TestMatchers;
import org.hamcrest.Matchers;
import java.nio.BufferUnderflowException;
import java.nio.charset.StandardCharsets;
@ -27,7 +32,7 @@ import static org.hamcrest.Matchers.notNullValue;
public class LicenseTests extends ESTestCase {
public void testFromXContent() throws Exception {
public void testFromXContentForGoldLicenseWithVersion2Signature() throws Exception {
String licenseString = "{\"license\":" +
"{\"uid\":\"4056779d-b823-4c12-a9cb-efa4a8d8c422\"," +
"\"type\":\"gold\"," +
@ -51,27 +56,107 @@ public class LicenseTests extends ESTestCase {
assertThat(license.issuedTo(), equalTo("customer"));
assertThat(license.expiryDate(), equalTo(1546596340459L));
assertThat(license.issueDate(), equalTo(1546589020459L));
assertThat(license.maxNodes(), equalTo(5));
assertThat(license.maxResourceUnits(), equalTo(-1));
assertThat(license.version(), equalTo(2));
}
public void testFromXContentForGoldLicenseWithVersion4Signature() throws Exception {
String licenseString = "{\"license\":{" +
"\"uid\":\"4056779d-b823-4c12-a9cb-efa4a8d8c422\"," +
"\"type\":\"gold\"," +
"\"issue_date_in_millis\":1546589020459," +
"\"expiry_date_in_millis\":1546596340459," +
"\"max_nodes\":5," +
"\"issued_to\":\"customer\"," +
"\"issuer\":\"elasticsearch\"," +
"\"signature\":\"AAAABAAAAA22vXffI41oM4jLCwZ6AAAAIAo5/x6hrsGh1GqqrJmy4qgmEC7gK0U4zQ6q5ZEMhm4jAAABAH3oL4weubwYGjLGNZsz90" +
"EerX6yOX3Dh6wswG9EfqCiyv6lcjuC7aeKKuOkqhMRTHZ9vHnfMuakHWVlpuGC14WyGqaMwSmgTZ9jVAzt/W3sIotRxM/3rtlCXUc1rOUXNFcii1i3Kkrc" +
"kTzhENTKjdkOmUN3qZlTEmHkp93eYpx8++iIukHYU9K9Vm2VKgydFfxvYaN/Qr+iPfJSbHJB8+DmS2ywdrmdqW+ScE+1ZNouPNhnP3RKTleNvixXPG9l5B" +
"qZ2So1IlCrxVDByA1E6JH5AvjbOucpcGiWCm7IzvfpkzphKHMyxhUaIByoHl9UAf4AdPLhowWAQk0eHMRDDlo=\"," +
"\"start_date_in_millis\":-1}}\n";
License license = License.fromSource(new BytesArray(licenseString.getBytes(StandardCharsets.UTF_8)),
XContentType.JSON);
assertThat(license.type(), equalTo("gold"));
assertThat(license.uid(), equalTo("4056779d-b823-4c12-a9cb-efa4a8d8c422"));
assertThat(license.issuer(), equalTo("elasticsearch"));
assertThat(license.issuedTo(), equalTo("customer"));
assertThat(license.expiryDate(), equalTo(1546596340459L));
assertThat(license.issueDate(), equalTo(1546589020459L));
assertThat(license.maxNodes(), equalTo(5));
assertThat(license.maxResourceUnits(), equalTo(-1));
assertThat(license.version(), equalTo(4));
}
public void testFromXContentForEnterpriseLicenseWithV5Signature() throws Exception {
String licenseString = "{\"license\":{" +
"\"uid\":\"4056779d-b823-4c12-a9cb-efa4a8d8c422\"," +
"\"type\":\"enterprise\"," +
"\"issue_date_in_millis\":1546589020459," +
"\"expiry_date_in_millis\":1546596340459," +
"\"max_nodes\":null," +
"\"max_resource_units\":15," +
"\"issued_to\":\"customer\"," +
"\"issuer\":\"elasticsearch\"," +
"\"signature\":\"AAAABQAAAA2MUoEqXb9K9Ie5d6JJAAAAIAo5/x6hrsGh1GqqrJmy4qgmEC7gK0U4zQ6q5ZEMhm4jAAABAAAwVZKGAmDELUlS5PScBkhQsZa" +
"DaQTtJ4ZP5EnZ/nLpmCt9Dj7d/FRsgMtHmSJLrr2CdrIo4Vx5VuhmbwzZvXMttLz2lrJzG7770PX3TnC9e7F9GdnE9ec0FP2U0ZlLOBOtPuirX0q+j6GfB+DLyE" +
"5D+Lo1NQ3eLJGvbd3DBYPWJxkb+EBVHczCH2OrIEVWnN/TafmkdZCPX5PcultkNOs3j7d3s7b51EXHKoye8UTcB/RGmzZwMah+E6I/VJkqu7UHL8bB01wJeqo6W" +
"xI4LC/9+f5kpmHrUu3CHe5pHbmMGDk7O6/cwt1pw/hnJXKIFCi36IGaKcHLgORxQdN0uzE=\"," +
"\"start_date_in_millis\":-1}}";
License license = License.fromSource(new BytesArray(licenseString.getBytes(StandardCharsets.UTF_8)),
XContentType.JSON);
assertThat(license.type(), equalTo("enterprise"));
assertThat(license.uid(), equalTo("4056779d-b823-4c12-a9cb-efa4a8d8c422"));
assertThat(license.issuer(), equalTo("elasticsearch"));
assertThat(license.issuedTo(), equalTo("customer"));
assertThat(license.expiryDate(), equalTo(1546596340459L));
assertThat(license.issueDate(), equalTo(1546589020459L));
assertThat(license.maxNodes(), equalTo(-1));
assertThat(license.maxResourceUnits(), equalTo(15));
assertThat(license.version(), equalTo(5));
}
public void testThatEnterpriseLicenseMayNotHaveMaxNodes() throws Exception {
License.Builder builder = randomLicense(License.LicenseType.ENTERPRISE)
.maxNodes(randomIntBetween(1, 50))
.maxResourceUnits(randomIntBetween(10, 500));
final IllegalStateException ex = expectThrows(IllegalStateException.class, builder::build);
assertThat(ex, TestMatchers.throwableWithMessage("maxNodes may not be set for enterprise licenses (type=[enterprise])"));
}
public void testThatEnterpriseLicenseMustHaveMaxResourceUnits() throws Exception {
License.Builder builder = randomLicense(License.LicenseType.ENTERPRISE)
.maxResourceUnits(-1);
final IllegalStateException ex = expectThrows(IllegalStateException.class, builder::build);
assertThat(ex, TestMatchers.throwableWithMessage("maxResourceUnits must be set for enterprise licenses (type=[enterprise])"));
}
public void testThatRegularLicensesMustHaveMaxNodes() throws Exception {
License.LicenseType type = randomValueOtherThan(License.LicenseType.ENTERPRISE, () -> randomFrom(License.LicenseType.values()));
License.Builder builder = randomLicense(type)
.maxNodes(-1);
final IllegalStateException ex = expectThrows(IllegalStateException.class, builder::build);
assertThat(ex, TestMatchers.throwableWithMessage("maxNodes has to be set"));
}
public void testThatRegularLicensesMayNotHaveMaxResourceUnits() throws Exception {
License.LicenseType type = randomValueOtherThan(License.LicenseType.ENTERPRISE, () -> randomFrom(License.LicenseType.values()));
License.Builder builder = randomLicense(type)
.maxResourceUnits(randomIntBetween(10, 500))
.maxNodes(randomIntBetween(1, 50));
final IllegalStateException ex = expectThrows(IllegalStateException.class, builder::build);
assertThat(ex, TestMatchers.throwableWithMessage("maxResourceUnits may only be set for enterprise licenses (not permitted " +
"for type=[" + type.getTypeName() + "])"));
}
public void testLicenseToAndFromXContentForEveryLicenseType() throws Exception {
for (License.LicenseType type : License.LicenseType.values()) {
final License license1 = License.builder()
.uid(UUIDs.randomBase64UUID(random()))
.type(type)
.issueDate(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(randomIntBetween(1, 10)))
.expiryDate(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(randomIntBetween(1, 1000)))
.maxNodes(randomIntBetween(1, 100))
.issuedTo(randomAlphaOfLengthBetween(5, 50))
.issuer(randomAlphaOfLengthBetween(5, 50))
final License license1 = randomLicense(type)
// We need a signature that parses correctly, but it doesn't need to verify
.signature("AAAAAgAAAA34V2kfTJVtvdL2LttwAAABmFJ6NGRnbEM3WVQrZVQwNkdKQmR1VytlMTMyM1J0dTZ1WGwyY2ZCVFhqMGtJU2gzZ3pnNTVpOW" +
"F5Y1NaUkwyN2VsTEtCYnlZR2c5WWtjQ0phaDlhRjlDUXViUmUwMWhjSkE2TFcwSGdneTJHbUV4N2RHUWJxV20ybjRsZHRzV2xkN0ZmdDlYblJmNVc" +
"xMlBWeU81V1hLUm1EK0V1dmF3cFdlSGZzTU5SZE1qUmFra3JkS1hCanBWVmVTaFFwV3BVZERzeG9Sci9rYnlJK2toODZXY09tNmFHUVNUL3IyUHEx" +
"V3VSTlBneWNJcFQ0bXl0cmhNNnRwbE1CWE4zWjJ5eGFuWFo0NGhsb3B5WFd1eTdYbFFWQkxFVFFPSlBERlB0eVVJYXVSZ0lsR2JpRS9rN1h4MSsvN" +
"UpOcGN6cU1NOHN1cHNtSTFIUGN1bWNGNEcxekhrblhNOXZ2VEQvYmRzQUFwbytUZEpRR3l6QU5oS2ZFSFdSbGxxNDZyZ0xvUHIwRjdBL2JqcnJnNG" +
"FlK09Cek9pYlJ5Umc9PQAAAQAth77fQLF7CCEL7wA6Z0/UuRm/weECcsjW/50kBnPLO8yEs+9/bPa5LSU0bF6byEXOVeO0ebUQfztpjulbXh8TrBD" +
"SG+6VdxGtohPo2IYPBaXzGs3LOOor6An/lhptxBWdwYmfbcp0m8mnXZh1vN9rmbTsZXnhBIoPTaRDwUBi3vJ3Ms3iLaEm4S8Slrfmtht2jUjgGZ2v" +
"AeZ9OHU2YsGtrSpz6f")
.signature("AAAABQAAAA2MUoEqXb9K9Ie5d6JJAAAAIAo5/x6hrsGh1GqqrJmy4qgmEC7gK0U4zQ6q5ZEMhm4jAAABAAAwVZKGAmDELUlS5PScBkhQsZa" +
"DaQTtJ4ZP5EnZ/nLpmCt9Dj7d/FRsgMtHmSJLrr2CdrIo4Vx5VuhmbwzZvXMttLz2lrJzG7770PX3TnC9e7F9GdnE9ec0FP2U0ZlLOBOtPuirX0q+j" +
"6GfB+DLyE5D+Lo1NQ3eLJGvbd3DBYPWJxkb+EBVHczCH2OrIEVWnN/TafmkdZCPX5PcultkNOs3j7d3s7b51EXHKoye8UTcB/RGmzZwMah+E6I/VJk" +
"qu7UHL8bB01wJeqo6WxI4LC/9+f5kpmHrUu3CHe5pHbmMGDk7O6/cwt1pw/hnJXKIFCi36IGaKcHLgORxQdN0uzE=")
.build();
XContentParser parser = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION,
Strings.toString(license1));
@ -83,6 +168,46 @@ public class LicenseTests extends ESTestCase {
assertThat(license2.issuedTo(), equalTo(license1.issuedTo()));
assertThat(license2.expiryDate(), equalTo(license1.expiryDate()));
assertThat(license2.issueDate(), equalTo(license1.issueDate()));
assertThat(license2.maxNodes(), equalTo(license1.maxNodes()));
assertThat(license2.maxResourceUnits(), equalTo(license1.maxResourceUnits()));
}
}
public void testSerializationOfLicenseForEveryLicenseType() throws Exception {
for (License.LicenseType type : License.LicenseType.values()) {
final String signature = randomBoolean() ? null : "AAAABQAAAA2MUoEqXb9K9Ie5d6JJAAAAIAo5/x6hrsGh1GqqrJmy4qgmEC7gK0U4zQ6q5ZEM" +
"hm4jAAABAAAwVZKGAmDELUlS5PScBkhQsZaDaQTtJ4ZP5EnZ/nLpmCt9Dj7d/FRsgMtHmSJLrr2CdrIo4Vx5VuhmbwzZvXMttLz2lrJzG7770PX3TnC9e7" +
"F9GdnE9ec0FP2U0ZlLOBOtPuirX0q+j6GfB+DLyE5D+Lo1NQ3eLJGvbd3DBYPWJxkb+EBVHczCH2OrIEVWnN/TafmkdZCPX5PcultkNOs3j7d3s7b51EXH" +
"Koye8UTcB/RGmzZwMah+E6I/VJkqu7UHL8bB01wJeqo6WxI4LC/9+f5kpmHrUu3CHe5pHbmMGDk7O6/cwt1pw/hnJXKIFCi36IGaKcHLgORxQdN0uzE=";
final int version;
if (type == License.LicenseType.ENTERPRISE) {
version = randomIntBetween(License.VERSION_ENTERPRISE, License.VERSION_CURRENT);
} else {
version = randomIntBetween(License.VERSION_NO_FEATURE_TYPE, License.VERSION_CURRENT);
}
final License license1 = randomLicense(type).signature(signature).version(version).build();
final BytesStreamOutput out = new BytesStreamOutput();
out.setVersion(Version.CURRENT);
license1.writeTo(out);
final StreamInput in = out.bytes().streamInput();
in.setVersion(Version.CURRENT);
final License license2 = License.readLicense(in);
assertThat(in.read(), Matchers.equalTo(-1));
assertThat(license2, notNullValue());
assertThat(license2.type(), equalTo(type.getTypeName()));
assertThat(license2.version(), equalTo(version));
assertThat(license2.signature(), equalTo(signature));
assertThat(license2.uid(), equalTo(license1.uid()));
assertThat(license2.issuer(), equalTo(license1.issuer()));
assertThat(license2.issuedTo(), equalTo(license1.issuedTo()));
assertThat(license2.expiryDate(), equalTo(license1.expiryDate()));
assertThat(license2.issueDate(), equalTo(license1.issueDate()));
assertThat(license2.maxNodes(), equalTo(license1.maxNodes()));
assertThat(license2.maxResourceUnits(), equalTo(license1.maxResourceUnits()));
}
}
@ -158,4 +283,17 @@ public class LicenseTests extends ESTestCase {
assertThat(exception.getMessage(), containsString("malformed signature for license [4056779d-b823-4c12-a9cb-efa4a8d8c422]"));
assertThat(exception.getCause(), instanceOf(IllegalArgumentException.class));
}
private License.Builder randomLicense(License.LicenseType type) {
return License.builder()
.uid(UUIDs.randomBase64UUID(random()))
.type(type)
.issueDate(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(randomIntBetween(1, 10)))
.expiryDate(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(randomIntBetween(1, 1000)))
.maxNodes(type == License.LicenseType.ENTERPRISE ? -1 : randomIntBetween(1, 100))
.maxResourceUnits(type == License.LicenseType.ENTERPRISE ? randomIntBetween(1, 100) : -1)
.issuedTo(randomAlphaOfLengthBetween(5, 50))
.issuer(randomAlphaOfLengthBetween(5, 50));
}
}

View File

@ -6,6 +6,7 @@
package org.elasticsearch.license;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.test.ESTestCase;
import java.util.Arrays;
@ -33,4 +34,8 @@ public class LicenseUtilsTests extends ESTestCase {
exception = new ElasticsearchSecurityException("msg");
assertFalse(LicenseUtils.isLicenseExpiredException(exception));
}
public void testVersionsUpToDate() {
assertThat(LicenseUtils.compatibleLicenseVersion(DiscoveryNodes.EMPTY_NODES), equalTo(License.VERSION_CURRENT));
}
}

View File

@ -366,6 +366,7 @@ public class ClusterStatsMonitoringDocTests extends BaseMonitoringDocTestCase<Cl
+ "\"expiry_date\":\"2017-08-07T12:03:22.133Z\","
+ "\"expiry_date_in_millis\":1502107402133,"
+ "\"max_nodes\":2,"
+ "\"max_resource_units\":null,"
+ "\"issued_to\":\"customer\","
+ "\"issuer\":\"elasticsearch\","
+ "\"start_date_in_millis\":-1"

View File

@ -20,7 +20,7 @@ teardown:
- do:
license.get: {}
## a license object has 11 attributes
## the rest API defaults to a v4 license output with 11 attributes
- length: { license: 11 }
## bwc for licenses format

View File

@ -13,35 +13,39 @@ teardown:
license.post:
acknowledge: true
body: |
{"license":{"uid":"6e57906b-a8d1-4c1f-acb7-73a16edc3934","type":"enterprise","issue_date_in_millis":1523456691721,"expiry_date_in_millis":1838816691721,"max_nodes":50,"issued_to":"rest-test","issuer":"elasticsearch","signature":"AAAABAAAAA03e8BZRVXaCV4CpPGRAAAAIAo5/x6hrsGh1GqqrJmy4qgmEC7gK0U4zQ6q5ZEMhm4jAAABAAZNhjABV6PRfa7P7sJgn70XCGoKtAVT75yU13JvKBd/UjD4TPhuZcztqZ/tcLEPxm/TSvGlogWmnw/Rw8xs8jMpBpKsJ+LOXjHhDdvXb2y7JJhCH8nlSEblMDRXysNvWpKe60Z/hb7hS4JynEUt0EBb6ji7BL42O07PNll1EGmkfsHazfs46iV91BG1VxXksI78XgWSaA0F/h7tvrNW9PTgsUaLo06InlQ8jA1dal90AoXp+MVDOHWQjVFZzUnO87/7lEb+VXt0IwchaW17ahihJqkCtGvKpWFwpuhx9xiFvkySN/g5LIVjYCvgBkiWExQ9p0Zzg3VoSlMBnVy0BWo=","start_date_in_millis":-1}}
{"license":{"uid":"6e57906b-a8d1-4c1f-acb7-73a16edc3934","type":"enterprise","issue_date_in_millis":1523456691721,"expiry_date_in_millis":1838816691721,"max_nodes":null,"max_resource_units":50,"issued_to":"rest-test","issuer":"elasticsearch","signature":"AAAABQAAAA0sKPJdf9T6DItbXVJKAAAAIAo5/x6hrsGh1GqqrJmy4qgmEC7gK0U4zQ6q5ZEMhm4jAAABAKFCHrix7w/xPG14+wdhld1RmphDmXmHfL1xeuI33Ahr1mOUYZ30eR6GZuh7CnK8BQhfq+z63lgctJepWlvwDSgkOvXWLHrJun7YSCrzz1bism0ZHWw7Swb9DO7vePomVBo/Hm9+eX0pV4/cFQNMmbFaX11tqJZYBEO6sNASVAFL7A1ZcVoB2evweGU9pUQYvFvmyzzySf99miDo3NH0XYdownEdtoNgFfmqa3+koCP7onmRZ1h9jhsDOi30RX/DTDXQKW+XoREnOHCoOAJFxwip/c1qaQAOqp1H6+P20ZGr2sIPiU97OVEU9kulm+E+jgiVW3LwGheOXsUOd1B8Mp0=","start_date_in_millis":-1}}
- match: { license_status: "valid" }
- do:
license.get: {}
## a license object has 11 attributes
## a v4 (7.X compatible) license object has 11 attributes
- length: { license: 11 }
## by default the enterprise license is "platinum"
## by default the enterprise license is "platinum", and return "max_nodes"
- match: { license.type: "platinum" }
- match: { license.max_nodes: 50 }
- do:
license.get:
accept_enterprise: "true"
## a license object has 11 attributes
- length: { license: 11 }
## a v5 license object has 12 attributes
- length: { license: 12 }
## opt-in to return real enterprise type
- match: { license.type: "enterprise" }
- match: { license.max_resource_units: 50 }
- match: { license.max_nodes: null }
- do:
license.get:
accept_enterprise: "false"
## a license object has 11 attributes
## a v4 license object has 11 attributes
- length: { license: 11 }
## opt-out of real enterprise type
- match: { license.type: "platinum" }
- match: { license.max_nodes: 50 }