Added Service Interfaces; start trial license impl; spec out licensesService

Original commit: elastic/x-pack-elasticsearch@1e5b311113
This commit is contained in:
Areek Zillur 2014-10-09 17:20:38 -04:00
parent 90466deb0c
commit f05ccaa3f9
10 changed files with 293 additions and 22 deletions

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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<DeleteLicenseRequest, DeleteLicenseResponse> {
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<DeleteLicenseResponse> listener) throws ElasticsearchException {
licensesService.unregisteredLicenses("delete_licenses []", request, new ActionListener<ClusterStateUpdateResponse>() {
final DeleteLicenseRequestHolder requestHolder = new DeleteLicenseRequestHolder(request, "delete licenses []");
licensesManagerService.unregisterLicenses(requestHolder, new ActionListener<ClusterStateUpdateResponse>() {
@Override
public void onResponse(ClusterStateUpdateResponse clusterStateUpdateResponse) {
listener.onResponse(new DeleteLicenseResponse(clusterStateUpdateResponse.isAcknowledged()));

View File

@ -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<PutLicenseRequest, PutLicenseResponse> {
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<PutLicenseResponse> listener) throws ElasticsearchException {
//TODO license validation
licensesService.registerLicenses("put_licenses []",request, new ActionListener<ClusterStateUpdateResponse>() {
final PutLicenseRequestHolder requestHolder = new PutLicenseRequestHolder(request, "put licenses []");
LicensesManagerService.registerLicenses(requestHolder, new ActionListener<ClusterStateUpdateResponse>() {
@Override
public void onResponse(ClusterStateUpdateResponse clusterStateUpdateResponse) {
listener.onResponse(new PutLicenseResponse(clusterStateUpdateResponse.isAcknowledged()));

View File

@ -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<ClusterStateUpdateResponse> listener);
public void unregisterLicenses(final DeleteLicenseRequestHolder requestHolder, final ActionListener<ClusterStateUpdateResponse> listener);
}

View File

@ -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<TrialLicense> 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<MetaData.XContentContext> context() {
return EnumSet.of(MetaData.XContentContext.API);
return MetaData.API_ONLY;
}

View File

@ -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<LicensesService> implements ClusterStateListener {
public class LicensesService extends AbstractLifecycleComponent<LicensesService> implements ClusterStateListener, LicensesManagerService, LicensesValidatorService {
private ESLicenseManager esLicenseManager;
@ -40,6 +43,8 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
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<LicensesService>
* 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<ClusterStateUpdateResponse> listener) {
@Override
public void registerLicenses(final PutLicenseRequestHolder requestHolder, final ActionListener<ClusterStateUpdateResponse> listener) {
final PutLicenseRequest request = requestHolder.request;
final LicensesMetaData newLicenseMetaData = new LicensesMetaData(request.license());
clusterService.submitStateUpdateTask(source, new AckedClusterStateUpdateTask<ClusterStateUpdateResponse>(request, listener) {
clusterService.submitStateUpdateTask(requestHolder.source, new AckedClusterStateUpdateTask<ClusterStateUpdateResponse>(request, listener) {
@Override
protected ClusterStateUpdateResponse newResponse(boolean acknowledged) {
return new ClusterStateUpdateResponse(acknowledged);
@ -73,6 +80,7 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
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<LicensesService>
}
public void unregisteredLicenses(String source, final DeleteLicenseRequest request, final ActionListener<ClusterStateUpdateResponse> listener) {
@Override
public void unregisterLicenses(final DeleteLicenseRequestHolder requestHolder, final ActionListener<ClusterStateUpdateResponse> listener) {
final DeleteLicenseRequest request = requestHolder.request;
final Set<ESLicenses.FeatureType> featuresToDelete = asFeatureTypes(request.features());
clusterService.submitStateUpdateTask(source, new AckedClusterStateUpdateTask<ClusterStateUpdateResponse>(request, listener) {
clusterService.submitStateUpdateTask(requestHolder.source, new AckedClusterStateUpdateTask<ClusterStateUpdateResponse>(request, listener) {
@Override
protected ClusterStateUpdateResponse newResponse(boolean acknowledged) {
return new ClusterStateUpdateResponse(acknowledged);
@ -98,6 +108,7 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
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<LicensesService>
@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<ESLicenses.FeatureType> asFeatureTypes(Set<String> featureTypeStrings) {
@ -138,4 +168,24 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
}
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;
}
}
}

View File

@ -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);
}

View File

@ -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<TrialLicense> trialLicenses();
public TrialLicense getTrialLicense(FeatureType featureType);
public interface TrialLicense {
public FeatureType feature();
public long issueDate();
public long expiryDate();
}
}

View File

@ -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<ESLicenses.FeatureType, TrialLicense> licenseBuilder = ImmutableMap.builder();
public TrialLicensesBuilder() {
}
public TrialLicensesBuilder license(TrialLicense trialLicense) {
licenseBuilder.put(trialLicense.feature(), trialLicense);
return this;
}
public TrialLicensesBuilder licenses(Collection<TrialLicense> trialLicenses) {
for (TrialLicense trialLicense : trialLicenses) {
license(trialLicense);
}
return this;
}
public TrialLicenses build() {
final ImmutableMap<ESLicenses.FeatureType, TrialLicense> licenseMap = licenseBuilder.build();
return new TrialLicenses() {
@Override
public Collection<TrialLicense> 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);
}
}
}
}