initial stab at LicensesClientServices
Original commit: elastic/x-pack-elasticsearch@99e334e890
This commit is contained in:
parent
0ffa8c2c8e
commit
d807b20f4a
|
@ -7,15 +7,34 @@ package org.elasticsearch.license.plugin.core;
|
||||||
|
|
||||||
import org.elasticsearch.common.inject.ImplementedBy;
|
import org.elasticsearch.common.inject.ImplementedBy;
|
||||||
|
|
||||||
|
import static org.elasticsearch.license.plugin.core.LicensesService.TrialLicenseOptions;
|
||||||
|
|
||||||
@ImplementedBy(LicensesService.class)
|
@ImplementedBy(LicensesService.class)
|
||||||
public interface LicensesClientService {
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import org.elasticsearch.common.component.AbstractLifecycleComponent;
|
||||||
import org.elasticsearch.common.inject.Inject;
|
import org.elasticsearch.common.inject.Inject;
|
||||||
import org.elasticsearch.common.inject.Singleton;
|
import org.elasticsearch.common.inject.Singleton;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.gateway.GatewayService;
|
||||||
import org.elasticsearch.license.core.ESLicenses;
|
import org.elasticsearch.license.core.ESLicenses;
|
||||||
import org.elasticsearch.license.core.LicenseBuilders;
|
import org.elasticsearch.license.core.LicenseBuilders;
|
||||||
import org.elasticsearch.license.manager.ESLicenseManager;
|
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 org.elasticsearch.license.plugin.core.trial.TrialLicensesBuilder;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
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.core.ESLicenses.FeatureType;
|
||||||
import static org.elasticsearch.license.plugin.core.trial.TrialLicenses.TrialLicense;
|
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.
|
* 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 ClusterService clusterService;
|
||||||
|
|
||||||
private volatile TrialLicenses trialLicenses = EMPTY;
|
private List<ListenerHolder> registeredListeners = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public LicensesService(Settings settings, ClusterService clusterService) {
|
public LicensesService(Settings settings, ClusterService clusterService) {
|
||||||
|
@ -78,18 +82,49 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
|
||||||
|
|
||||||
esLicenseManager.verifyLicenses(newLicenses);
|
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) {
|
if (currentLicenses == null) {
|
||||||
// no licenses were registered
|
// no licenses were registered
|
||||||
currentLicenses = new LicensesMetaData(newLicenses, null);
|
currentLicenses = new LicensesMetaData(newLicenses, null);
|
||||||
} else {
|
} else {
|
||||||
// merge previous license with new one
|
TrialLicenses reducedTrialLicenses = null;
|
||||||
ESLicenses mergedLicenses = LicenseBuilders.merge(currentLicenses.getLicenses(), newLicenses);
|
if (currentLicenses.getTrialLicenses() != null) {
|
||||||
currentLicenses = new LicensesMetaData(mergedLicenses, currentLicenses.getTrialLicenses());
|
// 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);
|
mdBuilder.putCustom(LicensesMetaData.TYPE, currentLicenses);
|
||||||
return ClusterState.builder(currentState).metaData(mdBuilder).build();
|
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) {
|
private void registerTrialLicense(final TrialLicense trialLicense) {
|
||||||
clusterService.submitStateUpdateTask("register trial license []", new ProcessedClusterStateUpdateTask() {
|
clusterService.submitStateUpdateTask("register trial license []", new ProcessedClusterStateUpdateTask() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -133,26 +167,22 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
|
||||||
MetaData metaData = currentState.metaData();
|
MetaData metaData = currentState.metaData();
|
||||||
MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData());
|
MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData());
|
||||||
LicensesMetaData currentLicenses = metaData.custom(LicensesMetaData.TYPE);
|
LicensesMetaData currentLicenses = metaData.custom(LicensesMetaData.TYPE);
|
||||||
TrialLicensesBuilder trialLicensesBuilder = TrialLicensesBuilder.trialLicensesBuilder().license(trialLicense);
|
if (trialLicenseCheck(trialLicense.feature().string())) {
|
||||||
if (currentLicenses != null) {
|
TrialLicensesBuilder trialLicensesBuilder = TrialLicensesBuilder.trialLicensesBuilder().license(trialLicense);
|
||||||
if (currentLicenses.getTrialLicenses() != null) {
|
if (currentLicenses != null) {
|
||||||
// had previous trial licenses
|
if (currentLicenses.getTrialLicenses() != null) {
|
||||||
if (trialLicenseCheck(trialLicense.feature().string())) {
|
// had previous trial licenses
|
||||||
trialLicensesBuilder = trialLicensesBuilder.licenses(currentLicenses.getTrialLicenses());
|
trialLicensesBuilder = trialLicensesBuilder.licenses(currentLicenses.getTrialLicenses());
|
||||||
currentLicenses = new LicensesMetaData(currentLicenses.getLicenses(), trialLicensesBuilder.build());
|
currentLicenses = new LicensesMetaData(currentLicenses.getLicenses(), trialLicensesBuilder.build());
|
||||||
} else {
|
} else {
|
||||||
// had previously generated trial license for the feature
|
// had no previous trial license
|
||||||
currentLicenses = new LicensesMetaData(currentLicenses.getLicenses(), currentLicenses.getTrialLicenses());
|
currentLicenses = new LicensesMetaData(currentLicenses.getLicenses(), trialLicensesBuilder.build());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// had no previous trial license
|
// had no license meta data
|
||||||
currentLicenses = new LicensesMetaData(currentLicenses.getLicenses(), trialLicensesBuilder.build());
|
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);
|
mdBuilder.putCustom(LicensesMetaData.TYPE, currentLicenses);
|
||||||
return ClusterState.builder(currentState).metaData(mdBuilder).build();
|
return ClusterState.builder(currentState).metaData(mdBuilder).build();
|
||||||
}
|
}
|
||||||
|
@ -163,8 +193,19 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean trialLicenseCheck(String feature) {
|
private boolean trialLicenseCheck(String feature) {
|
||||||
final TrialLicense license = trialLicenses.getTrialLicense(FeatureType.fromString(feature));
|
// check if actual license exists
|
||||||
return license == null;
|
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
|
@Override
|
||||||
public void clusterChanged(ClusterChangedEvent event) {
|
public void clusterChanged(ClusterChangedEvent event) {
|
||||||
//TODO
|
if (!event.state().blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) {
|
||||||
|
performMasterNodeOperations(event);
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
@Override
|
||||||
public void registerAndActivate(String feature, TrialLicenseOptions trialLicenseOptions, Listener listener) {
|
public void register(String feature, Listener listener) {
|
||||||
// check if one-time trial license has been generated for this feature
|
registeredListeners.add(new ListenerHolder(feature, listener));
|
||||||
|
|
||||||
// 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 boolean checkLicenseExpiry(String feature) {
|
private void performMasterNodeOperations(ClusterChangedEvent event) {
|
||||||
//TODO make validation cluster state aware
|
if (DiscoveryNode.masterNode(settings)) {
|
||||||
//check trial license existence
|
// register all interested plugins
|
||||||
// if found; use it to do the check
|
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));
|
// notify all interested plugins
|
||||||
if (trialLicense != null) {
|
final LicensesMetaData currentLicensesMetaData = event.state().getMetaData().custom(LicensesMetaData.TYPE);
|
||||||
return trialLicense.expiryDate() > System.currentTimeMillis();
|
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) {
|
private static Set<FeatureType> asFeatureTypes(Set<String> featureTypeStrings) {
|
||||||
|
@ -272,4 +335,16 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
|
||||||
this.maxNodes = maxNodes;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue