Skip to content

Commit 8727f27

Browse files
authored
Add max_resource_units to enterprise license (#50735)
The enterprise license type must has "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.
1 parent 8a655f3 commit 8727f27

File tree

13 files changed

+307
-82
lines changed

13 files changed

+307
-82
lines changed

docs/reference/licensing/get-license.asciidoc

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ GET /_license
6060
"expiry_date" : "2018-11-19T22:05:12.332Z",
6161
"expiry_date_in_millis" : 1542665112332,
6262
"max_nodes" : 1000,
63+
"max_resource_units" : null,
6364
"issued_to" : "test",
6465
"issuer" : "elasticsearch",
6566
"start_date_in_millis" : -1

x-pack/plugin/core/src/main/java/org/elasticsearch/license/License.java

+95-34
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,19 @@ static boolean isBasic(String typeName) {
113113
static boolean isTrial(String typeName) {
114114
return TRIAL.getTypeName().equals(typeName);
115115
}
116+
117+
static boolean isEnterprise(String typeName) {
118+
return ENTERPRISE.getTypeName().equals(typeName);
119+
}
120+
116121
}
117122

118123
public static final int VERSION_START = 1;
119124
public static final int VERSION_NO_FEATURE_TYPE = 2;
120125
public static final int VERSION_START_DATE = 3;
121126
public static final int VERSION_CRYPTO_ALGORITHMS = 4;
122-
public static final int VERSION_CURRENT = VERSION_CRYPTO_ALGORITHMS;
127+
public static final int VERSION_ENTERPRISE = 5;
128+
public static final int VERSION_CURRENT = VERSION_ENTERPRISE;
123129

124130
/**
125131
* XContent param name to deserialize license(s) with
@@ -153,13 +159,14 @@ static boolean isTrial(String typeName) {
153159
private final long expiryDate;
154160
private final long startDate;
155161
private final int maxNodes;
162+
private final int maxResourceUnits;
156163
private final OperationMode operationMode;
157164

158165
/**
159166
* Decouples operation mode of a license from the license type value.
160167
* <p>
161168
* Note: The mode indicates features that should be made available, but it does not indicate whether the license is active!
162-
*
169+
* <p>
163170
* The id byte is used for ordering operation modes
164171
*/
165172
public enum OperationMode {
@@ -176,13 +183,16 @@ public enum OperationMode {
176183
this.id = id;
177184
}
178185

179-
/** Returns non-zero positive number when <code>opMode1</code> is greater than <code>opMode2</code> */
186+
/**
187+
* Returns non-zero positive number when <code>opMode1</code> is greater than <code>opMode2</code>
188+
*/
180189
public static int compare(OperationMode opMode1, OperationMode opMode2) {
181190
return Integer.compare(opMode1.id, opMode2.id);
182191
}
183192

184193
/**
185194
* Determine the operating mode for a license type
195+
*
186196
* @see LicenseType#resolve(License)
187197
* @see #parse(String)
188198
*/
@@ -211,6 +221,7 @@ public static OperationMode resolve(LicenseType type) {
211221
* Parses an {@code OperatingMode} from a String.
212222
* The string must name an operating mode, and not a licensing level (that is, it cannot parse old style license levels
213223
* such as "dev" or "silver").
224+
*
214225
* @see #description()
215226
*/
216227
public static OperationMode parse(String mode) {
@@ -227,8 +238,8 @@ public String description() {
227238
}
228239
}
229240

230-
private License(int version, String uid, String issuer, String issuedTo, long issueDate, String type,
231-
String subscriptionType, String feature, String signature, long expiryDate, int maxNodes, long startDate) {
241+
private License(int version, String uid, String issuer, String issuedTo, long issueDate, String type, String subscriptionType,
242+
String feature, String signature, long expiryDate, int maxNodes, int maxResourceUnits, long startDate) {
232243
this.version = version;
233244
this.uid = uid;
234245
this.issuer = issuer;
@@ -246,6 +257,7 @@ private License(int version, String uid, String issuer, String issuedTo, long is
246257
this.expiryDate = expiryDate;
247258
}
248259
this.maxNodes = maxNodes;
260+
this.maxResourceUnits = maxResourceUnits;
249261
this.startDate = startDate;
250262
this.operationMode = OperationMode.resolve(LicenseType.resolve(this));
251263
validate();
@@ -294,12 +306,21 @@ public long expiryDate() {
294306
}
295307

296308
/**
297-
* @return the maximum number of nodes this license has been issued for
309+
* @return the maximum number of nodes this license has been issued for, or {@code -1} if this license is not node based.
298310
*/
299311
public int maxNodes() {
300312
return maxNodes;
301313
}
302314

315+
/**
316+
* @return the maximum number of "resource units" this license has been issued for, or {@code -1} if this license is not resource based.
317+
* A "resource unit" is a measure of computing power (RAM/CPU), the definition of which is maintained outside of the license format,
318+
* or this class.
319+
*/
320+
public int maxResourceUnits() {
321+
return maxResourceUnits;
322+
}
323+
303324
/**
304325
* @return a string representing the entity this licenses has been issued to
305326
*/
@@ -386,20 +407,39 @@ private void validate() {
386407
throw new IllegalStateException("uid can not be null");
387408
} else if (feature == null && version == VERSION_START) {
388409
throw new IllegalStateException("feature can not be null");
389-
} else if (maxNodes == -1) {
390-
throw new IllegalStateException("maxNodes has to be set");
391410
} else if (expiryDate == -1) {
392411
throw new IllegalStateException("expiryDate has to be set");
393412
} else if (expiryDate == LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS && LicenseType.isBasic(type) == false) {
394413
throw new IllegalStateException("only basic licenses are allowed to have no expiration");
395414
}
415+
416+
if (LicenseType.isEnterprise(type) && version < VERSION_ENTERPRISE) {
417+
throw new IllegalStateException("license type [" + type + "] is not a valid for version [" + version + "] licenses");
418+
}
419+
validateLimits(type, maxNodes, maxResourceUnits);
420+
}
421+
422+
private static void validateLimits(String type, int maxNodes, int maxResourceUnits) {
423+
if (LicenseType.isEnterprise(type)) {
424+
if (maxResourceUnits == -1) {
425+
throw new IllegalStateException("maxResourceUnits must be set for enterprise licenses (type=[" + type + "])");
426+
} else if (maxNodes != -1) {
427+
throw new IllegalStateException("maxNodes may not be set for enterprise licenses (type=[" + type + "])");
428+
}
429+
} else {
430+
if (maxNodes == -1) {
431+
throw new IllegalStateException("maxNodes has to be set");
432+
} else if (maxResourceUnits != -1) {
433+
throw new IllegalStateException("maxResourceUnits may only be set for enterprise licenses (not permitted for type=[" +
434+
type + "])");
435+
}
436+
}
396437
}
397438

398439
public static License readLicense(StreamInput in) throws IOException {
399440
int version = in.readVInt(); // Version for future extensibility
400441
if (version > VERSION_CURRENT) {
401-
throw new ElasticsearchException("Unknown license version found, please upgrade all nodes to the latest elasticsearch-license" +
402-
" plugin");
442+
throw new ElasticsearchException("Unknown license version found, please upgrade all nodes to the latest elasticsearch release");
403443
}
404444
Builder builder = builder();
405445
builder.version(version);
@@ -414,6 +454,9 @@ public static License readLicense(StreamInput in) throws IOException {
414454
}
415455
builder.expiryDate(in.readLong());
416456
builder.maxNodes(in.readInt());
457+
if (version >= VERSION_ENTERPRISE) {
458+
builder.maxResourceUnits(in.readInt());
459+
}
417460
builder.issuedTo(in.readString());
418461
builder.issuer(in.readString());
419462
builder.signature(in.readOptionalString());
@@ -436,6 +479,9 @@ public void writeTo(StreamOutput out) throws IOException {
436479
}
437480
out.writeLong(expiryDate);
438481
out.writeInt(maxNodes);
482+
if (version >= VERSION_ENTERPRISE) {
483+
out.writeInt(maxResourceUnits);
484+
}
439485
out.writeString(issuedTo);
440486
out.writeString(issuer);
441487
out.writeOptionalString(signature);
@@ -496,7 +542,14 @@ public XContentBuilder toInnerXContent(XContentBuilder builder, Params params) t
496542
if (expiryDate != LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS) {
497543
builder.timeField(Fields.EXPIRY_DATE_IN_MILLIS, Fields.EXPIRY_DATE, expiryDate);
498544
}
499-
builder.field(Fields.MAX_NODES, maxNodes);
545+
546+
if (version >= VERSION_ENTERPRISE) {
547+
builder.field(Fields.MAX_NODES, maxNodes == -1 ? null : maxNodes);
548+
builder.field(Fields.MAX_RESOURCE_UNITS, maxResourceUnits == -1 ? null : maxResourceUnits);
549+
} else {
550+
builder.field(Fields.MAX_NODES, maxNodes);
551+
}
552+
500553
builder.field(Fields.ISSUED_TO, issuedTo);
501554
builder.field(Fields.ISSUER, issuer);
502555
if (!licenseSpecMode && !restViewMode && signature != null) {
@@ -541,6 +594,8 @@ public static License fromXContent(XContentParser parser) throws IOException {
541594
builder.startDate(parser.longValue());
542595
} else if (Fields.MAX_NODES.equals(currentFieldName)) {
543596
builder.maxNodes(parser.intValue());
597+
} else if (Fields.MAX_RESOURCE_UNITS.equals(currentFieldName)) {
598+
builder.maxResourceUnits(parser.intValue());
544599
} else if (Fields.ISSUED_TO.equals(currentFieldName)) {
545600
builder.issuedTo(parser.text());
546601
} else if (Fields.ISSUER.equals(currentFieldName)) {
@@ -583,7 +638,7 @@ public static License fromXContent(XContentParser parser) throws IOException {
583638
throw new ElasticsearchException("malformed signature for license [" + builder.uid + "]");
584639
} else if (version > VERSION_CURRENT) {
585640
throw new ElasticsearchException("Unknown license version found, please upgrade all nodes to the latest " +
586-
"elasticsearch-license plugin");
641+
"elasticsearch-license plugin");
587642
}
588643
// signature version is the source of truth
589644
builder.version(version);
@@ -615,8 +670,7 @@ public static License fromSource(BytesReference bytes, XContentType xContentType
615670
// EMPTY is safe here because we don't call namedObject
616671
try (InputStream byteStream = bytes.streamInput();
617672
XContentParser parser = xContentType.xContent()
618-
.createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, byteStream))
619-
{
673+
.createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, byteStream)) {
620674
License license = null;
621675
if (parser.nextToken() == XContentParser.Token.START_OBJECT) {
622676
if (parser.nextToken() == XContentParser.Token.FIELD_NAME) {
@@ -665,7 +719,7 @@ public boolean equals(Object o) {
665719

666720
if (issueDate != license.issueDate) return false;
667721
if (expiryDate != license.expiryDate) return false;
668-
if (startDate!= license.startDate) return false;
722+
if (startDate != license.startDate) return false;
669723
if (maxNodes != license.maxNodes) return false;
670724
if (version != license.version) return false;
671725
if (uid != null ? !uid.equals(license.uid) : license.uid != null) return false;
@@ -690,7 +744,7 @@ public int hashCode() {
690744
result = 31 * result + (feature != null ? feature.hashCode() : 0);
691745
result = 31 * result + (signature != null ? signature.hashCode() : 0);
692746
result = 31 * result + (int) (expiryDate ^ (expiryDate >>> 32));
693-
result = 31 * result + (int) (startDate ^ (startDate>>> 32));
747+
result = 31 * result + (int) (startDate ^ (startDate >>> 32));
694748
result = 31 * result + maxNodes;
695749
result = 31 * result + version;
696750
return result;
@@ -709,6 +763,7 @@ public static final class Fields {
709763
public static final String START_DATE_IN_MILLIS = "start_date_in_millis";
710764
public static final String START_DATE = "start_date";
711765
public static final String MAX_NODES = "max_nodes";
766+
public static final String MAX_RESOURCE_UNITS = "max_resource_units";
712767
public static final String ISSUED_TO = "issued_to";
713768
public static final String ISSUER = "issuer";
714769
public static final String VERSION = "version";
@@ -752,6 +807,7 @@ public static class Builder {
752807
private long expiryDate = -1;
753808
private long startDate = -1;
754809
private int maxNodes = -1;
810+
private int maxResourceUnits = -1;
755811

756812
public Builder uid(String uid) {
757813
this.uid = uid;
@@ -807,6 +863,11 @@ public Builder maxNodes(int maxNodes) {
807863
return this;
808864
}
809865

866+
public Builder maxResourceUnits(int maxUnits) {
867+
this.maxResourceUnits = maxUnits;
868+
return this;
869+
}
870+
810871
public Builder signature(String signature) {
811872
if (signature != null) {
812873
this.signature = signature;
@@ -821,17 +882,18 @@ public Builder startDate(long startDate) {
821882

822883
public Builder fromLicenseSpec(License license, String signature) {
823884
return uid(license.uid())
824-
.version(license.version())
825-
.issuedTo(license.issuedTo())
826-
.issueDate(license.issueDate())
827-
.startDate(license.startDate())
828-
.type(license.type())
829-
.subscriptionType(license.subscriptionType)
830-
.feature(license.feature)
831-
.maxNodes(license.maxNodes())
832-
.expiryDate(license.expiryDate())
833-
.issuer(license.issuer())
834-
.signature(signature);
885+
.version(license.version())
886+
.issuedTo(license.issuedTo())
887+
.issueDate(license.issueDate())
888+
.startDate(license.startDate())
889+
.type(license.type())
890+
.subscriptionType(license.subscriptionType)
891+
.feature(license.feature)
892+
.maxNodes(license.maxNodes())
893+
.maxResourceUnits(license.maxResourceUnits())
894+
.expiryDate(license.expiryDate())
895+
.issuer(license.issuer())
896+
.signature(signature);
835897
}
836898

837899
/**
@@ -840,15 +902,15 @@ public Builder fromLicenseSpec(License license, String signature) {
840902
*/
841903
public Builder fromPre20LicenseSpec(License pre20License) {
842904
return uid(pre20License.uid())
843-
.issuedTo(pre20License.issuedTo())
844-
.issueDate(pre20License.issueDate())
845-
.maxNodes(pre20License.maxNodes())
846-
.expiryDate(pre20License.expiryDate());
905+
.issuedTo(pre20License.issuedTo())
906+
.issueDate(pre20License.issueDate())
907+
.maxNodes(pre20License.maxNodes())
908+
.expiryDate(pre20License.expiryDate());
847909
}
848910

849911
public License build() {
850912
return new License(version, uid, issuer, issuedTo, issueDate, type,
851-
subscriptionType, feature, signature, expiryDate, maxNodes, startDate);
913+
subscriptionType, feature, signature, expiryDate, maxNodes, maxResourceUnits, startDate);
852914
}
853915

854916
public Builder validate() {
@@ -864,11 +926,10 @@ public Builder validate() {
864926
throw new IllegalStateException("uid can not be null");
865927
} else if (signature == null) {
866928
throw new IllegalStateException("signature can not be null");
867-
} else if (maxNodes == -1) {
868-
throw new IllegalStateException("maxNodes has to be set");
869929
} else if (expiryDate == -1) {
870930
throw new IllegalStateException("expiryDate has to be set");
871931
}
932+
validateLimits(type, maxNodes, maxResourceUnits);
872933
return this;
873934
}
874935

x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java

+3-5
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ public class LicenseService extends AbstractLifecycleComponent implements Cluste
121121
* Max number of nodes licensed by generated trial license
122122
*/
123123
static final int SELF_GENERATED_LICENSE_MAX_NODES = 1000;
124+
static final int SELF_GENERATED_LICENSE_MAX_RESOURCE_UNITS = SELF_GENERATED_LICENSE_MAX_NODES;
124125

125126
public static final String LICENSE_JOB = "licenseJob";
126127

@@ -291,11 +292,8 @@ public ClusterState execute(ClusterState currentState) throws Exception {
291292
}
292293

293294
private static boolean licenseIsCompatible(License license, Version version) {
294-
if (License.LicenseType.ENTERPRISE.getTypeName().equalsIgnoreCase(license.type())) {
295-
return version.onOrAfter(Version.V_7_6_0);
296-
} else {
297-
return true;
298-
}
295+
final int maxVersion = LicenseUtils.getMaxLicenseVersion(version);
296+
return license.version() <= maxVersion;
299297
}
300298

301299
private boolean isAllowedLicenseType(License.LicenseType type) {

x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseUtils.java

+13-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package org.elasticsearch.license;
77

88
import org.elasticsearch.ElasticsearchSecurityException;
9+
import org.elasticsearch.Version;
910
import org.elasticsearch.cluster.node.DiscoveryNodes;
1011
import org.elasticsearch.license.License.LicenseType;
1112
import org.elasticsearch.rest.RestStatus;
@@ -46,18 +47,26 @@ public static boolean licenseNeedsExtended(License license) {
4647
* recreated with the new key
4748
*/
4849
public static boolean signatureNeedsUpdate(License license, DiscoveryNodes currentNodes) {
49-
assert License.VERSION_CRYPTO_ALGORITHMS == License.VERSION_CURRENT : "update this method when adding a new version";
50+
assert License.VERSION_ENTERPRISE == License.VERSION_CURRENT : "update this method when adding a new version";
5051

5152
String typeName = license.type();
5253
return (LicenseType.isBasic(typeName) || LicenseType.isTrial(typeName)) &&
5354
// only upgrade signature when all nodes are ready to deserialize the new signature
5455
(license.version() < License.VERSION_CRYPTO_ALGORITHMS &&
55-
compatibleLicenseVersion(currentNodes) == License.VERSION_CRYPTO_ALGORITHMS
56+
compatibleLicenseVersion(currentNodes) >= License.VERSION_CRYPTO_ALGORITHMS
5657
);
5758
}
5859

5960
public static int compatibleLicenseVersion(DiscoveryNodes currentNodes) {
60-
assert License.VERSION_CRYPTO_ALGORITHMS == License.VERSION_CURRENT : "update this method when adding a new version";
61-
return License.VERSION_CRYPTO_ALGORITHMS;
61+
return getMaxLicenseVersion(currentNodes.getMinNodeVersion());
62+
}
63+
64+
public static int getMaxLicenseVersion(Version version) {
65+
if (version != null && version.before(Version.V_7_6_0)) {
66+
return License.VERSION_CRYPTO_ALGORITHMS;
67+
} else {
68+
assert License.VERSION_ENTERPRISE == License.VERSION_CURRENT : "update this method when adding a new version";
69+
return License.VERSION_ENTERPRISE;
70+
}
6271
}
6372
}

0 commit comments

Comments
 (0)