Skip to content

Commit 08abd14

Browse files
committed
Add max_resource_units to enterprise license
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: elastic#50735
1 parent 985c95d commit 08abd14

File tree

14 files changed

+314
-90
lines changed

14 files changed

+314
-90
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

+97-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
@@ -159,13 +165,14 @@ static boolean isTrial(String typeName) {
159165
private final long expiryDate;
160166
private final long startDate;
161167
private final int maxNodes;
168+
private final int maxResourceUnits;
162169
private final OperationMode operationMode;
163170

164171
/**
165172
* Decouples operation mode of a license from the license type value.
166173
* <p>
167174
* Note: The mode indicates features that should be made available, but it does not indicate whether the license is active!
168-
*
175+
* <p>
169176
* The id byte is used for ordering operation modes
170177
*/
171178
public enum OperationMode {
@@ -182,13 +189,16 @@ public enum OperationMode {
182189
this.id = id;
183190
}
184191

185-
/** Returns non-zero positive number when <code>opMode1</code> is greater than <code>opMode2</code> */
192+
/**
193+
* Returns non-zero positive number when <code>opMode1</code> is greater than <code>opMode2</code>
194+
*/
186195
public static int compare(OperationMode opMode1, OperationMode opMode2) {
187196
return Integer.compare(opMode1.id, opMode2.id);
188197
}
189198

190199
/**
191200
* Determine the operating mode for a license type
201+
*
192202
* @see LicenseType#resolve(License)
193203
* @see #parse(String)
194204
*/
@@ -217,6 +227,7 @@ public static OperationMode resolve(LicenseType type) {
217227
* Parses an {@code OperatingMode} from a String.
218228
* The string must name an operating mode, and not a licensing level (that is, it cannot parse old style license levels
219229
* such as "dev" or "silver").
230+
*
220231
* @see #description()
221232
*/
222233
public static OperationMode parse(String mode) {
@@ -233,8 +244,8 @@ public String description() {
233244
}
234245
}
235246

236-
private License(int version, String uid, String issuer, String issuedTo, long issueDate, String type,
237-
String subscriptionType, String feature, String signature, long expiryDate, int maxNodes, long startDate) {
247+
private License(int version, String uid, String issuer, String issuedTo, long issueDate, String type, String subscriptionType,
248+
String feature, String signature, long expiryDate, int maxNodes, int maxResourceUnits, long startDate) {
238249
this.version = version;
239250
this.uid = uid;
240251
this.issuer = issuer;
@@ -252,6 +263,7 @@ private License(int version, String uid, String issuer, String issuedTo, long is
252263
this.expiryDate = expiryDate;
253264
}
254265
this.maxNodes = maxNodes;
266+
this.maxResourceUnits = maxResourceUnits;
255267
this.startDate = startDate;
256268
this.operationMode = OperationMode.resolve(LicenseType.resolve(this));
257269
validate();
@@ -300,12 +312,21 @@ public long expiryDate() {
300312
}
301313

302314
/**
303-
* @return the maximum number of nodes this license has been issued for
315+
* @return the maximum number of nodes this license has been issued for, or {@code -1} if this license is not node based.
304316
*/
305317
public int maxNodes() {
306318
return maxNodes;
307319
}
308320

321+
/**
322+
* @return the maximum number of "resource units" this license has been issued for, or {@code -1} if this license is not resource based.
323+
* A "resource unit" is a measure of computing power (RAM/CPU), the definition of which is maintained outside of the license format,
324+
* or this class.
325+
*/
326+
public int maxResourceUnits() {
327+
return maxResourceUnits;
328+
}
329+
309330
/**
310331
* @return a string representing the entity this licenses has been issued to
311332
*/
@@ -392,20 +413,39 @@ private void validate() {
392413
throw new IllegalStateException("uid can not be null");
393414
} else if (feature == null && version == VERSION_START) {
394415
throw new IllegalStateException("feature can not be null");
395-
} else if (maxNodes == -1) {
396-
throw new IllegalStateException("maxNodes has to be set");
397416
} else if (expiryDate == -1) {
398417
throw new IllegalStateException("expiryDate has to be set");
399418
} else if (expiryDate == LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS && LicenseType.isBasic(type) == false) {
400419
throw new IllegalStateException("only basic licenses are allowed to have no expiration");
401420
}
421+
422+
if (LicenseType.isEnterprise(type) && version < VERSION_ENTERPRISE) {
423+
throw new IllegalStateException("license type [" + type + "] is not a valid for version [" + version + "] licenses");
424+
}
425+
validateLimits(type, maxNodes, maxResourceUnits);
426+
}
427+
428+
private static void validateLimits(String type, int maxNodes, int maxResourceUnits) {
429+
if (LicenseType.isEnterprise(type)) {
430+
if (maxResourceUnits == -1) {
431+
throw new IllegalStateException("maxResourceUnits must be set for enterprise licenses (type=[" + type + "])");
432+
} else if (maxNodes != -1) {
433+
throw new IllegalStateException("maxNodes may not be set for enterprise licenses (type=[" + type + "])");
434+
}
435+
} else {
436+
if (maxNodes == -1) {
437+
throw new IllegalStateException("maxNodes has to be set");
438+
} else if (maxResourceUnits != -1) {
439+
throw new IllegalStateException("maxResourceUnits may only be set for enterprise licenses (not permitted for type=[" +
440+
type + "])");
441+
}
442+
}
402443
}
403444

404445
public static License readLicense(StreamInput in) throws IOException {
405446
int version = in.readVInt(); // Version for future extensibility
406447
if (version > VERSION_CURRENT) {
407-
throw new ElasticsearchException("Unknown license version found, please upgrade all nodes to the latest elasticsearch-license" +
408-
" plugin");
448+
throw new ElasticsearchException("Unknown license version found, please upgrade all nodes to the latest elasticsearch release");
409449
}
410450
Builder builder = builder();
411451
builder.version(version);
@@ -420,6 +460,9 @@ public static License readLicense(StreamInput in) throws IOException {
420460
}
421461
builder.expiryDate(in.readLong());
422462
builder.maxNodes(in.readInt());
463+
if (version >= VERSION_ENTERPRISE) {
464+
builder.maxResourceUnits(in.readInt());
465+
}
423466
builder.issuedTo(in.readString());
424467
builder.issuer(in.readString());
425468
builder.signature(in.readOptionalString());
@@ -442,6 +485,9 @@ public void writeTo(StreamOutput out) throws IOException {
442485
}
443486
out.writeLong(expiryDate);
444487
out.writeInt(maxNodes);
488+
if (version >= VERSION_ENTERPRISE) {
489+
out.writeInt(maxResourceUnits);
490+
}
445491
out.writeString(issuedTo);
446492
out.writeString(issuer);
447493
out.writeOptionalString(signature);
@@ -506,7 +552,16 @@ public XContentBuilder toInnerXContent(XContentBuilder builder, Params params) t
506552
if (expiryDate != LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS) {
507553
builder.timeField(Fields.EXPIRY_DATE_IN_MILLIS, Fields.EXPIRY_DATE, expiryDate);
508554
}
509-
builder.field(Fields.MAX_NODES, maxNodes);
555+
556+
if (version >= VERSION_ENTERPRISE) {
557+
builder.field(Fields.MAX_NODES, maxNodes == -1 ? null : maxNodes);
558+
builder.field(Fields.MAX_RESOURCE_UNITS, maxResourceUnits == -1 ? null : maxResourceUnits);
559+
} else if (hideEnterprise && maxNodes == -1) {
560+
builder.field(Fields.MAX_NODES, maxResourceUnits);
561+
} else {
562+
builder.field(Fields.MAX_NODES, maxNodes);
563+
}
564+
510565
builder.field(Fields.ISSUED_TO, issuedTo);
511566
builder.field(Fields.ISSUER, issuer);
512567
if (!licenseSpecMode && !restViewMode && signature != null) {
@@ -551,6 +606,8 @@ public static License fromXContent(XContentParser parser) throws IOException {
551606
builder.startDate(parser.longValue());
552607
} else if (Fields.MAX_NODES.equals(currentFieldName)) {
553608
builder.maxNodes(parser.intValue());
609+
} else if (Fields.MAX_RESOURCE_UNITS.equals(currentFieldName)) {
610+
builder.maxResourceUnits(parser.intValue());
554611
} else if (Fields.ISSUED_TO.equals(currentFieldName)) {
555612
builder.issuedTo(parser.text());
556613
} else if (Fields.ISSUER.equals(currentFieldName)) {
@@ -593,7 +650,7 @@ public static License fromXContent(XContentParser parser) throws IOException {
593650
throw new ElasticsearchException("malformed signature for license [" + builder.uid + "]");
594651
} else if (version > VERSION_CURRENT) {
595652
throw new ElasticsearchException("Unknown license version found, please upgrade all nodes to the latest " +
596-
"elasticsearch-license plugin");
653+
"elasticsearch-license plugin");
597654
}
598655
// signature version is the source of truth
599656
builder.version(version);
@@ -625,8 +682,7 @@ public static License fromSource(BytesReference bytes, XContentType xContentType
625682
// EMPTY is safe here because we don't call namedObject
626683
try (InputStream byteStream = bytes.streamInput();
627684
XContentParser parser = xContentType.xContent()
628-
.createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, byteStream))
629-
{
685+
.createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, byteStream)) {
630686
License license = null;
631687
if (parser.nextToken() == XContentParser.Token.START_OBJECT) {
632688
if (parser.nextToken() == XContentParser.Token.FIELD_NAME) {
@@ -675,7 +731,7 @@ public boolean equals(Object o) {
675731

676732
if (issueDate != license.issueDate) return false;
677733
if (expiryDate != license.expiryDate) return false;
678-
if (startDate!= license.startDate) return false;
734+
if (startDate != license.startDate) return false;
679735
if (maxNodes != license.maxNodes) return false;
680736
if (version != license.version) return false;
681737
if (uid != null ? !uid.equals(license.uid) : license.uid != null) return false;
@@ -700,7 +756,7 @@ public int hashCode() {
700756
result = 31 * result + (feature != null ? feature.hashCode() : 0);
701757
result = 31 * result + (signature != null ? signature.hashCode() : 0);
702758
result = 31 * result + (int) (expiryDate ^ (expiryDate >>> 32));
703-
result = 31 * result + (int) (startDate ^ (startDate>>> 32));
759+
result = 31 * result + (int) (startDate ^ (startDate >>> 32));
704760
result = 31 * result + maxNodes;
705761
result = 31 * result + version;
706762
return result;
@@ -719,6 +775,7 @@ public static final class Fields {
719775
public static final String START_DATE_IN_MILLIS = "start_date_in_millis";
720776
public static final String START_DATE = "start_date";
721777
public static final String MAX_NODES = "max_nodes";
778+
public static final String MAX_RESOURCE_UNITS = "max_resource_units";
722779
public static final String ISSUED_TO = "issued_to";
723780
public static final String ISSUER = "issuer";
724781
public static final String VERSION = "version";
@@ -762,6 +819,7 @@ public static class Builder {
762819
private long expiryDate = -1;
763820
private long startDate = -1;
764821
private int maxNodes = -1;
822+
private int maxResourceUnits = -1;
765823

766824
public Builder uid(String uid) {
767825
this.uid = uid;
@@ -817,6 +875,11 @@ public Builder maxNodes(int maxNodes) {
817875
return this;
818876
}
819877

878+
public Builder maxResourceUnits(int maxUnits) {
879+
this.maxResourceUnits = maxUnits;
880+
return this;
881+
}
882+
820883
public Builder signature(String signature) {
821884
if (signature != null) {
822885
this.signature = signature;
@@ -831,17 +894,18 @@ public Builder startDate(long startDate) {
831894

832895
public Builder fromLicenseSpec(License license, String signature) {
833896
return uid(license.uid())
834-
.version(license.version())
835-
.issuedTo(license.issuedTo())
836-
.issueDate(license.issueDate())
837-
.startDate(license.startDate())
838-
.type(license.type())
839-
.subscriptionType(license.subscriptionType)
840-
.feature(license.feature)
841-
.maxNodes(license.maxNodes())
842-
.expiryDate(license.expiryDate())
843-
.issuer(license.issuer())
844-
.signature(signature);
897+
.version(license.version())
898+
.issuedTo(license.issuedTo())
899+
.issueDate(license.issueDate())
900+
.startDate(license.startDate())
901+
.type(license.type())
902+
.subscriptionType(license.subscriptionType)
903+
.feature(license.feature)
904+
.maxNodes(license.maxNodes())
905+
.maxResourceUnits(license.maxResourceUnits())
906+
.expiryDate(license.expiryDate())
907+
.issuer(license.issuer())
908+
.signature(signature);
845909
}
846910

847911
/**
@@ -850,15 +914,15 @@ public Builder fromLicenseSpec(License license, String signature) {
850914
*/
851915
public Builder fromPre20LicenseSpec(License pre20License) {
852916
return uid(pre20License.uid())
853-
.issuedTo(pre20License.issuedTo())
854-
.issueDate(pre20License.issueDate())
855-
.maxNodes(pre20License.maxNodes())
856-
.expiryDate(pre20License.expiryDate());
917+
.issuedTo(pre20License.issuedTo())
918+
.issueDate(pre20License.issueDate())
919+
.maxNodes(pre20License.maxNodes())
920+
.expiryDate(pre20License.expiryDate());
857921
}
858922

859923
public License build() {
860924
return new License(version, uid, issuer, issuedTo, issueDate, type,
861-
subscriptionType, feature, signature, expiryDate, maxNodes, startDate);
925+
subscriptionType, feature, signature, expiryDate, maxNodes, maxResourceUnits, startDate);
862926
}
863927

864928
public Builder validate() {
@@ -874,11 +938,10 @@ public Builder validate() {
874938
throw new IllegalStateException("uid can not be null");
875939
} else if (signature == null) {
876940
throw new IllegalStateException("signature can not be null");
877-
} else if (maxNodes == -1) {
878-
throw new IllegalStateException("maxNodes has to be set");
879941
} else if (expiryDate == -1) {
880942
throw new IllegalStateException("expiryDate has to be set");
881943
}
944+
validateLimits(type, maxNodes, maxResourceUnits);
882945
return this;
883946
}
884947

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

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

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

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

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

302300
private boolean isAllowedLicenseType(License.LicenseType type) {

0 commit comments

Comments
 (0)