Implements cloud_internal license type

"cloud_internal" license type enables dynamically updating license operation mode via a config file.

When the installed license is "cloud_internal", the node level operation mode can be updated by writing
a `license_mode` file in the x-pack config directory (config/x-pack/license_mode). The file is expected
to have a string representing the desired license mode (e.g. "gold", "basic"). In case of a failure to
read a valid license mode from the `license_mode` file, the operation mode for "cloud_internal" license
defaults to PLATINUM.
This change also ensures that the correct operation mode is reported via the _xpack endpoint.

closes elastic/elasticsearch#2042

Original commit: elastic/x-pack-elasticsearch@6a2d788e45
This commit is contained in:
Areek Zillur 2016-07-14 16:36:15 -04:00
parent 4e81ef42a0
commit 0db0e2f0c9
28 changed files with 681 additions and 332 deletions

View File

@ -101,6 +101,7 @@ public class License implements ToXContent {
case "gold":
return GOLD;
case "platinum":
case "cloud_internal":
case "internal": // bwc for 1.x subscription_type field
return PLATINUM;
default:
@ -196,12 +197,42 @@ public class License implements ToXContent {
}
/**
* @return the operation mode of the license as computed from the license type
* @return the operation mode of the license as computed from the license type or from
* the license mode file
*/
public OperationMode operationMode() {
synchronized (this) {
if (canReadOperationModeFromFile() && operationModeFileWatcher != null) {
return operationModeFileWatcher.getCurrentOperationMode();
}
}
return operationMode;
}
private boolean canReadOperationModeFromFile() {
return type.equals("cloud_internal");
}
private volatile OperationModeFileWatcher operationModeFileWatcher;
/**
* Sets the operation mode file watcher for the license and initializes the
* file watcher when the license type allows to override operation mode from file
*/
public synchronized void setOperationModeFileWatcher(final OperationModeFileWatcher operationModeFileWatcher) {
this.operationModeFileWatcher = operationModeFileWatcher;
if (canReadOperationModeFromFile()) {
this.operationModeFileWatcher.init();
}
}
/**
* Removes operation mode file watcher, so unused license objects can be gc'ed
*/
public synchronized void removeOperationModeFileWatcher() {
this.operationModeFileWatcher = null;
}
/**
* @return the current license's status
*/

View File

@ -0,0 +1,113 @@
/*
* 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.core;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.license.core.License.OperationMode;
import org.elasticsearch.watcher.FileChangesListener;
import org.elasticsearch.watcher.FileWatcher;
import org.elasticsearch.watcher.ResourceWatcherService;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* File based watcher for license {@link OperationMode}
* Watches for changes in <code>licenseModePath</code>, use
* {@link #getCurrentOperationMode()} to access the latest mode
*
* In case of failure to read a valid operation mode from <code>licenseModePath</code>,
* the operation mode will default to PLATINUM
*/
public final class OperationModeFileWatcher extends FileChangesListener {
private final ResourceWatcherService resourceWatcherService;
private final Path licenseModePath;
private final AtomicBoolean initialized = new AtomicBoolean();
private final OperationMode defaultOperationMode = OperationMode.PLATINUM;
private volatile OperationMode currentOperationMode = defaultOperationMode;
private final ESLogger logger;
private final Runnable onChange;
public OperationModeFileWatcher(ResourceWatcherService resourceWatcherService, Path licenseModePath,
ESLogger logger, Runnable onChange) {
this.resourceWatcherService = resourceWatcherService;
this.licenseModePath = licenseModePath;
this.logger = logger;
this.onChange = onChange;
}
public void init() {
if (initialized.compareAndSet(false, true)) {
final FileWatcher watcher = new FileWatcher(licenseModePath);
watcher.addListener(this);
try {
resourceWatcherService.add(watcher, ResourceWatcherService.Frequency.HIGH);
if (Files.exists(licenseModePath)) {
onChange(licenseModePath);
}
} catch (IOException e) {
logger.error("couldn't initialize watching license mode file", e);
}
}
}
/**
* Returns the current operation mode based on license mode file.
* Defaults to {@link OperationMode#PLATINUM}
*/
public OperationMode getCurrentOperationMode() {
return currentOperationMode;
}
@Override
public void onFileInit(Path file) {
onChange(file);
}
@Override
public void onFileCreated(Path file) {
onChange(file);
}
@Override
public void onFileDeleted(Path file) {
onChange(file);
}
@Override
public void onFileChanged(Path file) {
onChange(file);
}
private synchronized void onChange(Path file) {
if (file.equals(licenseModePath)) {
currentOperationMode = defaultOperationMode;
if (Files.exists(licenseModePath)
&& Files.isReadable(licenseModePath)) {
final byte[] content;
try {
content = Files.readAllBytes(licenseModePath);
} catch (IOException e) {
logger.error("couldn't read operation mode from [{}]", e, licenseModePath.toAbsolutePath().toString());
return;
}
String operationMode = new String(content, StandardCharsets.UTF_8);
try {
currentOperationMode = OperationMode.resolve(operationMode);
} catch (IllegalArgumentException e) {
logger.error("invalid operation mode in [{}]", e, licenseModePath.toAbsolutePath().toString());
return;
}
}
onChange.run();
}
}
}

View File

@ -0,0 +1,73 @@
/*
* 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.core;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.watcher.FileWatcher;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.junit.Before;
import java.nio.file.Path;
import static org.elasticsearch.license.core.OperationModeFileWatcherTests.writeMode;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
public class LicenseOperationModeUpdateTests extends ESTestCase {
private OperationModeFileWatcher operationModeFileWatcher;
private Path licenseModeFile;
private ResourceWatcherService resourceWatcherService;
@Before
public void init() throws Exception {
licenseModeFile = createTempFile();
resourceWatcherService = mock(ResourceWatcherService.class);
operationModeFileWatcher = new OperationModeFileWatcher(resourceWatcherService, licenseModeFile, logger, () -> {});
}
public void testLicenseOperationModeUpdate() throws Exception {
String type = randomFrom("trial", "basic", "standard", "gold", "platinum");
License license = License.builder()
.uid("id")
.expiryDate(0)
.issueDate(0)
.issuedTo("elasticsearch")
.issuer("issuer")
.type(type)
.maxNodes(1)
.build();
assertThat(license.operationMode(), equalTo(License.OperationMode.resolve(type)));
writeMode("gold", licenseModeFile);
license.setOperationModeFileWatcher(operationModeFileWatcher);
verifyZeroInteractions(resourceWatcherService);
assertThat(license.operationMode(), equalTo(License.OperationMode.resolve(type)));
}
public void testCloudInternalLicenseOperationModeUpdate() throws Exception {
License license = License.builder()
.uid("id")
.expiryDate(0)
.issueDate(0)
.issuedTo("elasticsearch")
.issuer("issuer")
.type("cloud_internal")
.maxNodes(1)
.build();
assertThat(license.operationMode(), equalTo(License.OperationMode.PLATINUM));
writeMode("gold", licenseModeFile);
license.setOperationModeFileWatcher(operationModeFileWatcher);
verify(resourceWatcherService, times(1)).add(any(FileWatcher.class), eq(ResourceWatcherService.Frequency.HIGH));
assertThat(license.operationMode(), equalTo(License.OperationMode.GOLD));
}
}

View File

@ -0,0 +1,114 @@
/*
* 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.core;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.threadpool.TestThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.junit.After;
import org.junit.Before;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.atomic.AtomicInteger;
import static org.hamcrest.Matchers.equalTo;
public class OperationModeFileWatcherTests extends ESTestCase {
private ResourceWatcherService watcherService;
private TestThreadPool threadPool;
private Path licenseModePath;
private OperationModeFileWatcher operationModeFileWatcher;
private AtomicInteger onChangeCounter;
@Before
public void setup() throws Exception {
threadPool = new TestThreadPool("license mode file watcher tests");
Settings settings = Settings.builder()
.put("resource.reload.interval.high", "10ms")
.build();
watcherService = new ResourceWatcherService(settings,
threadPool);
watcherService.start();
licenseModePath = createTempFile();
onChangeCounter = new AtomicInteger();
operationModeFileWatcher = new OperationModeFileWatcher(watcherService, licenseModePath, logger,
() -> onChangeCounter.incrementAndGet());
}
@After
public void shutdown() throws InterruptedException {
terminate(threadPool);
watcherService.stop();
}
public void testInit() throws Exception {
writeMode("gold");
assertThat(operationModeFileWatcher.getCurrentOperationMode(), equalTo(License.OperationMode.PLATINUM));
operationModeFileWatcher.init();
assertThat(onChangeCounter.get(), equalTo(2));
assertThat(operationModeFileWatcher.getCurrentOperationMode(), equalTo(License.OperationMode.GOLD));
}
public void testUpdateModeFromFile() throws Exception {
Files.delete(licenseModePath);
operationModeFileWatcher.init();
assertThat(operationModeFileWatcher.getCurrentOperationMode(), equalTo(License.OperationMode.PLATINUM));
writeMode("gold");
assertBusy(() -> assertThat(operationModeFileWatcher.getCurrentOperationMode(), equalTo(License.OperationMode.GOLD)));
writeMode("basic");
assertBusy(() -> assertThat(operationModeFileWatcher.getCurrentOperationMode(), equalTo(License.OperationMode.BASIC)));
assertThat(onChangeCounter.get(), equalTo(2));
}
public void testDeleteModeFromFile() throws Exception {
Files.delete(licenseModePath);
operationModeFileWatcher.init();
writeMode("gold");
assertBusy(() -> assertThat(operationModeFileWatcher.getCurrentOperationMode(), equalTo(License.OperationMode.GOLD)));
Files.delete(licenseModePath);
assertBusy(() -> assertThat(operationModeFileWatcher.getCurrentOperationMode(), equalTo(License.OperationMode.PLATINUM)));
assertThat(onChangeCounter.get(), equalTo(2));
}
public void testInvalidModeFromFile() throws Exception {
writeMode("invalid");
operationModeFileWatcher.init();
assertThat(operationModeFileWatcher.getCurrentOperationMode(), equalTo(License.OperationMode.PLATINUM));
operationModeFileWatcher.onFileChanged(licenseModePath);
assertThat(operationModeFileWatcher.getCurrentOperationMode(), equalTo(License.OperationMode.PLATINUM));
}
public void testLicenseModeFileIsDirectory() throws Exception {
licenseModePath = createTempDir();
operationModeFileWatcher.init();
assertThat(operationModeFileWatcher.getCurrentOperationMode(), equalTo(License.OperationMode.PLATINUM));
operationModeFileWatcher.onFileChanged(licenseModePath);
assertThat(operationModeFileWatcher.getCurrentOperationMode(), equalTo(License.OperationMode.PLATINUM));
}
public void testLicenseModeFileCreatedAfterInit() throws Exception {
Files.delete(licenseModePath);
operationModeFileWatcher.init();
assertThat(operationModeFileWatcher.getCurrentOperationMode(), equalTo(License.OperationMode.PLATINUM));
Path tempFile = createTempFile();
writeMode("gold", tempFile);
licenseModePath = tempFile;
assertBusy(() -> assertThat(operationModeFileWatcher.getCurrentOperationMode(), equalTo(License.OperationMode.GOLD)));
}
private void writeMode(String mode) throws IOException {
writeMode(mode, licenseModePath);
}
static void writeMode(String mode, Path file) throws IOException {
Files.write(file, mode.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE);
}
}

View File

@ -7,7 +7,6 @@ package org.elasticsearch.xpack.graph;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.License.OperationMode;
import org.elasticsearch.license.plugin.core.AbstractLicenseeComponent;
import org.elasticsearch.license.plugin.core.LicenseState;
@ -28,17 +27,15 @@ public class GraphLicensee extends AbstractLicenseeComponent {
}
@Override
public String[] acknowledgmentMessages(License currentLicense, License newLicense) {
switch (newLicense.operationMode()) {
public String[] acknowledgmentMessages(OperationMode currentMode, OperationMode newMode) {
switch (newMode) {
case BASIC:
case STANDARD:
case GOLD:
if (currentLicense != null) {
switch (currentLicense.operationMode()) {
case TRIAL:
case PLATINUM:
return new String[] { "Graph will be disabled" };
}
switch (currentMode) {
case TRIAL:
case PLATINUM:
return new String[] { "Graph will be disabled" };
}
break;
}

View File

@ -12,6 +12,7 @@ import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.inject.Module;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseAction;
import org.elasticsearch.license.plugin.action.delete.TransportDeleteLicenseAction;
import org.elasticsearch.license.plugin.action.get.GetLicenseAction;
@ -25,6 +26,7 @@ import org.elasticsearch.license.plugin.rest.RestGetLicenseAction;
import org.elasticsearch.license.plugin.rest.RestPutLicenseAction;
import org.elasticsearch.plugins.ActionPlugin;
import org.elasticsearch.rest.RestHandler;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.graph.GraphLicensee;
import org.elasticsearch.xpack.monitoring.MonitoringLicensee;
import org.elasticsearch.xpack.security.SecurityLicenseState;
@ -83,14 +85,16 @@ public class Licensing implements ActionPlugin {
RestDeleteLicenseAction.class);
}
public Collection<Object> createComponents(ClusterService clusterService, Clock clock,
public Collection<Object> createComponents(ClusterService clusterService, Clock clock, Environment environment,
ResourceWatcherService resourceWatcherService,
SecurityLicenseState securityLicenseState) {
SecurityLicensee securityLicensee = new SecurityLicensee(settings, securityLicenseState);
WatcherLicensee watcherLicensee = new WatcherLicensee(settings);
MonitoringLicensee monitoringLicensee = new MonitoringLicensee(settings);
GraphLicensee graphLicensee = new GraphLicensee(settings);
LicensesService licensesService = new LicensesService(settings, clusterService, clock,
Arrays.asList(securityLicensee, watcherLicensee, monitoringLicensee, graphLicensee));
environment, resourceWatcherService,
Arrays.asList(securityLicensee, watcherLicensee, monitoringLicensee, graphLicensee));
return Arrays.asList(licensesService, securityLicenseState, securityLicensee, watcherLicensee, monitoringLicensee, graphLicensee);
}

View File

@ -8,8 +8,6 @@ package org.elasticsearch.license.plugin.core;
import org.elasticsearch.license.core.License;
import org.elasticsearch.xpack.scheduler.SchedulerEngine;
import static org.elasticsearch.license.plugin.core.LicensesService.GRACE_PERIOD_DURATION;
import static org.elasticsearch.license.plugin.core.LicensesService.getLicenseState;
public class LicenseSchedule implements SchedulerEngine.Schedule {
@ -22,12 +20,12 @@ public class LicenseSchedule implements SchedulerEngine.Schedule {
@Override
public long nextScheduledTimeAfter(long startTime, long time) {
long nextScheduledTime = -1;
switch (getLicenseState(license, time)) {
switch (LicenseState.resolve(license, time)) {
case ENABLED:
nextScheduledTime = license.expiryDate();
break;
case GRACE_PERIOD:
nextScheduledTime = license.expiryDate() + GRACE_PERIOD_DURATION.getMillis();
nextScheduledTime = license.expiryDate() + LicenseState.GRACE_PERIOD_DURATION.getMillis();
break;
case DISABLED:
if (license.issueDate() > time) {

View File

@ -5,6 +5,11 @@
*/
package org.elasticsearch.license.plugin.core;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.core.License;
import static org.elasticsearch.license.plugin.core.LicensesService.days;
/**
* States of a registered licensee
* based on the current license
@ -38,5 +43,26 @@ public enum LicenseState {
* changes to {@link #ENABLED}, otherwise
* remains unchanged
*/
DISABLED
DISABLED;
/**
* Duration of grace period after a license has expired
*/
public static final TimeValue GRACE_PERIOD_DURATION = days(7);
public static LicenseState resolve(final License license, long time) {
if (license == null) {
return DISABLED;
}
if (license.issueDate() > time) {
return DISABLED;
}
if (license.expiryDate() > time) {
return ENABLED;
}
if ((license.expiryDate() + GRACE_PERIOD_DURATION.getMillis()) > time) {
return GRACE_PERIOD;
}
return DISABLED;
}
}

