initial stab at LicensesClientServices

Original commit: elastic/x-pack-elasticsearch@99e334e890
This commit is contained in:
Areek Zillur 2014-10-14 23:07:59 -04:00
parent 0ffa8c2c8e
commit d807b20f4a
2 changed files with 148 additions and 54 deletions

View File

@ -7,15 +7,34 @@ package org.elasticsearch.license.plugin.core;
import org.elasticsearch.common.inject.ImplementedBy;
import static org.elasticsearch.license.plugin.core.LicensesService.TrialLicenseOptions;
@ImplementedBy(LicensesService.class)
public interface LicensesClientService {
interface Listener {
public interface Listener {
void onEnabled();
/**
* Called to enable a feature
*/
public void onEnabled();
void onDisabled();
/**
* Called to disable a feature
*/
public void onDisabled();
/**
* Trial license specification used to
* generate a one-time trial license for the feature
*/
public TrialLicenseOptions trialLicenseOptions();
}
void registerAndActivate(String feature, LicensesService.TrialLicenseOptions trialLicenseOptions, Listener listener);
/**
* Registers a feature for licensing
* @param feature - name of the feature to register (must be in sync with license Generator feature name)
* @param listener - used to notify on feature enable/disable and specify trial license specification
*/
void register(String feature, Listener listener);
}

View File

@ -16,6 +16,7 @@ import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.Singleton;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.gateway.GatewayService;
import org.elasticsearch.license.core.ESLicenses;
import org.elasticsearch.license.core.LicenseBuilders;
import org.elasticsearch.license.manager.ESLicenseManager;
@ -25,11 +26,14 @@ import org.elasticsearch.license.plugin.core.trial.TrialLicenses;
import org.elasticsearch.license.plugin.core.trial.TrialLicensesBuilder;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.elasticsearch.license.core.ESLicenses.FeatureType;
import static org.elasticsearch.license.plugin.core.trial.TrialLicenses.TrialLicense;
import static org.elasticsearch.license.plugin.core.trial.TrialLicensesBuilder.EMPTY;
import static org.elasticsearch.license.plugin.core.trial.TrialLicensesBuilder.trialLicensesBuilder;
/**
* Service responsible for maintaining and providing access to licenses on nodes.
@ -45,7 +49,7 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
private ClusterService clusterService;
private volatile TrialLicenses trialLicenses = EMPTY;
private List<ListenerHolder> registeredListeners = new CopyOnWriteArrayList<>();
@Inject
public LicensesService(Settings settings, ClusterService clusterService) {
@ -78,18 +82,49 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
esLicenseManager.verifyLicenses(newLicenses);
/*
Four cases:
- no metadata - just add new license
- only trial license - check if trial exists for feature; if so remove it to replace with signed
- only signed license - add signed license
- both trial & signed license - same as only trial & only signed case combined
*/
if (currentLicenses == null) {
// no licenses were registered
currentLicenses = new LicensesMetaData(newLicenses, null);
} else {
// merge previous license with new one
ESLicenses mergedLicenses = LicenseBuilders.merge(currentLicenses.getLicenses(), newLicenses);
currentLicenses = new LicensesMetaData(mergedLicenses, currentLicenses.getTrialLicenses());
TrialLicenses reducedTrialLicenses = null;
if (currentLicenses.getTrialLicenses() != null) {
// has trial licenses for some features; reduce trial licenses according to new signed licenses
reducedTrialLicenses = reduceTrialLicenses(newLicenses, currentLicenses.getTrialLicenses());
}
if (currentLicenses.getLicenses() != null) {
// merge previous signed license with new one
ESLicenses mergedLicenses = LicenseBuilders.merge(currentLicenses.getLicenses(), newLicenses);
currentLicenses = new LicensesMetaData(mergedLicenses, reducedTrialLicenses);
} else {
// no previous signed licenses to merge with
currentLicenses = new LicensesMetaData(newLicenses, reducedTrialLicenses);
}
}
mdBuilder.putCustom(LicensesMetaData.TYPE, currentLicenses);
return ClusterState.builder(currentState).metaData(mdBuilder).build();
}
/**
* If signed license is found for any feature, remove the trial license for it
*/
private TrialLicenses reduceTrialLicenses(ESLicenses currentLicenses, TrialLicenses currentTrialLicenses) {
TrialLicensesBuilder builder = trialLicensesBuilder();
for (TrialLicense currentTrialLicense : currentTrialLicenses) {
if (currentLicenses.get(currentTrialLicense.feature()) == null) {
builder.license(currentTrialLicense);
}
}
return builder.build();
}
});
}
@ -120,7 +155,6 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
});
}
//TODO: hook this up
private void registerTrialLicense(final TrialLicense trialLicense) {
clusterService.submitStateUpdateTask("register trial license []", new ProcessedClusterStateUpdateTask() {
@Override
@ -133,26 +167,22 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
MetaData metaData = currentState.metaData();
MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData());
LicensesMetaData currentLicenses = metaData.custom(LicensesMetaData.TYPE);
TrialLicensesBuilder trialLicensesBuilder = TrialLicensesBuilder.trialLicensesBuilder().license(trialLicense);
if (currentLicenses != null) {
if (currentLicenses.getTrialLicenses() != null) {
// had previous trial licenses
if (trialLicenseCheck(trialLicense.feature().string())) {
if (trialLicenseCheck(trialLicense.feature().string())) {
TrialLicensesBuilder trialLicensesBuilder = TrialLicensesBuilder.trialLicensesBuilder().license(trialLicense);
if (currentLicenses != null) {
if (currentLicenses.getTrialLicenses() != null) {
// had previous trial licenses
trialLicensesBuilder = trialLicensesBuilder.licenses(currentLicenses.getTrialLicenses());
currentLicenses = new LicensesMetaData(currentLicenses.getLicenses(), trialLicensesBuilder.build());
} else {
// had previously generated trial license for the feature
currentLicenses = new LicensesMetaData(currentLicenses.getLicenses(), currentLicenses.getTrialLicenses());
// had no previous trial license
currentLicenses = new LicensesMetaData(currentLicenses.getLicenses(), trialLicensesBuilder.build());
}
} else {
// had no previous trial license
currentLicenses = new LicensesMetaData(currentLicenses.getLicenses(), trialLicensesBuilder.build());
// had no license meta data
currentLicenses = new LicensesMetaData(null, trialLicensesBuilder.build());
}
} else {
// had no license meta data
currentLicenses = new LicensesMetaData(null, trialLicensesBuilder.build());
}
trialLicenses = currentLicenses.getTrialLicenses();
mdBuilder.putCustom(LicensesMetaData.TYPE, currentLicenses);
return ClusterState.builder(currentState).metaData(mdBuilder).build();
}
@ -163,8 +193,19 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
}
private boolean trialLicenseCheck(String feature) {
final TrialLicense license = trialLicenses.getTrialLicense(FeatureType.fromString(feature));
return license == null;
// check if actual license exists
if (esLicenseManager.hasLicenseForFeature(FeatureType.fromString(feature))) {
return false;
}
// check if trial license for feature exists
for (ListenerHolder holder : registeredListeners) {
if (holder.feature.equals(feature) && holder.registered.get()) {
if (holder.trialLicenseGenerated.compareAndSet(false, true)) {
return true;
}
}
}
return false;
}
});
}
@ -186,42 +227,64 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
@Override
public void clusterChanged(ClusterChangedEvent event) {
//TODO
// check for registered plugin
// if appropriate registered plugin is found; push one-time trial license
// generate one-time trial license
// registerTrialLicense(generateTrialLicense(feature, 30, 1000));
// check for cluster status (recovery)
// switch validation enforcement
if (!event.state().blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) {
performMasterNodeOperations(event);
}
}
private boolean checkIfUpdatedMetaData(ClusterChangedEvent event) {
LicensesMetaData oldMetaData = event.previousState().getMetaData().custom(LicensesMetaData.TYPE);
LicensesMetaData newMetaData = event.state().getMetaData().custom(LicensesMetaData.TYPE);
return !((oldMetaData == null && newMetaData == null) || (oldMetaData != null && oldMetaData.equals(newMetaData)));
}
@Override
public void registerAndActivate(String feature, TrialLicenseOptions trialLicenseOptions, Listener listener) {
// check if one-time trial license has been generated for this feature
// if not call registerTrialLicense()
// if trial license generated, onEnabled should return
// else check actual licenses
TrialLicense trialLicense = generateTrialLicense(feature, trialLicenseOptions.durationInDays, trialLicenseOptions.maxNodes);
registerTrialLicense(trialLicense);
public void register(String feature, Listener listener) {
registeredListeners.add(new ListenerHolder(feature, listener));
}
public boolean checkLicenseExpiry(String feature) {
//TODO make validation cluster state aware
//check trial license existence
// if found; use it to do the check
private void performMasterNodeOperations(ClusterChangedEvent event) {
if (DiscoveryNode.masterNode(settings)) {
// register all interested plugins
for (ListenerHolder listenerHolder : registeredListeners) {
if (listenerHolder.registered.compareAndSet(false, true)) {
if (!esLicenseManager.hasLicenseForFeature(FeatureType.fromString(listenerHolder.feature))) {
// does not have actual license so generate a trial license
TrialLicenseOptions options = listenerHolder.listener.trialLicenseOptions();
TrialLicense trialLicense = generateTrialLicense(listenerHolder.feature, options.durationInDays, options.maxNodes);
registerTrialLicense(trialLicense);
}
}
}
final TrialLicense trialLicense = trialLicenses.getTrialLicense(FeatureType.fromString(feature));
if (trialLicense != null) {
return trialLicense.expiryDate() > System.currentTimeMillis();
// notify all interested plugins
final LicensesMetaData currentLicensesMetaData = event.state().getMetaData().custom(LicensesMetaData.TYPE);
if (currentLicensesMetaData != null) {
notifyFeatures(currentLicensesMetaData);
}
}
}
//TODO: have a timed task to invoke listener.onDisabled upon latest expiry for a feature
// currently dependant on clusterChangeEvents
private void notifyFeatures(LicensesMetaData currentLicensesMetaData) {
for (ListenerHolder listenerHolder : registeredListeners) {
long expiryDate = -1l;
if (esLicenseManager.hasLicenseForFeature(FeatureType.fromString(listenerHolder.feature))) {
expiryDate = esLicenseManager.getExpiryDateForLicense(FeatureType.fromString(listenerHolder.feature));
} else if (currentLicensesMetaData.getTrialLicenses() != null) {
final TrialLicense trialLicense = currentLicensesMetaData.getTrialLicenses().getTrialLicense(FeatureType.fromString(listenerHolder.feature));
if (trialLicense != null) {
expiryDate = trialLicense.expiryDate();
}
}
if (expiryDate > System.currentTimeMillis()) {
listenerHolder.listener.onEnabled();
} else {
listenerHolder.listener.onDisabled();
}
}
return esLicenseManager.hasLicenseForFeature(FeatureType.fromString(feature));
}
private static Set<FeatureType> asFeatureTypes(Set<String> featureTypeStrings) {
@ -272,4 +335,16 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
this.maxNodes = maxNodes;
}
}
private static class ListenerHolder {
final String feature;
final Listener listener;
final AtomicBoolean registered = new AtomicBoolean(false);
final AtomicBoolean trialLicenseGenerated = new AtomicBoolean(false);
private ListenerHolder(String feature, Listener listener) {
this.feature = feature;
this.listener = listener;
}
}
}