Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add max_resource_units to enterprise license #50735

Merged
merged 12 commits into from
Jan 13, 2020
1 change: 1 addition & 0 deletions docs/reference/licensing/get-license.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
* <p>
* Note: The mode indicates features that should be made available, but it does not indicate whether the license is active!
*
* <p>
* The id byte is used for ordering operation modes
*/
public enum OperationMode {
Expand All @@ -176,13 +183,16 @@ public enum OperationMode {
this.id = id;
}

/** Returns non-zero positive number when <code>opMode1</code> is greater than <code>opMode2</code> */
/**
* Returns non-zero positive number when <code>opMode1</code> is greater than <code>opMode2</code>
*/
public static int compare(OperationMode opMode1, OperationMode opMode2) {
return Integer.compare(opMode1.id, opMode2.id);
}

/**
* Determine the operating mode for a license type
*
* @see LicenseType#resolve(License)
* @see #parse(String)
*/
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
Expand All @@ -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();
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -386,20 +407,38 @@ 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");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
throw new IllegalStateException("maxResourceUnits must be set for enterprise licenses");
throw new IllegalStateException("maxResourceUnits must be set for license type [" + type + "]");

} else if (maxNodes != -1) {
throw new IllegalStateException("maxNodes may not be set for enterprise licenses");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw new IllegalStateException("maxNodes may not be set for enterprise licenses");
throw new IllegalStateException("maxNodes may not be set license 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");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw new IllegalStateException("maxResourceUnits may only be set for enterprise licenses");
throw new IllegalStateException("maxResourceUnits may only be set license type [" + type + "]");

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This suggestion is backwards. I could change it to say that they "may not be set for license type [" + type + "]", but it seemed more helpful to explain when they are permitted than to just reject them for the current type.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, makes sense, sorry for the half-baked suggestion

}
}
}

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);
Expand All @@ -414,6 +453,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());
Expand All @@ -436,6 +478,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);
Expand Down Expand Up @@ -496,7 +541,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) {
Expand Down Expand Up @@ -541,6 +593,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)) {
Expand Down Expand Up @@ -583,7 +637,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);
Expand Down Expand Up @@ -615,8 +669,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) {
Expand Down Expand Up @@ -665,7 +718,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;
Expand All @@ -690,7 +743,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;
Expand All @@ -709,6 +762,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";
Expand Down Expand Up @@ -752,6 +806,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;
Expand Down Expand Up @@ -807,6 +862,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;
Expand All @@ -821,17 +881,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);
}

/**
Expand All @@ -840,15 +901,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() {
Expand All @@ -864,11 +925,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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,11 +291,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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading