[License] Feature agnostic licensing model

This commit changes the license plugin to work with license that are not tied to any specific feature in a bwc way. It refactors the license plugin api into a lighter weight API, enabling the license plugin to manage license expiration and acknowledgment triggers.

closes elastic/elasticsearch#683, elastic/elasticsearch#686, elastic/elasticsearch#687, elastic/elasticsearch#691

Original commit: elastic/x-pack-elasticsearch@537cd3933a
This commit is contained in:
Areek Zillur 2015-10-09 00:32:15 -04:00
parent fa85d04523
commit daf4a9765c
17 changed files with 258 additions and 400 deletions

View File

@ -60,7 +60,8 @@ public class ClusterInfoCollector extends AbstractCollector<ClusterInfoMarvelDoc
List<MarvelDoc> results = new ArrayList<>(1); List<MarvelDoc> results = new ArrayList<>(1);
// Retrieves all licenses // Retrieves all licenses
List<License> licenses = licenseService.licenses(); // TODO: we should only work with one license
List<License> licenses = Collections.singletonList(licenseService.license());
// Retrieves additional cluster stats // Retrieves additional cluster stats
ClusterStatsResponse clusterStats = client.admin().cluster().prepareClusterStats().get(marvelSettings.clusterStatsTimeout()); ClusterStatsResponse clusterStats = client.admin().cluster().prepareClusterStats().get(marvelSettings.clusterStatsTimeout());

View File

@ -37,7 +37,6 @@ public class ClusterInfoRenderer extends AbstractRenderer<ClusterInfoMarvelDoc>
builder.field(Fields.UID, license.uid()); builder.field(Fields.UID, license.uid());
builder.field(Fields.TYPE, license.type()); builder.field(Fields.TYPE, license.type());
builder.dateValueField(Fields.ISSUE_DATE_IN_MILLIS, Fields.ISSUE_DATE, license.issueDate()); builder.dateValueField(Fields.ISSUE_DATE_IN_MILLIS, Fields.ISSUE_DATE, license.issueDate());
builder.field(Fields.FEATURE, license.feature());
builder.dateValueField(Fields.EXPIRY_DATE_IN_MILLIS, Fields.EXPIRY_DATE, license.expiryDate()); builder.dateValueField(Fields.EXPIRY_DATE_IN_MILLIS, Fields.EXPIRY_DATE, license.expiryDate());
builder.field(Fields.MAX_NODES, license.maxNodes()); builder.field(Fields.MAX_NODES, license.maxNodes());
builder.field(Fields.ISSUED_TO, license.issuedTo()); builder.field(Fields.ISSUED_TO, license.issuedTo());
@ -77,7 +76,6 @@ public class ClusterInfoRenderer extends AbstractRenderer<ClusterInfoMarvelDoc>
static final XContentBuilderString STATUS = new XContentBuilderString("status"); static final XContentBuilderString STATUS = new XContentBuilderString("status");
static final XContentBuilderString UID = new XContentBuilderString("uid"); static final XContentBuilderString UID = new XContentBuilderString("uid");
static final XContentBuilderString TYPE = new XContentBuilderString("type"); static final XContentBuilderString TYPE = new XContentBuilderString("type");
static final XContentBuilderString FEATURE = new XContentBuilderString("feature");
static final XContentBuilderString ISSUE_DATE_IN_MILLIS = new XContentBuilderString("issue_date_in_millis"); static final XContentBuilderString ISSUE_DATE_IN_MILLIS = new XContentBuilderString("issue_date_in_millis");
static final XContentBuilderString ISSUE_DATE = new XContentBuilderString("issue_date"); static final XContentBuilderString ISSUE_DATE = new XContentBuilderString("issue_date");
static final XContentBuilderString EXPIRY_DATE_IN_MILLIS = new XContentBuilderString("expiry_date_in_millis"); static final XContentBuilderString EXPIRY_DATE_IN_MILLIS = new XContentBuilderString("expiry_date_in_millis");

View File

@ -6,110 +6,45 @@
package org.elasticsearch.marvel.license; package org.elasticsearch.marvel.license;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.component.AbstractLifecycleComponent; import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
import org.elasticsearch.common.joda.Joda;
import org.elasticsearch.common.logging.support.LoggerMessageFormat; import org.elasticsearch.common.logging.support.LoggerMessageFormat;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.core.License; import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.core.LicensesClientService; import org.elasticsearch.license.plugin.core.LicenseState;
import org.elasticsearch.license.plugin.core.Licensee;
import org.elasticsearch.license.plugin.core.LicenseeRegistry;
import org.elasticsearch.license.plugin.core.LicensesManagerService; import org.elasticsearch.license.plugin.core.LicensesManagerService;
import org.elasticsearch.license.plugin.core.LicensesService;
import org.elasticsearch.marvel.MarvelPlugin; import org.elasticsearch.marvel.MarvelPlugin;
import org.elasticsearch.marvel.agent.settings.MarvelSettings; import org.elasticsearch.marvel.agent.settings.MarvelSettings;
import org.elasticsearch.marvel.mode.Mode; import org.elasticsearch.marvel.mode.Mode;
import java.util.*;
public class LicenseService extends AbstractLifecycleComponent<LicenseService> { public class LicenseService extends AbstractLifecycleComponent<LicenseService> implements Licensee {
public static final String FEATURE_NAME = MarvelPlugin.NAME; public static final String FEATURE_NAME = MarvelPlugin.NAME;
private static final LicensesService.TrialLicenseOptions TRIAL_LICENSE_OPTIONS =
new LicensesService.TrialLicenseOptions(TimeValue.timeValueHours(30 * 24), 1000);
private static final FormatDateTimeFormatter DATE_FORMATTER = Joda.forPattern("EEEE, MMMMM dd, yyyy", Locale.ROOT);
static final TimeValue GRACE_PERIOD = days(7);
private final LicensesManagerService managerService; private final LicensesManagerService managerService;
private final LicensesClientService clientService; private final LicenseeRegistry clientService;
private final MarvelSettings marvelSettings; private final MarvelSettings marvelSettings;
private final Collection<LicensesService.ExpirationCallback> expirationLoggers;
private final LicensesClientService.AcknowledgementCallback acknowledgementCallback;
private volatile Mode mode; private volatile Mode mode;
private volatile boolean enabled; private volatile LicenseState state;
private volatile long expiryDate; private volatile long expiryDate;
@Inject @Inject
public LicenseService(Settings settings, LicensesClientService clientService, LicensesManagerService managerService, MarvelSettings marvelSettings) { public LicenseService(Settings settings, LicenseeRegistry clientService, LicensesManagerService managerService, MarvelSettings marvelSettings) {
super(settings); super(settings);
this.managerService = managerService; this.managerService = managerService;
this.clientService = clientService; this.clientService = clientService;
this.marvelSettings = marvelSettings; this.marvelSettings = marvelSettings;
this.mode = Mode.LITE; this.mode = Mode.LITE;
this.expirationLoggers = Arrays.asList(
new LicensesService.ExpirationCallback.Pre(days(7), days(30), days(1)) {
@Override
public void on(License license, LicensesService.ExpirationStatus status) {
logger.error("\n" +
"#\n" +
"# Marvel license will expire on [{}].\n" +
"# Have a new license? please update it. Otherwise, please reach out to your support contact.\n" +
"#", DATE_FORMATTER.printer().print(license.expiryDate()));
}
},
new LicensesService.ExpirationCallback.Pre(days(0), days(7), minutes(10)) {
@Override
public void on(License license, LicensesService.ExpirationStatus status) {
logger.error("\n" +
"#\n" +
"# Marvel license will expire on [{}].\n" +
"# Have a new license? please update it. Otherwise, please reach out to your support contact.\n" +
"#", DATE_FORMATTER.printer().print(license.expiryDate()));
}
},
new LicensesService.ExpirationCallback.Post(days(0), GRACE_PERIOD, minutes(10)) {
@Override
public void on(License license, LicensesService.ExpirationStatus status) {
long endOfGracePeriod = license.expiryDate() + GRACE_PERIOD.getMillis();
logger.error("\n" +
"#\n" +
"# MARVEL LICENSE HAS EXPIRED ON [{}].\n" +
"# MARVEL WILL STOP COLLECTING DATA ON [{}].\n" +
"# HAVE A NEW LICENSE? PLEASE UPDATE IT. OTHERWISE, PLEASE REACH OUT TO YOUR SUPPORT CONTACT.\n" +
"#", DATE_FORMATTER.printer().print(endOfGracePeriod), DATE_FORMATTER.printer().print(license.expiryDate()));
}
}
);
this.acknowledgementCallback = new LicensesClientService.AcknowledgementCallback() {
@Override
public List<String> acknowledge(License currentLicense, License newLicense) {
switch (newLicense.type()) {
case "trial":
case "gold":
case "platinum":
return Collections.emptyList();
default: // "basic" - we also fall back to basic for an unknown type
return Collections.singletonList(LoggerMessageFormat.format(
"Marvel: Multi-cluster support is disabled for clusters with [{}] licenses.\n" +
"If you are running multiple customers, users won't be able to access this\n" +
"all the clusters with [{}] licenses from a single Marvel instance. To access them\n" +
"a dedicated and separated marvel instance will be required for each cluster",
newLicense.type(), newLicense.type()));
}
}
};
} }
@Override @Override
protected void doStart() throws ElasticsearchException { protected void doStart() throws ElasticsearchException {
clientService.register(FEATURE_NAME, TRIAL_LICENSE_OPTIONS, expirationLoggers, acknowledgementCallback, new InternalListener(this)); clientService.register(this);
} }
@Override @Override
@ -120,14 +55,6 @@ public class LicenseService extends AbstractLifecycleComponent<LicenseService> {
protected void doClose() throws ElasticsearchException { protected void doClose() throws ElasticsearchException {
} }
static TimeValue days(int days) {
return TimeValue.timeValueHours(days * 24);
}
static TimeValue minutes(int minutes) {
return TimeValue.timeValueMinutes(minutes);
}
/** /**
* @return the current marvel's operating mode * @return the current marvel's operating mode
*/ */
@ -138,18 +65,20 @@ public class LicenseService extends AbstractLifecycleComponent<LicenseService> {
/** /**
* @return all registered licenses * @return all registered licenses
*/ */
public List<License> licenses() { public License license() {
return managerService.getLicenses(); return managerService.getLicense();
} }
/** /**
* @return true if the marvel license is enabled * @return true if the marvel license is enabled
*/ */
public boolean enabled() { public boolean enabled() {
return enabled; return state == LicenseState.ENABLED || state == LicenseState.GRACE_PERIOD;
} }
/** /**
* TODO: remove licensing grace period, just check for state == LicensesClientService.LicenseState.GRACE_PERIOD instead
*
* @return true if marvel is running within the "grace period", ie when the license * @return true if marvel is running within the "grace period", ie when the license
* is expired but a given extra delay is not yet elapsed * is expired but a given extra delay is not yet elapsed
*/ */
@ -164,30 +93,57 @@ public class LicenseService extends AbstractLifecycleComponent<LicenseService> {
return expiryDate; return expiryDate;
} }
class InternalListener implements LicensesClientService.Listener { @Override
public String id() {
return FEATURE_NAME;
}
private final LicenseService service; @Override
public String[] expirationMessages() {
// TODO add messages to be logged around license expiry
return Strings.EMPTY_ARRAY;
}
public InternalListener(LicenseService service) { @Override
this.service = service; public String[] acknowledgmentMessages(License currentLicense, License newLicense) {
switch (newLicense.operationMode()) {
case BASIC:
if (currentLicense != null) {
switch (currentLicense.operationMode()) {
case TRIAL:
case GOLD:
case PLATINUM:
return new String[] {
LoggerMessageFormat.format(
"Multi-cluster support is disabled for clusters with [{}] licenses.\n" +
"If you are running multiple customers, users won't be able to access this\n" +
"all the clusters with [{}] licenses from a single Marvel instance. To access them\n" +
"a dedicated and separated marvel instance will be required for each cluster",
newLicense.type(), newLicense.type())
};
}
}
} }
return Strings.EMPTY_ARRAY;
}
@Override @Override
public void onEnabled(License license) { public void onChange(License license, LicenseState state) {
try { synchronized (this) {
service.enabled = true; this.state = state;
service.expiryDate = license.expiryDate(); if (license != null) {
service.mode = Mode.fromName(license.type()); try {
} catch (IllegalArgumentException e) { mode = Mode.fromName(license.type());
service.mode = Mode.LITE; } catch (IllegalArgumentException e) {
mode = Mode.LITE;
}
expiryDate = license.expiryDate();
} else {
mode = Mode.LITE;
}
if (state == LicenseState.DISABLED) {
mode = Mode.LITE;
} }
}
@Override
public void onDisabled(License license) {
service.enabled = false;
service.expiryDate = license.expiryDate();
service.mode = Mode.LITE;
} }
} }
} }

View File

@ -16,6 +16,8 @@ public enum Mode {
/** /**
* Marvel runs in downgraded mode * Marvel runs in downgraded mode
*
* TODO: do we really need mode?
*/ */
TRIAL(0), TRIAL(0),
@ -55,9 +57,13 @@ public enum Mode {
public static Mode fromName(String name) { public static Mode fromName(String name) {
switch (name.toLowerCase(Locale.ROOT)) { switch (name.toLowerCase(Locale.ROOT)) {
case "trial": return TRIAL; case "trial":
case "lite": return LITE; return LITE;
case "standard" : return STANDARD; case "basic":
case "gold" :
case "silver":
case "platinum":
return STANDARD;
default: default:
throw new ElasticsearchException("unknown marvel mode name [" + name + "]"); throw new ElasticsearchException("unknown marvel mode name [" + name + "]");
} }

View File

@ -33,7 +33,7 @@ public class MarvelInternalUserHolder {
// and full access to .marvel-* and .marvel-data indices // and full access to .marvel-* and .marvel-data indices
.add(Privilege.Index.ALL, MarvelSettings.MARVEL_INDICES_PREFIX + "*") .add(Privilege.Index.ALL, MarvelSettings.MARVEL_INDICES_PREFIX + "*")
// note, we don't need _licenses permission as we're taking the licenses // note, we don't need _license permission as we're taking the licenses
// directly form the license service. // directly form the license service.
.build(); .build();

View File

@ -17,12 +17,11 @@ import org.elasticsearch.common.inject.Module;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.core.License; import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.core.LicensesClientService; import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequest;
import org.elasticsearch.license.plugin.core.LicensesManagerService; import org.elasticsearch.license.plugin.action.put.PutLicenseRequest;
import org.elasticsearch.license.plugin.core.LicensesService; import org.elasticsearch.license.plugin.core.*;
import org.elasticsearch.marvel.MarvelPlugin; import org.elasticsearch.marvel.MarvelPlugin;
import org.elasticsearch.marvel.agent.settings.MarvelSettings; import org.elasticsearch.marvel.agent.settings.MarvelSettings;
import org.elasticsearch.marvel.license.LicenseService;
import org.elasticsearch.marvel.shield.MarvelShieldIntegration; import org.elasticsearch.marvel.shield.MarvelShieldIntegration;
import org.elasticsearch.marvel.shield.SecuredClient; import org.elasticsearch.marvel.shield.SecuredClient;
import org.elasticsearch.marvel.test.MarvelIntegTestCase; import org.elasticsearch.marvel.test.MarvelIntegTestCase;
@ -88,15 +87,13 @@ public class AbstractCollectorTestCase extends MarvelIntegTestCase {
private static License createTestingLicense(long issueDate, long expiryDate) { private static License createTestingLicense(long issueDate, long expiryDate) {
return License.builder() return License.builder()
.feature(LicenseService.FEATURE_NAME)
.expiryDate(expiryDate) .expiryDate(expiryDate)
.issueDate(issueDate) .issueDate(issueDate)
.issuedTo("AbstractCollectorTestCase") .issuedTo("AbstractCollectorTestCase")
.issuer("test") .issuer("test")
.maxNodes(Integer.MAX_VALUE) .maxNodes(Integer.MAX_VALUE)
.signature("_signature") .signature("_signature")
.type("standard") .type("basic")
.subscriptionType("all_is_good")
.uid(String.valueOf(RandomizedTest.systemPropertyAsInt(SysGlobals.CHILDVM_SYSPROP_JVM_ID, 0)) + System.identityHashCode(AbstractCollectorTestCase.class)) .uid(String.valueOf(RandomizedTest.systemPropertyAsInt(SysGlobals.CHILDVM_SYSPROP_JVM_ID, 0)) + System.identityHashCode(AbstractCollectorTestCase.class))
.build(); .build();
} }
@ -107,7 +104,7 @@ public class AbstractCollectorTestCase extends MarvelIntegTestCase {
final License license = createTestingLicense(issueDate, expiryDate); final License license = createTestingLicense(issueDate, expiryDate);
for (LicenseServiceForCollectors service : internalCluster().getInstances(LicenseServiceForCollectors.class)) { for (LicenseServiceForCollectors service : internalCluster().getInstances(LicenseServiceForCollectors.class)) {
service.enable(license); service.onChange(license, LicenseState.ENABLED);
} }
for (LicensesManagerServiceForCollectors service : internalCluster().getInstances(LicensesManagerServiceForCollectors.class)) { for (LicensesManagerServiceForCollectors service : internalCluster().getInstances(LicensesManagerServiceForCollectors.class)) {
service.update(license); service.update(license);
@ -120,7 +117,7 @@ public class AbstractCollectorTestCase extends MarvelIntegTestCase {
final License license = createTestingLicense(issueDate, expiryDate); final License license = createTestingLicense(issueDate, expiryDate);
for (LicenseServiceForCollectors service : internalCluster().getInstances(LicenseServiceForCollectors.class)) { for (LicenseServiceForCollectors service : internalCluster().getInstances(LicenseServiceForCollectors.class)) {
service.disable(license); service.onChange(license, LicenseState.GRACE_PERIOD);
} }
for (LicensesManagerServiceForCollectors service : internalCluster().getInstances(LicensesManagerServiceForCollectors.class)) { for (LicensesManagerServiceForCollectors service : internalCluster().getInstances(LicensesManagerServiceForCollectors.class)) {
service.update(license); service.update(license);
@ -133,7 +130,7 @@ public class AbstractCollectorTestCase extends MarvelIntegTestCase {
final License license = createTestingLicense(issueDate, expiryDate); final License license = createTestingLicense(issueDate, expiryDate);
for (LicenseServiceForCollectors service : internalCluster().getInstances(LicenseServiceForCollectors.class)) { for (LicenseServiceForCollectors service : internalCluster().getInstances(LicenseServiceForCollectors.class)) {
service.disable(license); service.onChange(license, LicenseState.DISABLED);
} }
for (LicensesManagerServiceForCollectors service : internalCluster().getInstances(LicensesManagerServiceForCollectors.class)) { for (LicensesManagerServiceForCollectors service : internalCluster().getInstances(LicensesManagerServiceForCollectors.class)) {
service.update(license); service.update(license);
@ -146,7 +143,7 @@ public class AbstractCollectorTestCase extends MarvelIntegTestCase {
final License license = createTestingLicense(issueDate, expiryDate); final License license = createTestingLicense(issueDate, expiryDate);
for (LicenseServiceForCollectors service : internalCluster().getInstances(LicenseServiceForCollectors.class)) { for (LicenseServiceForCollectors service : internalCluster().getInstances(LicenseServiceForCollectors.class)) {
service.disable(license); service.onChange(license, LicenseState.DISABLED);
} }
for (LicensesManagerServiceForCollectors service : internalCluster().getInstances(LicensesManagerServiceForCollectors.class)) { for (LicensesManagerServiceForCollectors service : internalCluster().getInstances(LicensesManagerServiceForCollectors.class)) {
service.update(license); service.update(license);
@ -205,7 +202,7 @@ public class AbstractCollectorTestCase extends MarvelIntegTestCase {
@Override @Override
protected void configure() { protected void configure() {
bind(LicenseServiceForCollectors.class).asEagerSingleton(); bind(LicenseServiceForCollectors.class).asEagerSingleton();
bind(LicensesClientService.class).to(LicenseServiceForCollectors.class); bind(LicenseeRegistry.class).to(LicenseServiceForCollectors.class);
bind(LicensesManagerServiceForCollectors.class).asEagerSingleton(); bind(LicensesManagerServiceForCollectors.class).asEagerSingleton();
bind(LicensesManagerService.class).to(LicensesManagerServiceForCollectors.class); bind(LicensesManagerService.class).to(LicensesManagerServiceForCollectors.class);
} }
@ -213,9 +210,9 @@ public class AbstractCollectorTestCase extends MarvelIntegTestCase {
} }
} }
public static class LicenseServiceForCollectors extends AbstractComponent implements LicensesClientService { public static class LicenseServiceForCollectors extends AbstractComponent implements LicenseeRegistry {
private final List<Listener> listeners = new ArrayList<>(); private final List<Licensee> licensees = new ArrayList<>();
@Inject @Inject
public LicenseServiceForCollectors(Settings settings) { public LicenseServiceForCollectors(Settings settings) {
@ -223,19 +220,13 @@ public class AbstractCollectorTestCase extends MarvelIntegTestCase {
} }
@Override @Override
public void register(String feature, TrialLicenseOptions trialLicenseOptions, Collection<ExpirationCallback> expirationCallbacks, AcknowledgementCallback acknowledgementCallback, Listener listener) { public void register(Licensee licensee) {
listeners.add(listener); licensees.add(licensee);
} }
public void enable(License license) { public void onChange(License license, LicenseState state) {
for (Listener listener : listeners) { for (Licensee licensee : licensees) {
listener.onEnabled(license); licensee.onChange(license, state);
}
}
public void disable(License license) {
for (Listener listener : listeners) {
listener.onDisabled(license);
} }
} }
} }
@ -245,21 +236,24 @@ public class AbstractCollectorTestCase extends MarvelIntegTestCase {
private final Map<String, License> licenses = Collections.synchronizedMap(new HashMap<String, License>()); private final Map<String, License> licenses = Collections.synchronizedMap(new HashMap<String, License>());
@Override @Override
public void registerLicenses(LicensesService.PutLicenseRequestHolder requestHolder, ActionListener<LicensesService.LicensesUpdateResponse> listener) { public void registerLicense(PutLicenseRequest request, ActionListener<LicensesService.LicensesUpdateResponse> listener) {
} }
@Override @Override
public void removeLicenses(LicensesService.DeleteLicenseRequestHolder requestHolder, ActionListener<ClusterStateUpdateResponse> listener) { public void removeLicense(DeleteLicenseRequest request, ActionListener<ClusterStateUpdateResponse> listener) {
} }
@Override @Override
public Set<String> enabledFeatures() { public List<String> licenseesWithState(LicenseState state) {
return null; return null;
} }
@Override @Override
public List<License> getLicenses() { public License getLicense() {
return new ArrayList<>(licenses.values()); // TODO: we only take the first of the licenses that are updated
// FIXME
Iterator<License> iterator = licenses.values().iterator();
return iterator.hasNext() ? iterator.next() : null;
} }
public void update(License license) { public void update(License license) {

View File

@ -93,7 +93,6 @@ public class ClusterInfoIT extends MarvelIntegTestCase {
String recalculated = ClusterInfoRenderer.hash(status, uid, type, String.valueOf(expiryDate), clusterUUID); String recalculated = ClusterInfoRenderer.hash(status, uid, type, String.valueOf(expiryDate), clusterUUID);
assertThat(hkey, equalTo(recalculated)); assertThat(hkey, equalTo(recalculated));
assertThat((String) license.get(ClusterInfoRenderer.Fields.FEATURE.underscore().toString()), not(isEmptyOrNullString()));
assertThat((String) license.get(ClusterInfoRenderer.Fields.ISSUER.underscore().toString()), not(isEmptyOrNullString())); assertThat((String) license.get(ClusterInfoRenderer.Fields.ISSUER.underscore().toString()), not(isEmptyOrNullString()));
assertThat((String) license.get(ClusterInfoRenderer.Fields.ISSUED_TO.underscore().toString()), not(isEmptyOrNullString())); assertThat((String) license.get(ClusterInfoRenderer.Fields.ISSUED_TO.underscore().toString()), not(isEmptyOrNullString()));
assertThat((Long) license.get(ClusterInfoRenderer.Fields.ISSUE_DATE_IN_MILLIS.underscore().toString()), greaterThan(0L)); assertThat((Long) license.get(ClusterInfoRenderer.Fields.ISSUE_DATE_IN_MILLIS.underscore().toString()), greaterThan(0L));

View File

@ -13,8 +13,9 @@ import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.Module; import org.elasticsearch.common.inject.Module;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.core.License; import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.core.LicensesClientService; import org.elasticsearch.license.plugin.core.LicenseState;
import org.elasticsearch.license.plugin.core.LicensesService; import org.elasticsearch.license.plugin.core.Licensee;
import org.elasticsearch.license.plugin.core.LicenseeRegistry;
import org.elasticsearch.marvel.MarvelPlugin; import org.elasticsearch.marvel.MarvelPlugin;
import org.elasticsearch.marvel.mode.Mode; import org.elasticsearch.marvel.mode.Mode;
import org.elasticsearch.marvel.test.MarvelIntegTestCase; import org.elasticsearch.marvel.test.MarvelIntegTestCase;
@ -106,26 +107,24 @@ public class LicenseIntegrationTests extends MarvelIntegTestCase {
@Override @Override
protected void configure() { protected void configure() {
bind(MockLicenseService.class).asEagerSingleton(); bind(MockLicenseService.class).asEagerSingleton();
bind(LicensesClientService.class).to(MockLicenseService.class); bind(LicenseeRegistry.class).to(MockLicenseService.class);
} }
} }
public static class MockLicenseService extends AbstractComponent implements LicensesClientService { public static class MockLicenseService extends AbstractComponent implements LicenseeRegistry {
static final License DUMMY_LICENSE = License.builder() static final License DUMMY_LICENSE = License.builder()
.feature(LicenseService.FEATURE_NAME)
.expiryDate(System.currentTimeMillis()) .expiryDate(System.currentTimeMillis())
.issueDate(System.currentTimeMillis()) .issueDate(System.currentTimeMillis())
.issuedTo("LicensingTests") .issuedTo("LicensingTests")
.issuer("test") .issuer("test")
.maxNodes(Integer.MAX_VALUE) .maxNodes(Integer.MAX_VALUE)
.signature("_signature") .signature("_signature")
.type("standard") .type("basic")
.subscriptionType("all_is_good")
.uid(String.valueOf(RandomizedTest.systemPropertyAsInt(SysGlobals.CHILDVM_SYSPROP_JVM_ID, 0)) + System.identityHashCode(LicenseIntegrationTests.class)) .uid(String.valueOf(RandomizedTest.systemPropertyAsInt(SysGlobals.CHILDVM_SYSPROP_JVM_ID, 0)) + System.identityHashCode(LicenseIntegrationTests.class))
.build(); .build();
private final List<Listener> listeners = new ArrayList<>(); private final List<Licensee> licensees = new ArrayList<>();
@Inject @Inject
public MockLicenseService(Settings settings) { public MockLicenseService(Settings settings) {
@ -134,22 +133,20 @@ public class LicenseIntegrationTests extends MarvelIntegTestCase {
} }
@Override @Override
public void register(String s, LicensesService.TrialLicenseOptions trialLicenseOptions, Collection<LicensesService.ExpirationCallback> collection, AcknowledgementCallback acknowledgementCallback, Listener listener) { public void register(Licensee licensee) {
listeners.add(listener); licensees.add(licensee);
enable(); enable();
} }
public void enable() { public void enable() {
// enabled all listeners (incl. shield) for (Licensee licensee : licensees) {
for (Listener listener : listeners) { licensee.onChange(DUMMY_LICENSE, randomBoolean() ? LicenseState.GRACE_PERIOD : LicenseState.ENABLED);
listener.onEnabled(DUMMY_LICENSE);
} }
} }
public void disable() { public void disable() {
// only disable watcher listener (we need shield to work) for (Licensee licensee : licensees) {
for (Listener listener : listeners) { licensee.onChange(DUMMY_LICENSE, LicenseState.DISABLED);
listener.onDisabled(DUMMY_LICENSE);
} }
} }
} }

View File

@ -22,7 +22,7 @@ To install or update the license use the following REST API:
[source,shell] [source,shell]
----------------------------------------------------------------------- -----------------------------------------------------------------------
curl -XPUT -u admin 'http://<host>:<port>/_licenses' -d @license.json curl -XPUT -u admin 'http://<host>:<port>/_license' -d @license.json
----------------------------------------------------------------------- -----------------------------------------------------------------------
Where: Where:
@ -42,7 +42,7 @@ You can list all currently installed licenses by executing the following REST AP
[source,shell] [source,shell]
----------------------------------------------------- -----------------------------------------------------
curl -XGET -u admin:password 'http://<host>:<port>/_licenses' curl -XGET -u admin:password 'http://<host>:<port>/_license'
----------------------------------------------------- -----------------------------------------------------
The response of this command will be a JSON listing all available licenses. In the case of Shield, the following The response of this command will be a JSON listing all available licenses. In the case of Shield, the following

View File

@ -16,6 +16,7 @@ import org.elasticsearch.action.support.ActionFilterChain;
import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.plugin.core.LicenseState;
import org.elasticsearch.license.plugin.core.LicenseUtils; import org.elasticsearch.license.plugin.core.LicenseUtils;
import org.elasticsearch.shield.User; import org.elasticsearch.shield.User;
import org.elasticsearch.shield.action.interceptor.RequestInterceptor; import org.elasticsearch.shield.action.interceptor.RequestInterceptor;
@ -62,13 +63,8 @@ public class ShieldActionFilter extends AbstractComponent implements ActionFilte
this.actionMapper = actionMapper; this.actionMapper = actionMapper;
licenseEventsNotifier.register(new LicenseEventsNotifier.Listener() { licenseEventsNotifier.register(new LicenseEventsNotifier.Listener() {
@Override @Override
public void enabled() { public void notify(LicenseState state) {
licenseEnabled = true; licenseEnabled = state != LicenseState.DISABLED;
}
@Override
public void disabled() {
licenseEnabled = false;
} }
}); });
this.requestInterceptors = requestInterceptors; this.requestInterceptors = requestInterceptors;

View File

@ -5,6 +5,8 @@
*/ */
package org.elasticsearch.shield.license; package org.elasticsearch.shield.license;
import org.elasticsearch.license.plugin.core.LicenseState;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@ -26,22 +28,14 @@ public class LicenseEventsNotifier {
listeners.add(listener); listeners.add(listener);
} }
protected void notifyEnabled() { protected void notify(LicenseState state) {
for (Listener listener : listeners) { for (Listener listener : listeners) {
listener.enabled(); listener.notify(state);
}
}
protected void notifyDisabled() {
for (Listener listener : listeners) {
listener.disabled();
} }
} }
public static interface Listener { public static interface Listener {
void enabled(); void notify(LicenseState state);
void disabled();
} }
} }

View File

@ -6,99 +6,95 @@
package org.elasticsearch.shield.license; package org.elasticsearch.shield.license;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.component.AbstractLifecycleComponent; import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
import org.elasticsearch.common.joda.Joda;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.core.License; import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.core.LicensesClientService; import org.elasticsearch.license.plugin.core.LicenseState;
import org.elasticsearch.license.plugin.core.Licensee;
import org.elasticsearch.license.plugin.core.LicenseeRegistry;
import org.elasticsearch.shield.ShieldPlugin; import org.elasticsearch.shield.ShieldPlugin;
import java.util.*;
/** /**
* *
*/ */
public class LicenseService extends AbstractLifecycleComponent<LicenseService> { public class LicenseService extends AbstractLifecycleComponent<LicenseService> implements Licensee {
public static final String FEATURE_NAME = ShieldPlugin.NAME; public static final String FEATURE_NAME = ShieldPlugin.NAME;
private static final LicensesClientService.TrialLicenseOptions TRIAL_LICENSE_OPTIONS = private final LicenseeRegistry licenseeRegistry;
new LicensesClientService.TrialLicenseOptions(TimeValue.timeValueHours(30 * 24), 1000);
private static final FormatDateTimeFormatter DATE_FORMATTER = Joda.forPattern("EEEE, MMMMM dd, yyyy", Locale.ROOT);
private final LicensesClientService licensesClientService;
private final LicenseEventsNotifier notifier; private final LicenseEventsNotifier notifier;
private final Collection<LicensesClientService.ExpirationCallback> expirationLoggers;
private final LicensesClientService.AcknowledgementCallback acknowledgementCallback;
private boolean enabled = false; private volatile LicenseState state = LicenseState.DISABLED;
@Inject @Inject
public LicenseService(Settings settings, LicensesClientService licensesClientService, LicenseEventsNotifier notifier) { public LicenseService(Settings settings, LicenseeRegistry licenseeRegistry, LicenseEventsNotifier notifier) {
super(settings); super(settings);
this.licensesClientService = licensesClientService; this.licenseeRegistry = licenseeRegistry;
this.notifier = notifier; this.notifier = notifier;
this.expirationLoggers = Arrays.asList( }
new LicensesClientService.ExpirationCallback.Pre(days(7), days(30), days(1)) {
@Override @Override
public void on(License license, LicensesClientService.ExpirationStatus status) { public String id() {
logger.error("\n" + return FEATURE_NAME;
"#\n" + }
"# Shield license will expire on [{}]. Cluster health, cluster stats and indices stats operations are\n" +
"# blocked on Shield license expiration. All data operations (read and write) continue to work. If you\n" + @Override
"# have a new license, please update it. Otherwise, please reach out to your support contact.\n" + public String[] expirationMessages() {
"#", DATE_FORMATTER.printer().print(license.expiryDate())); return new String[] {
} "Cluster health, cluster stats and indices stats operations are blocked",
}, "All data operations (read and write) continue to work"
new LicensesClientService.ExpirationCallback.Pre(days(0), days(7), minutes(10)) {
@Override
public void on(License license, LicensesClientService.ExpirationStatus status) {
logger.error("\n" +
"#\n" +
"# Shield license will expire on [{}]. Cluster health, cluster stats and indices stats operations are\n" +
"# blocked on Shield license expiration. All data operations (read and write) continue to work. If you\n" +
"# have a new license, please update it. Otherwise, please reach out to your support contact.\n" +
"#", DATE_FORMATTER.printer().print(license.expiryDate()));
}
},
new LicensesClientService.ExpirationCallback.Post(days(0), null, minutes(10)) {
@Override
public void on(License license, LicensesClientService.ExpirationStatus status) {
logger.error("\n" +
"#\n" +
"# SHIELD LICENSE EXPIRED ON [{}]! CLUSTER HEALTH, CLUSTER STATS AND INDICES STATS OPERATIONS ARE\n" +
"# NOW BLOCKED. ALL DATA OPERATIONS (READ AND WRITE) CONTINUE TO WORK. IF YOU HAVE A NEW LICENSE, PLEASE\n" +
"# UPDATE IT. OTHERWISE, PLEASE REACH OUT TO YOUR SUPPORT CONTACT.\n" +
"#", DATE_FORMATTER.printer().print(license.expiryDate()));
}
}
);
this.acknowledgementCallback = new LicensesClientService.AcknowledgementCallback() {
@Override
public List<String> acknowledge(License currentLicense, License newLicense) {
// TODO: add messages to be acknowledged when installing newLicense from currentLicense
// NOTE: currentLicense can be null, as a license registration can happen before
// a trial license could be generated
return Collections.emptyList();
}
}; };
} }
public synchronized boolean enabled() { @Override
return enabled; public String[] acknowledgmentMessages(License currentLicense, License newLicense) {
switch (newLicense.operationMode()) {
case BASIC:
if (currentLicense != null) {
switch (currentLicense.operationMode()) {
case TRIAL:
case GOLD:
case PLATINUM:
return new String[] { "The following Shield functionality will be disabled: authentication, authorization, ip filtering, auditing, SSL will be disabled on node restart. Please restart your node after applying the license." };
}
}
break;
case GOLD:
if (currentLicense != null) {
switch (currentLicense.operationMode()) {
case TRIAL:
case BASIC:
case PLATINUM:
return new String[] {
"Field and document level access control will be disabled"
};
}
}
break;
}
return Strings.EMPTY_ARRAY;
}
@Override
public void onChange(License license, LicenseState state) {
synchronized (this) {
this.state = state;
notifier.notify(state);
}
}
public LicenseState state() {
return state;
} }
@Override @Override
protected void doStart() throws ElasticsearchException { protected void doStart() throws ElasticsearchException {
if (settings.getGroups("tribe", true).isEmpty()) { if (settings.getGroups("tribe", true).isEmpty()) {
licensesClientService.register(FEATURE_NAME, TRIAL_LICENSE_OPTIONS, expirationLoggers, acknowledgementCallback, new InternalListener()); licenseeRegistry.register(this);
} else { } else {
//TODO currently we disable licensing on tribe node. remove this once es core supports merging cluster //TODO currently we disable licensing on tribe node. remove this once es core supports merging cluster
new InternalListener().onEnabled(null); onChange(null, LicenseState.ENABLED);
} }
} }
@ -109,34 +105,4 @@ public class LicenseService extends AbstractLifecycleComponent<LicenseService> {
@Override @Override
protected void doClose() throws ElasticsearchException { protected void doClose() throws ElasticsearchException {
} }
static TimeValue days(int days) {
return TimeValue.timeValueHours(days * 24);
}
static TimeValue minutes(int minutes) {
return TimeValue.timeValueMinutes(minutes);
}
class InternalListener implements LicensesClientService.Listener {
@Override
public void onEnabled(License license) {
synchronized (LicenseService.this) {
logger.info("enabling license for [{}]", FEATURE_NAME);
enabled = true;
notifier.notifyEnabled();
}
}
@Override
public void onDisabled(License license) {
synchronized (LicenseService.this) {
logger.info("DISABLING LICENSE FOR [{}]", FEATURE_NAME);
enabled = false;
notifier.notifyDisabled();
}
}
}
} }

View File

@ -12,6 +12,7 @@ import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.internal.Nullable; import org.elasticsearch.common.inject.internal.Nullable;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.license.plugin.core.LicenseState;
import org.elasticsearch.rest.*; import org.elasticsearch.rest.*;
import org.elasticsearch.shield.ShieldBuild; import org.elasticsearch.shield.ShieldBuild;
import org.elasticsearch.shield.ShieldPlugin; import org.elasticsearch.shield.ShieldPlugin;
@ -71,7 +72,7 @@ public class RestShieldInfoAction extends BaseRestHandler {
private Status resolveStatus() { private Status resolveStatus() {
if (shieldEnabled) { if (shieldEnabled) {
if (licenseService.enabled()) { if (licenseService.state() != LicenseState.DISABLED) {
return Status.ENABLED; return Status.ENABLED;
} }
return Status.UNLICENSED; return Status.UNLICENSED;

View File

@ -19,7 +19,9 @@ import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.Module; import org.elasticsearch.common.inject.Module;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.core.License; import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.core.LicensesClientService; import org.elasticsearch.license.plugin.core.LicenseState;
import org.elasticsearch.license.plugin.core.Licensee;
import org.elasticsearch.license.plugin.core.LicenseeRegistry;
import org.elasticsearch.plugins.Plugin; import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.shield.license.LicenseService; import org.elasticsearch.shield.license.LicenseService;
@ -169,13 +171,13 @@ public class LicensingTests extends ShieldIntegTestCase {
} }
public static void disableLicensing() { public static void disableLicensing() {
for (InternalLicensesClientService service : internalCluster().getInstances(InternalLicensesClientService.class)) { for (InternalLicenseeRegistry service : internalCluster().getInstances(InternalLicenseeRegistry.class)) {
service.disable(); service.disable();
} }
} }
public static void enableLicensing() { public static void enableLicensing() {
for (InternalLicensesClientService service : internalCluster().getInstances(InternalLicensesClientService.class)) { for (InternalLicenseeRegistry service : internalCluster().getInstances(InternalLicenseeRegistry.class)) {
service.enable(); service.enable();
} }
} }
@ -203,49 +205,47 @@ public class LicensingTests extends ShieldIntegTestCase {
public static class InternalLicenseModule extends AbstractModule { public static class InternalLicenseModule extends AbstractModule {
@Override @Override
protected void configure() { protected void configure() {
bind(InternalLicensesClientService.class).asEagerSingleton(); bind(InternalLicenseeRegistry.class).asEagerSingleton();
bind(LicensesClientService.class).to(InternalLicensesClientService.class); bind(LicenseeRegistry.class).to(InternalLicenseeRegistry.class);
} }
} }
public static class InternalLicensesClientService extends AbstractComponent implements LicensesClientService { public static class InternalLicenseeRegistry extends AbstractComponent implements LicenseeRegistry {
private final List<Listener> listeners = new ArrayList<>(); private final List<Licensee> licensees = new ArrayList<>();
static final License DUMMY_LICENSE = License.builder() static final License DUMMY_LICENSE = License.builder()
.feature(LicenseService.FEATURE_NAME)
.expiryDate(System.currentTimeMillis()) .expiryDate(System.currentTimeMillis())
.issueDate(System.currentTimeMillis()) .issueDate(System.currentTimeMillis())
.issuedTo("LicensingTests") .issuedTo("LicensingTests")
.issuer("test") .issuer("test")
.maxNodes(Integer.MAX_VALUE) .maxNodes(Integer.MAX_VALUE)
.signature("_signature") .signature("_signature")
.type("test_license_for_shield") .type("basic")
.subscriptionType("all_is_good")
.uid(String.valueOf(randomLong()) + System.identityHashCode(LicensingTests.class)) .uid(String.valueOf(randomLong()) + System.identityHashCode(LicensingTests.class))
.build(); .build();
@Inject @Inject
public InternalLicensesClientService(Settings settings) { public InternalLicenseeRegistry(Settings settings) {
super(settings); super(settings);
enable(); enable();
} }
@Override @Override
public void register(String s, LicensesClientService.TrialLicenseOptions trialLicenseOptions, Collection<LicensesClientService.ExpirationCallback> collection, AcknowledgementCallback acknowledgementCallback, Listener listener) { public void register(Licensee licensee) {
listeners.add(listener); licensees.add(licensee);
enable(); enable();
} }
void enable() { void enable() {
for (Listener listener : listeners) { for (Licensee licensee : licensees) {
listener.onEnabled(DUMMY_LICENSE); licensee.onChange(DUMMY_LICENSE, LicenseState.ENABLED);
} }
} }
void disable() { void disable() {
for (Listener listener : listeners) { for (Licensee licensee : licensees) {
listener.onDisabled(DUMMY_LICENSE); licensee.onChange(DUMMY_LICENSE, LicenseState.DISABLED);
} }
} }
} }

View File

@ -11,6 +11,7 @@ import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.search.SearchScrollRequest; import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.action.support.ActionFilterChain; import org.elasticsearch.action.support.ActionFilterChain;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.plugin.core.LicenseState;
import org.elasticsearch.shield.User; import org.elasticsearch.shield.User;
import org.elasticsearch.shield.action.interceptor.RequestInterceptor; import org.elasticsearch.shield.action.interceptor.RequestInterceptor;
import org.elasticsearch.shield.audit.AuditTrail; import org.elasticsearch.shield.audit.AuditTrail;
@ -112,7 +113,7 @@ public class ShieldActionFilterTests extends ESTestCase {
private class MockLicenseEventsNotifier extends LicenseEventsNotifier { private class MockLicenseEventsNotifier extends LicenseEventsNotifier {
@Override @Override
public void register(MockLicenseEventsNotifier.Listener listener) { public void register(MockLicenseEventsNotifier.Listener listener) {
listener.enabled(); listener.notify(LicenseState.ENABLED);
} }
} }
} }

View File

@ -6,93 +6,70 @@
package org.elasticsearch.watcher.license; package org.elasticsearch.watcher.license;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.component.AbstractLifecycleComponent; import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
import org.elasticsearch.common.joda.Joda;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.core.License; import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.core.LicensesClientService; import org.elasticsearch.license.plugin.core.LicenseState;
import org.elasticsearch.license.plugin.core.LicensesService; import org.elasticsearch.license.plugin.core.Licensee;
import org.elasticsearch.license.plugin.core.LicenseeRegistry;
import org.elasticsearch.watcher.WatcherPlugin; import org.elasticsearch.watcher.WatcherPlugin;
import java.util.*;
/** /**
* *
*/ */
public class LicenseService extends AbstractLifecycleComponent<LicenseService> { public class LicenseService extends AbstractLifecycleComponent<LicenseService> implements Licensee {
public static final String FEATURE_NAME = WatcherPlugin.NAME; public static final String FEATURE_NAME = WatcherPlugin.NAME;
private static final LicensesClientService.TrialLicenseOptions TRIAL_LICENSE_OPTIONS = private final LicenseeRegistry clientService;
new LicensesClientService.TrialLicenseOptions(TimeValue.timeValueHours(30 * 24), 1000); private volatile LicenseState state;
private static final FormatDateTimeFormatter DATE_FORMATTER = Joda.forPattern("EEEE, MMMMM dd, yyyy", Locale.ROOT);
private final LicensesClientService clientService;
private final Collection<LicensesService.ExpirationCallback> expirationLoggers;
private final LicensesClientService.AcknowledgementCallback acknowledgementCallback;
private volatile boolean enabled;
@Inject @Inject
public LicenseService(Settings settings, LicensesClientService clientService) { public LicenseService(Settings settings, LicenseeRegistry clientService) {
super(settings); super(settings);
this.clientService = clientService; this.clientService = clientService;
this.expirationLoggers = Arrays.asList( }
new LicensesService.ExpirationCallback.Pre(days(7), days(30), days(1)) {
@Override @Override
public void on(License license, LicensesService.ExpirationStatus status) { public String id() {
logger.error("\n" + return FEATURE_NAME;
"#\n" + }
"# Watcher license will expire on [{}]. All configured actions on\n" +
"# all registered watches are throttled (not executed) on Watcher license expiration. \n" + @Override
"# Watches will continue be evaluated and watch history will continue being recorded.\n" + public String[] expirationMessages() {
"# Have a new license? please update it. Otherwise, please reach out to your support contact.\n" + // TODO add messages to be logged around license expiry
"#", DATE_FORMATTER.printer().print(license.expiryDate())); return new String[0];
} }
},
new LicensesService.ExpirationCallback.Pre(days(0), days(7), minutes(10)) { @Override
@Override public String[] acknowledgmentMessages(License currentLicense, License newLicense) {
public void on(License license, LicensesService.ExpirationStatus status) { switch (newLicense.operationMode()) {
logger.error("\n" + case BASIC:
"#\n" + if (currentLicense != null) {
"# Watcher license will expire on [{}]. All configured actions on\n" + switch (currentLicense.operationMode()) {
"# all registered watches are throttled (not executed) on Watcher license expiration. \n" + case TRIAL:
"# Watches will continue be evaluated and watch history will continue being recorded.\n" + case GOLD:
"# Have a new license? please update it. Otherwise, please reach out to your support contact.\n" + case PLATINUM:
"#", DATE_FORMATTER.printer().print(license.expiryDate())); return new String[] { "Watcher will be disabled" };
}
},
new LicensesService.ExpirationCallback.Post(days(0), null, minutes(10)) {
@Override
public void on(License license, LicensesService.ExpirationStatus status) {
logger.error("\n" +
"#\n" +
"# WATCHER LICENSE WAS EXPIRED ON [{}]. ALL CONFIGURED ACTIONS ON\n" +
"# ALL REGISTERED WATCHES ARE THROTTLED (NOT EXECUTED) ON WATCHER LICENSE EXPIRATION. \n" +
"# WATCHES WILL CONTINUE BE EVALUATED AND WATCH HISTORY WILL CONTINUE BEING RECORDED.\n" +
"# HAVE A NEW LICENSE? PLEASE UPDATE IT. OTHERWISE, PLEASE REACH OUT TO YOUR SUPPORT CONTACT.\n" +
"#", DATE_FORMATTER.printer().print(license.expiryDate()));
} }
} }
); break;
this.acknowledgementCallback = new LicensesClientService.AcknowledgementCallback() { }
@Override return Strings.EMPTY_ARRAY;
public List<String> acknowledge(License currentLicense, License newLicense) { }
// TODO: add messages to be acknowledged when installing newLicense from currentLicense
// NOTE: currentLicense can be null, as a license registration can happen before @Override
// a trial license could be generated public void onChange(License license, LicenseState state) {
return Collections.emptyList(); synchronized (this) {
} this.state = state;
}; }
} }
@Override @Override
protected void doStart() throws ElasticsearchException { protected void doStart() throws ElasticsearchException {
clientService.register(FEATURE_NAME, TRIAL_LICENSE_OPTIONS, expirationLoggers, acknowledgementCallback, new InternalListener(this)); clientService.register(this);
} }
@Override @Override
@ -104,33 +81,6 @@ public class LicenseService extends AbstractLifecycleComponent<LicenseService> {
} }
public boolean enabled() { public boolean enabled() {
return enabled; return state != LicenseState.DISABLED;
}
static TimeValue days(int days) {
return TimeValue.timeValueHours(days * 24);
}
static TimeValue minutes(int minutes) {
return TimeValue.timeValueMinutes(minutes);
}
class InternalListener implements LicensesClientService.Listener {
private final LicenseService service;
public InternalListener(LicenseService service) {
this.service = service;
}
@Override
public void onEnabled(License license) {
service.enabled = true;
}
@Override
public void onDisabled(License license) {
service.enabled = false;
}
} }
} }

View File

@ -14,8 +14,9 @@ import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.Module; import org.elasticsearch.common.inject.Module;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.core.License; import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.core.LicensesClientService; import org.elasticsearch.license.plugin.core.LicenseState;
import org.elasticsearch.license.plugin.core.LicensesService; import org.elasticsearch.license.plugin.core.Licensee;
import org.elasticsearch.license.plugin.core.LicenseeRegistry;
import org.elasticsearch.plugins.Plugin; import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.watcher.actions.ActionStatus; import org.elasticsearch.watcher.actions.ActionStatus;
@ -46,15 +47,13 @@ import static org.hamcrest.Matchers.*;
public class LicenseIntegrationTests extends AbstractWatcherIntegrationTestCase { public class LicenseIntegrationTests extends AbstractWatcherIntegrationTestCase {
static final License DUMMY_LICENSE = License.builder() static final License DUMMY_LICENSE = License.builder()
.feature(LicenseService.FEATURE_NAME)
.expiryDate(System.currentTimeMillis()) .expiryDate(System.currentTimeMillis())
.issueDate(System.currentTimeMillis()) .issueDate(System.currentTimeMillis())
.issuedTo("LicensingTests") .issuedTo("LicensingTests")
.issuer("test") .issuer("test")
.maxNodes(Integer.MAX_VALUE) .maxNodes(Integer.MAX_VALUE)
.signature("_signature") .signature("_signature")
.type("test_license_for_watcher") .type("basic")
.subscriptionType("all_is_good")
.uid(String.valueOf(RandomizedTest.systemPropertyAsInt(SysGlobals.CHILDVM_SYSPROP_JVM_ID, 0)) + System.identityHashCode(LicenseIntegrationTests.class)) .uid(String.valueOf(RandomizedTest.systemPropertyAsInt(SysGlobals.CHILDVM_SYSPROP_JVM_ID, 0)) + System.identityHashCode(LicenseIntegrationTests.class))
.build(); .build();
@ -301,13 +300,13 @@ public class LicenseIntegrationTests extends AbstractWatcherIntegrationTestCase
@Override @Override
protected void configure() { protected void configure() {
bind(MockLicenseService.class).asEagerSingleton(); bind(MockLicenseService.class).asEagerSingleton();
bind(LicensesClientService.class).to(MockLicenseService.class); bind(LicenseeRegistry.class).to(MockLicenseService.class);
} }
} }
public static class MockLicenseService extends AbstractComponent implements LicensesClientService { public static class MockLicenseService extends AbstractComponent implements LicenseeRegistry {
private final List<Listener> listeners = new ArrayList<>(); private final List<Licensee> licensees = new ArrayList<>();
@Inject @Inject
public MockLicenseService(Settings settings) { public MockLicenseService(Settings settings) {
@ -316,23 +315,23 @@ public class LicenseIntegrationTests extends AbstractWatcherIntegrationTestCase
} }
@Override @Override
public void register(String s, LicensesService.TrialLicenseOptions trialLicenseOptions, Collection<LicensesService.ExpirationCallback> collection, AcknowledgementCallback acknowledgementCallback, Listener listener) { public void register(Licensee licensee) {
listeners.add(listener); licensees.add(licensee);
enable(); enable();
} }
public void enable() { public void enable() {
// enabled all listeners (incl. shield) // enabled all listeners (incl. shield)
for (Listener listener : listeners) { for (Licensee licensee : licensees) {
listener.onEnabled(DUMMY_LICENSE); licensee.onChange(DUMMY_LICENSE, LicenseState.ENABLED);
} }
} }
public void disable() { public void disable() {
// only disable watcher listener (we need shield to work) // only disable watcher listener (we need shield to work)
for (Listener listener : listeners) { for (Licensee licensee : licensees) {
if (listener instanceof LicenseService.InternalListener) { if (licensee instanceof LicenseService) {
listener.onDisabled(DUMMY_LICENSE); licensee.onChange(DUMMY_LICENSE, LicenseState.DISABLED);
} }
} }
} }