diff --git a/docs/reference/licensing/get-license.asciidoc b/docs/reference/licensing/get-license.asciidoc index 807a40729f9fd..34f12ef514406 100644 --- a/docs/reference/licensing/get-license.asciidoc +++ b/docs/reference/licensing/get-license.asciidoc @@ -60,6 +60,7 @@ GET /_license "expiry_date" : "2018-11-19T22:05:12.332Z", "expiry_date_in_millis" : 1542665112332, "max_nodes" : 1000, + "max_resource_units" : null, "issued_to" : "test", "issuer" : "elasticsearch", "start_date_in_millis" : -1 diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/License.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/License.java index 004c9ff987764..210beaaedecaf 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/License.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/License.java @@ -113,13 +113,19 @@ static boolean isBasic(String typeName) { 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 @@ -153,13 +159,14 @@ static boolean isTrial(String typeName) { 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. *
* Note: The mode indicates features that should be made available, but it does not indicate whether the license is active! - * + *
* The id byte is used for ordering operation modes
*/
public enum OperationMode {
@@ -176,13 +183,16 @@ public enum OperationMode {
this.id = id;
}
- /** Returns non-zero positive number when opMode1
is greater than opMode2
*/
+ /**
+ * Returns non-zero positive number when opMode1
is greater than opMode2
+ */
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)
*/
@@ -211,6 +221,7 @@ public static OperationMode resolve(LicenseType type) {
* 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) {
@@ -227,8 +238,8 @@ public String description() {
}
}
- 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;
@@ -246,6 +257,7 @@ private License(int version, String uid, String issuer, String issuedTo, long is
this.expiryDate = expiryDate;
}
this.maxNodes = maxNodes;
+ this.maxResourceUnits = maxResourceUnits;
this.startDate = startDate;
this.operationMode = OperationMode.resolve(LicenseType.resolve(this));
validate();
@@ -294,12 +306,21 @@ public long expiryDate() {
}
/**
- * @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
*/
@@ -386,20 +407,39 @@ private void validate() {
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);
@@ -414,6 +454,9 @@ public static License readLicense(StreamInput in) throws IOException {
}
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());
@@ -436,6 +479,9 @@ public void writeTo(StreamOutput out) throws IOException {
}
out.writeLong(expiryDate);
out.writeInt(maxNodes);
+ if (version >= VERSION_ENTERPRISE) {
+ out.writeInt(maxResourceUnits);
+ }
out.writeString(issuedTo);
out.writeString(issuer);
out.writeOptionalString(signature);
@@ -496,7 +542,14 @@ public XContentBuilder toInnerXContent(XContentBuilder builder, Params params) t
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 {
+ builder.field(Fields.MAX_NODES, maxNodes);
+ }
+
builder.field(Fields.ISSUED_TO, issuedTo);
builder.field(Fields.ISSUER, issuer);
if (!licenseSpecMode && !restViewMode && signature != null) {
@@ -541,6 +594,8 @@ public static License fromXContent(XContentParser parser) throws IOException {
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)) {
@@ -583,7 +638,7 @@ public static License fromXContent(XContentParser parser) throws IOException {
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);
@@ -615,8 +670,7 @@ public static License fromSource(BytesReference bytes, XContentType xContentType
// 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) {
@@ -665,7 +719,7 @@ public boolean equals(Object o) {
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;
@@ -690,7 +744,7 @@ public int hashCode() {
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;
@@ -709,6 +763,7 @@ public static final class Fields {
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";
@@ -752,6 +807,7 @@ public static class Builder {
private long expiryDate = -1;
private long startDate = -1;
private int maxNodes = -1;
+ private int maxResourceUnits = -1;
public Builder uid(String uid) {
this.uid = uid;
@@ -807,6 +863,11 @@ public Builder maxNodes(int maxNodes) {
return this;
}
+ public Builder maxResourceUnits(int maxUnits) {
+ this.maxResourceUnits = maxUnits;
+ return this;
+ }
+
public Builder signature(String signature) {
if (signature != null) {
this.signature = signature;
@@ -821,17 +882,18 @@ public Builder startDate(long startDate) {
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);
}
/**
@@ -840,15 +902,15 @@ public Builder fromLicenseSpec(License license, String signature) {
*/
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() {
@@ -864,11 +926,10 @@ public Builder validate() {
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;
}
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java
index 8168445f8c3ad..515f8462f98aa 100644
--- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java
@@ -121,6 +121,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";
@@ -291,11 +292,8 @@ public ClusterState execute(ClusterState currentState) throws Exception {
}
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) {
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseUtils.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseUtils.java
index dd4cb1ae6a589..66fd005614017 100644
--- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseUtils.java
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseUtils.java
@@ -6,6 +6,7 @@
package org.elasticsearch.license;
import org.elasticsearch.ElasticsearchSecurityException;
+import org.elasticsearch.Version;
import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.license.License.LicenseType;
import org.elasticsearch.rest.RestStatus;
@@ -46,18 +47,26 @@ public static boolean licenseNeedsExtended(License license) {
* 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";
- return License.VERSION_CRYPTO_ALGORITHMS;
+ return getMaxLicenseVersion(currentNodes.getMinNodeVersion());
+ }
+
+ public static int getMaxLicenseVersion(Version version) {
+ if (version != null && version.before(Version.V_7_6_0)) {
+ return License.VERSION_CRYPTO_ALGORITHMS;
+ } else {
+ assert License.VERSION_ENTERPRISE == License.VERSION_CURRENT : "update this method when adding a new version";
+ return License.VERSION_ENTERPRISE;
+ }
}
}
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/StartTrialClusterTask.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/StartTrialClusterTask.java
index 98fb6115710e3..c2d23ebf47aa7 100644
--- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/StartTrialClusterTask.java
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/StartTrialClusterTask.java
@@ -75,10 +75,14 @@ public ClusterState execute(ClusterState currentState) throws Exception {
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);
diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/AbstractLicenseServiceTestCase.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/AbstractLicenseServiceTestCase.java
index 5bc33ae330a18..aa482707e286c 100644
--- a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/AbstractLicenseServiceTestCase.java
+++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/AbstractLicenseServiceTestCase.java
@@ -70,6 +70,7 @@ protected void setInitialState(License license, XPackLicenseState licenseState,
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);
diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseOperationModeUpdateTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseOperationModeUpdateTests.java
index 20df885261fed..2ef2438123650 100644
--- a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseOperationModeUpdateTests.java
+++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseOperationModeUpdateTests.java
@@ -35,15 +35,19 @@ public void init() throws Exception {
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);
diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseServiceTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseServiceTests.java
index b1b22f15c259f..0f465834ad553 100644
--- a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseServiceTests.java
+++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseServiceTests.java
@@ -190,7 +190,8 @@ private License buildLicense(License.LicenseType type, TimeValue expires) {
.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();
}
diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseTests.java
index aa209f9a520ac..05f54383eedd3 100644
--- a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseTests.java
+++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseTests.java
@@ -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 @@
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 void testFromXContent() throws Exception {
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 void testLicenseToAndFromXContentForEveryLicenseType() throws Exception {
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 void testUnableToBase64DecodeFromXContent() throws Exception {
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));
+ }
+
}
diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseUtilsTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseUtilsTests.java
index 26ed6f5e446db..1c96fef045a64 100644
--- a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseUtilsTests.java
+++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseUtilsTests.java
@@ -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 void testIsLicenseExpiredException() {
exception = new ElasticsearchSecurityException("msg");
assertFalse(LicenseUtils.isLicenseExpiredException(exception));
}
+
+ public void testVersionsUpToDate() {
+ assertThat(LicenseUtils.compatibleLicenseVersion(DiscoveryNodes.EMPTY_NODES), equalTo(License.VERSION_CURRENT));
+ }
}
diff --git a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/cluster/ClusterStatsMonitoringDocTests.java b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/cluster/ClusterStatsMonitoringDocTests.java
index f7da3118c5e29..7dd46eb460cea 100644
--- a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/cluster/ClusterStatsMonitoringDocTests.java
+++ b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/cluster/ClusterStatsMonitoringDocTests.java
@@ -367,6 +367,7 @@ public void testToXContent() throws IOException {
+ "\"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"
diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/license/20_put_license.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/license/20_put_license.yml
index 0a3b2bc135b57..8b24d73fcb476 100644
--- a/x-pack/plugin/src/test/resources/rest-api-spec/test/license/20_put_license.yml
+++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/license/20_put_license.yml
@@ -20,8 +20,8 @@ teardown:
- do:
license.get: {}
- ## a license object has 11 attributes
- - length: { license: 11 }
+ ## a v5 license object has 12 attributes & the Rest API always outputs in current version
+ - length: { license: 12 }
## bwc for licenses format
- do:
@@ -35,7 +35,7 @@ teardown:
- do:
license.get: {}
- - length: { license: 11 }
+ - length: { license: 12 }
## license version: 1.x
- do:
@@ -49,7 +49,7 @@ teardown:
- do:
license.get: {}
- - length: { license: 11 }
+ - length: { license: 12 }
## multiple licenses version: 1.x
- do:
@@ -63,7 +63,7 @@ teardown:
- do:
license.get: {}
- - length: { license: 11 }
+ - length: { license: 12 }
- match: { license.uid: "893361dc-9749-4997-93cb-802e3dofh7aa" }
---
"Should throw 404 after license deletion":
@@ -91,7 +91,7 @@ teardown:
- do:
license.get: {}
- - length: { license: 11 }
+ - length: { license: 12 }
---
"Cannot start basic":
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 5f4582c747b5b..9b0fd906f2930 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
@@ -15,18 +15,20 @@ 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
- - length: { license: 11 }
+ ## a v5 (enterprise) license object has 12 attributes
+ - length: { license: 12 }
## In 8.0, the enterprise license is always reports truthfully
- match: { license.type: "enterprise" }
+ - match: { license.max_resource_units: 50 }
+ - match: { license.max_nodes: null }
- do:
warnings:
@@ -34,8 +36,8 @@ teardown:
license.get:
accept_enterprise: "true"
- ## a license object has 11 attributes
- - length: { license: 11 }
+ ## a v5 license object has 12 attributes
+ - length: { license: 12 }
## Always returns real type
- match: { license.type: "enterprise" }