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 b59ecf9bebd..34e515bfcf9 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 @@ 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. *
* 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 {
@@ -182,13 +189,16 @@ public class License implements ToXContentObject {
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)
*/
@@ -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;
}
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 aeb7fe5fcef..a346141e41d 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
@@ -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) {
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 52b98810a82..b32c0a74160 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
@@ -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;
+ }
}
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/RestGetLicenseAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/RestGetLicenseAction.java
index fed6c456fc0..d2b96846131 100644
--- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/RestGetLicenseAction.java
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/RestGetLicenseAction.java
@@ -51,12 +51,13 @@ public class RestGetLicenseAction extends XPackRestHandler {
*/
@Override
public RestChannelConsumer doPrepareRequest(final RestRequest request, final XPackClient client) throws IOException {
- final Map