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" }