View File

@ -5,10 +5,10 @@
*/
package org.elasticsearch.license.plugin.core;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.License.OperationMode;
import java.util.Locale;
import java.util.Objects;
public interface Licensee {
@ -26,11 +26,10 @@ public interface Licensee {
/**
* Messages to be returned when
* installing <code>newLicense</code>
* when <code>currentLicense</code> is
* active
* changing from current operation mode
* to new operation mode
*/
String[] acknowledgmentMessages(License currentLicense, License newLicense);
String[] acknowledgmentMessages(OperationMode currentMode, OperationMode newMode);
/**
* Notifies when a new license is activated
@ -85,6 +84,20 @@ public interface Licensee {
return licenseState;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Status status = (Status) o;
return Objects.equals(mode, status.mode) && Objects.equals(licenseState, status.licenseState);
}
@Override
public int hashCode() {
return Objects.hash(mode, licenseState);
}
@Override
public String toString() {
switch (licenseState) {

View File

@ -18,18 +18,21 @@ import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.component.Lifecycle;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
import org.elasticsearch.common.joda.Joda;
import org.elasticsearch.common.logging.LoggerMessageFormat;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.env.Environment;
import org.elasticsearch.gateway.GatewayService;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.LicenseVerifier;
import org.elasticsearch.license.core.OperationModeFileWatcher;
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequest;
import org.elasticsearch.license.plugin.action.put.PutLicenseRequest;
import org.elasticsearch.license.plugin.action.put.PutLicenseResponse;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.XPackPlugin;
import org.elasticsearch.xpack.scheduler.SchedulerEngine;
import org.elasticsearch.xpack.support.clock.Clock;
@ -40,7 +43,6 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
@ -74,6 +76,11 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
private SchedulerEngine scheduler;
private final Clock clock;
/**
* File watcher for operation mode changes
*/
private final OperationModeFileWatcher operationModeFileWatcher;
/**
* Callbacks to notify relative to license expiry
*/
@ -84,27 +91,24 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
*/
private int trialLicenseMaxNodes = 1000;
/**
* Duration of grace period after a license has expired
*/
public static final TimeValue GRACE_PERIOD_DURATION = days(7);
private static final String LICENSE_JOB = "licenseJob";
public static final String LICENSE_JOB = "licenseJob";
private static final FormatDateTimeFormatter DATE_FORMATTER = Joda.forPattern("EEEE, MMMMM dd, yyyy", Locale.ROOT);
private static final String ACKNOWLEDGEMENT_HEADER = "This license update requires acknowledgement. To acknowledge the license, " +
"please read the following messages and update the license again, this time with the \"acknowledge=true\" parameter:";
public LicensesService(Settings settings, ClusterService clusterService, Clock clock,
List<Licensee> registeredLicensees) {
public LicensesService(Settings settings, ClusterService clusterService, Clock clock, Environment env,
ResourceWatcherService resourceWatcherService, List<Licensee> registeredLicensees) {
super(settings);
this.clusterService = clusterService;
populateExpirationCallbacks();
this.clock = clock;
this.scheduler = new SchedulerEngine(clock);
this.scheduler.register(this);
this.registeredLicensees = registeredLicensees.stream().map(InternalLicensee::new).collect(Collectors.toList());
this.operationModeFileWatcher = new OperationModeFileWatcher(resourceWatcherService,
XPackPlugin.resolveConfigFile(env, "license_mode"), logger, () -> notifyLicensees(getLicense()));
this.scheduler.register(this);
populateExpirationCallbacks();
}
private void populateExpirationCallbacks() {
@ -229,7 +233,8 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
"override the current license?"});
}
for (InternalLicensee licensee : registeredLicensees) {
String[] listenerAcknowledgeMessages = licensee.acknowledgmentMessages(currentLicense, newLicense);
String[] listenerAcknowledgeMessages = licensee.acknowledgmentMessages(
currentLicense.operationMode(), newLicense.operationMode());
if (listenerAcknowledgeMessages.length > 0) {
acknowledgeMessages.put(licensee.id(), listenerAcknowledgeMessages);
}
@ -305,13 +310,12 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
});
}
public LicenseState licenseState() {
if (registeredLicensees.size() > 0) {
return registeredLicensees.get(0).currentLicenseState;
} else {
final License license = getLicense(clusterService.state().metaData().custom(LicensesMetaData.TYPE));
return getLicenseState(license, clock.millis());
public Licensee.Status licenseeStatus() {
final License license = getLicense();
if (license == null) {
return Licensee.Status.MISSING;
}
return new Licensee.Status(license.operationMode(), LicenseState.resolve(license, clock.millis()));
}
public License getLicense() {
@ -376,7 +380,6 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
scheduler.stop();
// clear all handlers
registeredLicensees.clear();
// clear current license
currentLicense.set(null);
}
@ -430,45 +433,22 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
}
if (license != null) {
logger.debug("notifying [{}] listeners", registeredLicensees.size());
switch (getLicenseState(license, clock.millis())) {
final LicenseState licenseState = LicenseState.resolve(license, clock.millis());
Licensee.Status status = new Licensee.Status(license.operationMode(), licenseState);
for (InternalLicensee licensee : registeredLicensees) {
licensee.onChange(status);
}
switch (status.getLicenseState()) {
case ENABLED:
for (InternalLicensee licensee : registeredLicensees) {
licensee.onChange(license, LicenseState.ENABLED);
}
logger.debug("license [{}] - valid", license.uid());
break;
logger.debug("license [{}] - valid", license.uid()); break;
case GRACE_PERIOD:
for (InternalLicensee licensee : registeredLicensees) {
licensee.onChange(license, LicenseState.GRACE_PERIOD);
}
logger.warn("license [{}] - grace", license.uid());
break;
logger.warn("license [{}] - grace", license.uid()); break;
case DISABLED:
for (InternalLicensee licensee : registeredLicensees) {
licensee.onChange(license, LicenseState.DISABLED);
}
logger.warn("license [{}] - expired", license.uid());
break;
logger.warn("license [{}] - expired", license.uid()); break;
}
}
}
static LicenseState getLicenseState(final License license, long time) {
if (license == null) {
return LicenseState.DISABLED;
}
if (license.issueDate() > time) {
return LicenseState.DISABLED;
}
if (license.expiryDate() > time) {
return LicenseState.ENABLED;
}
if ((license.expiryDate() + GRACE_PERIOD_DURATION.getMillis()) > time) {
return LicenseState.GRACE_PERIOD;
}
return LicenseState.DISABLED;
}
/**
* Notifies registered licensees of license state change and/or new active license
* based on the license in <code>currentLicensesMetaData</code>.
@ -480,16 +460,22 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
// license can be null if the trial license is yet to be auto-generated
// in this case, it is a no-op
if (license != null) {
notifyLicensees(license);
if (license.equals(currentLicense.get()) == false) {
final License previousLicense = currentLicense.get();
if (license.equals(previousLicense) == false) {
currentLicense.set(license);
license.setOperationModeFileWatcher(operationModeFileWatcher);
scheduler.add(new SchedulerEngine.Job(LICENSE_JOB, new LicenseSchedule(license)));
for (ExpirationCallback expirationCallback : expirationCallbacks) {
scheduler.add(new SchedulerEngine.Job(expirationCallback.getId(),
(startTime, now) ->
expirationCallback.nextScheduledTimeForExpiry(license.expiryDate(), startTime, now)));
}
if (previousLicense != null) {
// remove operationModeFileWatcher to gc the old license object
previousLicense.removeOperationModeFileWatcher();
}
}
notifyLicensees(license);
}
}
@ -516,7 +502,7 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
License license = metaData.getLicense();
if (license == LicensesMetaData.LICENSE_TOMBSTONE) {
return license;
} else {
} else if (license != null) {
boolean autoGeneratedLicense = License.isAutoGeneratedLicense(license.signature());
if ((autoGeneratedLicense && TrialLicense.verify(license))
|| (!autoGeneratedLicense && LicenseVerifier.verifyLicense(license))) {
@ -531,9 +517,9 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
* Stores acknowledgement, expiration and license notification callbacks
* for a registered listener
*/
private class InternalLicensee {
volatile License currentLicense = null;
volatile LicenseState currentLicenseState = LicenseState.DISABLED;
private final class InternalLicensee {
volatile Licensee.Status currentStatus = Licensee.Status.MISSING;
private final Licensee licensee;
private InternalLicensee(Licensee licensee) {
@ -542,7 +528,7 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
@Override
public String toString() {
return "(listener: " + licensee.id() + ", state: " + currentLicenseState.name() + ")";
return "(listener: " + licensee.id() + ", state: " + currentStatus + ")";
}
public String id() {
@ -553,31 +539,21 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust
return licensee.expirationMessages();
}
public String[] acknowledgmentMessages(License currentLicense, License newLicense) {
return licensee.acknowledgmentMessages(currentLicense, newLicense);
public String[] acknowledgmentMessages(License.OperationMode currentMode, License.OperationMode newMode) {
return licensee.acknowledgmentMessages(currentMode, newMode);
}
public void onChange(License license, LicenseState state) {
synchronized (this) {
if (currentLicense == null // not yet initialized
|| !currentLicense.equals(license) // current license has changed
|| currentLicenseState != state) { // same license but state has changed
logger.debug("licensee [{}] notified", licensee.id());
licensee.onChange(new Licensee.Status(license.operationMode(), state));
currentLicense = license;
currentLicenseState = state;
}
public synchronized void onChange(final Licensee.Status status) {
if (currentStatus == null // not yet initialized
|| !currentStatus.equals(status)) { // current license has changed
logger.debug("licensee [{}] notified", licensee.id());
licensee.onChange(status);
currentStatus = status;
}
}
public void onRemove() {
synchronized (this) {
if (currentLicense != null || currentLicenseState != LicenseState.DISABLED) {
currentLicense = null;
currentLicenseState = LicenseState.DISABLED;
licensee.onChange(Licensee.Status.MISSING);
}
}
onChange(Licensee.Status.MISSING);
}
}
}

View File

@ -5,37 +5,27 @@
*/
package org.elasticsearch.license.plugin;
import org.elasticsearch.client.ClusterAdminClient;
import org.elasticsearch.common.network.NetworkModule;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.env.Environment;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseAction;
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequestBuilder;
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseResponse;
import org.elasticsearch.license.plugin.action.get.GetLicenseAction;
import org.elasticsearch.license.plugin.action.get.GetLicenseRequestBuilder;
import org.elasticsearch.license.plugin.action.get.GetLicenseResponse;
import org.elasticsearch.license.plugin.action.put.PutLicenseAction;
import org.elasticsearch.license.plugin.action.put.PutLicenseRequestBuilder;
import org.elasticsearch.license.plugin.action.put.PutLicenseResponse;
import org.elasticsearch.license.plugin.core.LicenseState;
import org.elasticsearch.license.plugin.core.LicensesMetaData;
import org.elasticsearch.license.plugin.core.LicensesService;
import org.elasticsearch.license.plugin.core.LicensesStatus;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
import org.elasticsearch.xpack.MockNetty3Plugin;
import org.elasticsearch.xpack.XPackPlugin;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import static org.elasticsearch.license.plugin.TestUtils.generateSignedLicense;
import static org.elasticsearch.test.ESIntegTestCase.Scope.TEST;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
@ClusterScope(scope = TEST, numDataNodes = 0, numClientNodes = 0, maxNumDataNodes = 0, transportClientRatio = 0)
@ -55,6 +45,7 @@ public class LicensesServiceClusterTests extends AbstractLicensesIntegrationTest
return Settings.builder()
.put(super.nodeSettings(nodeOrdinal))
.put("node.data", true)
.put("resource.reload.interval.high", "500ms") // for license mode file watcher
.put(NetworkModule.HTTP_ENABLED.getKey(), true);
}
@ -79,37 +70,54 @@ public class LicensesServiceClusterTests extends AbstractLicensesIntegrationTest
ensureGreen();
logger.info("--> put signed license");
License license = generateAndPutLicenses();
getAndCheckLicense(license);
LicensingClient licensingClient = new LicensingClient(client());
License license = generateSignedLicense(TimeValue.timeValueMinutes(1));
putLicense(license);
assertThat(licensingClient.prepareGetLicense().get().license(), equalTo(license));
assertOperationMode(license.operationMode());
logger.info("--> restart all nodes");
internalCluster().fullRestart();
ensureYellow();
licensingClient = new LicensingClient(client());
logger.info("--> get and check signed license");
getAndCheckLicense(license);
assertThat(licensingClient.prepareGetLicense().get().license(), equalTo(license));
logger.info("--> remove licenses");
removeLicense();
assertNoLicense();
licensingClient.prepareDeleteLicense().get();
assertOperationMode(License.OperationMode.MISSING);
logger.info("--> restart all nodes");
internalCluster().fullRestart();
licensingClient = new LicensingClient(client());
ensureYellow();
assertNoLicense();
assertThat(licensingClient.prepareGetLicense().get().license(), nullValue());
assertOperationMode(License.OperationMode.MISSING);
wipeAllLicenses();
}
public void testCloudInternalLicense() throws Exception {
wipeAllLicenses();
private void assertLicenseState(LicenseState state) throws InterruptedException {
boolean success = awaitBusy(() -> {
for (LicensesService service : internalCluster().getDataNodeInstances(LicensesService.class)) {
if (service.licenseState() == state) {
return true;
}
}
return false;
});
assertTrue(success);
int numNodes = randomIntBetween(1, 5);
logger.info("--> starting {} node(s)", numNodes);
for (int i = 0; i < numNodes; i++) {
internalCluster().startNode();
}
ensureGreen();
logger.info("--> put signed license");
LicensingClient licensingClient = new LicensingClient(client());
License license = generateSignedLicense("cloud_internal", License.VERSION_CURRENT, System.currentTimeMillis(),
TimeValue.timeValueMinutes(1));
putLicense(license);
assertThat(licensingClient.prepareGetLicense().get().license(), equalTo(license));
assertOperationMode(License.OperationMode.PLATINUM);
writeCloudInternalMode("gold");
assertOperationMode(License.OperationMode.GOLD);
writeCloudInternalMode("basic");
assertOperationMode(License.OperationMode.BASIC);
}
public void testClusterRestartWhileEnabled() throws Exception {
@ -143,7 +151,7 @@ public class LicensesServiceClusterTests extends AbstractLicensesIntegrationTest
internalCluster().startNode();
ensureGreen();
assertLicenseState(LicenseState.ENABLED);
putLicense(TestUtils.generateExpiredLicense(System.currentTimeMillis() - LicensesService.GRACE_PERIOD_DURATION.getMillis()));
putLicense(TestUtils.generateExpiredLicense(System.currentTimeMillis() - LicenseState.GRACE_PERIOD_DURATION.getMillis()));
assertLicenseState(LicenseState.DISABLED);
logger.info("--> restart node");
internalCluster().fullRestart();
@ -160,44 +168,36 @@ public class LicensesServiceClusterTests extends AbstractLicensesIntegrationTest
assertLicenseState(LicenseState.ENABLED);
}
private void removeLicense() throws Exception {
ClusterAdminClient cluster = internalCluster().client().admin().cluster();
ensureGreen();
License putLicenses = generateSignedLicense(TimeValue.timeValueMinutes(1));
PutLicenseRequestBuilder putLicenseRequestBuilder = new PutLicenseRequestBuilder(cluster, PutLicenseAction.INSTANCE);
putLicenseRequestBuilder.setLicense(putLicenses);
DeleteLicenseResponse response = new DeleteLicenseRequestBuilder(cluster, DeleteLicenseAction.INSTANCE).get();
assertThat(response.isAcknowledged(), equalTo(true));
private void assertLicenseState(LicenseState state) throws InterruptedException {
boolean success = awaitBusy(() -> {
for (LicensesService service : internalCluster().getDataNodeInstances(LicensesService.class)) {
if (service.licenseeStatus().getLicenseState() == state) {
return true;
}
}
return false;
});
assertTrue(success);
}
private License generateAndPutLicenses() throws Exception {
ClusterAdminClient cluster = internalCluster().client().admin().cluster();
ensureGreen();
License putLicenses = generateSignedLicense(TimeValue.timeValueMinutes(1));
PutLicenseRequestBuilder putLicenseRequestBuilder = new PutLicenseRequestBuilder(cluster, PutLicenseAction.INSTANCE);
putLicenseRequestBuilder.setLicense(putLicenses);
putLicenseRequestBuilder.setAcknowledge(true);
final PutLicenseResponse putLicenseResponse = putLicenseRequestBuilder.get();
assertThat(putLicenseResponse.isAcknowledged(), equalTo(true));
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));
return putLicenses;
private void assertOperationMode(License.OperationMode operationMode) throws InterruptedException {
boolean success = awaitBusy(() -> {
for (LicensesService service : internalCluster().getDataNodeInstances(LicensesService.class)) {
if (service.licenseeStatus().getMode() == operationMode) {
return true;
}
}
return false;
});
assertTrue(success);
}
private void assertNoLicense() {
ClusterAdminClient cluster = internalCluster().client().admin().cluster();
final GetLicenseResponse response = new GetLicenseRequestBuilder(cluster, GetLicenseAction.INSTANCE).get();
assertThat(response.license(), nullValue());
LicensesMetaData licensesMetaData = clusterService().state().metaData().custom(LicensesMetaData.TYPE);
assertThat(licensesMetaData, notNullValue());
assertThat(licensesMetaData.getLicense(), equalTo(LicensesMetaData.LICENSE_TOMBSTONE));
private void writeCloudInternalMode(String mode) throws Exception {
for (Environment environment : internalCluster().getDataOrMasterNodeInstances(Environment.class)) {
Path licenseModePath = XPackPlugin.resolveConfigFile(environment, "license_mode");
Files.createDirectories(licenseModePath.getParent());
Files.write(licenseModePath, mode.getBytes(StandardCharsets.UTF_8));
}
}
private void getAndCheckLicense(License license) {
ClusterAdminClient cluster = internalCluster().client().admin().cluster();
final GetLicenseResponse response = new GetLicenseRequestBuilder(cluster, GetLicenseAction.INSTANCE).get();
assertThat(response.license(), equalTo(license));
LicensesMetaData licensesMetaData = clusterService().state().metaData().custom(LicensesMetaData.TYPE);
assertThat(licensesMetaData, notNullValue());
assertThat(licensesMetaData.getLicense(), not(LicensesMetaData.LICENSE_TOMBSTONE));
}
}

View File

@ -6,9 +6,6 @@
package org.elasticsearch.license.plugin;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.block.ClusterBlock;
import org.elasticsearch.cluster.block.ClusterBlockLevel;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.joda.DateMathParser;
@ -29,10 +26,8 @@ import org.elasticsearch.license.plugin.core.LicensesService;
import org.elasticsearch.license.plugin.core.LicensesStatus;
import org.junit.Assert;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
@ -92,8 +87,11 @@ public class TestUtils {
}
public static License generateSignedLicense(String type, long issueDate, TimeValue expiryDuration) throws Exception {
return generateSignedLicense(type, randomIntBetween(License.VERSION_START, License.VERSION_CURRENT), issueDate, expiryDuration);
}
public static License generateSignedLicense(String type, int version, long issueDate, TimeValue expiryDuration) throws Exception {
long issue = (issueDate != -1L) ? issueDate : System.currentTimeMillis() - TimeValue.timeValueHours(2).getMillis();
int version = randomIntBetween(License.VERSION_START, License.VERSION_CURRENT);
final String licenseType;
if (version < License.VERSION_NO_FEATURE_TYPE) {
licenseType = randomFrom("subscription", "internal", "development");
@ -117,17 +115,26 @@ public class TestUtils {
return signer.sign(builder.build());
}
public static License generateExpiredLicense() throws Exception {
return generateExpiredLicense(System.currentTimeMillis() - TimeValue.timeValueHours(randomIntBetween(1, 10)).getMillis());
public static License generateExpiredLicense(long expiryDate) throws Exception {
return generateExpiredLicense(randomFrom("basic", "silver", "dev", "gold", "platinum"), expiryDate);
}
public static License generateExpiredLicense(long expiryDate) throws Exception {
public static License generateExpiredLicense() throws Exception {
return generateExpiredLicense(randomFrom("basic", "silver", "dev", "gold", "platinum"));
}
public static License generateExpiredLicense(String type) throws Exception {
return generateExpiredLicense(type,
System.currentTimeMillis() - TimeValue.timeValueHours(randomIntBetween(1, 10)).getMillis());
}
public static License generateExpiredLicense(String type, long expiryDate) throws Exception {
final License.Builder builder = License.builder()
.uid(UUID.randomUUID().toString())
.version(License.VERSION_CURRENT)
.expiryDate(expiryDate)
.issueDate(expiryDate - TimeValue.timeValueMinutes(10).getMillis())
.type(randomFrom("basic", "silver", "dev", "gold", "platinum"))
.type(type)
.issuedTo("customer")
.issuer("elasticsearch")
.maxNodes(5);
@ -139,20 +146,6 @@ public class TestUtils {
return PathUtils.get(TestUtils.class.getResource(resource).toURI());
}
public static void awaitNoBlock(final Client client) throws InterruptedException {
boolean success = awaitBusy(() -> {
Set<ClusterBlock> clusterBlocks = client.admin().cluster().prepareState().setLocal(true).execute().actionGet()
.getState().blocks().global(ClusterBlockLevel.METADATA_WRITE);
return clusterBlocks.isEmpty();
});
assertThat("awaiting no block for too long", success, equalTo(true));
}
public static void awaitNoPendingTasks(final Client client) throws InterruptedException {
boolean success = awaitBusy(() -> client.admin().cluster().preparePendingClusterTasks().get().getPendingTasks().isEmpty());
assertThat("awaiting no pending tasks for too long", success, equalTo(true));
}
public static void registerAndAckSignedLicenses(final LicensesService licensesService, License license,
final LicensesStatus expectedStatus) {
PutLicenseRequest putLicenseRequest = new PutLicenseRequest().license(license).acknowledge(true);
@ -183,7 +176,7 @@ public class TestUtils {
public final String id;
public final List<Licensee.Status> statuses = new CopyOnWriteArrayList<>();
public final AtomicInteger expirationMessagesCalled = new AtomicInteger(0);
public final List<Tuple<License, License>> acknowledgementRequested = new CopyOnWriteArrayList<>();
public final List<Tuple<License.OperationMode, License.OperationMode>> acknowledgementRequested = new CopyOnWriteArrayList<>();
private String[] acknowledgmentMessages = new String[0];
@ -207,8 +200,8 @@ public class TestUtils {
}
@Override
public String[] acknowledgmentMessages(License currentLicense, License newLicense) {
acknowledgementRequested.add(new Tuple<>(currentLicense, newLicense));
public String[] acknowledgmentMessages(License.OperationMode currentMode, License.OperationMode newMode) {
acknowledgementRequested.add(new Tuple<>(currentMode, newMode));
return acknowledgmentMessages;
}

View File

@ -6,7 +6,6 @@
package org.elasticsearch.license.plugin.core;
import java.util.Arrays;
import java.util.Collections;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.ClusterName;
@ -19,11 +18,16 @@ import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.component.Lifecycle;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.LocalTransportAddress;
import org.elasticsearch.env.Environment;
import org.elasticsearch.license.core.License;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.support.clock.ClockMock;
import org.junit.After;
import org.junit.Before;
import java.nio.file.Path;
import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import static org.mockito.Mockito.mock;
@ -33,18 +37,25 @@ public abstract class AbstractLicenseServiceTestCase extends ESTestCase {
protected LicensesService licensesService;
protected ClusterService clusterService;
protected ResourceWatcherService resourceWatcherService;
protected ClockMock clock;
protected DiscoveryNodes discoveryNodes;
protected Environment environment;
@Before
public void init() throws Exception {
clusterService = mock(ClusterService.class);
clock = new ClockMock();
discoveryNodes = mock(DiscoveryNodes.class);
resourceWatcherService = mock(ResourceWatcherService.class);
environment = mock(Environment.class);
}
protected void setInitialState(License license, Licensee... licensees) {
licensesService = new LicensesService(Settings.EMPTY, clusterService, clock, Arrays.asList(licensees));
Path tempDir = createTempDir();
when(environment.configFile()).thenReturn(tempDir);
licensesService = new LicensesService(Settings.EMPTY, clusterService, clock, environment,
resourceWatcherService, Arrays.asList(licensees));
ClusterState state = mock(ClusterState.class);
final ClusterBlocks noBlock = ClusterBlocks.builder().build();
when(state.blocks()).thenReturn(noBlock);
@ -60,4 +71,9 @@ public abstract class AbstractLicenseServiceTestCase extends ESTestCase {
when(clusterService.lifecycleState()).thenReturn(Lifecycle.State.STARTED);
when(clusterService.getClusterName()).thenReturn(new ClusterName("a"));
}
@After
public void after() {
licensesService.stop();
}
}

View File

@ -39,17 +39,8 @@ public abstract class AbstractLicenseeTestCase extends ESTestCase {
* @param licensee The licensee to test
*/
public static void assertEmptyAck(OperationMode fromMode, OperationMode toMode, Licensee licensee) {
License fromLicense = mock(License.class);
when(fromLicense.operationMode()).thenReturn(fromMode);
License toLicense = mock(License.class);
when(toLicense.operationMode()).thenReturn(toMode);
if (randomBoolean()) {
fromLicense = null;
}
// test it
String[] messages = licensee.acknowledgmentMessages(fromLicense, toLicense);
String[] messages = licensee.acknowledgmentMessages(fromMode, toMode);
assertThat(fromToMessage(fromMode, toMode), messages.length, equalTo(0));
}
@ -77,12 +68,7 @@ public abstract class AbstractLicenseeTestCase extends ESTestCase {
* @param licensee The licensee to test
*/
public static String[] ackLicenseChange(OperationMode fromMode, OperationMode toMode, Licensee licensee) {
License fromLicense = mock(License.class);
when(fromLicense.operationMode()).thenReturn(fromMode);
License toLicense = mock(License.class);
when(toLicense.operationMode()).thenReturn(toMode);
return licensee.acknowledgmentMessages(fromLicense, toLicense);
return licensee.acknowledgmentMessages(fromMode, toMode);
}
/**

View File

@ -35,8 +35,6 @@ public class LicenseRegistrationTests extends AbstractLicenseServiceTestCase {
assertNotNull(licenseMetaData);
assertNotNull(licenseMetaData.getLicense());
assertEquals(clock.millis() + LicensesService.TRIAL_LICENSE_DURATION.millis(), licenseMetaData.getLicense().expiryDate());
licensesService.stop();
}
public void testNotificationOnRegistration() throws Exception {
@ -47,6 +45,5 @@ public class LicenseRegistrationTests extends AbstractLicenseServiceTestCase {
assertThat(licensee.statuses.size(), equalTo(1));
final LicenseState licenseState = licensee.statuses.get(0).getLicenseState();
assertTrue(licenseState == LicenseState.ENABLED);
licensesService.stop();
}
}

View File

@ -32,13 +32,13 @@ public class LicenseScheduleTests extends ESTestCase {
public void testGraceLicenseSchedule() throws Exception {
long triggeredTime = license.expiryDate() + between(1,
((int) LicensesService.GRACE_PERIOD_DURATION.getMillis()));
((int) LicenseState.GRACE_PERIOD_DURATION.getMillis()));
assertThat(schedule.nextScheduledTimeAfter(license.issueDate(), triggeredTime),
equalTo(license.expiryDate() + LicensesService.GRACE_PERIOD_DURATION.getMillis()));
equalTo(license.expiryDate() + LicenseState.GRACE_PERIOD_DURATION.getMillis()));
}
public void testExpiredLicenseSchedule() throws Exception {
long triggeredTime = license.expiryDate() + LicensesService.GRACE_PERIOD_DURATION.getMillis() +
long triggeredTime = license.expiryDate() + LicenseState.GRACE_PERIOD_DURATION.getMillis() +
randomIntBetween(1, 1000);
assertThat(schedule.nextScheduledTimeAfter(license.issueDate(), triggeredTime),
equalTo(-1L));

View File

@ -41,7 +41,7 @@ public class LicensesAcknowledgementTests extends AbstractLicenseServiceTestCase
licensesService.registerLicense(putLicenseRequest, new AssertingLicensesUpdateResponse(false, LicensesStatus.VALID,
Collections.singletonMap(id, acknowledgeMessages)));
assertThat(licensee.acknowledgementRequested.size(), equalTo(1));
assertThat(licensee.acknowledgementRequested.get(0).v2(), equalTo(signedLicense));
assertThat(licensee.acknowledgementRequested.get(0).v2(), equalTo(signedLicense.operationMode()));
assertThat(licensesService.getLicense(), not(signedLicense));
// try installing a signed license with acknowledgement
@ -52,8 +52,7 @@ public class LicensesAcknowledgementTests extends AbstractLicenseServiceTestCase
Collections.<String, String[]>emptyMap()));
verify(clusterService, times(1)).submitStateUpdateTask(any(String.class), any(ClusterStateUpdateTask.class));
assertThat(licensee.acknowledgementRequested.size(), equalTo(1));
assertThat(licensee.acknowledgementRequested.get(0).v2(), equalTo(signedLicense));
licensesService.stop();
assertThat(licensee.acknowledgementRequested.get(0).v2(), equalTo(signedLicense.operationMode()));
}
public void testAcknowledgementMultipleLicensee() throws Exception {
@ -78,9 +77,9 @@ public class LicensesAcknowledgementTests extends AbstractLicenseServiceTestCase
expectedMessages));
verify(clusterService, times(0)).submitStateUpdateTask(any(String.class), any(ClusterStateUpdateTask.class));
assertThat(licensee2.acknowledgementRequested.size(), equalTo(1));
assertThat(licensee2.acknowledgementRequested.get(0).v2(), equalTo(signedLicense));
assertThat(licensee2.acknowledgementRequested.get(0).v2(), equalTo(signedLicense.operationMode()));
assertThat(licensee1.acknowledgementRequested.size(), equalTo(1));
assertThat(licensee1.acknowledgementRequested.get(0).v2(), equalTo(signedLicense));
assertThat(licensee1.acknowledgementRequested.get(0).v2(), equalTo(signedLicense.operationMode()));
assertThat(licensesService.getLicense(), not(signedLicense));
// try installing a signed license with acknowledgement
@ -91,7 +90,6 @@ public class LicensesAcknowledgementTests extends AbstractLicenseServiceTestCase
licensesService.registerLicense(putLicenseRequest, new AssertingLicensesUpdateResponse(true, LicensesStatus.VALID,
Collections.<String, String[]>emptyMap()));
verify(clusterService, times(1)).submitStateUpdateTask(any(String.class), any(ClusterStateUpdateTask.class));
licensesService.stop();
}
private static class AssertingLicensesUpdateResponse implements ActionListener<PutLicenseResponse> {

View File

@ -37,7 +37,7 @@ public class LicensesNotificationTests extends AbstractLicenseServiceTestCase {
assertLicenseStates(assertingLicensee, LicenseState.ENABLED, LicenseState.GRACE_PERIOD);
}
clock.fastForward(TimeValue.timeValueMillis((license.expiryDate() +
LicensesService.GRACE_PERIOD_DURATION.getMillis()) - clock.millis()));
LicenseState.GRACE_PERIOD_DURATION.getMillis()) - clock.millis()));
licensesService.onUpdate(licensesMetaData);
for (AssertingLicensee assertingLicensee : assertingLicensees) {
assertLicenseStates(assertingLicensee, LicenseState.ENABLED, LicenseState.GRACE_PERIOD, LicenseState.DISABLED);
@ -45,12 +45,12 @@ public class LicensesNotificationTests extends AbstractLicenseServiceTestCase {
clock.setTime(new DateTime(DateTimeZone.UTC));
final License newLicense = TestUtils.generateSignedLicense(TimeValue.timeValueHours(2));
clock.fastForward(TimeValue.timeValueHours(1));
licensesService.onUpdate(new LicensesMetaData(newLicense));
LicensesMetaData licensesMetaData1 = new LicensesMetaData(newLicense);
licensesService.onUpdate(licensesMetaData1);
for (AssertingLicensee assertingLicensee : assertingLicensees) {
assertLicenseStates(assertingLicensee, LicenseState.ENABLED, LicenseState.GRACE_PERIOD, LicenseState.DISABLED,
LicenseState.ENABLED);
}
licensesService.stop();
}
private void assertLicenseStates(AssertingLicensee licensee, LicenseState... states) {

View File

@ -8,7 +8,6 @@ package org.elasticsearch.xpack.monitoring;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.logging.LoggerMessageFormat;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.License.OperationMode;
import org.elasticsearch.license.plugin.core.AbstractLicenseeComponent;
import org.elasticsearch.license.plugin.core.LicenseState;
@ -43,27 +42,25 @@ public class MonitoringLicensee extends AbstractLicenseeComponent {
}
@Override
public String[] acknowledgmentMessages(License currentLicense, License newLicense) {
switch (newLicense.operationMode()) {
public String[] acknowledgmentMessages(OperationMode currentMode, OperationMode newMode) {
switch (newMode) {
case BASIC:
if (currentLicense != null) {
switch (currentLicense.operationMode()) {
case TRIAL:
case STANDARD:
case GOLD:
case PLATINUM:
return new String[] {
LoggerMessageFormat.format(
"Multi-cluster support is disabled for clusters with [{}] license. If you are\n" +
"running multiple clusters, users won't be able to access the clusters with\n" +
"[{}] licenses from within a single X-Pack Kibana instance. You will have to deploy a\n" +
"separate and dedicated X-pack Kibana instance for each [{}] cluster you wish to monitor.",
newLicense.type(), newLicense.type(), newLicense.type()),
LoggerMessageFormat.format(
"Automatic index cleanup is locked to {} days for clusters with [{}] license.",
MonitoringSettings.HISTORY_DURATION.getDefault(Settings.EMPTY).days(), newLicense.type())
};
}
switch (currentMode) {
case TRIAL:
case STANDARD:
case GOLD:
case PLATINUM:
return new String[] {
LoggerMessageFormat.format(
"Multi-cluster support is disabled for clusters with [{}] license. If you are\n" +
"running multiple clusters, users won't be able to access the clusters with\n" +
"[{}] licenses from within a single X-Pack Kibana instance. You will have to deploy a\n" +
"separate and dedicated X-pack Kibana instance for each [{}] cluster you wish to monitor.",
newMode, newMode, newMode),
LoggerMessageFormat.format(
"Automatic index cleanup is locked to {} days for clusters with [{}] license.",
MonitoringSettings.HISTORY_DURATION.getDefault(Settings.EMPTY).days(), newMode)
};
}
break;
}

View File

@ -22,6 +22,7 @@ import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.Module;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.env.Environment;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.Licensing;
import org.elasticsearch.license.plugin.core.LicenseState;
@ -31,6 +32,7 @@ import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.rest.RestHandler;
import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.XPackPlugin;
import org.elasticsearch.xpack.graph.GraphLicensee;
import org.elasticsearch.xpack.monitoring.MonitoringLicensee;
@ -194,13 +196,14 @@ public abstract class AbstractCollectorTestCase extends MonitoringIntegTestCase
}
@Override
public Collection<Object> createComponents(ClusterService clusterService, Clock clock,
public Collection<Object> createComponents(ClusterService clusterService, Clock clock, Environment environment,
ResourceWatcherService resourceWatcherService,
SecurityLicenseState securityLicenseState) {
WatcherLicensee watcherLicensee = new WatcherLicensee(settings);
MonitoringLicensee monitoringLicensee = new MonitoringLicensee(settings);
GraphLicensee graphLicensee = new GraphLicensee(settings);
LicensesService licensesService = new LicenseServiceForCollectors(settings,
Arrays.asList(watcherLicensee, monitoringLicensee, graphLicensee));
LicensesService licensesService = new LicenseServiceForCollectors(settings, environment,
resourceWatcherService, Arrays.asList(watcherLicensee, monitoringLicensee, graphLicensee));
return Arrays.asList(licensesService, watcherLicensee, monitoringLicensee, graphLicensee);
}
@ -229,8 +232,9 @@ public abstract class AbstractCollectorTestCase extends MonitoringIntegTestCase
private volatile License license;
@Inject
public LicenseServiceForCollectors(Settings settings, List<Licensee> licensees) {
super(settings, null, null, licensees);
public LicenseServiceForCollectors(Settings settings, Environment env,
ResourceWatcherService resourceWatcherService, List<Licensee> licensees) {
super(settings, null, null, env, resourceWatcherService, licensees);
this.licensees = licensees;
}
@ -241,7 +245,7 @@ public abstract class AbstractCollectorTestCase extends MonitoringIntegTestCase
}
@Override
public LicenseState licenseState() {
public Licensee.Status licenseeStatus() {
return null;
}

View File

@ -11,14 +11,17 @@ import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.Module;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.Licensing;
import org.elasticsearch.license.plugin.core.AbstractLicenseeComponent;
import org.elasticsearch.license.plugin.core.LicenseState;
import org.elasticsearch.license.plugin.core.Licensee;
import org.elasticsearch.license.plugin.core.LicensesService;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.rest.RestHandler;
import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.XPackPlugin;
import org.elasticsearch.xpack.graph.GraphLicensee;
import org.elasticsearch.xpack.monitoring.MonitoringLicensee;
@ -97,13 +100,14 @@ public class LicenseIntegrationTests extends MonitoringIntegTestCase {
}
@Override
public Collection<Object> createComponents(ClusterService clusterService, Clock clock,
public Collection<Object> createComponents(ClusterService clusterService, Clock clock, Environment environment,
ResourceWatcherService resourceWatcherService,
SecurityLicenseState securityLicenseState) {
WatcherLicensee watcherLicensee = new WatcherLicensee(settings);
MonitoringLicensee monitoringLicensee = new MonitoringLicensee(settings);
GraphLicensee graphLicensee = new GraphLicensee(settings);
LicensesService licensesService = new MockLicenseService(settings,
Arrays.asList(watcherLicensee, monitoringLicensee, graphLicensee));
LicensesService licensesService = new MockLicenseService(settings, environment, resourceWatcherService,
Arrays.asList(watcherLicensee, monitoringLicensee, graphLicensee));
return Arrays.asList(licensesService, watcherLicensee, monitoringLicensee, graphLicensee);
}
@ -123,8 +127,9 @@ public class LicenseIntegrationTests extends MonitoringIntegTestCase {
private final List<Licensee> licensees;
@Inject
public MockLicenseService(Settings settings, List<Licensee> licensees) {
super(settings, null, null, licensees);
public MockLicenseService(Settings settings, Environment environment,
ResourceWatcherService resourceWatcherService, List<Licensee> licensees) {
super(settings, null, null, environment, resourceWatcherService, licensees);
this.licensees = licensees;
enable();
}
@ -143,7 +148,7 @@ public class LicenseIntegrationTests extends MonitoringIntegTestCase {
}
@Override
public LicenseState licenseState() {
public Licensee.Status licenseeStatus() {
return null;
}

View File

@ -37,54 +37,48 @@ public class SecurityLicensee extends AbstractLicenseeComponent {
}
@Override
public String[] acknowledgmentMessages(License currentLicense, License newLicense) {
switch (newLicense.operationMode()) {
public String[] acknowledgmentMessages(License.OperationMode currentMode, License.OperationMode newMode) {
switch (newMode) {
case BASIC:
if (currentLicense != null) {
switch (currentLicense.operationMode()) {
case TRIAL:
case STANDARD:
case GOLD:
case PLATINUM:
return new String[] {
"The following X-Pack security functionality will be disabled: authentication, authorization, " +
"ip filtering, and auditing. Please restart your node after applying the license.",
"Field and document level access control will be disabled.",
"Custom realms will be ignored."
};
}
switch (currentMode) {
case TRIAL:
case STANDARD:
case GOLD:
case PLATINUM:
return new String[] {
"The following X-Pack security functionality will be disabled: authentication, authorization, " +
"ip filtering, and auditing. Please restart your node after applying the license.",
"Field and document level access control will be disabled.",
"Custom realms will be ignored."
};
}
break;
case GOLD:
if (currentLicense != null) {
switch (currentLicense.operationMode()) {
case BASIC:
case STANDARD:
// ^^ though technically it was already disabled, it's not bad to remind them
case TRIAL:
case PLATINUM:
return new String[] {
"Field and document level access control will be disabled.",
"Custom realms will be ignored."
};
}
switch (currentMode) {
case BASIC:
case STANDARD:
// ^^ though technically it was already disabled, it's not bad to remind them
case TRIAL:
case PLATINUM:
return new String[] {
"Field and document level access control will be disabled.",
"Custom realms will be ignored."
};
}
break;
case STANDARD:
if (currentLicense != null) {
switch (currentLicense.operationMode()) {
case BASIC:
// ^^ though technically it was already disabled, it's not bad to remind them
case GOLD:
case PLATINUM:
case TRIAL:
return new String[] {
"Authentication will be limited to the native realms.",
"IP filtering and auditing will be disabled.",
"Field and document level access control will be disabled.",
"Custom realms will be ignored."
};
}
switch (currentMode) {
case BASIC:
// ^^ though technically it was already disabled, it's not bad to remind them
case GOLD:
case PLATINUM:
case TRIAL:
return new String[] {
"Authentication will be limited to the native realms.",
"IP filtering and auditing will be disabled.",
"Field and document level access control will be disabled.",
"Custom realms will be ignored."
};
}
}
return Strings.EMPTY_ARRAY;

View File

@ -31,6 +31,7 @@ import org.elasticsearch.common.inject.Module;
import org.elasticsearch.common.network.NetworkModule;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.env.Environment;
import org.elasticsearch.license.core.License.OperationMode;
import org.elasticsearch.license.plugin.Licensing;
import org.elasticsearch.license.plugin.core.LicenseState;
@ -42,6 +43,7 @@ import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.test.SecurityIntegTestCase;
import org.elasticsearch.test.SecuritySettingsSource;
import org.elasticsearch.transport.Transport;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.MockNetty3Plugin;
import org.elasticsearch.xpack.XPackPlugin;
import org.elasticsearch.xpack.graph.GraphLicensee;
@ -258,16 +260,17 @@ public class LicensingTests extends SecurityIntegTestCase {
}
@Override
public Collection<Object> createComponents(ClusterService clusterService, Clock clock,
public Collection<Object> createComponents(ClusterService clusterService, Clock clock, Environment environment,
ResourceWatcherService resourceWatcherService,
SecurityLicenseState securityLicenseState) {
SecurityLicensee securityLicensee = new SecurityLicensee(settings, securityLicenseState);
WatcherLicensee watcherLicensee = new WatcherLicensee(settings);
MonitoringLicensee monitoringLicensee = new MonitoringLicensee(settings);
GraphLicensee graphLicensee = new GraphLicensee(settings);
TestLicensesService licensesService = new TestLicensesService(settings,
Arrays.asList(securityLicensee, watcherLicensee, monitoringLicensee, graphLicensee));
TestLicensesService licensesService = new TestLicensesService(settings, environment, resourceWatcherService,
Arrays.asList(securityLicensee, watcherLicensee, monitoringLicensee, graphLicensee));
return Arrays.asList(securityLicensee, licensesService, watcherLicensee, monitoringLicensee,
graphLicensee, securityLicenseState);
graphLicensee, securityLicenseState);
}
public InternalLicensing() {
@ -297,8 +300,9 @@ public class LicensingTests extends SecurityIntegTestCase {
private final List<Licensee> licensees;
public TestLicensesService(Settings settings, List<Licensee> licensees) {
super(settings, null, null, Collections.emptyList());
public TestLicensesService(Settings settings, Environment env, ResourceWatcherService resourceWatcherService,
List<Licensee> licensees) {
super(settings, null, null, env, resourceWatcherService, Collections.emptyList());
this.licensees = licensees;
enable(OperationMode.BASIC);
}

View File

@ -191,7 +191,8 @@ public class XPackPlugin extends Plugin implements ScriptPlugin, ActionPlugin {
final InternalClient internalClient = new InternalClient(settings, threadPool, client, security.getCryptoService());
components.add(internalClient);
components.addAll(licensing.createComponents(clusterService, getClock(), security.getSecurityLicenseState()));
components.addAll(licensing.createComponents(clusterService, getClock(), env, resourceWatcherService,
security.getSecurityLicenseState()));
components.addAll(security.createComponents(resourceWatcherService));
// watcher http stuff

View File

@ -82,20 +82,23 @@ public class XPackInfoResponse extends ActionResponse {
private final String uid;
private final String type;
private final String mode;
private final long expiryDate;
private final License.Status status;
public LicenseInfo(License license) {
this(license.uid(), license.type(), license.status(), license.expiryDate());
this(license.uid(), license.type(), license.operationMode().name().toLowerCase(Locale.ROOT),
license.status(), license.expiryDate());
}
public LicenseInfo(StreamInput in) throws IOException {
this(in.readString(), in.readString(), License.Status.readFrom(in), in.readLong());
this(in.readString(), in.readString(), in.readString(), License.Status.readFrom(in), in.readLong());
}
public LicenseInfo(String uid, String type, License.Status status, long expiryDate) {
public LicenseInfo(String uid, String type, String mode, License.Status status, long expiryDate) {
this.uid = uid;
this.type = type;
this.mode = mode;
this.status = status;
this.expiryDate = expiryDate;
}
@ -108,6 +111,10 @@ public class XPackInfoResponse extends ActionResponse {
return type;
}
public String getMode() {
return mode;
}
public long getExpiryDate() {
return expiryDate;
}
@ -121,7 +128,7 @@ public class XPackInfoResponse extends ActionResponse {
return builder.startObject()
.field("uid", uid)
.field("type", type)
.field("mode", License.OperationMode.resolve(type).name().toLowerCase(Locale.ROOT))
.field("mode", mode)
.field("status", status.label())
.dateValueField("expiry_date_in_millis", "expiry_date", expiryDate)
.endObject();
@ -130,6 +137,7 @@ public class XPackInfoResponse extends ActionResponse {
public void writeTo(StreamOutput out) throws IOException {
out.writeString(uid);
out.writeString(type);
out.writeString(mode);
status.writeTo(out);
out.writeLong(expiryDate);
}

View File

@ -22,6 +22,7 @@ import org.junit.Before;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
@ -80,6 +81,8 @@ public class TransportXPackInfoActionTests extends ESTestCase {
when(license.status()).thenReturn(status);
String type = randomAsciiOfLength(10);
when(license.type()).thenReturn(type);
License.OperationMode mode = randomFrom(License.OperationMode.values());
when(license.operationMode()).thenReturn(mode);
String uid = randomAsciiOfLength(30);
when(license.uid()).thenReturn(uid);
when(licensesService.getLicense()).thenReturn(license);
@ -129,6 +132,7 @@ public class TransportXPackInfoActionTests extends ESTestCase {
assertThat(response.get().getLicenseInfo().getExpiryDate(), is(expiryDate));
assertThat(response.get().getLicenseInfo().getStatus(), is(status));
assertThat(response.get().getLicenseInfo().getType(), is(type));
assertThat(response.get().getLicenseInfo().getMode(), is(mode.name().toLowerCase(Locale.ROOT)));
assertThat(response.get().getLicenseInfo().getUid(), is(uid));
} else {
assertThat(response.get().getLicenseInfo(), nullValue());

View File

@ -61,7 +61,7 @@
- is_true: license
- match: { license.uid: "893361dc-9749-4997-93cb-802e3dofh7aa" }
- match: { license.type: "internal" }
- match: { license.mode: "platinum" }
- match: { license.mode: "trial" }
- match: { license.status: "active" }
- match: { license.expiry_date_in_millis: 1914278399999 }
- is_true: features
@ -133,7 +133,7 @@
- is_true: license
- match: { license.uid: "893361dc-9749-4997-93cb-802e3dofh7aa" }
- match: { license.type: "internal" }
- match: { license.mode: "platinum" }
- match: { license.mode: "trial" }
- match: { license.status: "active" }
- match: { license.expiry_date_in_millis: 1914278399999 }
@ -147,7 +147,7 @@
- is_true: license
- match: { license.uid: "893361dc-9749-4997-93cb-802e3dofh7aa" }
- match: { license.type: "internal" }
- match: { license.mode: "platinum" }
- match: { license.mode: "trial" }
- match: { license.status: "active" }
- match: { license.expiry_date_in_millis: 1914278399999 }
- is_true: features

View File

@ -7,7 +7,6 @@ package org.elasticsearch.xpack.watcher;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.License.OperationMode;
import org.elasticsearch.license.plugin.core.AbstractLicenseeComponent;
import org.elasticsearch.license.plugin.core.LicenseState;
@ -30,17 +29,15 @@ public class WatcherLicensee extends AbstractLicenseeComponent {
}
@Override
public String[] acknowledgmentMessages(License currentLicense, License newLicense) {
switch (newLicense.operationMode()) {
public String[] acknowledgmentMessages(OperationMode currentMode, OperationMode newMode) {
switch (newMode) {
case BASIC:
if (currentLicense != null) {
switch (currentLicense.operationMode()) {
case TRIAL:
case STANDARD:
case GOLD:
case PLATINUM:
return new String[] { "Watcher will be disabled" };
}
switch (currentMode) {
case TRIAL:
case STANDARD:
case GOLD:
case PLATINUM:
return new String[] { "Watcher will be disabled" };
}
break;
}