diff --git a/plugin-api/.gitignore b/plugin-api/.gitignore new file mode 100644 index 00000000000..ab956abf6f9 --- /dev/null +++ b/plugin-api/.gitignore @@ -0,0 +1 @@ +/eclipse-build/ diff --git a/plugin-api/pom.xml b/plugin-api/pom.xml new file mode 100644 index 00000000000..e5943d9932f --- /dev/null +++ b/plugin-api/pom.xml @@ -0,0 +1,67 @@ + + + + elasticsearch-license + org.elasticsearch + 2.0.0-SNAPSHOT + + 4.0.0 + + elasticsearch-license-plugin-api + + ${project.parent.basedir} + + + + + + org.elasticsearch + elasticsearch-license-licensor + 2.0.0-SNAPSHOT + test + + + org.elasticsearch + elasticsearch-license-core + 2.0.0-SNAPSHOT + compile + + + + + + + src/main/resources + true + + + + + + + + + com.carrotsearch.randomizedtesting + junit4-maven-plugin + + true + + + + + + \ No newline at end of file diff --git a/plugin-api/src/main/java/org/elasticsearch/license/plugin/LicenseVersion.java b/plugin-api/src/main/java/org/elasticsearch/license/plugin/LicenseVersion.java new file mode 100644 index 00000000000..4e2cad0d5cd --- /dev/null +++ b/plugin-api/src/main/java/org/elasticsearch/license/plugin/LicenseVersion.java @@ -0,0 +1,191 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.license.plugin; + +import org.elasticsearch.Version; +import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.license.core.License; + +import java.io.IOException; +import java.io.Serializable; + +@SuppressWarnings("deprecation") +public class LicenseVersion implements Serializable { + + // The logic for ID is: XXYYZZAA, where XX is major version, YY is minor version, ZZ is revision, and AA is Beta/RC indicator + // AA values below 50 are beta builds, and below 99 are RC builds, with 99 indicating a release + // the (internal) format of the id is there so we can easily do after/before checks on the id + + public static final int V_1_0_0_ID = /*00*/1000099; + public static final int V_2_0_0_ID = /*00*/2000099; + public static final LicenseVersion V_1_0_0 = new LicenseVersion(V_1_0_0_ID, false, License.VERSION_START, Version.V_1_4_0_Beta1); + public static final LicenseVersion V_2_0_0 = new LicenseVersion(V_2_0_0_ID, true, License.VERSION_START, Version.V_2_0_0); + + public static final LicenseVersion CURRENT = V_2_0_0; + + public static LicenseVersion readVersion(StreamInput in) throws IOException { + return fromId(in.readVInt()); + } + + public static LicenseVersion fromId(int id) { + switch (id) { + case V_1_0_0_ID: + return V_1_0_0; + + default: + return new LicenseVersion(id, null, License.VERSION_CURRENT, Version.CURRENT); + } + } + + public static void writeVersion(LicenseVersion version, StreamOutput out) throws IOException { + out.writeVInt(version.id); + } + + /** + * Returns the smallest version between the 2. + */ + public static LicenseVersion smallest(LicenseVersion version1, LicenseVersion version2) { + return version1.id < version2.id ? version1 : version2; + } + + /** + * Returns the version given its string representation, current version if the argument is null or empty + */ + public static LicenseVersion fromString(String version) { + if (!Strings.hasLength(version)) { + return LicenseVersion.CURRENT; + } + + String[] parts = version.split("\\."); + if (parts.length < 3 || parts.length > 4) { + throw new IllegalArgumentException("the version needs to contain major, minor and revision, and optionally the build"); + } + + try { + //we reverse the version id calculation based on some assumption as we can't reliably reverse the modulo + int major = Integer.parseInt(parts[0]) * 1000000; + int minor = Integer.parseInt(parts[1]) * 10000; + int revision = Integer.parseInt(parts[2]) * 100; + + int build = 99; + if (parts.length == 4) { + String buildStr = parts[3]; + if (buildStr.startsWith("Beta")) { + build = Integer.parseInt(buildStr.substring(4)); + } + if (buildStr.startsWith("RC")) { + build = Integer.parseInt(buildStr.substring(2)) + 50; + } + } + + return fromId(major + minor + revision + build); + + } catch(NumberFormatException e) { + throw new IllegalArgumentException("unable to parse version " + version, e); + } + } + + public final int id; + public final byte major; + public final byte minor; + public final byte revision; + public final byte build; + public final Boolean snapshot; + public final int minSignatureVersion; + public final Version minEsCompatibilityVersion; + + LicenseVersion(int id, @Nullable Boolean snapshot, int minSignatureVersion, Version minEsCompatibilityVersion) { + this.id = id; + this.major = (byte) ((id / 1000000) % 100); + this.minor = (byte) ((id / 10000) % 100); + this.revision = (byte) ((id / 100) % 100); + this.build = (byte) (id % 100); + this.snapshot = snapshot; + this.minSignatureVersion = minSignatureVersion; + this.minEsCompatibilityVersion = minEsCompatibilityVersion; + } + + public boolean snapshot() { + return snapshot != null && snapshot; + } + + public boolean after(LicenseVersion version) { + return version.id < id; + } + + public boolean before(LicenseVersion version) { + return version.id > id; + } + + /** + * Returns the minimum compatible version based on the current + * version. Ie a node needs to have at least the return version in order + * to communicate with a node running the current version. The returned version + * is in most of the cases the smallest major version release unless the current version + * is a beta or RC release then the version itself is returned. + */ + public LicenseVersion minimumCompatibilityVersion() { + return LicenseVersion.smallest(this, fromId(major * 1000000 + 99)); + } + + /** + * @return The minimum elasticsearch version this license version is compatible with. + */ + public Version minimumEsCompatiblityVersion() { + return minEsCompatibilityVersion; + } + + /** + * @return The minimum license signature version this license plugin is compatible with. + */ + public int minimumSignatureVersion() { + return minSignatureVersion; + } + + /** + * Just the version number (without -SNAPSHOT if snapshot). + */ + public String number() { + StringBuilder sb = new StringBuilder(); + sb.append(major).append('.').append(minor).append('.').append(revision); + if (build < 50) { + sb.append(".Beta").append(build); + } else if (build < 99) { + sb.append(".RC").append(build - 50); + } + return sb.toString(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(number()); + if (snapshot()) { + sb.append("-SNAPSHOT"); + } + return sb.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + LicenseVersion that = (LicenseVersion) o; + + if (id != that.id) return false; + + return true; + } + + @Override + public int hashCode() { + return id; + } +} diff --git a/plugin-api/src/main/java/org/elasticsearch/license/plugin/core/LicenseExpiredException.java b/plugin-api/src/main/java/org/elasticsearch/license/plugin/core/LicenseExpiredException.java new file mode 100644 index 00000000000..4fbbe351804 --- /dev/null +++ b/plugin-api/src/main/java/org/elasticsearch/license/plugin/core/LicenseExpiredException.java @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.license.plugin.core; + +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.rest.RestStatus; + +/** + * Exception to be thrown when a feature action requires a valid license + */ +public class LicenseExpiredException extends ElasticsearchException { + + private final String feature; + + public LicenseExpiredException(String feature) { + super("license expired for feature [" + feature + "]"); + this.feature = feature; + } + + @Override + public RestStatus status() { + return RestStatus.UNAUTHORIZED; + } + + public String feature() { + return feature; + } +} diff --git a/plugin-api/src/main/java/org/elasticsearch/license/plugin/core/LicensesClientService.java b/plugin-api/src/main/java/org/elasticsearch/license/plugin/core/LicensesClientService.java new file mode 100644 index 00000000000..548e87d72ea --- /dev/null +++ b/plugin-api/src/main/java/org/elasticsearch/license/plugin/core/LicensesClientService.java @@ -0,0 +1,164 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.license.plugin.core; + +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.license.core.License; + +import java.util.Collection; + + +//@ImplementedBy(LicensesService.class) +public interface LicensesClientService { + + public interface Listener { + + /** + * Called to enable a feature + */ + public void onEnabled(License license); + + /** + * Called to disable a feature + */ + public void onDisabled(License license); + + } + + /** + * Registers a feature for licensing + * + * @param feature - name of the feature to register (must be in sync with license Generator feature name) + * @param trialLicenseOptions - Trial license specification used to generate a one-time trial license for the feature; + * use null if no trial license should be generated for the feature + * @param expirationCallbacks - A collection of Pre and/or Post expiration callbacks + * @param listener - used to notify on feature enable/disable + */ + void register(String feature, TrialLicenseOptions trialLicenseOptions, Collection expirationCallbacks, Listener listener); + + public static class TrialLicenseOptions { + final TimeValue duration; + final int maxNodes; + + public TrialLicenseOptions(TimeValue duration, int maxNodes) { + this.duration = duration; + this.maxNodes = maxNodes; + } + } + + + public static interface LicenseCallback { + void on(License license, ExpirationStatus status); + } + + public static abstract class ExpirationCallback implements LicenseCallback { + + public enum Orientation { PRE, POST } + + public static abstract class Pre extends ExpirationCallback { + + /** + * Callback schedule prior to license expiry + * + * @param min latest relative time to execute before license expiry + * @param max earliest relative time to execute before license expiry + * @param frequency interval between execution + */ + public Pre(TimeValue min, TimeValue max, TimeValue frequency) { + super(Orientation.PRE, min, max, frequency); + } + + @Override + public boolean matches(long expirationDate, long now) { + long expiryDuration = expirationDate - now; + if (expiryDuration > 0l) { + if (expiryDuration <= max().getMillis()) { + return expiryDuration >= min().getMillis(); + } + } + return false; + } + } + + public static abstract class Post extends ExpirationCallback { + + /** + * Callback schedule after license expiry + * + * @param min earliest relative time to execute after license expiry + * @param max latest relative time to execute after license expiry + * @param frequency interval between execution + */ + public Post(TimeValue min, TimeValue max, TimeValue frequency) { + super(Orientation.POST, min, max, frequency); + } + + @Override + public boolean matches(long expirationDate, long now) { + long postExpiryDuration = now - expirationDate; + if (postExpiryDuration > 0l) { + if (postExpiryDuration <= max().getMillis()) { + return postExpiryDuration >= min().getMillis(); + } + } + return false; + } + } + + private final Orientation orientation; + private final TimeValue min; + private final TimeValue max; + private final TimeValue frequency; + + private ExpirationCallback(Orientation orientation, TimeValue min, TimeValue max, TimeValue frequency) { + this.orientation = orientation; + this.min = (min == null) ? TimeValue.timeValueMillis(0) : min; + this.max = (max == null) ? TimeValue.timeValueMillis(Long.MAX_VALUE) : max; + this.frequency = frequency; + if (frequency == null) { + throw new IllegalArgumentException("frequency can not be null"); + } + } + + public Orientation orientation() { + return orientation; + } + + public TimeValue min() { + return min; + } + + public TimeValue max() { + return max; + } + + public TimeValue frequency() { + return frequency; + } + + public abstract boolean matches(long expirationDate, long now); + } + + public static class ExpirationStatus { + private final boolean expired; + private final TimeValue time; + + ExpirationStatus(boolean expired, TimeValue time) { + this.expired = expired; + this.time = time; + } + + public boolean expired() { + return expired; + } + + public TimeValue time() { + return time; + } + } + + +} diff --git a/plugin/.gitignore b/plugin/.gitignore new file mode 100644 index 00000000000..ab956abf6f9 --- /dev/null +++ b/plugin/.gitignore @@ -0,0 +1 @@ +/eclipse-build/ diff --git a/plugin/pom.xml b/plugin/pom.xml index ba579d18649..9ba064ca882 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -12,7 +12,8 @@ elasticsearch-license-plugin - + ${basedir}/src/test/resources ${project.parent.basedir} @@ -30,6 +31,12 @@ 2.0.0-SNAPSHOT compile + + org.elasticsearch + elasticsearch-license-plugin-api + 2.0.0-SNAPSHOT + compile + diff --git a/plugin/src/main/java/org/elasticsearch/license/plugin/LicenseModule.java b/plugin/src/main/java/org/elasticsearch/license/plugin/LicenseModule.java index dc9c4df64b3..4ec46ec33f0 100644 --- a/plugin/src/main/java/org/elasticsearch/license/plugin/LicenseModule.java +++ b/plugin/src/main/java/org/elasticsearch/license/plugin/LicenseModule.java @@ -8,6 +8,7 @@ package org.elasticsearch.license.plugin; import org.elasticsearch.common.inject.AbstractModule; import org.elasticsearch.common.inject.Scopes; import org.elasticsearch.license.core.LicenseVerifier; +import org.elasticsearch.license.plugin.core.LicensesClientService; import org.elasticsearch.license.plugin.core.LicensesService; public class LicenseModule extends AbstractModule { @@ -15,5 +16,6 @@ public class LicenseModule extends AbstractModule { protected void configure() { bind(LicenseVerifier.class).in(Scopes.SINGLETON); bind(LicensesService.class).in(Scopes.SINGLETON); + bind(LicensesClientService.class).to(LicensesService.class).in(Scopes.SINGLETON); } } diff --git a/plugin/src/main/java/org/elasticsearch/license/plugin/core/LicensesClientService.java b/plugin/src/main/java/org/elasticsearch/license/plugin/core/LicensesClientService.java deleted file mode 100644 index eb289cdb0e4..00000000000 --- a/plugin/src/main/java/org/elasticsearch/license/plugin/core/LicensesClientService.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.license.plugin.core; - -import org.elasticsearch.common.inject.ImplementedBy; -import org.elasticsearch.license.core.License; - -import java.util.Collection; - -import static org.elasticsearch.license.plugin.core.LicensesService.*; -import static org.elasticsearch.license.plugin.core.LicensesService.TrialLicenseOptions; - -@ImplementedBy(LicensesService.class) -public interface LicensesClientService { - - public interface Listener { - - /** - * Called to enable a feature - */ - public void onEnabled(License license); - - /** - * Called to disable a feature - */ - public void onDisabled(License license); - - } - - /** - * Registers a feature for licensing - * - * @param feature - name of the feature to register (must be in sync with license Generator feature name) - * @param trialLicenseOptions - Trial license specification used to generate a one-time trial license for the feature; - * use null if no trial license should be generated for the feature - * @param expirationCallbacks - A collection of Pre and/or Post expiration callbacks - * @param listener - used to notify on feature enable/disable - */ - void register(String feature, TrialLicenseOptions trialLicenseOptions, Collection expirationCallbacks, Listener listener); -} diff --git a/plugin/src/main/java/org/elasticsearch/license/plugin/core/LicensesService.java b/plugin/src/main/java/org/elasticsearch/license/plugin/core/LicensesService.java index 70c1d7e306b..ba638ab83bd 100644 --- a/plugin/src/main/java/org/elasticsearch/license/plugin/core/LicensesService.java +++ b/plugin/src/main/java/org/elasticsearch/license/plugin/core/LicensesService.java @@ -741,125 +741,8 @@ public class LicensesService extends AbstractLifecycleComponent } } - public static class TrialLicenseOptions { - final TimeValue duration; - final int maxNodes; - public TrialLicenseOptions(TimeValue duration, int maxNodes) { - this.duration = duration; - this.maxNodes = maxNodes; - } - } - public static class ExpirationStatus { - private final boolean expired; - private final TimeValue time; - - private ExpirationStatus(boolean expired, TimeValue time) { - this.expired = expired; - this.time = time; - } - - public boolean expired() { - return expired; - } - - public TimeValue time() { - return time; - } - } - - public static interface LicenseCallback { - void on(License license, ExpirationStatus status); - } - - public static abstract class ExpirationCallback implements LicenseCallback { - - public enum Orientation { PRE, POST } - - public static abstract class Pre extends ExpirationCallback { - - /** - * Callback schedule prior to license expiry - * - * @param min latest relative time to execute before license expiry - * @param max earliest relative time to execute before license expiry - * @param frequency interval between execution - */ - public Pre(TimeValue min, TimeValue max, TimeValue frequency) { - super(Orientation.PRE, min, max, frequency); - } - - @Override - public boolean matches(long expirationDate, long now) { - long expiryDuration = expirationDate - now; - if (expiryDuration > 0l) { - if (expiryDuration <= max().getMillis()) { - return expiryDuration >= min().getMillis(); - } - } - return false; - } - } - - public static abstract class Post extends ExpirationCallback { - - /** - * Callback schedule after license expiry - * - * @param min earliest relative time to execute after license expiry - * @param max latest relative time to execute after license expiry - * @param frequency interval between execution - */ - public Post(TimeValue min, TimeValue max, TimeValue frequency) { - super(Orientation.POST, min, max, frequency); - } - - @Override - public boolean matches(long expirationDate, long now) { - long postExpiryDuration = now - expirationDate; - if (postExpiryDuration > 0l) { - if (postExpiryDuration <= max().getMillis()) { - return postExpiryDuration >= min().getMillis(); - } - } - return false; - } - } - - private final Orientation orientation; - private final TimeValue min; - private final TimeValue max; - private final TimeValue frequency; - - private ExpirationCallback(Orientation orientation, TimeValue min, TimeValue max, TimeValue frequency) { - this.orientation = orientation; - this.min = (min == null) ? TimeValue.timeValueMillis(0) : min; - this.max = (max == null) ? TimeValue.timeValueMillis(Long.MAX_VALUE) : max; - this.frequency = frequency; - if (frequency == null) { - throw new IllegalArgumentException("frequency can not be null"); - } - } - - public Orientation orientation() { - return orientation; - } - - public TimeValue min() { - return min; - } - - public TimeValue max() { - return max; - } - - public TimeValue frequency() { - return frequency; - } - - public abstract boolean matches(long expirationDate, long now); - } /** * Stores configuration and listener for a feature @@ -993,6 +876,7 @@ public class LicensesService extends AbstractLifecycleComponent } } + @Override public String toString() { return "(feature: " + feature + ", enabled: " + enabled + ")"; } diff --git a/pom.xml b/pom.xml index b48a978ed53..d1884dd5614 100644 --- a/pom.xml +++ b/pom.xml @@ -19,6 +19,7 @@ core licensor plugin + plugin-api