diff --git a/src/main/java/org/elasticsearch/license/core/DateUtils.java b/src/main/java/org/elasticsearch/license/core/DateUtils.java index fc9e2bbc3dc..da59ef6ec8f 100644 --- a/src/main/java/org/elasticsearch/license/core/DateUtils.java +++ b/src/main/java/org/elasticsearch/license/core/DateUtils.java @@ -22,6 +22,23 @@ public class DateUtils { return dateFormat; } + public static long expiryDateAfterDays(long startDate, int days) { + Date dateObj = new Date(startDate); + + Calendar calendar = Calendar.getInstance(); + calendar.clear(); + calendar.setTimeZone(TIME_ZONE); + calendar.setTimeInMillis(dateObj.getTime()); + + calendar.add(Calendar.DAY_OF_YEAR, days); + + calendar.set(Calendar.HOUR, 23); + calendar.set(Calendar.MINUTE, 59); + calendar.set(Calendar.SECOND, 59); + + return calendar.getTimeInMillis(); + } + public static long longExpiryDateFromDate(long date) { Date dateObj = new Date(date); diff --git a/src/main/java/org/elasticsearch/license/plugin/LicenseModule.java b/src/main/java/org/elasticsearch/license/plugin/LicenseModule.java index 3d502a1bb85..70af7c3f45a 100644 --- a/src/main/java/org/elasticsearch/license/plugin/LicenseModule.java +++ b/src/main/java/org/elasticsearch/license/plugin/LicenseModule.java @@ -6,6 +6,7 @@ package org.elasticsearch.license.plugin; import org.elasticsearch.common.inject.AbstractModule; +import org.elasticsearch.license.plugin.core.LicensesManagerService; import org.elasticsearch.license.plugin.core.LicensesService; public class LicenseModule extends AbstractModule { @@ -13,5 +14,6 @@ public class LicenseModule extends AbstractModule { protected void configure() { //TODO: bind LicensesManagementService and LicensesValidationService to LicensesServices instead bind(LicensesService.class).asEagerSingleton(); + bind(LicensesManagerService.class).to(LicensesService.class).asEagerSingleton(); } } diff --git a/src/main/java/org/elasticsearch/license/plugin/action/delete/TransportDeleteLicenseAction.java b/src/main/java/org/elasticsearch/license/plugin/action/delete/TransportDeleteLicenseAction.java index 1d69da04497..5c08c9f115d 100644 --- a/src/main/java/org/elasticsearch/license/plugin/action/delete/TransportDeleteLicenseAction.java +++ b/src/main/java/org/elasticsearch/license/plugin/action/delete/TransportDeleteLicenseAction.java @@ -14,23 +14,23 @@ import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse; import org.elasticsearch.cluster.block.ClusterBlockException; import org.elasticsearch.cluster.block.ClusterBlockLevel; -import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.license.plugin.core.LicensesMetaData; -import org.elasticsearch.license.plugin.core.LicensesService; +import org.elasticsearch.license.plugin.core.LicensesManagerService; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; +import static org.elasticsearch.license.plugin.core.LicensesService.DeleteLicenseRequestHolder; + public class TransportDeleteLicenseAction extends TransportMasterNodeOperationAction { - private final LicensesService licensesService; + private final LicensesManagerService licensesManagerService; @Inject - public TransportDeleteLicenseAction(Settings settings, TransportService transportService, ClusterService clusterService, LicensesService licensesService, + public TransportDeleteLicenseAction(Settings settings, TransportService transportService, ClusterService clusterService, LicensesManagerService licensesManagerService, ThreadPool threadPool, ActionFilters actionFilters) { super(settings, DeleteLicenseAction.NAME, transportService, clusterService, threadPool, actionFilters); - this.licensesService = licensesService; + this.licensesManagerService = licensesManagerService; } @Override @@ -56,7 +56,8 @@ public class TransportDeleteLicenseAction extends TransportMasterNodeOperationAc @Override protected void masterOperation(final DeleteLicenseRequest request, ClusterState state, final ActionListener listener) throws ElasticsearchException { - licensesService.unregisteredLicenses("delete_licenses []", request, new ActionListener() { + final DeleteLicenseRequestHolder requestHolder = new DeleteLicenseRequestHolder(request, "delete licenses []"); + licensesManagerService.unregisterLicenses(requestHolder, new ActionListener() { @Override public void onResponse(ClusterStateUpdateResponse clusterStateUpdateResponse) { listener.onResponse(new DeleteLicenseResponse(clusterStateUpdateResponse.isAcknowledged())); diff --git a/src/main/java/org/elasticsearch/license/plugin/action/put/TransportPutLicenseAction.java b/src/main/java/org/elasticsearch/license/plugin/action/put/TransportPutLicenseAction.java index 5a42272bb7b..caef2742071 100644 --- a/src/main/java/org/elasticsearch/license/plugin/action/put/TransportPutLicenseAction.java +++ b/src/main/java/org/elasticsearch/license/plugin/action/put/TransportPutLicenseAction.java @@ -16,20 +16,23 @@ import org.elasticsearch.cluster.block.ClusterBlockException; import org.elasticsearch.cluster.block.ClusterBlockLevel; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.license.plugin.core.LicensesManagerService; import org.elasticsearch.license.plugin.core.LicensesService; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; import org.elasticsearch.node.Node; +import static org.elasticsearch.license.plugin.core.LicensesService.PutLicenseRequestHolder; + public class TransportPutLicenseAction extends TransportMasterNodeOperationAction { - private final LicensesService licensesService; + private final LicensesManagerService LicensesManagerService; @Inject public TransportPutLicenseAction(Settings settings, TransportService transportService, ClusterService clusterService, - LicensesService licensesService, ThreadPool threadPool, ActionFilters actionFilters) { + LicensesManagerService LicensesManagerService, ThreadPool threadPool, ActionFilters actionFilters) { super(settings, PutLicenseAction.NAME, transportService, clusterService, threadPool, actionFilters); - this.licensesService = licensesService; + this.LicensesManagerService = LicensesManagerService; } @@ -55,9 +58,8 @@ public class TransportPutLicenseAction extends TransportMasterNodeOperationActio @Override protected void masterOperation(final PutLicenseRequest request, ClusterState state, final ActionListener listener) throws ElasticsearchException { - //TODO license validation - - licensesService.registerLicenses("put_licenses []",request, new ActionListener() { + final PutLicenseRequestHolder requestHolder = new PutLicenseRequestHolder(request, "put licenses []"); + LicensesManagerService.registerLicenses(requestHolder, new ActionListener() { @Override public void onResponse(ClusterStateUpdateResponse clusterStateUpdateResponse) { listener.onResponse(new PutLicenseResponse(clusterStateUpdateResponse.isAcknowledged())); diff --git a/src/main/java/org/elasticsearch/license/plugin/core/LicensesManagerService.java b/src/main/java/org/elasticsearch/license/plugin/core/LicensesManagerService.java new file mode 100644 index 00000000000..586750f6d55 --- /dev/null +++ b/src/main/java/org/elasticsearch/license/plugin/core/LicensesManagerService.java @@ -0,0 +1,19 @@ +/* + * 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.action.ActionListener; +import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse; + +import static org.elasticsearch.license.plugin.core.LicensesService.DeleteLicenseRequestHolder; +import static org.elasticsearch.license.plugin.core.LicensesService.PutLicenseRequestHolder; + +public interface LicensesManagerService { + + public void registerLicenses(final PutLicenseRequestHolder requestHolder, final ActionListener listener); + + public void unregisterLicenses(final DeleteLicenseRequestHolder requestHolder, final ActionListener listener); +} diff --git a/src/main/java/org/elasticsearch/license/plugin/core/LicensesMetaData.java b/src/main/java/org/elasticsearch/license/plugin/core/LicensesMetaData.java index d5124d5ab42..443ab294e47 100644 --- a/src/main/java/org/elasticsearch/license/plugin/core/LicensesMetaData.java +++ b/src/main/java/org/elasticsearch/license/plugin/core/LicensesMetaData.java @@ -22,8 +22,10 @@ import static org.elasticsearch.license.plugin.action.Utils.*; /** * Contains metadata about registered licenses + * + * TODO: add trial licenses to MetaData */ -public class LicensesMetaData implements MetaData.Custom, ESLicenses { +public class LicensesMetaData implements MetaData.Custom, ESLicenses, TrialLicenses { public static final String TYPE = "licenses"; @@ -64,6 +66,7 @@ public class LicensesMetaData implements MetaData.Custom, ESLicenses { return licenses.keySet(); } + @Override public ESLicense get(FeatureType featureType) { return licenses.get(featureType); @@ -74,6 +77,18 @@ public class LicensesMetaData implements MetaData.Custom, ESLicenses { return licenses.values().iterator(); } + @Override + public Collection trialLicenses() { + //todo trial license functionality + return null; + } + + @Override + public TrialLicense getTrialLicense(FeatureType featureType) { + //todo trial license functionality + return null; + } + /** * Licenses metadata factory */ @@ -147,7 +162,7 @@ public class LicensesMetaData implements MetaData.Custom, ESLicenses { @Override public EnumSet context() { - return EnumSet.of(MetaData.XContentContext.API); + return MetaData.API_ONLY; } diff --git a/src/main/java/org/elasticsearch/license/plugin/core/LicensesService.java b/src/main/java/org/elasticsearch/license/plugin/core/LicensesService.java index 9b24cf3d558..b628309ed4c 100644 --- a/src/main/java/org/elasticsearch/license/plugin/core/LicensesService.java +++ b/src/main/java/org/elasticsearch/license/plugin/core/LicensesService.java @@ -17,6 +17,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.license.core.ESLicenses; import org.elasticsearch.license.core.LicenseBuilders; import org.elasticsearch.license.manager.ESLicenseManager; +import org.elasticsearch.license.plugin.action.delete.DeleteLicenseAction; import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequest; import org.elasticsearch.license.plugin.action.put.PutLicenseRequest; import org.elasticsearch.node.Node; @@ -25,6 +26,8 @@ import org.elasticsearch.node.internal.InternalNode; import java.util.HashSet; import java.util.Set; +import static org.elasticsearch.license.plugin.core.TrialLicensesBuilder.EMPTY; + /** * Service responsible for maintaining and providing access to licenses on nodes. * @@ -32,7 +35,7 @@ import java.util.Set; * - implement logic in clusterChanged * - interface with LicenseManager */ -public class LicensesService extends AbstractLifecycleComponent implements ClusterStateListener { +public class LicensesService extends AbstractLifecycleComponent implements ClusterStateListener, LicensesManagerService, LicensesValidatorService { private ESLicenseManager esLicenseManager; @@ -40,6 +43,8 @@ public class LicensesService extends AbstractLifecycleComponent private ClusterService clusterService; + private volatile TrialLicenses trialLicenses = EMPTY; + @Inject public LicensesService(Settings settings, Node node) { super(settings); @@ -52,9 +57,11 @@ public class LicensesService extends AbstractLifecycleComponent * This method can be only called on the master node. It tries to create a new licenses on the master * and if it was successful it adds the license to cluster metadata. */ - public void registerLicenses(String source, final PutLicenseRequest request, final ActionListener listener) { + @Override + public void registerLicenses(final PutLicenseRequestHolder requestHolder, final ActionListener listener) { + final PutLicenseRequest request = requestHolder.request; final LicensesMetaData newLicenseMetaData = new LicensesMetaData(request.license()); - clusterService.submitStateUpdateTask(source, new AckedClusterStateUpdateTask(request, listener) { + clusterService.submitStateUpdateTask(requestHolder.source, new AckedClusterStateUpdateTask(request, listener) { @Override protected ClusterStateUpdateResponse newResponse(boolean acknowledged) { return new ClusterStateUpdateResponse(acknowledged); @@ -73,6 +80,7 @@ public class LicensesService extends AbstractLifecycleComponent currentLicenses = newLicenseMetaData; } else { // merge previous license with new one + //TODO: proper merge for trial licenses currentLicenses = new LicensesMetaData(LicenseBuilders.merge(currentLicenses, newLicenseMetaData)); } @@ -83,9 +91,11 @@ public class LicensesService extends AbstractLifecycleComponent } - public void unregisteredLicenses(String source, final DeleteLicenseRequest request, final ActionListener listener) { + @Override + public void unregisterLicenses(final DeleteLicenseRequestHolder requestHolder, final ActionListener listener) { + final DeleteLicenseRequest request = requestHolder.request; final Set featuresToDelete = asFeatureTypes(request.features()); - clusterService.submitStateUpdateTask(source, new AckedClusterStateUpdateTask(request, listener) { + clusterService.submitStateUpdateTask(requestHolder.source, new AckedClusterStateUpdateTask(request, listener) { @Override protected ClusterStateUpdateResponse newResponse(boolean acknowledged) { return new ClusterStateUpdateResponse(acknowledged); @@ -98,6 +108,7 @@ public class LicensesService extends AbstractLifecycleComponent LicensesMetaData currentLicenses = metaData.custom(LicensesMetaData.TYPE); if (currentLicenses != null) { + //TODO: proper delete for trial licenses currentLicenses = new LicensesMetaData(LicenseBuilders.removeFeatures(currentLicenses, featuresToDelete)); } mdBuilder.putCustom(LicensesMetaData.TYPE, currentLicenses); @@ -118,17 +129,36 @@ public class LicensesService extends AbstractLifecycleComponent @Override protected void doStop() throws ElasticsearchException { - //TODO } @Override protected void doClose() throws ElasticsearchException { - //TODO } @Override public void clusterChanged(ClusterChangedEvent event) { //TODO + + // check for registered plugin + // if appropriate registered plugin is found; push one-time trial license + + // check for cluster status (recovery) + // switch validation enforcement + } + + @Override + public boolean checkLicenseExpiry(String feature) { + //TODO make validation cluster state aware + //check trial license existence + // if found; use it to do the check + + return esLicenseManager.hasLicenseForFeature(ESLicenses.FeatureType.fromString(feature)); + } + + @Override + public boolean checkMaxNode(String feature) { + //TODO make validation cluster state aware + return false; } private static Set asFeatureTypes(Set featureTypeStrings) { @@ -138,4 +168,24 @@ public class LicensesService extends AbstractLifecycleComponent } return featureTypes; } + + public static class PutLicenseRequestHolder { + private final PutLicenseRequest request; + private final String source; + + public PutLicenseRequestHolder(PutLicenseRequest request, String source) { + this.request = request; + this.source = source; + } + } + + public static class DeleteLicenseRequestHolder { + private final DeleteLicenseRequest request; + private final String source; + + public DeleteLicenseRequestHolder(DeleteLicenseRequest request, String source) { + this.request = request; + this.source = source; + } + } } diff --git a/src/main/java/org/elasticsearch/license/plugin/core/LicensesValidatorService.java b/src/main/java/org/elasticsearch/license/plugin/core/LicensesValidatorService.java new file mode 100644 index 00000000000..d508058129d --- /dev/null +++ b/src/main/java/org/elasticsearch/license/plugin/core/LicensesValidatorService.java @@ -0,0 +1,14 @@ +/* + * 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; + +public interface LicensesValidatorService { + + public boolean checkLicenseExpiry(String feature); + + public boolean checkMaxNode(String feature); + +} diff --git a/src/main/java/org/elasticsearch/license/plugin/core/TrialLicenses.java b/src/main/java/org/elasticsearch/license/plugin/core/TrialLicenses.java new file mode 100644 index 00000000000..aff0f434da8 --- /dev/null +++ b/src/main/java/org/elasticsearch/license/plugin/core/TrialLicenses.java @@ -0,0 +1,26 @@ +/* + * 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 java.util.Collection; + +import static org.elasticsearch.license.core.ESLicenses.FeatureType; + +public interface TrialLicenses { + + public Collection trialLicenses(); + + public TrialLicense getTrialLicense(FeatureType featureType); + + public interface TrialLicense { + + public FeatureType feature(); + + public long issueDate(); + + public long expiryDate(); + } +} diff --git a/src/main/java/org/elasticsearch/license/plugin/core/TrialLicensesBuilder.java b/src/main/java/org/elasticsearch/license/plugin/core/TrialLicensesBuilder.java new file mode 100644 index 00000000000..915c41ebafd --- /dev/null +++ b/src/main/java/org/elasticsearch/license/plugin/core/TrialLicensesBuilder.java @@ -0,0 +1,125 @@ +/* + * 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.collect.ImmutableMap; +import org.elasticsearch.license.core.DateUtils; +import org.elasticsearch.license.core.ESLicenses; + +import java.util.*; + +import static org.elasticsearch.license.plugin.core.TrialLicenses.TrialLicense; + +public class TrialLicensesBuilder { + + public static TrialLicenses EMPTY = trialLicensesBuilder().build(); + + public static TrialLicensesBuilder trialLicensesBuilder() { + return new TrialLicensesBuilder(); + } + + private final ImmutableMap.Builder licenseBuilder = ImmutableMap.builder(); + + public TrialLicensesBuilder() { + } + + public TrialLicensesBuilder license(TrialLicense trialLicense) { + licenseBuilder.put(trialLicense.feature(), trialLicense); + return this; + } + + public TrialLicensesBuilder licenses(Collection trialLicenses) { + for (TrialLicense trialLicense : trialLicenses) { + license(trialLicense); + } + return this; + } + + public TrialLicenses build() { + final ImmutableMap licenseMap = licenseBuilder.build(); + return new TrialLicenses() { + + @Override + public Collection trialLicenses() { + return licenseMap.values(); + } + + @Override + public TrialLicense getTrialLicense(ESLicenses.FeatureType featureType) { + return licenseMap.get(featureType); + } + + }; + } + + public static class TrialLicenseBuilder { + private ESLicenses.FeatureType featureType; + private long expiryDate = -1; + private long issueDate = -1; + private int durationInDays = -1; + + public TrialLicenseBuilder() { + } + + public TrialLicenseBuilder feature(ESLicenses.FeatureType featureType) { + this.featureType = featureType; + return this; + } + + public TrialLicenseBuilder issueDate(long issueDate) { + this.issueDate = issueDate; + return this; + } + + public TrialLicenseBuilder durationInDays(int days) { + this.durationInDays = days; + return this; + } + + public TrialLicenseBuilder expiryDate(long expiryDate) { + this.expiryDate = expiryDate; + return this; + } + + public TrialLicense build() { + verify(); + if (expiryDate == -1) { + assert durationInDays != -1; + expiryDate = DateUtils.expiryDateAfterDays(issueDate, durationInDays); + } + return new TrialLicense() { + @Override + public ESLicenses.FeatureType feature() { + return featureType; + } + + @Override + public long issueDate() { + return issueDate; + } + + @Override + public long expiryDate() { + return expiryDate; + } + }; + } + + private void verify() { + String msg = null; + if (featureType == null) { + msg = "feature has to be set"; + } else if (issueDate == -1) { + msg = "issueDate has to be set"; + } else if (durationInDays == -1 && expiryDate == -1) { + msg = "durationInDays or expiryDate has to be set"; + } + if (msg != null) { + throw new IllegalArgumentException(msg); + } + } + } +}