Merge Marvel 2.0
Original commit: elastic/x-pack-elasticsearch@ce68b3b983
This commit is contained in:
commit
315055012f
|
@ -0,0 +1,14 @@
|
|||
ELASTICSEARCH CONFIDENTIAL
|
||||
__________________
|
||||
|
||||
[2014] Elasticsearch Incorporated. All Rights Reserved.
|
||||
|
||||
NOTICE: All information contained herein is, and remains
|
||||
the property of Elasticsearch Incorporated and its suppliers,
|
||||
if any. The intellectual and technical concepts contained
|
||||
herein are proprietary to Elasticsearch Incorporated
|
||||
and its suppliers and may be covered by U.S. and Foreign Patents,
|
||||
patents in process, and are protected by trade secret or copyright law.
|
||||
Dissemination of this information or reproduction of this material
|
||||
is strictly forbidden unless prior written permission is obtained
|
||||
from Elasticsearch Incorporated.
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<additionalHeaders>
|
||||
<javadoc_style>
|
||||
<firstLine>/*</firstLine>
|
||||
<beforeEachLine> * </beforeEachLine>
|
||||
<endLine> */EOL</endLine>
|
||||
<!--skipLine></skipLine-->
|
||||
<firstLineDetectionPattern>(\s|\t)*/\*.*$</firstLineDetectionPattern>
|
||||
<lastLineDetectionPattern>.*\*/(\s|\t)*$</lastLineDetectionPattern>
|
||||
<allowBlankLines>false</allowBlankLines>
|
||||
<isMultiline>true</isMultiline>
|
||||
</javadoc_style>
|
||||
</additionalHeaders>
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.elasticsearch</groupId>
|
||||
<artifactId>elasticsearch-marvel</artifactId>
|
||||
<name>Elastic X-Plugins - Marvel</name>
|
||||
|
||||
<parent>
|
||||
<groupId>org.elasticsearch</groupId>
|
||||
<artifactId>x-plugins</artifactId>
|
||||
<version>2.0.0-beta1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.elasticsearch</groupId>
|
||||
<artifactId>elasticsearch-license-plugin</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
<includes>
|
||||
<include>**/*.properties</include>
|
||||
<include>marvel_index_template.json</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,17 @@
|
|||
<assembly>
|
||||
<id>plugin</id>
|
||||
<formats>
|
||||
<format>zip</format>
|
||||
</formats>
|
||||
<includeBaseDirectory>false</includeBaseDirectory>
|
||||
<dependencySets>
|
||||
<dependencySet>
|
||||
<outputDirectory>/</outputDirectory>
|
||||
<useProjectArtifact>true</useProjectArtifact>
|
||||
<useTransitiveFiltering>true</useTransitiveFiltering>
|
||||
<excludes>
|
||||
<exclude>org.elasticsearch:elasticsearch</exclude>
|
||||
</excludes>
|
||||
</dependencySet>
|
||||
</dependencySets>
|
||||
</assembly>
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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.marvel;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.elasticsearch.common.inject.AbstractModule;
|
||||
import org.elasticsearch.common.inject.Module;
|
||||
import org.elasticsearch.common.inject.Scopes;
|
||||
import org.elasticsearch.common.inject.SpawnModules;
|
||||
import org.elasticsearch.marvel.agent.AgentService;
|
||||
import org.elasticsearch.marvel.agent.collector.CollectorModule;
|
||||
import org.elasticsearch.marvel.agent.exporter.ExporterModule;
|
||||
import org.elasticsearch.marvel.license.LicenseModule;
|
||||
|
||||
public class MarvelModule extends AbstractModule implements SpawnModules {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(AgentService.class).in(Scopes.SINGLETON);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<? extends Module> spawnModules() {
|
||||
return ImmutableList.of(new LicenseModule(), new CollectorModule(), new ExporterModule());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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.marvel;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.elasticsearch.client.Client;
|
||||
import org.elasticsearch.common.component.LifecycleComponent;
|
||||
import org.elasticsearch.common.inject.Module;
|
||||
import org.elasticsearch.common.logging.ESLogger;
|
||||
import org.elasticsearch.common.logging.Loggers;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.marvel.agent.AgentService;
|
||||
import org.elasticsearch.marvel.license.LicenseService;
|
||||
import org.elasticsearch.plugins.AbstractPlugin;
|
||||
import org.elasticsearch.tribe.TribeService;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class MarvelPlugin extends AbstractPlugin {
|
||||
|
||||
private static final ESLogger logger = Loggers.getLogger(MarvelPlugin.class);
|
||||
|
||||
public static final String NAME = "marvel";
|
||||
public static final String ENABLED = NAME + ".enabled";
|
||||
|
||||
private final boolean enabled;
|
||||
|
||||
public MarvelPlugin(Settings settings) {
|
||||
this.enabled = marvelEnabled(settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "Elasticsearch Marvel";
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends Module>> modules() {
|
||||
if (!enabled) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
return ImmutableList.<Class<? extends Module>>of(MarvelModule.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends LifecycleComponent>> services() {
|
||||
if (!enabled) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
return ImmutableList.<Class<? extends LifecycleComponent>>of(LicenseService.class, AgentService.class);
|
||||
}
|
||||
|
||||
public static boolean marvelEnabled(Settings settings) {
|
||||
String tribe = settings.get(TribeService.TRIBE_NAME);
|
||||
if (tribe != null) {
|
||||
logger.trace("marvel cannot be started on tribe node [{}]", tribe);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!"node".equals(settings.get(Client.CLIENT_TYPE_SETTING))) {
|
||||
logger.trace("marvel cannot be started on a transport client");
|
||||
return false;
|
||||
}
|
||||
return settings.getAsBoolean(ENABLED, true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
* 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.marvel;
|
||||
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.license.plugin.LicenseVersion;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
|
||||
public class MarvelVersion implements Serializable {
|
||||
|
||||
// The logic for ID is: XXYYZZAA, where XX is major version, YY is minor version, ZZ is revision, and AA is Beta/RC indicator
|
||||
// AA values below 50 are beta builds, and below 99 are RC builds, with 99 indicating a release
|
||||
// the (internal) format of the id is there so we can easily do after/before checks on the id
|
||||
|
||||
public static final int V_2_0_0_Beta1_ID = /*00*/2000001;
|
||||
public static final MarvelVersion V_2_0_0_Beta1 = new MarvelVersion(V_2_0_0_Beta1_ID, true, Version.V_2_0_0_Beta1, LicenseVersion.V_1_0_0);
|
||||
|
||||
public static final MarvelVersion CURRENT = V_2_0_0_Beta1;
|
||||
|
||||
public static MarvelVersion readVersion(StreamInput in) throws IOException {
|
||||
return fromId(in.readVInt());
|
||||
}
|
||||
|
||||
public static MarvelVersion fromId(int id) {
|
||||
switch (id) {
|
||||
case V_2_0_0_Beta1_ID:
|
||||
return V_2_0_0_Beta1;
|
||||
default:
|
||||
return new MarvelVersion(id, null, Version.CURRENT, LicenseVersion.CURRENT);
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeVersion(MarvelVersion version, StreamOutput out) throws IOException {
|
||||
out.writeVInt(version.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the smallest version between the 2.
|
||||
*/
|
||||
public static MarvelVersion smallest(MarvelVersion version1, MarvelVersion version2) {
|
||||
return version1.id < version2.id ? version1 : version2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version given its string representation, current version if the argument is null or empty
|
||||
*/
|
||||
public static MarvelVersion fromString(String version) {
|
||||
if (!Strings.hasLength(version)) {
|
||||
return MarvelVersion.CURRENT;
|
||||
}
|
||||
|
||||
String[] parts = version.split("[\\.-]");
|
||||
if (parts.length < 3 || parts.length > 4) {
|
||||
throw new IllegalArgumentException("the version needs to contain major, minor and revision, and optionally the build");
|
||||
}
|
||||
|
||||
try {
|
||||
//we reverse the version id calculation based on some assumption as we can't reliably reverse the modulo
|
||||
int major = Integer.parseInt(parts[0]) * 1000000;
|
||||
int minor = Integer.parseInt(parts[1]) * 10000;
|
||||
int revision = Integer.parseInt(parts[2]) * 100;
|
||||
|
||||
int build = 99;
|
||||
if (parts.length == 4) {
|
||||
String buildStr = parts[3];
|
||||
if (buildStr.startsWith("beta")) {
|
||||
build = Integer.parseInt(buildStr.substring(4));
|
||||
}
|
||||
if (buildStr.startsWith("rc")) {
|
||||
build = Integer.parseInt(buildStr.substring(2)) + 50;
|
||||
}
|
||||
}
|
||||
|
||||
return fromId(major + minor + revision + build);
|
||||
|
||||
} catch(NumberFormatException e) {
|
||||
throw new IllegalArgumentException("unable to parse version " + version, e);
|
||||
}
|
||||
}
|
||||
|
||||
public final int id;
|
||||
public final byte major;
|
||||
public final byte minor;
|
||||
public final byte revision;
|
||||
public final byte build;
|
||||
public final Boolean snapshot;
|
||||
public final Version minEsCompatibilityVersion;
|
||||
public final LicenseVersion minLicenseCompatibilityVersion;
|
||||
|
||||
MarvelVersion(int id, @Nullable Boolean snapshot, Version minEsCompatibilityVersion, LicenseVersion minLicenseCompatibilityVersion) {
|
||||
this.id = id;
|
||||
this.major = (byte) ((id / 1000000) % 100);
|
||||
this.minor = (byte) ((id / 10000) % 100);
|
||||
this.revision = (byte) ((id / 100) % 100);
|
||||
this.build = (byte) (id % 100);
|
||||
this.snapshot = snapshot;
|
||||
this.minEsCompatibilityVersion = minEsCompatibilityVersion;
|
||||
this.minLicenseCompatibilityVersion = minLicenseCompatibilityVersion;
|
||||
}
|
||||
|
||||
public boolean snapshot() {
|
||||
return snapshot != null && snapshot;
|
||||
}
|
||||
|
||||
public boolean after(MarvelVersion version) {
|
||||
return version.id < id;
|
||||
}
|
||||
|
||||
public boolean onOrAfter(MarvelVersion version) {
|
||||
return version.id <= id;
|
||||
}
|
||||
|
||||
public boolean before(MarvelVersion version) {
|
||||
return version.id > id;
|
||||
}
|
||||
|
||||
public boolean onOrBefore(MarvelVersion version) {
|
||||
return version.id >= id;
|
||||
}
|
||||
|
||||
public boolean compatibleWith(MarvelVersion version) {
|
||||
return version.onOrAfter(minimumCompatibilityVersion());
|
||||
}
|
||||
|
||||
public boolean compatibleWith(Version esVersion) {
|
||||
return esVersion.onOrAfter(minEsCompatibilityVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum compatible version based on the current
|
||||
* version. Ie a node needs to have at least the return version in order
|
||||
* to communicate with a node running the current version. The returned version
|
||||
* is in most of the cases the smallest major version release unless the current version
|
||||
* is a beta or RC release then the version itself is returned.
|
||||
*/
|
||||
public MarvelVersion minimumCompatibilityVersion() {
|
||||
return MarvelVersion.smallest(this, fromId(major * 1000000 + 99));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The minimum elasticsearch version this marvel version is compatible with.
|
||||
*/
|
||||
public Version minimumEsCompatiblityVersion() {
|
||||
return minEsCompatibilityVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The minimum license plugin version this marvel version is compatible with.
|
||||
*/
|
||||
public LicenseVersion minimumLicenseCompatibilityVersion() {
|
||||
return minLicenseCompatibilityVersion;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Just the version number (without -SNAPSHOT if snapshot).
|
||||
*/
|
||||
public String number() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(major).append('.').append(minor).append('.').append(revision);
|
||||
if (build < 50) {
|
||||
sb.append("-beta").append(build);
|
||||
} else if (build < 99) {
|
||||
sb.append("-rc").append(build - 50);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(number());
|
||||
if (snapshot()) {
|
||||
sb.append("-SNAPSHOT");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
MarvelVersion that = (MarvelVersion) o;
|
||||
|
||||
if (id != that.id) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
* 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.marvel.agent;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.elasticsearch.cluster.settings.ClusterDynamicSettings;
|
||||
import org.elasticsearch.cluster.settings.DynamicSettings;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.component.AbstractLifecycleComponent;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.common.util.concurrent.EsExecutors;
|
||||
import org.elasticsearch.marvel.agent.collector.Collector;
|
||||
import org.elasticsearch.marvel.agent.exporter.Exporter;
|
||||
import org.elasticsearch.marvel.agent.exporter.MarvelDoc;
|
||||
import org.elasticsearch.marvel.license.LicenseService;
|
||||
import org.elasticsearch.node.settings.NodeSettingsService;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
public class AgentService extends AbstractLifecycleComponent<AgentService> implements NodeSettingsService.Listener {
|
||||
|
||||
private static final String SETTINGS_BASE = "marvel.agent.";
|
||||
|
||||
public static final String SETTINGS_INTERVAL = SETTINGS_BASE + "interval";
|
||||
public static final String SETTINGS_INDICES = SETTINGS_BASE + "indices";
|
||||
public static final String SETTINGS_ENABLED = SETTINGS_BASE + "enabled";
|
||||
|
||||
public static final String SETTINGS_STATS_TIMEOUT = SETTINGS_BASE + "stats.timeout";
|
||||
public static final String SETTINGS_INDICES_STATS_TIMEOUT = SETTINGS_BASE + "stats.indices.timeout";
|
||||
|
||||
private volatile ExportingWorker exportingWorker;
|
||||
|
||||
private volatile Thread workerThread;
|
||||
private volatile long samplingInterval;
|
||||
volatile private String[] indicesToExport = Strings.EMPTY_ARRAY;
|
||||
|
||||
private volatile TimeValue indicesStatsTimeout;
|
||||
private volatile TimeValue clusterStatsTimeout;
|
||||
|
||||
private final Collection<Collector> collectors;
|
||||
private final Collection<Exporter> exporters;
|
||||
|
||||
@Inject
|
||||
public AgentService(Settings settings, NodeSettingsService nodeSettingsService,
|
||||
@ClusterDynamicSettings DynamicSettings dynamicSettings,
|
||||
LicenseService licenseService,
|
||||
Set<Collector> collectors, Set<Exporter> exporters) {
|
||||
super(settings);
|
||||
this.samplingInterval = settings.getAsTime(SETTINGS_INTERVAL, TimeValue.timeValueSeconds(10)).millis();
|
||||
this.indicesToExport = settings.getAsArray(SETTINGS_INDICES, this.indicesToExport, true);
|
||||
|
||||
TimeValue statsTimeout = settings.getAsTime(SETTINGS_STATS_TIMEOUT, TimeValue.timeValueMinutes(10));
|
||||
indicesStatsTimeout = settings.getAsTime(SETTINGS_INDICES_STATS_TIMEOUT, statsTimeout);
|
||||
|
||||
if (settings.getAsBoolean(SETTINGS_ENABLED, true)) {
|
||||
this.collectors = ImmutableSet.copyOf(collectors);
|
||||
this.exporters = ImmutableSet.copyOf(exporters);
|
||||
} else {
|
||||
this.collectors = ImmutableSet.of();
|
||||
this.exporters = ImmutableSet.of();
|
||||
logger.info("collecting disabled by settings");
|
||||
}
|
||||
|
||||
nodeSettingsService.addListener(this);
|
||||
dynamicSettings.addDynamicSetting(SETTINGS_INTERVAL);
|
||||
dynamicSettings.addDynamicSetting(SETTINGS_INDICES + ".*"); // array settings
|
||||
dynamicSettings.addDynamicSetting(SETTINGS_STATS_TIMEOUT);
|
||||
dynamicSettings.addDynamicSetting(SETTINGS_INDICES_STATS_TIMEOUT);
|
||||
|
||||
logger.trace("marvel is running in [{}] mode", licenseService.mode());
|
||||
}
|
||||
|
||||
protected void applyIntervalSettings() {
|
||||
if (samplingInterval <= 0) {
|
||||
logger.info("data sampling is disabled due to interval settings [{}]", samplingInterval);
|
||||
if (workerThread != null) {
|
||||
|
||||
// notify worker to stop on its leisure, not to disturb an exporting operation
|
||||
exportingWorker.closed = true;
|
||||
|
||||
exportingWorker = null;
|
||||
workerThread = null;
|
||||
}
|
||||
} else if (workerThread == null || !workerThread.isAlive()) {
|
||||
|
||||
exportingWorker = new ExportingWorker();
|
||||
workerThread = new Thread(exportingWorker, EsExecutors.threadName(settings, "marvel.exporters"));
|
||||
workerThread.setDaemon(true);
|
||||
workerThread.start();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStart() {
|
||||
if (exporters.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Collector collector : collectors) {
|
||||
collector.start();
|
||||
}
|
||||
|
||||
for (Exporter exporter : exporters) {
|
||||
exporter.start();
|
||||
}
|
||||
|
||||
applyIntervalSettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStop() {
|
||||
if (exporters.size() == 0) {
|
||||
return;
|
||||
}
|
||||
if (workerThread != null && workerThread.isAlive()) {
|
||||
exportingWorker.closed = true;
|
||||
workerThread.interrupt();
|
||||
try {
|
||||
workerThread.join(60000);
|
||||
} catch (InterruptedException e) {
|
||||
// we don't care...
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for (Collector collector : collectors) {
|
||||
collector.stop();
|
||||
}
|
||||
|
||||
for (Exporter exporter : exporters) {
|
||||
exporter.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doClose() {
|
||||
for (Collector collector : collectors) {
|
||||
collector.close();
|
||||
}
|
||||
|
||||
for (Exporter exporter : exporters) {
|
||||
exporter.close();
|
||||
}
|
||||
}
|
||||
|
||||
// used for testing
|
||||
public Collection<Exporter> getExporters() {
|
||||
return exporters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRefreshSettings(Settings settings) {
|
||||
TimeValue newSamplingInterval = settings.getAsTime(SETTINGS_INTERVAL, null);
|
||||
if (newSamplingInterval != null && newSamplingInterval.millis() != samplingInterval) {
|
||||
logger.info("sampling interval updated to [{}]", newSamplingInterval);
|
||||
samplingInterval = newSamplingInterval.millis();
|
||||
applyIntervalSettings();
|
||||
}
|
||||
|
||||
String[] indices = settings.getAsArray(SETTINGS_INDICES, null, true);
|
||||
if (indices != null) {
|
||||
logger.info("sampling indices updated to [{}]", Strings.arrayToCommaDelimitedString(indices));
|
||||
indicesToExport = indices;
|
||||
}
|
||||
|
||||
TimeValue statsTimeout = settings.getAsTime(SETTINGS_STATS_TIMEOUT, TimeValue.timeValueMinutes(10));
|
||||
TimeValue newTimeValue = settings.getAsTime(SETTINGS_INDICES_STATS_TIMEOUT, statsTimeout);
|
||||
if (!indicesStatsTimeout.equals(newTimeValue)) {
|
||||
logger.info("indices stats timeout updated to [{}]", newTimeValue);
|
||||
indicesStatsTimeout = newTimeValue;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class ExportingWorker implements Runnable {
|
||||
|
||||
volatile boolean closed = false;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (!closed) {
|
||||
// sleep first to allow node to complete initialization before collecting the first start
|
||||
try {
|
||||
Thread.sleep(samplingInterval);
|
||||
if (closed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Collector collector : collectors) {
|
||||
logger.trace("collecting {}", collector.name());
|
||||
Collection<MarvelDoc> results = collector.collect();
|
||||
|
||||
if (results != null && !results.isEmpty()) {
|
||||
for (Exporter exporter : exporters) {
|
||||
exporter.export(results);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (Throwable t) {
|
||||
logger.error("Background thread had an uncaught exception:", t);
|
||||
}
|
||||
}
|
||||
logger.debug("worker shutdown");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* 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.marvel.agent.collector;
|
||||
|
||||
|
||||
import org.elasticsearch.ElasticsearchTimeoutException;
|
||||
import org.elasticsearch.cluster.ClusterService;
|
||||
import org.elasticsearch.common.component.AbstractLifecycleComponent;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.marvel.agent.exporter.MarvelDoc;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public abstract class AbstractCollector<T> extends AbstractLifecycleComponent<T> implements Collector<T> {
|
||||
|
||||
private final String name;
|
||||
|
||||
protected final ClusterService clusterService;
|
||||
|
||||
@Inject
|
||||
public AbstractCollector(Settings settings, String name, ClusterService clusterService) {
|
||||
super(settings);
|
||||
this.name = name;
|
||||
this.clusterService = clusterService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T start() {
|
||||
logger.debug("starting collector [{}]", name());
|
||||
return super.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStart() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the current collector should
|
||||
* be executed on master node only.
|
||||
*/
|
||||
protected boolean masterOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MarvelDoc> collect() {
|
||||
if (masterOnly() && !clusterService.state().nodes().localNodeMaster()) {
|
||||
logger.trace("collector [{}] runs on master only", name());
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return doCollect();
|
||||
} catch (ElasticsearchTimeoutException e) {
|
||||
logger.error("collector [{}] timed out when collecting data");
|
||||
} catch (Exception e) {
|
||||
logger.error("collector [{}] throws exception when collecting data", e, name());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected abstract Collection<MarvelDoc> doCollect() throws Exception;
|
||||
|
||||
@Override
|
||||
public T stop() {
|
||||
logger.debug("stopping collector [{}]", name());
|
||||
return super.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStop() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
logger.trace("closing collector [{}]", name());
|
||||
super.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doClose() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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.marvel.agent.collector;
|
||||
|
||||
import org.elasticsearch.common.component.LifecycleComponent;
|
||||
import org.elasticsearch.marvel.agent.exporter.MarvelDoc;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface Collector<T> extends LifecycleComponent<T> {
|
||||
|
||||
String name();
|
||||
|
||||
Collection<MarvelDoc> collect();
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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.marvel.agent.collector;
|
||||
|
||||
import org.elasticsearch.common.inject.AbstractModule;
|
||||
import org.elasticsearch.common.inject.multibindings.Multibinder;
|
||||
import org.elasticsearch.marvel.agent.collector.indices.IndexCollector;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class CollectorModule extends AbstractModule {
|
||||
|
||||
private final Set<Class<? extends Collector>> collectors = new HashSet<>();
|
||||
|
||||
public CollectorModule() {
|
||||
// Registers default collectors
|
||||
registerCollector(IndexCollector.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
Multibinder<Collector> binder = Multibinder.newSetBinder(binder(), Collector.class);
|
||||
for (Class<? extends Collector> collector : collectors) {
|
||||
bind(collector).asEagerSingleton();
|
||||
binder.addBinding().to(collector);
|
||||
}
|
||||
}
|
||||
|
||||
public void registerCollector(Class<? extends Collector> collector) {
|
||||
collectors.add(collector);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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.marvel.agent.collector.indices;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.elasticsearch.action.admin.indices.stats.IndexStats;
|
||||
import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse;
|
||||
import org.elasticsearch.client.Client;
|
||||
import org.elasticsearch.cluster.ClusterName;
|
||||
import org.elasticsearch.cluster.ClusterService;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.marvel.agent.collector.AbstractCollector;
|
||||
import org.elasticsearch.marvel.agent.exporter.MarvelDoc;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Collector for indices statistics.
|
||||
*
|
||||
* This collector runs on the master node only and collect a {@link IndexMarvelDoc} document
|
||||
* for each existing index in the cluster.
|
||||
*/
|
||||
public class IndexCollector extends AbstractCollector<IndexCollector> {
|
||||
|
||||
public static final String NAME = "index-collector";
|
||||
protected static final String TYPE = "marvel_index";
|
||||
|
||||
private final ClusterName clusterName;
|
||||
private final Client client;
|
||||
|
||||
@Inject
|
||||
public IndexCollector(Settings settings, ClusterService clusterService, ClusterName clusterName, Client client) {
|
||||
super(settings, NAME, clusterService);
|
||||
this.client = client;
|
||||
this.clusterName = clusterName;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean masterOnly() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<MarvelDoc> doCollect() throws Exception {
|
||||
ImmutableList.Builder<MarvelDoc> results = ImmutableList.builder();
|
||||
|
||||
IndicesStatsResponse indicesStats = client.admin().indices().prepareStats().all()
|
||||
.setStore(true)
|
||||
.setIndexing(true)
|
||||
.setDocs(true)
|
||||
.get();
|
||||
|
||||
long timestamp = System.currentTimeMillis();
|
||||
for (IndexStats indexStats : indicesStats.getIndices().values()) {
|
||||
results.add(buildMarvelDoc(clusterName.value(), TYPE, timestamp, indexStats));
|
||||
}
|
||||
return results.build();
|
||||
}
|
||||
|
||||
protected MarvelDoc buildMarvelDoc(String clusterName, String type, long timestamp, IndexStats indexStats) {
|
||||
return IndexMarvelDoc.createMarvelDoc(clusterName, type, timestamp,
|
||||
indexStats.getIndex(),
|
||||
indexStats.getTotal().getDocs().getCount(),
|
||||
indexStats.getTotal().getStore().sizeInBytes(), indexStats.getTotal().getStore().throttleTime().millis(),
|
||||
indexStats.getTotal().getIndexing().getTotal().getThrottleTimeInMillis());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* 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.marvel.agent.collector.indices;
|
||||
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilderString;
|
||||
import org.elasticsearch.marvel.agent.exporter.MarvelDoc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class IndexMarvelDoc extends MarvelDoc<IndexMarvelDoc> {
|
||||
|
||||
private final String index;
|
||||
private final Docs docs;
|
||||
private final Store store;
|
||||
private final Indexing indexing;
|
||||
|
||||
public IndexMarvelDoc(String clusterName, String type, long timestamp,
|
||||
String index, Docs docs, Store store, Indexing indexing) {
|
||||
super(clusterName, type, timestamp);
|
||||
this.index = index;
|
||||
this.docs = docs;
|
||||
this.store = store;
|
||||
this.indexing = indexing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexMarvelDoc payload() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public Docs getDocs() {
|
||||
return docs;
|
||||
}
|
||||
|
||||
public Store getStore() {
|
||||
return store;
|
||||
}
|
||||
|
||||
public Indexing getIndexing() {
|
||||
return indexing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject();
|
||||
super.toXContent(builder, params);
|
||||
builder.field(Fields.INDEX, index);
|
||||
if (docs != null) {
|
||||
docs.toXContent(builder, params);
|
||||
}
|
||||
if (store != null) {
|
||||
store.toXContent(builder, params);
|
||||
}
|
||||
if (indexing != null) {
|
||||
indexing.toXContent(builder, params);
|
||||
}
|
||||
builder.endObject();
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static IndexMarvelDoc createMarvelDoc(String clusterName, String type, long timestamp,
|
||||
String index, long docsCount, long storeSizeInBytes, long storeThrottleTimeInMillis, long indexingThrottleTimeInMillis) {
|
||||
return new IndexMarvelDoc(clusterName, type, timestamp, index,
|
||||
new Docs(docsCount),
|
||||
new Store(storeSizeInBytes, storeThrottleTimeInMillis),
|
||||
new Indexing(indexingThrottleTimeInMillis));
|
||||
}
|
||||
|
||||
static final class Fields {
|
||||
static final XContentBuilderString INDEX = new XContentBuilderString("index");
|
||||
}
|
||||
|
||||
static class Docs implements ToXContent {
|
||||
|
||||
private final long count;
|
||||
|
||||
Docs(long count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public long getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject(Fields.DOCS);
|
||||
builder.field(Fields.COUNT, count);
|
||||
builder.endObject();
|
||||
return builder;
|
||||
}
|
||||
|
||||
static final class Fields {
|
||||
static final XContentBuilderString DOCS = new XContentBuilderString("docs");
|
||||
static final XContentBuilderString COUNT = new XContentBuilderString("count");
|
||||
}
|
||||
}
|
||||
|
||||
static class Store implements ToXContent {
|
||||
|
||||
private final long sizeInBytes;
|
||||
private final long throttleTimeInMillis;
|
||||
|
||||
public Store(long sizeInBytes, long throttleTimeInMillis) {
|
||||
this.sizeInBytes = sizeInBytes;
|
||||
this.throttleTimeInMillis = throttleTimeInMillis;
|
||||
}
|
||||
|
||||
public long getSizeInBytes() {
|
||||
return sizeInBytes;
|
||||
}
|
||||
|
||||
public long getThrottleTimeInMillis() {
|
||||
return throttleTimeInMillis;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject(Fields.STORE);
|
||||
builder.field(Fields.SIZE_IN_BYTES, sizeInBytes);
|
||||
builder.timeValueField(Fields.THROTTLE_TIME_IN_MILLIS, Fields.THROTTLE_TIME, new TimeValue(throttleTimeInMillis, TimeUnit.MILLISECONDS));
|
||||
builder.endObject();
|
||||
return builder;
|
||||
}
|
||||
|
||||
static final class Fields {
|
||||
static final XContentBuilderString STORE = new XContentBuilderString("store");
|
||||
static final XContentBuilderString SIZE_IN_BYTES = new XContentBuilderString("size_in_bytes");
|
||||
static final XContentBuilderString THROTTLE_TIME = new XContentBuilderString("throttle_time");
|
||||
static final XContentBuilderString THROTTLE_TIME_IN_MILLIS = new XContentBuilderString("throttle_time_in_millis");
|
||||
}
|
||||
}
|
||||
|
||||
static class Indexing implements ToXContent {
|
||||
|
||||
private final long throttleTimeInMillis;
|
||||
|
||||
public Indexing(long throttleTimeInMillis) {
|
||||
this.throttleTimeInMillis = throttleTimeInMillis;
|
||||
}
|
||||
|
||||
public long getThrottleTimeInMillis() {
|
||||
return throttleTimeInMillis;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject(Fields.INDEXING);
|
||||
builder.timeValueField(Fields.THROTTLE_TIME_IN_MILLIS, Fields.THROTTLE_TIME, new TimeValue(throttleTimeInMillis, TimeUnit.MILLISECONDS));
|
||||
builder.endObject();
|
||||
return builder;
|
||||
}
|
||||
|
||||
static final class Fields {
|
||||
static final XContentBuilderString INDEXING = new XContentBuilderString("indexing");
|
||||
static final XContentBuilderString THROTTLE_TIME = new XContentBuilderString("throttle_time");
|
||||
static final XContentBuilderString THROTTLE_TIME_IN_MILLIS = new XContentBuilderString("throttle_time_in_millis");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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.marvel.agent.exporter;
|
||||
|
||||
|
||||
import org.elasticsearch.cluster.ClusterService;
|
||||
import org.elasticsearch.common.component.AbstractLifecycleComponent;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public abstract class AbstractExporter<T> extends AbstractLifecycleComponent<T> implements Exporter<T> {
|
||||
|
||||
private final String name;
|
||||
|
||||
protected final ClusterService clusterService;
|
||||
|
||||
@Inject
|
||||
public AbstractExporter(Settings settings, String name, ClusterService clusterService) {
|
||||
super(settings);
|
||||
this.name = name;
|
||||
this.clusterService = clusterService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T start() {
|
||||
logger.debug("starting exporter [{}]", name());
|
||||
return super.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStart() {
|
||||
}
|
||||
|
||||
protected boolean masterOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void export(Collection<MarvelDoc> marvelDocs) {
|
||||
if (masterOnly() && !clusterService.state().nodes().localNodeMaster()) {
|
||||
logger.trace("exporter [{}] runs on master only", name());
|
||||
return;
|
||||
}
|
||||
|
||||
if (marvelDocs == null) {
|
||||
logger.debug("no objects to export for [{}]", name());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
doExport(marvelDocs);
|
||||
} catch (Exception e) {
|
||||
logger.error("export [{}] throws exception when exporting data", e, name());
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void doExport(Collection<MarvelDoc> marvelDocs) throws Exception;
|
||||
|
||||
@Override
|
||||
public T stop() {
|
||||
logger.debug("stopping exporter [{}]", name());
|
||||
return super.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStop() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
logger.trace("closing exporter [{}]", name());
|
||||
super.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doClose() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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.marvel.agent.exporter;
|
||||
|
||||
import org.elasticsearch.common.component.LifecycleComponent;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface Exporter<T> extends LifecycleComponent<T> {
|
||||
|
||||
String name();
|
||||
|
||||
void export(Collection<MarvelDoc> marvelDocs);
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.marvel.agent.exporter;
|
||||
|
||||
import org.elasticsearch.common.inject.AbstractModule;
|
||||
import org.elasticsearch.common.inject.multibindings.Multibinder;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class ExporterModule extends AbstractModule {
|
||||
|
||||
private final Set<Class<? extends Exporter>> exporters = new HashSet<>();
|
||||
|
||||
public ExporterModule() {
|
||||
// Registers default exporter
|
||||
registerExporter(HttpESExporter.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
Multibinder<Exporter> binder = Multibinder.newSetBinder(binder(), Exporter.class);
|
||||
for (Class<? extends Exporter> exporter : exporters) {
|
||||
bind(exporter).asEagerSingleton();
|
||||
binder.addBinding().to(exporter);
|
||||
}
|
||||
}
|
||||
|
||||
public void registerExporter(Class<? extends Exporter> exporter) {
|
||||
exporters.add(exporter);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,652 @@
|
|||
/*
|
||||
* 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.marvel.agent.exporter;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
import org.elasticsearch.ExceptionsHelper;
|
||||
import org.elasticsearch.cluster.ClusterName;
|
||||
import org.elasticsearch.cluster.ClusterService;
|
||||
import org.elasticsearch.cluster.settings.ClusterDynamicSettings;
|
||||
import org.elasticsearch.cluster.settings.DynamicSettings;
|
||||
import org.elasticsearch.common.Base64;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.component.Lifecycle;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.io.Streams;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.transport.BoundTransportAddress;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.common.util.concurrent.EsExecutors;
|
||||
import org.elasticsearch.common.xcontent.*;
|
||||
import org.elasticsearch.common.xcontent.smile.SmileXContent;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.http.HttpServer;
|
||||
import org.elasticsearch.marvel.agent.support.AgentUtils;
|
||||
import org.elasticsearch.node.Node;
|
||||
import org.elasticsearch.node.service.NodeService;
|
||||
import org.elasticsearch.node.settings.NodeSettingsService;
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.KeyStore;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
public class HttpESExporter extends AbstractExporter<HttpESExporter> implements NodeSettingsService.Listener {
|
||||
|
||||
private static final String NAME = "es_exporter";
|
||||
|
||||
private static final String SETTINGS_PREFIX = "marvel.agent.exporter.es.";
|
||||
public static final String SETTINGS_HOSTS = SETTINGS_PREFIX + "hosts";
|
||||
public static final String SETTINGS_INDEX_PREFIX = SETTINGS_PREFIX + "index.prefix";
|
||||
public static final String SETTINGS_INDEX_TIME_FORMAT = SETTINGS_PREFIX + "index.timeformat";
|
||||
public static final String SETTINGS_TIMEOUT = SETTINGS_PREFIX + "timeout";
|
||||
public static final String SETTINGS_READ_TIMEOUT = SETTINGS_PREFIX + "read_timeout";
|
||||
|
||||
// es level timeout used when checking and writing templates (used to speed up tests)
|
||||
public static final String SETTINGS_CHECK_TEMPLATE_TIMEOUT = SETTINGS_PREFIX + ".template.master_timeout";
|
||||
|
||||
// es level timeout used for bulk indexing (used to speed up tests)
|
||||
public static final String SETTINGS_BULK_TIMEOUT = SETTINGS_PREFIX + ".bulk.timeout";
|
||||
|
||||
volatile String[] hosts;
|
||||
volatile boolean boundToLocalNode = false;
|
||||
final String indexPrefix;
|
||||
final DateTimeFormatter indexTimeFormatter;
|
||||
volatile int timeoutInMillis;
|
||||
volatile int readTimeoutInMillis;
|
||||
|
||||
|
||||
/** https support * */
|
||||
final SSLSocketFactory sslSocketFactory;
|
||||
volatile boolean hostnameVerification;
|
||||
|
||||
final ClusterService clusterService;
|
||||
final ClusterName clusterName;
|
||||
final NodeService nodeService;
|
||||
final Environment environment;
|
||||
|
||||
HttpServer httpServer;
|
||||
final boolean httpEnabled;
|
||||
|
||||
@Nullable
|
||||
final TimeValue templateCheckTimeout;
|
||||
@Nullable
|
||||
final TimeValue bulkTimeout;
|
||||
|
||||
volatile boolean checkedAndUploadedIndexTemplate = false;
|
||||
|
||||
final ConnectionKeepAliveWorker keepAliveWorker;
|
||||
Thread keepAliveThread;
|
||||
|
||||
@Inject
|
||||
public HttpESExporter(Settings settings, ClusterService clusterService, ClusterName clusterName,
|
||||
@ClusterDynamicSettings DynamicSettings dynamicSettings,
|
||||
NodeSettingsService nodeSettingsService,
|
||||
NodeService nodeService, Environment environment) {
|
||||
super(settings, NAME, clusterService);
|
||||
|
||||
this.clusterService = clusterService;
|
||||
|
||||
this.clusterName = clusterName;
|
||||
this.nodeService = nodeService;
|
||||
this.environment = environment;
|
||||
|
||||
httpEnabled = settings.getAsBoolean(Node.HTTP_ENABLED, true);
|
||||
|
||||
hosts = settings.getAsArray(SETTINGS_HOSTS, Strings.EMPTY_ARRAY);
|
||||
|
||||
validateHosts(hosts);
|
||||
|
||||
indexPrefix = settings.get(SETTINGS_INDEX_PREFIX, ".marvel");
|
||||
String indexTimeFormat = settings.get(SETTINGS_INDEX_TIME_FORMAT, "YYYY.MM.dd");
|
||||
indexTimeFormatter = DateTimeFormat.forPattern(indexTimeFormat).withZoneUTC();
|
||||
|
||||
timeoutInMillis = (int) settings.getAsTime(SETTINGS_TIMEOUT, new TimeValue(6000)).millis();
|
||||
readTimeoutInMillis = (int) settings.getAsTime(SETTINGS_READ_TIMEOUT, new TimeValue(timeoutInMillis * 10)).millis();
|
||||
|
||||
templateCheckTimeout = settings.getAsTime(SETTINGS_CHECK_TEMPLATE_TIMEOUT, null);
|
||||
bulkTimeout = settings.getAsTime(SETTINGS_CHECK_TEMPLATE_TIMEOUT, null);
|
||||
|
||||
keepAliveWorker = new ConnectionKeepAliveWorker();
|
||||
|
||||
dynamicSettings.addDynamicSetting(SETTINGS_HOSTS);
|
||||
dynamicSettings.addDynamicSetting(SETTINGS_HOSTS + ".*");
|
||||
dynamicSettings.addDynamicSetting(SETTINGS_TIMEOUT);
|
||||
dynamicSettings.addDynamicSetting(SETTINGS_READ_TIMEOUT);
|
||||
dynamicSettings.addDynamicSetting(SETTINGS_SSL_HOSTNAME_VERIFICATION);
|
||||
nodeSettingsService.addListener(this);
|
||||
|
||||
if (!settings.getByPrefix(SETTINGS_SSL_PREFIX).getAsMap().isEmpty()) {
|
||||
sslSocketFactory = createSSLSocketFactory(settings);
|
||||
} else {
|
||||
logger.trace("no ssl context configured");
|
||||
sslSocketFactory = null;
|
||||
}
|
||||
hostnameVerification = settings.getAsBoolean(SETTINGS_SSL_HOSTNAME_VERIFICATION, true);
|
||||
|
||||
logger.debug("initialized with targets: {}, index prefix [{}], index time format [{}]",
|
||||
AgentUtils.santizeUrlPwds(Strings.arrayToCommaDelimitedString(hosts)), indexPrefix, indexTimeFormat);
|
||||
}
|
||||
|
||||
static private void validateHosts(String[] hosts) {
|
||||
for (String host : hosts) {
|
||||
try {
|
||||
AgentUtils.parseHostWithPath(host, "");
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException("[marvel.agent.exporter] invalid host: [" + AgentUtils.santizeUrlPwds(host) + "]." +
|
||||
" error: [" + AgentUtils.santizeUrlPwds(e.getMessage()) + "]");
|
||||
} catch (MalformedURLException e) {
|
||||
throw new RuntimeException("[marvel.agent.exporter] invalid host: [" + AgentUtils.santizeUrlPwds(host) + "]." +
|
||||
" error: [" + AgentUtils.santizeUrlPwds(e.getMessage()) + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Inject(optional = true)
|
||||
public void setHttpServer(HttpServer httpServer) {
|
||||
this.httpServer = httpServer;
|
||||
}
|
||||
|
||||
private HttpURLConnection openExportingConnection() {
|
||||
logger.trace("setting up an export connection");
|
||||
String queryString = "";
|
||||
if (bulkTimeout != null) {
|
||||
queryString = "?master_timeout=" + bulkTimeout;
|
||||
}
|
||||
HttpURLConnection conn = openAndValidateConnection("POST", getIndexName() + "/_bulk" + queryString, XContentType.SMILE.restContentType());
|
||||
if (conn != null && (keepAliveThread == null || !keepAliveThread.isAlive())) {
|
||||
// start keep alive upon successful connection if not there.
|
||||
initKeepAliveThread();
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
|
||||
private void addMarvelDocToConnection(HttpURLConnection conn,
|
||||
MarvelDoc marvelDoc) throws IOException {
|
||||
OutputStream os = conn.getOutputStream();
|
||||
// TODO: find a way to disable builder's substream flushing or something neat solution
|
||||
XContentBuilder builder = XContentFactory.smileBuilder();
|
||||
builder.startObject().startObject("index")
|
||||
.field("_type", marvelDoc.type())
|
||||
.endObject().endObject();
|
||||
builder.close();
|
||||
builder.bytes().writeTo(os);
|
||||
os.write(SmileXContent.smileXContent.streamSeparator());
|
||||
|
||||
builder = XContentFactory.smileBuilder();
|
||||
builder.humanReadable(false);
|
||||
marvelDoc.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||
builder.close();
|
||||
builder.bytes().writeTo(os);
|
||||
os.write(SmileXContent.smileXContent.streamSeparator());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void sendCloseExportingConnection(HttpURLConnection conn) throws IOException {
|
||||
logger.trace("sending content");
|
||||
OutputStream os = conn.getOutputStream();
|
||||
os.close();
|
||||
if (conn.getResponseCode() != 200) {
|
||||
logConnectionError("remote target didn't respond with 200 OK", conn);
|
||||
return;
|
||||
}
|
||||
|
||||
InputStream inputStream = conn.getInputStream();
|
||||
try (XContentParser parser = XContentType.SMILE.xContent().createParser(inputStream)) {
|
||||
Map<String, Object> response = parser.map();
|
||||
if (response.get("items") != null) {
|
||||
ArrayList<Object> list = (ArrayList<Object>) response.get("items");
|
||||
for (Object itemObject : list) {
|
||||
Map<String, Object> actions = (Map<String, Object>) itemObject;
|
||||
for (String actionKey : actions.keySet()) {
|
||||
Map<String, Object> action = (Map<String, Object>) actions.get(actionKey);
|
||||
if (action.get("error") != null) {
|
||||
logger.error("{} failure (index:[{}] type: [{}]): {}", actionKey, action.get("_index"), action.get("_type"), action.get("error"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doExport(Collection<MarvelDoc> marvelDocs) throws Exception {
|
||||
HttpURLConnection conn = openExportingConnection();
|
||||
if (conn == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
for (MarvelDoc marvelDoc : marvelDocs) {
|
||||
addMarvelDocToConnection(conn, marvelDoc);
|
||||
}
|
||||
sendCloseExportingConnection(conn);
|
||||
} catch (IOException e) {
|
||||
logger.error("error sending data to [{}]: {}", AgentUtils.santizeUrlPwds(conn.getURL()), AgentUtils.santizeUrlPwds(ExceptionsHelper.detailedMessage(e)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStart() {
|
||||
// not initializing keep alive worker here but rather upon first exporting.
|
||||
// In the case we are sending metrics to the same ES as where the plugin is hosted
|
||||
// we want to give it some time to start.
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void doStop() {
|
||||
if (keepAliveThread != null && keepAliveThread.isAlive()) {
|
||||
keepAliveWorker.closed = true;
|
||||
keepAliveThread.interrupt();
|
||||
try {
|
||||
keepAliveThread.join(6000);
|
||||
} catch (InterruptedException e) {
|
||||
// don't care.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doClose() {
|
||||
}
|
||||
|
||||
// used for testing
|
||||
String[] getHosts() {
|
||||
return hosts;
|
||||
}
|
||||
|
||||
private String getIndexName() {
|
||||
return indexPrefix + "-" + indexTimeFormatter.print(System.currentTimeMillis());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* open a connection to any host, validating it has the template installed if needed
|
||||
*
|
||||
* @return a url connection to the selected host or null if no current host is available.
|
||||
*/
|
||||
private HttpURLConnection openAndValidateConnection(String method, String path) {
|
||||
return openAndValidateConnection(method, path, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* open a connection to any host, validating it has the template installed if needed
|
||||
*
|
||||
* @return a url connection to the selected host or null if no current host is available.
|
||||
*/
|
||||
private HttpURLConnection openAndValidateConnection(String method, String path, String contentType) {
|
||||
if (hosts.length == 0) {
|
||||
// Due to how Guice injection works and because HttpServer can be optional,
|
||||
// we can't be 100% sure that the HttpServer is created when the ESExporter
|
||||
// instance is created. This is specially true in integration tests.
|
||||
// So if HttpServer is enabled in settings we can safely use the NodeService
|
||||
// to retrieve the bound address.
|
||||
BoundTransportAddress boundAddress = null;
|
||||
if (httpEnabled) {
|
||||
if ((httpServer != null) && (httpServer.lifecycleState() == Lifecycle.State.STARTED)) {
|
||||
logger.debug("deriving host setting from httpServer");
|
||||
boundAddress = httpServer.info().address();
|
||||
} else if (nodeService.info().getHttp() != null) {
|
||||
logger.debug("deriving host setting from node info API");
|
||||
boundAddress = nodeService.info().getHttp().address();
|
||||
}
|
||||
} else {
|
||||
logger.warn("http server is not enabled no hosts are manually configured");
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] extractedHosts = AgentUtils.extractHostsFromAddress(boundAddress, logger);
|
||||
if (extractedHosts == null || extractedHosts.length == 0) {
|
||||
return null;
|
||||
}
|
||||
hosts = extractedHosts;
|
||||
logger.trace("auto-resolved hosts to {}", extractedHosts);
|
||||
boundToLocalNode = true;
|
||||
}
|
||||
|
||||
// it's important to have boundToLocalNode persistent to prevent calls during shutdown (causing ugly exceptions)
|
||||
if (boundToLocalNode && (httpServer != null) && (httpServer.lifecycleState() != Lifecycle.State.STARTED)) {
|
||||
logger.debug("local node http server is not started. can't connect");
|
||||
return null;
|
||||
}
|
||||
|
||||
// out of for to move faulty hosts to the end
|
||||
int hostIndex = 0;
|
||||
try {
|
||||
for (; hostIndex < hosts.length; hostIndex++) {
|
||||
String host = hosts[hostIndex];
|
||||
if (!checkedAndUploadedIndexTemplate) {
|
||||
// check templates first on the host
|
||||
checkedAndUploadedIndexTemplate = checkAndUploadIndexTemplate(host);
|
||||
if (!checkedAndUploadedIndexTemplate) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
HttpURLConnection connection = openConnection(host, method, path, contentType);
|
||||
if (connection != null) {
|
||||
return connection;
|
||||
}
|
||||
// failed hosts - reset template check , someone may have restarted the target cluster and deleted
|
||||
// it's data folder. be safe.
|
||||
checkedAndUploadedIndexTemplate = false;
|
||||
}
|
||||
} finally {
|
||||
if (hostIndex > 0 && hostIndex < hosts.length) {
|
||||
logger.debug("moving [{}] failed hosts to the end of the list", hostIndex);
|
||||
String[] newHosts = new String[hosts.length];
|
||||
System.arraycopy(hosts, hostIndex, newHosts, 0, hosts.length - hostIndex);
|
||||
System.arraycopy(hosts, 0, newHosts, hosts.length - hostIndex, hostIndex);
|
||||
hosts = newHosts;
|
||||
logger.debug("preferred target host is now [{}]", AgentUtils.santizeUrlPwds(hosts[0]));
|
||||
}
|
||||
}
|
||||
|
||||
logger.error("could not connect to any configured elasticsearch instances: [{}]", AgentUtils.santizeUrlPwds(Strings.arrayToCommaDelimitedString(hosts)));
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/** open a connection to the given hosts, returning null when not successful * */
|
||||
private HttpURLConnection openConnection(String host, String method, String path, @Nullable String contentType) {
|
||||
try {
|
||||
final URL url = AgentUtils.parseHostWithPath(host, path);
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
|
||||
if (conn instanceof HttpsURLConnection && sslSocketFactory != null) {
|
||||
HttpsURLConnection httpsConn = (HttpsURLConnection) conn;
|
||||
httpsConn.setSSLSocketFactory(sslSocketFactory);
|
||||
if (!hostnameVerification) {
|
||||
httpsConn.setHostnameVerifier(TrustAllHostnameVerifier.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
conn.setRequestMethod(method);
|
||||
conn.setConnectTimeout(timeoutInMillis);
|
||||
conn.setReadTimeout(readTimeoutInMillis);
|
||||
if (contentType != null) {
|
||||
conn.setRequestProperty("Content-Type", contentType);
|
||||
}
|
||||
if (url.getUserInfo() != null) {
|
||||
String basicAuth = "Basic " + Base64.encodeBytes(url.getUserInfo().getBytes("ISO-8859-1"));
|
||||
conn.setRequestProperty("Authorization", basicAuth);
|
||||
}
|
||||
conn.setUseCaches(false);
|
||||
if (method.equalsIgnoreCase("POST") || method.equalsIgnoreCase("PUT")) {
|
||||
conn.setDoOutput(true);
|
||||
}
|
||||
conn.connect();
|
||||
|
||||
return conn;
|
||||
} catch (URISyntaxException e) {
|
||||
logErrorBasedOnLevel(e, "error parsing host [{}]", AgentUtils.santizeUrlPwds(host));
|
||||
} catch (IOException e) {
|
||||
logErrorBasedOnLevel(e, "error connecting to [{}]", AgentUtils.santizeUrlPwds(host));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void logErrorBasedOnLevel(Throwable t, String msg, Object... params) {
|
||||
logger.error(msg + " [" + AgentUtils.santizeUrlPwds(t.getMessage()) + "]", params);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(msg + ". full error details:\n[{}]", params, AgentUtils.santizeUrlPwds(ExceptionsHelper.detailedMessage(t)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the index templates already exist and if not uploads it
|
||||
* Any critical error that should prevent data exporting is communicated via an exception.
|
||||
*
|
||||
* @return true if template exists or was uploaded successfully.
|
||||
*/
|
||||
private boolean checkAndUploadIndexTemplate(final String host) {
|
||||
byte[] template;
|
||||
try {
|
||||
template = Streams.copyToBytesFromClasspath("/marvel_index_template.json");
|
||||
} catch (IOException e) {
|
||||
// throwing an exception to stop exporting process - we don't want to send data unless
|
||||
// we put in the template for it.
|
||||
throw new RuntimeException("failed to load marvel_index_template.json", e);
|
||||
}
|
||||
|
||||
try {
|
||||
int expectedVersion = AgentUtils.parseIndexVersionFromTemplate(template);
|
||||
if (expectedVersion < 0) {
|
||||
throw new RuntimeException("failed to find an index version in pre-configured index template");
|
||||
}
|
||||
|
||||
String queryString = "";
|
||||
if (templateCheckTimeout != null) {
|
||||
queryString = "?timeout=" + templateCheckTimeout;
|
||||
}
|
||||
HttpURLConnection conn = openConnection(host, "GET", "_template/marvel" + queryString, null);
|
||||
if (conn == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean hasTemplate = false;
|
||||
if (conn.getResponseCode() == 200) {
|
||||
// verify content.
|
||||
InputStream is = conn.getInputStream();
|
||||
byte[] existingTemplate = ByteStreams.toByteArray(is);
|
||||
is.close();
|
||||
int foundVersion = AgentUtils.parseIndexVersionFromTemplate(existingTemplate);
|
||||
if (foundVersion < 0) {
|
||||
logger.warn("found an existing index template but couldn't extract it's version. leaving it as is.");
|
||||
hasTemplate = true;
|
||||
} else if (foundVersion >= expectedVersion) {
|
||||
logger.debug("accepting existing index template (version [{}], needed [{}])", foundVersion, expectedVersion);
|
||||
hasTemplate = true;
|
||||
} else {
|
||||
logger.debug("replacing existing index template (version [{}], needed [{}])", foundVersion, expectedVersion);
|
||||
}
|
||||
}
|
||||
// nothing there, lets create it
|
||||
if (!hasTemplate) {
|
||||
logger.debug("uploading index template");
|
||||
conn = openConnection(host, "PUT", "_template/marvel" + queryString, XContentType.JSON.restContentType());
|
||||
OutputStream os = conn.getOutputStream();
|
||||
Streams.copy(template, os);
|
||||
if (!(conn.getResponseCode() == 200 || conn.getResponseCode() == 201)) {
|
||||
logConnectionError("error adding the marvel template to [" + host + "]", conn);
|
||||
} else {
|
||||
hasTemplate = true;
|
||||
}
|
||||
conn.getInputStream().close(); // close and release to connection pool.
|
||||
}
|
||||
|
||||
return hasTemplate;
|
||||
} catch (IOException e) {
|
||||
logger.error("failed to verify/upload the marvel template to [{}]:\n{}", AgentUtils.santizeUrlPwds(host), AgentUtils.santizeUrlPwds(e.getMessage()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void logConnectionError(String msg, HttpURLConnection conn) {
|
||||
InputStream inputStream = conn.getErrorStream();
|
||||
String err = "";
|
||||
if (inputStream != null) {
|
||||
java.util.Scanner s = new java.util.Scanner(inputStream, "UTF-8").useDelimiter("\\A");
|
||||
err = s.hasNext() ? s.next() : "";
|
||||
}
|
||||
|
||||
try {
|
||||
logger.error("{} response code [{} {}]. content: [{}]",
|
||||
AgentUtils.santizeUrlPwds(msg), conn.getResponseCode(),
|
||||
AgentUtils.santizeUrlPwds(conn.getResponseMessage()),
|
||||
AgentUtils.santizeUrlPwds(err));
|
||||
} catch (IOException e) {
|
||||
logger.error("{}. connection had an error while reporting the error. tough life.", AgentUtils.santizeUrlPwds(msg));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRefreshSettings(Settings settings) {
|
||||
TimeValue newTimeout = settings.getAsTime(SETTINGS_TIMEOUT, null);
|
||||
if (newTimeout != null) {
|
||||
logger.info("connection timeout set to [{}]", newTimeout);
|
||||
timeoutInMillis = (int) newTimeout.millis();
|
||||
}
|
||||
|
||||
newTimeout = settings.getAsTime(SETTINGS_READ_TIMEOUT, null);
|
||||
if (newTimeout != null) {
|
||||
logger.info("connection read timeout set to [{}]", newTimeout);
|
||||
readTimeoutInMillis = (int) newTimeout.millis();
|
||||
}
|
||||
|
||||
String[] newHosts = settings.getAsArray(SETTINGS_HOSTS, null);
|
||||
if (newHosts != null) {
|
||||
logger.info("hosts set to [{}]", AgentUtils.santizeUrlPwds(Strings.arrayToCommaDelimitedString(newHosts)));
|
||||
this.hosts = newHosts;
|
||||
this.checkedAndUploadedIndexTemplate = false;
|
||||
this.boundToLocalNode = false;
|
||||
}
|
||||
|
||||
Boolean newHostnameVerification = settings.getAsBoolean(SETTINGS_SSL_HOSTNAME_VERIFICATION, null);
|
||||
if (newHostnameVerification != null) {
|
||||
logger.info("hostname verification set to [{}]", newHostnameVerification);
|
||||
this.hostnameVerification = newHostnameVerification;
|
||||
}
|
||||
}
|
||||
|
||||
protected void initKeepAliveThread() {
|
||||
keepAliveThread = new Thread(keepAliveWorker, EsExecutors.threadName(settings, "keep_alive"));
|
||||
keepAliveThread.setDaemon(true);
|
||||
keepAliveThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sadly we need to make sure we keep the connection open to the target ES a
|
||||
* Java's connection pooling closes connections if idle for 5sec.
|
||||
*/
|
||||
class ConnectionKeepAliveWorker implements Runnable {
|
||||
volatile boolean closed = false;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
logger.trace("starting keep alive thread");
|
||||
while (!closed) {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
String[] currentHosts = hosts;
|
||||
if (currentHosts.length == 0) {
|
||||
logger.trace("keep alive thread shutting down. no hosts defined");
|
||||
return; // no hosts configured at the moment.
|
||||
}
|
||||
HttpURLConnection conn = openConnection(currentHosts[0], "GET", "", null);
|
||||
if (conn == null) {
|
||||
logger.trace("keep alive thread shutting down. failed to open connection to current host [{}]", AgentUtils.santizeUrlPwds(currentHosts[0]));
|
||||
return;
|
||||
} else {
|
||||
conn.getInputStream().close(); // close and release to connection pool.
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// ignore, if closed, good....
|
||||
} catch (Throwable t) {
|
||||
logger.debug("error in keep alive thread, shutting down (will be restarted after a successful connection has been made) {}",
|
||||
AgentUtils.santizeUrlPwds(ExceptionsHelper.detailedMessage(t)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final String SETTINGS_SSL_PREFIX = SETTINGS_PREFIX + "ssl.";
|
||||
|
||||
public static final String SETTINGS_SSL_PROTOCOL = SETTINGS_SSL_PREFIX + "protocol";
|
||||
public static final String SETTINGS_SSL_TRUSTSTORE = SETTINGS_SSL_PREFIX + "truststore.path";
|
||||
public static final String SETTINGS_SSL_TRUSTSTORE_PASSWORD = SETTINGS_SSL_PREFIX + "truststore.password";
|
||||
public static final String SETTINGS_SSL_TRUSTSTORE_ALGORITHM = SETTINGS_SSL_PREFIX + "truststore.algorithm";
|
||||
public static final String SETTINGS_SSL_HOSTNAME_VERIFICATION = SETTINGS_SSL_PREFIX + "hostname_verification";
|
||||
|
||||
/** SSL Initialization * */
|
||||
public SSLSocketFactory createSSLSocketFactory(Settings settings) {
|
||||
SSLContext sslContext;
|
||||
// Initialize sslContext
|
||||
try {
|
||||
String sslContextProtocol = settings.get(SETTINGS_SSL_PROTOCOL, "TLS");
|
||||
String trustStore = settings.get(SETTINGS_SSL_TRUSTSTORE, System.getProperty("javax.net.ssl.trustStore"));
|
||||
String trustStorePassword = settings.get(SETTINGS_SSL_TRUSTSTORE_PASSWORD, System.getProperty("javax.net.ssl.trustStorePassword"));
|
||||
String trustStoreAlgorithm = settings.get(SETTINGS_SSL_TRUSTSTORE_ALGORITHM, System.getProperty("ssl.TrustManagerFactory.algorithm"));
|
||||
|
||||
if (trustStore == null) {
|
||||
throw new RuntimeException("truststore is not configured, use " + SETTINGS_SSL_TRUSTSTORE);
|
||||
}
|
||||
|
||||
if (trustStoreAlgorithm == null) {
|
||||
trustStoreAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
|
||||
}
|
||||
|
||||
logger.debug("SSL: using trustStore[{}], trustAlgorithm[{}]", trustStore, trustStoreAlgorithm);
|
||||
|
||||
Path trustStorePath = environment.configFile().resolve(trustStore);
|
||||
if (!Files.exists(trustStorePath)) {
|
||||
throw new FileNotFoundException("Truststore at path [" + trustStorePath + "] does not exist");
|
||||
}
|
||||
|
||||
TrustManager[] trustManagers;
|
||||
try (InputStream trustStoreStream = Files.newInputStream(trustStorePath)) {
|
||||
// Load TrustStore
|
||||
KeyStore ks = KeyStore.getInstance("jks");
|
||||
ks.load(trustStoreStream, trustStorePassword == null ? null : trustStorePassword.toCharArray());
|
||||
|
||||
// Initialize a trust manager factory with the trusted store
|
||||
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(trustStoreAlgorithm);
|
||||
trustFactory.init(ks);
|
||||
|
||||
// Retrieve the trust managers from the factory
|
||||
trustManagers = trustFactory.getTrustManagers();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to initialize a TrustManagerFactory", e);
|
||||
}
|
||||
|
||||
sslContext = SSLContext.getInstance(sslContextProtocol);
|
||||
sslContext.init(null, trustManagers, null);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("[marvel.agent.exporter] failed to initialize the SSLContext", e);
|
||||
}
|
||||
return sslContext.getSocketFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trust all hostname verifier. This simply returns true to completely disable hostname verification
|
||||
*/
|
||||
static class TrustAllHostnameVerifier implements HostnameVerifier {
|
||||
static final HostnameVerifier INSTANCE = new TrustAllHostnameVerifier();
|
||||
|
||||
private TrustAllHostnameVerifier() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String s, SSLSession sslSession) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.marvel.agent.exporter;
|
||||
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilderString;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public abstract class MarvelDoc<T> implements ToXContent {
|
||||
|
||||
private final String clusterName;
|
||||
private final String type;
|
||||
private final long timestamp;
|
||||
|
||||
public MarvelDoc(String clusterName, String type, long timestamp) {
|
||||
this.clusterName = clusterName;
|
||||
this.type = type;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String clusterName() {
|
||||
return clusterName;
|
||||
}
|
||||
|
||||
public String type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public long timestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public abstract T payload();
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.field(Fields.CLUSTER_NAME, clusterName());
|
||||
DateTime timestampDateTime = new DateTime(timestamp(), DateTimeZone.UTC);
|
||||
builder.field(Fields.TIMESTAMP, timestampDateTime.toString());
|
||||
return builder;
|
||||
}
|
||||
|
||||
static final class Fields {
|
||||
static final XContentBuilderString CLUSTER_NAME = new XContentBuilderString("cluster_name");
|
||||
static final XContentBuilderString TIMESTAMP = new XContentBuilderString("timestamp");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* 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.marvel.agent.support;
|
||||
|
||||
import org.elasticsearch.cluster.node.DiscoveryNode;
|
||||
import org.elasticsearch.common.logging.ESLogger;
|
||||
import org.elasticsearch.common.transport.BoundTransportAddress;
|
||||
import org.elasticsearch.common.transport.InetSocketTransportAddress;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.*;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class AgentUtils {
|
||||
|
||||
public static XContentBuilder nodeToXContent(DiscoveryNode node, XContentBuilder builder) throws IOException {
|
||||
return nodeToXContent(node, null, builder);
|
||||
}
|
||||
|
||||
public static XContentBuilder nodeToXContent(DiscoveryNode node, Boolean isMasterNode, XContentBuilder builder) throws IOException {
|
||||
builder.field("id", node.id());
|
||||
builder.field("name", node.name());
|
||||
builder.field("transport_address", node.address());
|
||||
|
||||
if (node.address().uniqueAddressTypeId() == 1) { // InetSocket
|
||||
InetSocketTransportAddress address = (InetSocketTransportAddress) node.address();
|
||||
InetSocketAddress inetSocketAddress = address.address();
|
||||
InetAddress inetAddress = inetSocketAddress.getAddress();
|
||||
if (inetAddress != null) {
|
||||
builder.field("ip", inetAddress.getHostAddress());
|
||||
builder.field("host", inetAddress.getHostName());
|
||||
builder.field("ip_port", inetAddress.getHostAddress() + ":" + inetSocketAddress.getPort());
|
||||
}
|
||||
} else if (node.address().uniqueAddressTypeId() == 2) { // local transport
|
||||
builder.field("ip_port", "_" + node.address()); // will end up being "_local[ID]"
|
||||
}
|
||||
|
||||
builder.field("master_node", node.isMasterNode());
|
||||
builder.field("data_node", node.isDataNode());
|
||||
if (isMasterNode != null) {
|
||||
builder.field("master", isMasterNode.booleanValue());
|
||||
}
|
||||
|
||||
if (!node.attributes().isEmpty()) {
|
||||
builder.startObject("attributes");
|
||||
for (Map.Entry<String, String> attr : node.attributes().entrySet()) {
|
||||
builder.field(attr.getKey(), attr.getValue());
|
||||
}
|
||||
builder.endObject();
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static String nodeDescription(DiscoveryNode node) {
|
||||
StringBuilder builder = new StringBuilder().append("[").append(node.name()).append("]");
|
||||
if (node.address().uniqueAddressTypeId() == 1) { // InetSocket
|
||||
InetSocketTransportAddress address = (InetSocketTransportAddress) node.address();
|
||||
InetSocketAddress inetSocketAddress = address.address();
|
||||
InetAddress inetAddress = inetSocketAddress.getAddress();
|
||||
if (inetAddress != null) {
|
||||
builder.append("[").append(inetAddress.getHostAddress()).append(":").append(inetSocketAddress.getPort()).append("]");
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static String[] extractHostsFromAddress(BoundTransportAddress boundAddress, ESLogger logger) {
|
||||
if (boundAddress == null || boundAddress.boundAddress() == null) {
|
||||
logger.debug("local http server is not yet started. can't connect");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (boundAddress.boundAddress().uniqueAddressTypeId() != 1) {
|
||||
logger.error("local node is not bound via the http transport. can't connect");
|
||||
return null;
|
||||
}
|
||||
InetSocketTransportAddress address = (InetSocketTransportAddress) boundAddress.boundAddress();
|
||||
InetSocketAddress inetSocketAddress = address.address();
|
||||
InetAddress inetAddress = inetSocketAddress.getAddress();
|
||||
if (inetAddress == null) {
|
||||
logger.error("failed to extract the ip address of current node.");
|
||||
return null;
|
||||
}
|
||||
|
||||
String host = inetAddress.getHostAddress();
|
||||
if (host.indexOf(":") >= 0) {
|
||||
// ipv6
|
||||
host = "[" + host + "]";
|
||||
}
|
||||
|
||||
return new String[]{host + ":" + inetSocketAddress.getPort()};
|
||||
}
|
||||
|
||||
public static URL parseHostWithPath(String host, String path) throws URISyntaxException, MalformedURLException {
|
||||
|
||||
if (!host.contains("://")) {
|
||||
// prefix with http
|
||||
host = "http://" + host;
|
||||
}
|
||||
if (!host.endsWith("/")) {
|
||||
// make sure we can safely resolves sub paths and not replace parent folders
|
||||
host = host + "/";
|
||||
}
|
||||
|
||||
URL hostUrl = new URL(host);
|
||||
|
||||
if (hostUrl.getPort() == -1) {
|
||||
// url has no port, default to 9200 - sadly we need to rebuild..
|
||||
StringBuilder newUrl = new StringBuilder(hostUrl.getProtocol() + "://");
|
||||
if (hostUrl.getUserInfo() != null) {
|
||||
newUrl.append(hostUrl.getUserInfo()).append("@");
|
||||
}
|
||||
newUrl.append(hostUrl.getHost()).append(":9200").append(hostUrl.toURI().getPath());
|
||||
|
||||
hostUrl = new URL(newUrl.toString());
|
||||
|
||||
}
|
||||
return new URL(hostUrl, path);
|
||||
|
||||
}
|
||||
|
||||
public static int parseIndexVersionFromTemplate(byte[] template) throws UnsupportedEncodingException {
|
||||
Pattern versionRegex = Pattern.compile("marvel.index_format\"\\s*:\\s*\"?(\\d+)\"?");
|
||||
Matcher matcher = versionRegex.matcher(new String(template, "UTF-8"));
|
||||
if (matcher.find()) {
|
||||
return Integer.parseInt(matcher.group(1));
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String userInfoChars = "\\w-\\._~!$&\\'\\(\\)*+,;=%";
|
||||
private static Pattern urlPwdSanitizer = Pattern.compile("([" + userInfoChars + "]+?):[" + userInfoChars + "]+?@");
|
||||
|
||||
public static String santizeUrlPwds(Object text) {
|
||||
Matcher matcher = urlPwdSanitizer.matcher(text.toString());
|
||||
return matcher.replaceAll("$1:XXXXXX@");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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.marvel.license;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.inject.AbstractModule;
|
||||
import org.elasticsearch.license.plugin.LicenseVersion;
|
||||
import org.elasticsearch.marvel.MarvelVersion;
|
||||
|
||||
public class LicenseModule extends AbstractModule {
|
||||
|
||||
public LicenseModule() {
|
||||
verifyLicensePlugin();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(LicenseService.class).asEagerSingleton();
|
||||
}
|
||||
|
||||
private void verifyLicensePlugin() {
|
||||
try {
|
||||
getClass().getClassLoader().loadClass("org.elasticsearch.license.plugin.LicensePlugin");
|
||||
} catch (ClassNotFoundException cnfe) {
|
||||
throw new IllegalStateException("marvel plugin requires the license plugin to be installed");
|
||||
}
|
||||
|
||||
if (LicenseVersion.CURRENT.before(MarvelVersion.CURRENT.minLicenseCompatibilityVersion)) {
|
||||
throw new ElasticsearchException("marvel [" + MarvelVersion.CURRENT +
|
||||
"] requires minimum license plugin version [" + MarvelVersion.CURRENT.minLicenseCompatibilityVersion +
|
||||
"], but installed license plugin version is [" + LicenseVersion.CURRENT + "]");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* 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.marvel.license;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.component.AbstractLifecycleComponent;
|
||||
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.unit.TimeValue;
|
||||
import org.elasticsearch.license.core.License;
|
||||
import org.elasticsearch.license.plugin.core.LicensesClientService;
|
||||
import org.elasticsearch.license.plugin.core.LicensesService;
|
||||
import org.elasticsearch.marvel.MarvelPlugin;
|
||||
import org.elasticsearch.marvel.mode.Mode;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
|
||||
public class LicenseService extends AbstractLifecycleComponent<LicenseService> {
|
||||
|
||||
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);
|
||||
|
||||
private final LicensesClientService clientService;
|
||||
private final Collection<LicensesService.ExpirationCallback> expirationLoggers;
|
||||
|
||||
private volatile Mode mode;
|
||||
|
||||
@Inject
|
||||
public LicenseService(Settings settings, LicensesClientService clientService) {
|
||||
super(settings);
|
||||
this.clientService = clientService;
|
||||
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), null, minutes(10)) {
|
||||
@Override
|
||||
public void on(License license, LicensesService.ExpirationStatus status) {
|
||||
logger.error("\n" +
|
||||
"#\n" +
|
||||
"# MARVEL LICENSE WAS EXPIRED ON [{}].\n" +
|
||||
"# HAVE A NEW LICENSE? PLEASE UPDATE IT. OTHERWISE, PLEASE REACH OUT TO YOUR SUPPORT CONTACT.\n" +
|
||||
"#", DATE_FORMATTER.printer().print(license.expiryDate()));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStart() throws ElasticsearchException {
|
||||
clientService.register(FEATURE_NAME, TRIAL_LICENSE_OPTIONS, expirationLoggers, new InternalListener(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStop() throws ElasticsearchException {
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
*/
|
||||
public Mode mode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
class InternalListener implements LicensesClientService.Listener {
|
||||
|
||||
private final LicenseService service;
|
||||
|
||||
public InternalListener(LicenseService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnabled(License license) {
|
||||
try {
|
||||
service.mode = Mode.fromName(license.type());
|
||||
} catch (IllegalArgumentException e) {
|
||||
service.mode = Mode.LITE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisabled(License license) {
|
||||
service.mode = Mode.LITE;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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.marvel.mode;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Marvel's operating mode
|
||||
*/
|
||||
public enum Mode {
|
||||
|
||||
/**
|
||||
* Marvel runs in downgraded mode
|
||||
*/
|
||||
TRIAL(0),
|
||||
|
||||
/**
|
||||
* Marvel runs in downgraded mode
|
||||
*/
|
||||
LITE(0),
|
||||
|
||||
/**
|
||||
* Marvel runs in normal mode
|
||||
*/
|
||||
STANDARD(1);
|
||||
|
||||
private final byte id;
|
||||
|
||||
Mode(int id) {
|
||||
this.id = (byte) id;
|
||||
}
|
||||
|
||||
public byte getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public static Mode fromId(byte id) {
|
||||
switch (id) {
|
||||
case 0:
|
||||
return TRIAL;
|
||||
case 1:
|
||||
return LITE;
|
||||
case 2:
|
||||
return STANDARD;
|
||||
case 3:
|
||||
default:
|
||||
throw new ElasticsearchException("unknown marvel mode id [" + id + "]");
|
||||
}
|
||||
}
|
||||
|
||||
public static Mode fromName(String name) {
|
||||
switch (name.toLowerCase(Locale.ROOT)) {
|
||||
case "trial": return TRIAL;
|
||||
case "lite": return LITE;
|
||||
case "standard" : return STANDARD;
|
||||
default:
|
||||
throw new ElasticsearchException("unknown marvel mode name [" + name + "]");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
plugin=org.elasticsearch.marvel.MarvelPlugin
|
||||
version=${project.version}
|
|
@ -0,0 +1,446 @@
|
|||
{
|
||||
"template": ".marvel*",
|
||||
"settings": {
|
||||
"number_of_shards": 1,
|
||||
"number_of_replicas": 1,
|
||||
"analysis": {
|
||||
"analyzer": {
|
||||
"default": {
|
||||
"type": "standard",
|
||||
"stopwords": "_none_"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mapper.dynamic": true,
|
||||
"marvel.index_format": 6
|
||||
},
|
||||
"mappings": {
|
||||
"_default_": {
|
||||
"dynamic_templates": [
|
||||
{
|
||||
"string_fields": {
|
||||
"match": "*",
|
||||
"match_mapping_type": "string",
|
||||
"mapping": {
|
||||
"type": "multi_field",
|
||||
"fields": {
|
||||
"{name}": {
|
||||
"type": "string",
|
||||
"index": "analyzed",
|
||||
"omit_norms": true
|
||||
},
|
||||
"raw": {
|
||||
"type": "string",
|
||||
"index": "not_analyzed",
|
||||
"ignore_above": 256
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_stats": {
|
||||
"properties": {
|
||||
"breakers": {
|
||||
"properties": {
|
||||
"fielddata": {
|
||||
"properties": {
|
||||
"estimated_size_in_bytes": {
|
||||
"type": "long"
|
||||
},
|
||||
"tripped": {
|
||||
"type": "long"
|
||||
},
|
||||
"limit_size_in_bytes": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"request": {
|
||||
"properties": {
|
||||
"estimated_size_in_bytes": {
|
||||
"type": "long"
|
||||
},
|
||||
"tripped": {
|
||||
"type": "long"
|
||||
},
|
||||
"limit_size_in_bytes": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parent": {
|
||||
"properties": {
|
||||
"estimated_size_in_bytes": {
|
||||
"type": "long"
|
||||
},
|
||||
"tripped": {
|
||||
"type": "long"
|
||||
},
|
||||
"limit_size_in_bytes": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"fs": {
|
||||
"properties": {
|
||||
"total": {
|
||||
"properties": {
|
||||
"disk_io_op": {
|
||||
"type": "long"
|
||||
},
|
||||
"disk_reads": {
|
||||
"type": "long"
|
||||
},
|
||||
"disk_writes": {
|
||||
"type": "long"
|
||||
},
|
||||
"disk_io_size_in_bytes": {
|
||||
"type": "long"
|
||||
},
|
||||
"disk_read_size_in_bytes": {
|
||||
"type": "long"
|
||||
},
|
||||
"disk_write_size_in_bytes": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"jvm": {
|
||||
"properties": {
|
||||
"buffer_pools": {
|
||||
"properties": {
|
||||
"direct": {
|
||||
"properties": {
|
||||
"used_in_bytes": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mapped": {
|
||||
"properties": {
|
||||
"used_in_bytes": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"gc": {
|
||||
"properties": {
|
||||
"collectors": {
|
||||
"properties": {
|
||||
"young": {
|
||||
"properties": {
|
||||
"collection_count": {
|
||||
"type": "long"
|
||||
},
|
||||
"collection_time_in_millis": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"old": {
|
||||
"properties": {
|
||||
"collection_count": {
|
||||
"type": "long"
|
||||
},
|
||||
"collection_time_in_millis": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"indices": {
|
||||
"properties": {
|
||||
"indexing": {
|
||||
"properties": {
|
||||
"throttle_time_in_millis": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"percolate": {
|
||||
"properties": {
|
||||
"total": {
|
||||
"type": "long"
|
||||
},
|
||||
"time_in_millis": {
|
||||
"type": "long"
|
||||
},
|
||||
"queries": {
|
||||
"type": "long"
|
||||
},
|
||||
"memory_size_in_bytes": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"segments": {
|
||||
"properties": {
|
||||
"index_writer_memory_in_bytes": {
|
||||
"type": "long"
|
||||
},
|
||||
"version_map_memory_in_bytes": {
|
||||
"type": "long"
|
||||
},
|
||||
"index_writer_max_memory_in_bytes": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"query_cache": {
|
||||
"properties": {
|
||||
"memory_size_in_bytes": {
|
||||
"type": "long"
|
||||
},
|
||||
"evictions": {
|
||||
"type": "long"
|
||||
},
|
||||
"hit_count": {
|
||||
"type": "long"
|
||||
},
|
||||
"miss_count": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"os": {
|
||||
"properties": {
|
||||
"load_average": {
|
||||
"properties": {
|
||||
"1m": {
|
||||
"type": "float"
|
||||
},
|
||||
"5m": {
|
||||
"type": "float"
|
||||
},
|
||||
"15m": {
|
||||
"type": "float"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"thread_pool": {
|
||||
"properties": {
|
||||
"listener": {
|
||||
"properties": {
|
||||
"threads": {
|
||||
"type": "long"
|
||||
},
|
||||
"rejected": {
|
||||
"type": "long"
|
||||
},
|
||||
"completed": {
|
||||
"type": "long"
|
||||
},
|
||||
"queue": {
|
||||
"type": "long"
|
||||
},
|
||||
"largest": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"index_stats": {
|
||||
"properties": {
|
||||
"index": {
|
||||
"type": "multi_field",
|
||||
"fields": {
|
||||
"index": {
|
||||
"type": "string",
|
||||
"norms": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"raw": {
|
||||
"type": "string",
|
||||
"index": "not_analyzed",
|
||||
"norms": {
|
||||
"enabled": false
|
||||
},
|
||||
"index_options": "docs",
|
||||
"include_in_all": false,
|
||||
"ignore_above": 256
|
||||
}
|
||||
}
|
||||
},
|
||||
"total": {
|
||||
"properties": {
|
||||
"fielddata": {
|
||||
"properties": {
|
||||
"memory_size_in_bytes": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"indexing": {
|
||||
"properties": {
|
||||
"throttle_time_in_millis": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"merges": {
|
||||
"properties": {
|
||||
"total_size_in_bytes": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"percolate": {
|
||||
"properties": {
|
||||
"total": {
|
||||
"type": "long"
|
||||
},
|
||||
"time_in_millis": {
|
||||
"type": "long"
|
||||
},
|
||||
"queries": {
|
||||
"type": "long"
|
||||
},
|
||||
"memory_size_in_bytes": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"properties": {
|
||||
"query": {
|
||||
"properties": {
|
||||
"query_total": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"segments": {
|
||||
"properties": {
|
||||
"index_writer_memory_in_bytes": {
|
||||
"type": "long"
|
||||
},
|
||||
"version_map_memory_in_bytes": {
|
||||
"type": "long"
|
||||
},
|
||||
"index_writer_max_memory_in_bytes": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"query_cache": {
|
||||
"properties": {
|
||||
"memory_size_in_bytes": {
|
||||
"type": "long"
|
||||
},
|
||||
"evictions": {
|
||||
"type": "long"
|
||||
},
|
||||
"hit_count": {
|
||||
"type": "long"
|
||||
},
|
||||
"miss_count": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"primaries": {
|
||||
"properties": {
|
||||
"docs": {
|
||||
"properties": {
|
||||
"count": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"indexing": {
|
||||
"properties": {
|
||||
"index_total": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cluster_event": {},
|
||||
"shard_event": {},
|
||||
"indices_stats": {
|
||||
"properties": {
|
||||
"primaries": {
|
||||
"properties": {
|
||||
"indexing": {
|
||||
"properties": {
|
||||
"index_total": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"docs": {
|
||||
"properties": {
|
||||
"count": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"total": {
|
||||
"properties": {
|
||||
"search": {
|
||||
"properties": {
|
||||
"query_total": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cluster_stats": {},
|
||||
"index_event": {},
|
||||
"node_event": {},
|
||||
"routing_event": {},
|
||||
"cluster_state": {
|
||||
"properties": {
|
||||
"blocks": {
|
||||
"type": "object",
|
||||
"enabled": false
|
||||
},
|
||||
"nodes": {
|
||||
"type": "object",
|
||||
"enabled": false
|
||||
},
|
||||
"routing_nodes": {
|
||||
"type": "object",
|
||||
"enabled": false
|
||||
},
|
||||
"routing_table": {
|
||||
"type": "object",
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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.marvel;
|
||||
|
||||
import org.elasticsearch.client.Client;
|
||||
import org.elasticsearch.client.transport.TransportClient;
|
||||
import org.elasticsearch.common.inject.Module;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
public class MarvelPluginClientTests extends ElasticsearchTestCase {
|
||||
|
||||
@Test
|
||||
public void testModulesWithClientSettings() {
|
||||
Settings settings = Settings.builder()
|
||||
.put(Client.CLIENT_TYPE_SETTING, TransportClient.CLIENT_TYPE)
|
||||
.build();
|
||||
|
||||
MarvelPlugin plugin = new MarvelPlugin(settings);
|
||||
assertThat(plugin.isEnabled(), is(false));
|
||||
Collection<Class<? extends Module>> modules = plugin.modules();
|
||||
assertThat(modules.size(), is(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testModulesWithNodeSettings() {
|
||||
// these settings mimic what ES does when running as a node...
|
||||
Settings settings = Settings.builder()
|
||||
.put(Client.CLIENT_TYPE_SETTING, "node")
|
||||
.build();
|
||||
MarvelPlugin plugin = new MarvelPlugin(settings);
|
||||
assertThat(plugin.isEnabled(), is(true));
|
||||
Collection<Class<? extends Module>> modules = plugin.modules();
|
||||
assertThat(modules.size(), is(1));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* 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.marvel;
|
||||
|
||||
import org.elasticsearch.action.admin.cluster.node.info.NodeInfo;
|
||||
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse;
|
||||
import org.elasticsearch.action.admin.cluster.node.info.PluginInfo;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.marvel.agent.AgentService;
|
||||
import org.elasticsearch.plugins.PluginsService;
|
||||
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
||||
import org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
|
||||
import org.elasticsearch.tribe.TribeService;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.TEST;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
@ClusterScope(scope = TEST, transportClientRatio = 0, numClientNodes = 0, numDataNodes = 0)
|
||||
public class MarvelPluginTests extends ElasticsearchIntegrationTest {
|
||||
|
||||
@Override
|
||||
protected Settings nodeSettings(int nodeOrdinal) {
|
||||
return Settings.settingsBuilder()
|
||||
.put(super.nodeSettings(nodeOrdinal))
|
||||
.put("plugin.types", MarvelPlugin.class.getName())
|
||||
.put(PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, false)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMarvelEnabled() {
|
||||
internalCluster().startNode(Settings.builder().put(MarvelPlugin.ENABLED, true).build());
|
||||
assertPluginIsLoaded();
|
||||
assertServiceIsBound(AgentService.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMarvelDisabled() {
|
||||
internalCluster().startNode(Settings.builder().put(MarvelPlugin.ENABLED, false).build());
|
||||
assertPluginIsLoaded();
|
||||
assertServiceIsNotBound(AgentService.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMarvelDisabledOnTribeNode() {
|
||||
internalCluster().startNode(Settings.builder().put(TribeService.TRIBE_NAME, "t1").build());
|
||||
assertPluginIsLoaded();
|
||||
assertServiceIsNotBound(AgentService.class);
|
||||
}
|
||||
|
||||
private void assertPluginIsLoaded() {
|
||||
NodesInfoResponse response = client().admin().cluster().prepareNodesInfo().setPlugins(true).get();
|
||||
for (NodeInfo nodeInfo : response) {
|
||||
assertNotNull(nodeInfo.getPlugins());
|
||||
|
||||
boolean found = false;
|
||||
for (PluginInfo plugin : nodeInfo.getPlugins().getInfos()) {
|
||||
assertNotNull(plugin);
|
||||
|
||||
if (MarvelPlugin.NAME.equals(plugin.getName())) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertThat("marvel plugin not found", found, equalTo(true));
|
||||
}
|
||||
}
|
||||
|
||||
private void assertServiceIsBound(Class klass) {
|
||||
try {
|
||||
Object binding = internalCluster().getDataNodeInstance(klass);
|
||||
assertNotNull(binding);
|
||||
assertTrue(klass.isInstance(binding));
|
||||
} catch (Exception e) {
|
||||
fail("no service bound for class " + klass.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
private void assertServiceIsNotBound(Class klass) {
|
||||
try {
|
||||
internalCluster().getDataNodeInstance(klass);
|
||||
fail("should have thrown an exception about missing implementation");
|
||||
} catch (Exception ce) {
|
||||
assertThat("message contains error about missing implemention: " + ce.getMessage(),
|
||||
ce.getMessage().contains("No implementation"), equalTo(true));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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.marvel;
|
||||
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
public class MarvelVersionTests extends ElasticsearchTestCase {
|
||||
|
||||
@Test
|
||||
public void testVersionFromString() {
|
||||
assertThat(MarvelVersion.fromString("2.0.0-beta1"), equalTo(MarvelVersion.V_2_0_0_Beta1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVersionNumber() {
|
||||
assertThat(MarvelVersion.V_2_0_0_Beta1.number(), equalTo("2.0.0-beta1"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* 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.marvel.agent;
|
||||
|
||||
import org.elasticsearch.common.io.Streams;
|
||||
import org.elasticsearch.marvel.agent.support.AgentUtils;
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
|
||||
|
||||
public class AgentUtilsTests extends ElasticsearchTestCase {
|
||||
|
||||
@Test
|
||||
public void testVersionIsExtractableFromIndexTemplate() throws IOException {
|
||||
byte[] template = Streams.copyToBytesFromClasspath("/marvel_index_template.json");
|
||||
MatcherAssert.assertThat(AgentUtils.parseIndexVersionFromTemplate(template), Matchers.greaterThan(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHostParsing() throws MalformedURLException, URISyntaxException {
|
||||
URL url = AgentUtils.parseHostWithPath("localhost:9200", "");
|
||||
verifyUrl(url, "http", "localhost", 9200, "/");
|
||||
|
||||
url = AgentUtils.parseHostWithPath("localhost", "_bulk");
|
||||
verifyUrl(url, "http", "localhost", 9200, "/_bulk");
|
||||
|
||||
url = AgentUtils.parseHostWithPath("http://localhost:9200", "_bulk");
|
||||
verifyUrl(url, "http", "localhost", 9200, "/_bulk");
|
||||
|
||||
url = AgentUtils.parseHostWithPath("http://localhost", "_bulk");
|
||||
verifyUrl(url, "http", "localhost", 9200, "/_bulk");
|
||||
|
||||
url = AgentUtils.parseHostWithPath("https://localhost:9200", "_bulk");
|
||||
verifyUrl(url, "https", "localhost", 9200, "/_bulk");
|
||||
|
||||
url = AgentUtils.parseHostWithPath("https://boaz-air.local:9200", "_bulk");
|
||||
verifyUrl(url, "https", "boaz-air.local", 9200, "/_bulk");
|
||||
|
||||
url = AgentUtils.parseHostWithPath("boaz:test@localhost:9200", "");
|
||||
verifyUrl(url, "http", "localhost", 9200, "/", "boaz:test");
|
||||
|
||||
url = AgentUtils.parseHostWithPath("boaz:test@localhost", "_bulk");
|
||||
verifyUrl(url, "http", "localhost", 9200, "/_bulk", "boaz:test");
|
||||
|
||||
url = AgentUtils.parseHostWithPath("http://boaz:test@localhost:9200", "_bulk");
|
||||
verifyUrl(url, "http", "localhost", 9200, "/_bulk", "boaz:test");
|
||||
|
||||
url = AgentUtils.parseHostWithPath("http://boaz:test@localhost", "_bulk");
|
||||
verifyUrl(url, "http", "localhost", 9200, "/_bulk", "boaz:test");
|
||||
|
||||
url = AgentUtils.parseHostWithPath("https://boaz:test@localhost:9200", "_bulk");
|
||||
verifyUrl(url, "https", "localhost", 9200, "/_bulk", "boaz:test");
|
||||
|
||||
url = AgentUtils.parseHostWithPath("boaz:test@localhost:9200/suburl", "");
|
||||
verifyUrl(url, "http", "localhost", 9200, "/suburl/", "boaz:test");
|
||||
|
||||
url = AgentUtils.parseHostWithPath("boaz:test@localhost:9200/suburl/", "");
|
||||
verifyUrl(url, "http", "localhost", 9200, "/suburl/", "boaz:test");
|
||||
|
||||
url = AgentUtils.parseHostWithPath("localhost/suburl", "_bulk");
|
||||
verifyUrl(url, "http", "localhost", 9200, "/suburl/_bulk");
|
||||
|
||||
url = AgentUtils.parseHostWithPath("http://boaz:test@localhost:9200/suburl/suburl1", "_bulk");
|
||||
verifyUrl(url, "http", "localhost", 9200, "/suburl/suburl1/_bulk", "boaz:test");
|
||||
|
||||
url = AgentUtils.parseHostWithPath("http://boaz:test@localhost/suburl", "_bulk");
|
||||
verifyUrl(url, "http", "localhost", 9200, "/suburl/_bulk", "boaz:test");
|
||||
|
||||
url = AgentUtils.parseHostWithPath("https://boaz:test@localhost:9200/suburl", "_bulk");
|
||||
verifyUrl(url, "https", "localhost", 9200, "/suburl/_bulk", "boaz:test");
|
||||
|
||||
url = AgentUtils.parseHostWithPath("https://user:test@server_with_underscore:9300", "_bulk");
|
||||
verifyUrl(url, "https", "server_with_underscore", 9300, "/_bulk", "user:test");
|
||||
|
||||
url = AgentUtils.parseHostWithPath("user:test@server_with_underscore:9300", "_bulk");
|
||||
verifyUrl(url, "http", "server_with_underscore", 9300, "/_bulk", "user:test");
|
||||
|
||||
url = AgentUtils.parseHostWithPath("server_with_underscore:9300", "_bulk");
|
||||
verifyUrl(url, "http", "server_with_underscore", 9300, "/_bulk");
|
||||
|
||||
url = AgentUtils.parseHostWithPath("server_with_underscore", "_bulk");
|
||||
verifyUrl(url, "http", "server_with_underscore", 9200, "/_bulk");
|
||||
|
||||
url = AgentUtils.parseHostWithPath("https://user:test@server-dash:9300", "_bulk");
|
||||
verifyUrl(url, "https", "server-dash", 9300, "/_bulk", "user:test");
|
||||
|
||||
url = AgentUtils.parseHostWithPath("user:test@server-dash:9300", "_bulk");
|
||||
verifyUrl(url, "http", "server-dash", 9300, "/_bulk", "user:test");
|
||||
|
||||
url = AgentUtils.parseHostWithPath("server-dash:9300", "_bulk");
|
||||
verifyUrl(url, "http", "server-dash", 9300, "/_bulk");
|
||||
|
||||
url = AgentUtils.parseHostWithPath("server-dash", "_bulk");
|
||||
verifyUrl(url, "http", "server-dash", 9200, "/_bulk");
|
||||
}
|
||||
|
||||
void verifyUrl(URL url, String protocol, String host, int port, String path) throws URISyntaxException {
|
||||
assertThat(url.getProtocol(), equalTo(protocol));
|
||||
assertThat(url.getHost(), equalTo(host));
|
||||
assertThat(url.getPort(), equalTo(port));
|
||||
assertThat(url.toURI().getPath(), equalTo(path));
|
||||
}
|
||||
|
||||
void verifyUrl(URL url, String protocol, String host, int port, String path, String userInfo) throws URISyntaxException {
|
||||
verifyUrl(url, protocol, host, port, path);
|
||||
assertThat(url.getUserInfo(), equalTo(userInfo));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sanitizeUrlPadTest() throws UnsupportedEncodingException {
|
||||
String pwd = URLEncoder.encode(randomRealisticUnicodeOfCodepointLengthBetween(3, 20), "UTF-8");
|
||||
String[] inputs = new String[]{
|
||||
"https://boaz:" + pwd + "@hostname:9200",
|
||||
"http://boaz:" + pwd + "@hostname:9200",
|
||||
"boaz:" + pwd + "@hostname",
|
||||
"boaz:" + pwd + "@hostname/hello",
|
||||
"Parse exception in [boaz:" + pwd + "@hostname:9200,boaz1:" + pwd + "@hostname \n" +
|
||||
"caused: by exception ,boaz1:" + pwd + "@hostname",
|
||||
"failed to upload index template, stopping export\n" +
|
||||
"java.lang.RuntimeException: failed to load/verify index template\n" +
|
||||
" at org.elasticsearch.marvel.agent.exporter.ESExporter.checkAndUploadIndexTemplate(ESExporter.java:525)\n" +
|
||||
" at org.elasticsearch.marvel.agent.exporter.ESExporter.openExportingConnection(ESExporter.java:213)\n" +
|
||||
" at org.elasticsearch.marvel.agent.exporter.ESExporter.exportXContent(ESExporter.java:285)\n" +
|
||||
" at org.elasticsearch.marvel.agent.exporter.ESExporter.exportClusterStats(ESExporter.java:206)\n" +
|
||||
" at org.elasticsearch.marvel.agent.AgentService$ExportingWorker.exportClusterStats(AgentService.java:288)\n" +
|
||||
" at org.elasticsearch.marvel.agent.AgentService$ExportingWorker.run(AgentService.java:245)\n" +
|
||||
" at java.lang.Thread.run(Thread.java:745)\n" +
|
||||
"Caused by: java.io.IOException: Server returned HTTP response code: 401 for URL: http://marvel_exporter:" + pwd + "@localhost:9200/_template/marvel\n" +
|
||||
" at sun.reflect.GeneratedConstructorAccessor3.createMarvelDoc(Unknown Source)\n" +
|
||||
" at sun.reflect.DelegatingConstructorAccessorImpl.createMarvelDoc(DelegatingConstructorAccessorImpl.java:45)\n" +
|
||||
" at java.lang.reflect.Constructor.createMarvelDoc(Constructor.java:526)\n" +
|
||||
" at sun.net.www.protocol.http.HttpURLConnection$6.run(HttpURLConnection.java:1675)\n" +
|
||||
" at sun.net.www.protocol.http.HttpURLConnection$6.run(HttpURLConnection.java:1673)\n" +
|
||||
" at java.security.AccessController.doPrivileged(Native Method)\n" +
|
||||
" at sun.net.www.protocol.http.HttpURLConnection.getChainedException(HttpURLConnection.java:1671)\n" +
|
||||
" at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1244)\n" +
|
||||
" at org.elasticsearch.marvel.agent.exporter.ESExporter.checkAndUploadIndexTemplate(ESExporter.java:519)\n" +
|
||||
" ... 6 more\n" +
|
||||
"Caused by: java.io.IOException: Server returned HTTP response code: 401 for URL: http://marvel_exporter:" + pwd + "@localhost:9200/_template/marvel\n" +
|
||||
" at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1626)\n" +
|
||||
" at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:468)\n" +
|
||||
" at org.elasticsearch.marvel.agent.exporter.ESExporter.checkAndUploadIndexTemplate(ESExporter.java:514)\n" +
|
||||
" ... 6 more"
|
||||
};
|
||||
|
||||
for (String input : inputs) {
|
||||
String sanitized = AgentUtils.santizeUrlPwds(input);
|
||||
assertThat(sanitized, not(containsString(pwd)));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* 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.marvel.agent.collector.indices;
|
||||
|
||||
import org.elasticsearch.cluster.ClusterName;
|
||||
import org.elasticsearch.cluster.ClusterService;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.marvel.agent.exporter.MarvelDoc;
|
||||
import org.elasticsearch.test.ElasticsearchSingleNodeTest;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
public class IndexCollectorTests extends ElasticsearchSingleNodeTest {
|
||||
|
||||
@Test
|
||||
public void testIndexCollectorNoIndices() throws Exception {
|
||||
Collection<MarvelDoc> results = newIndexCollector().doCollect();
|
||||
assertThat(results, is(empty()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIndexCollectorOneIndex() throws Exception {
|
||||
int nbDocs = randomIntBetween(1, 20);
|
||||
for (int i = 0; i < nbDocs; i++) {
|
||||
client().prepareIndex("test", "test").setSource("num", i).get();
|
||||
}
|
||||
client().admin().indices().prepareRefresh().get();
|
||||
assertHitCount(client().prepareCount().get(), nbDocs);
|
||||
|
||||
Collection<MarvelDoc> results = newIndexCollector().doCollect();
|
||||
assertThat(results, hasSize(1));
|
||||
|
||||
MarvelDoc marvelDoc = results.iterator().next();
|
||||
assertNotNull(marvelDoc);
|
||||
assertThat(marvelDoc, instanceOf(IndexMarvelDoc.class));
|
||||
|
||||
IndexMarvelDoc indexMarvelDoc = (IndexMarvelDoc) marvelDoc;
|
||||
assertThat(indexMarvelDoc.clusterName(), equalTo(client().admin().cluster().prepareHealth().get().getClusterName()));
|
||||
assertThat(indexMarvelDoc.timestamp(), greaterThan(0L));
|
||||
assertThat(indexMarvelDoc.type(), equalTo(IndexCollector.TYPE));
|
||||
|
||||
assertThat(indexMarvelDoc.getIndex(), equalTo("test"));
|
||||
assertNotNull(indexMarvelDoc.getDocs());
|
||||
assertThat(indexMarvelDoc.getDocs().getCount(), equalTo((long) nbDocs));
|
||||
assertNotNull(indexMarvelDoc.getStore());
|
||||
assertThat(indexMarvelDoc.getStore().getSizeInBytes(), greaterThan(0L));
|
||||
assertThat(indexMarvelDoc.getStore().getThrottleTimeInMillis(), equalTo(0L));
|
||||
assertNotNull(indexMarvelDoc.getIndexing());
|
||||
assertThat(indexMarvelDoc.getIndexing().getThrottleTimeInMillis(), equalTo(0L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIndexCollectorMultipleIndices() throws Exception {
|
||||
int nbIndices = randomIntBetween(1, 5);
|
||||
int[] docsPerIndex = new int[nbIndices];
|
||||
|
||||
for (int i = 0; i < nbIndices; i++) {
|
||||
docsPerIndex[i] = randomIntBetween(1, 20);
|
||||
for (int j = 0; j < docsPerIndex[i]; j++) {
|
||||
client().prepareIndex("test-" + i, "test").setSource("num", i).get();
|
||||
}
|
||||
}
|
||||
|
||||
String clusterName = client().admin().cluster().prepareHealth().get().getClusterName();
|
||||
client().admin().indices().prepareRefresh().get();
|
||||
for (int i = 0; i < nbIndices; i++) {
|
||||
assertHitCount(client().prepareCount("test-" + i).get(), docsPerIndex[i]);
|
||||
}
|
||||
|
||||
Collection<MarvelDoc> results = newIndexCollector().doCollect();
|
||||
assertThat(results, hasSize(nbIndices));
|
||||
|
||||
for (int i = 0; i < nbIndices; i++) {
|
||||
boolean found = false;
|
||||
|
||||
Iterator<MarvelDoc> it = results.iterator();
|
||||
while (!found && it.hasNext()) {
|
||||
MarvelDoc marvelDoc = it.next();
|
||||
assertThat(marvelDoc, instanceOf(IndexMarvelDoc.class));
|
||||
|
||||
IndexMarvelDoc indexMarvelDoc = (IndexMarvelDoc) marvelDoc;
|
||||
if (indexMarvelDoc.getIndex().equals("test-" + i)) {
|
||||
assertThat(indexMarvelDoc.clusterName(), equalTo(clusterName));
|
||||
assertThat(indexMarvelDoc.timestamp(), greaterThan(0L));
|
||||
assertThat(indexMarvelDoc.type(), equalTo(IndexCollector.TYPE));
|
||||
|
||||
assertNotNull(indexMarvelDoc.getDocs());
|
||||
assertThat(indexMarvelDoc.getDocs().getCount(), equalTo((long) docsPerIndex[i]));
|
||||
assertNotNull(indexMarvelDoc.getStore());
|
||||
assertThat(indexMarvelDoc.getStore().getSizeInBytes(), greaterThan(0L));
|
||||
assertThat(indexMarvelDoc.getStore().getThrottleTimeInMillis(), equalTo(0L));
|
||||
assertNotNull(indexMarvelDoc.getIndexing());
|
||||
assertThat(indexMarvelDoc.getIndexing().getThrottleTimeInMillis(), equalTo(0L));
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
assertThat("could not find collected stats for index [test-" + i + "]", found, is(true));
|
||||
}
|
||||
}
|
||||
|
||||
private IndexCollector newIndexCollector() {
|
||||
return new IndexCollector(getInstanceFromNode(Settings.class), getInstanceFromNode(ClusterService.class), getInstanceFromNode(ClusterName.class), client());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.marvel.agent.collector.indices;
|
||||
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
public class IndexMarvelDocTests extends ElasticsearchTestCase {
|
||||
|
||||
@Test
|
||||
public void testCreateMarvelDoc() {
|
||||
String cluster = randomUnicodeOfLength(10);
|
||||
String type = randomUnicodeOfLength(10);
|
||||
long timestamp = randomLong();
|
||||
String index = randomUnicodeOfLength(10);
|
||||
long docsCount = randomLong();
|
||||
long storeSize = randomLong();
|
||||
long storeThrottle = randomLong();
|
||||
long indexingThrottle = randomLong();
|
||||
|
||||
IndexMarvelDoc marvelDoc = IndexMarvelDoc.createMarvelDoc(cluster, type, timestamp,
|
||||
index, docsCount, storeSize, storeThrottle, indexingThrottle);
|
||||
|
||||
assertNotNull(marvelDoc);
|
||||
assertThat(marvelDoc.clusterName(), equalTo(cluster));
|
||||
assertThat(marvelDoc.type(), equalTo(type));
|
||||
assertThat(marvelDoc.timestamp(), equalTo(timestamp));
|
||||
assertThat(marvelDoc.getIndex(), equalTo(index));
|
||||
assertNotNull(marvelDoc.getDocs());
|
||||
assertThat(marvelDoc.getDocs().getCount(), equalTo(docsCount));
|
||||
assertNotNull(marvelDoc.getStore());
|
||||
assertThat(marvelDoc.getStore().getSizeInBytes(), equalTo(storeSize));
|
||||
assertThat(marvelDoc.getStore().getThrottleTimeInMillis(), equalTo(storeThrottle));
|
||||
assertNotNull(marvelDoc.getIndexing());
|
||||
assertThat(marvelDoc.getIndexing().getThrottleTimeInMillis(), equalTo(indexingThrottle));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
* 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.marvel.agent.exporter;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
import org.elasticsearch.cluster.metadata.IndexTemplateMetaData;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.marvel.agent.AgentService;
|
||||
import org.elasticsearch.marvel.agent.collector.indices.IndexMarvelDoc;
|
||||
import org.elasticsearch.node.Node;
|
||||
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
||||
import org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
|
||||
|
||||
|
||||
// Transport Client instantiation also calls the marvel plugin, which then fails to find modules
|
||||
@ClusterScope(transportClientRatio = 0.0, scope = ElasticsearchIntegrationTest.Scope.TEST, numDataNodes = 0, numClientNodes = 0)
|
||||
public class HttpESExporterTests extends ElasticsearchIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void testHttpServerOff() {
|
||||
Settings.Builder builder = Settings.builder()
|
||||
.put(AgentService.SETTINGS_INTERVAL, "200m")
|
||||
.put(Node.HTTP_ENABLED, false);
|
||||
internalCluster().startNode(builder);
|
||||
HttpESExporter httpEsExporter = getEsExporter();
|
||||
logger.info("trying exporting despite of no target");
|
||||
httpEsExporter.export(ImmutableList.of(newRandomMarvelDoc()));
|
||||
}
|
||||
/*
|
||||
@Test
|
||||
public void testLargeClusterStateSerialization() throws InterruptedException {
|
||||
// make sure no other exporting is done (quicker)..
|
||||
internalCluster().startNode(Settings.builder().put(AgentService.SETTINGS_INTERVAL, "200m").put(Node.HTTP_ENABLED, true));
|
||||
ESExporter esExporter = internalCluster().getInstance(ESExporter.class);
|
||||
DiscoveryNodes.Builder nodesBuilder = new DiscoveryNodes.Builder();
|
||||
int nodeCount = randomIntBetween(10, 200);
|
||||
for (int i = 0; i < nodeCount; i++) {
|
||||
nodesBuilder.put(new DiscoveryNode("node_" + i, new LocalTransportAddress("node_" + i), Version.CURRENT));
|
||||
}
|
||||
|
||||
// get the current cluster state rather then construct one because the constructors have changed across ES versions
|
||||
ClusterService clusterService = internalCluster().getInstance(ClusterService.class);
|
||||
ClusterState state = ClusterState.builder(clusterService.state()).nodes(nodesBuilder).build();
|
||||
logger.info("exporting cluster state with {} nodes", state.nodes().size());
|
||||
esExporter.exportEvents(new Event[]{
|
||||
new ClusterEvent.ClusterStateChange(1234l, state, "test", ClusterHealthStatus.GREEN, "testing_1234", "test_source_unique")
|
||||
});
|
||||
logger.info("done exporting");
|
||||
|
||||
ensureYellow();
|
||||
client().admin().indices().prepareRefresh(".marvel-*").get();
|
||||
assertHitCount(client().prepareSearch().setQuery(QueryBuilders.termQuery("event_source", "test_source_unique")).get(), 1);
|
||||
}
|
||||
*/
|
||||
|
||||
@Test
|
||||
@LuceneTestCase.Slow
|
||||
public void testTemplateAdditionDespiteOfLateClusterForming() {
|
||||
Settings.Builder builder = Settings.builder()
|
||||
.put(AgentService.SETTINGS_INTERVAL, "200m")
|
||||
.put(Node.HTTP_ENABLED, true)
|
||||
.put("discovery.type", "zen")
|
||||
.put("discovery.zen.ping_timeout", "1s")
|
||||
.put("discovery.initial_state_timeout", "100ms")
|
||||
.put("discovery.zen.minimum_master_nodes", 2)
|
||||
.put(HttpESExporter.SETTINGS_BULK_TIMEOUT, "1s")
|
||||
.put(HttpESExporter.SETTINGS_CHECK_TEMPLATE_TIMEOUT, "1s");
|
||||
internalCluster().startNode(builder);
|
||||
|
||||
HttpESExporter httpEsExporter = getEsExporter();
|
||||
logger.info("exporting events while there is no cluster");
|
||||
httpEsExporter.export(ImmutableList.of(newRandomMarvelDoc()));
|
||||
|
||||
logger.info("bringing up a second node");
|
||||
internalCluster().startNode(builder);
|
||||
ensureGreen();
|
||||
logger.info("exporting a second event");
|
||||
httpEsExporter.export(ImmutableList.of(newRandomMarvelDoc()));
|
||||
|
||||
logger.info("verifying template is inserted");
|
||||
assertMarvelTemplate();
|
||||
}
|
||||
|
||||
private void assertMarvelTemplate() {
|
||||
boolean found;
|
||||
found = findMarvelTemplate();
|
||||
assertTrue("failed to find a template named `marvel`", found);
|
||||
}
|
||||
|
||||
private boolean findMarvelTemplate() {
|
||||
for (IndexTemplateMetaData template : client().admin().indices().prepareGetTemplates("marvel").get().getIndexTemplates()) {
|
||||
if (template.getName().equals("marvel")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDynamicHostChange() {
|
||||
// disable exporting to be able to use non valid hosts
|
||||
Settings.Builder builder = Settings.builder()
|
||||
.put(AgentService.SETTINGS_INTERVAL, "-1");
|
||||
internalCluster().startNode(builder);
|
||||
|
||||
HttpESExporter httpEsExporter = getEsExporter();
|
||||
|
||||
assertAcked(client().admin().cluster().prepareUpdateSettings().setTransientSettings(Settings.builder().put(HttpESExporter.SETTINGS_HOSTS, "test1")));
|
||||
assertThat(httpEsExporter.getHosts(), Matchers.arrayContaining("test1"));
|
||||
|
||||
// wipes the non array settings
|
||||
assertAcked(client().admin().cluster().prepareUpdateSettings().setTransientSettings(Settings.builder()
|
||||
.putArray(HttpESExporter.SETTINGS_HOSTS, "test2").put(HttpESExporter.SETTINGS_HOSTS, "")));
|
||||
assertThat(httpEsExporter.getHosts(), Matchers.arrayContaining("test2"));
|
||||
|
||||
assertAcked(client().admin().cluster().prepareUpdateSettings().setTransientSettings(Settings.builder().putArray(HttpESExporter.SETTINGS_HOSTS, "test3")));
|
||||
assertThat(httpEsExporter.getHosts(), Matchers.arrayContaining("test3"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHostChangeReChecksTemplate() {
|
||||
Settings.Builder builder = Settings.builder()
|
||||
.put(AgentService.SETTINGS_INTERVAL, "200m")
|
||||
.put(Node.HTTP_ENABLED, true);
|
||||
internalCluster().startNode(builder);
|
||||
|
||||
HttpESExporter httpEsExporter = getEsExporter();
|
||||
|
||||
logger.info("exporting an event");
|
||||
httpEsExporter.export(ImmutableList.of(newRandomMarvelDoc()));
|
||||
|
||||
logger.info("removing the marvel template");
|
||||
|
||||
assertAcked(client().admin().indices().prepareDeleteTemplate("marvel").get());
|
||||
|
||||
assertAcked(client().admin().cluster().prepareUpdateSettings().setTransientSettings(
|
||||
Settings.builder().putArray(HttpESExporter.SETTINGS_HOSTS, httpEsExporter.getHosts())).get());
|
||||
|
||||
logger.info("exporting a second event");
|
||||
httpEsExporter.export(ImmutableList.of(newRandomMarvelDoc()));
|
||||
|
||||
logger.info("verifying template is inserted");
|
||||
assertMarvelTemplate();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHostFailureChecksTemplate() throws InterruptedException, IOException {
|
||||
Settings.Builder builder = Settings.builder()
|
||||
.put(AgentService.SETTINGS_INTERVAL, "200m")
|
||||
.put(Node.HTTP_ENABLED, true);
|
||||
final String node0 = internalCluster().startNode(builder);
|
||||
String node1 = internalCluster().startNode(builder);
|
||||
|
||||
HttpESExporter httpEsExporter0 = getEsExporter(node0);
|
||||
final HttpESExporter httpEsExporter1 = getEsExporter(node1);
|
||||
|
||||
logger.info("--> exporting events to force host resolution");
|
||||
httpEsExporter0.export(ImmutableList.of(newRandomMarvelDoc()));
|
||||
httpEsExporter1.export(ImmutableList.of(newRandomMarvelDoc()));
|
||||
|
||||
logger.info("--> setting exporting hosts to {} + {}", httpEsExporter0.getHosts(), httpEsExporter1.getHosts());
|
||||
ArrayList<String> mergedHosts = new ArrayList<String>();
|
||||
mergedHosts.addAll(Arrays.asList(httpEsExporter0.getHosts()));
|
||||
mergedHosts.addAll(Arrays.asList(httpEsExporter1.getHosts()));
|
||||
|
||||
assertAcked(client().admin().cluster().prepareUpdateSettings().setTransientSettings(
|
||||
Settings.builder().putArray(HttpESExporter.SETTINGS_HOSTS, mergedHosts.toArray(Strings.EMPTY_ARRAY))).get());
|
||||
|
||||
logger.info("--> exporting events to have new settings take effect");
|
||||
httpEsExporter0.export(ImmutableList.of(newRandomMarvelDoc()));
|
||||
httpEsExporter1.export(ImmutableList.of(newRandomMarvelDoc()));
|
||||
|
||||
assertMarvelTemplate();
|
||||
|
||||
logger.info("--> removing the marvel template");
|
||||
assertAcked(client().admin().indices().prepareDeleteTemplate("marvel").get());
|
||||
|
||||
logger.info("--> shutting down node0");
|
||||
internalCluster().stopRandomNode(new Predicate<Settings>() {
|
||||
@Override
|
||||
public boolean apply(Settings settings) {
|
||||
return settings.get("name").equals(node0);
|
||||
}
|
||||
});
|
||||
|
||||
logger.info("--> exporting events from node1");
|
||||
// we use assert busy node because url caching may cause the node failure to be only detected while sending the event
|
||||
assertTrue("failed to find a template named 'marvel'", awaitBusy(new Predicate<Object>() {
|
||||
@Override
|
||||
public boolean apply(Object o) {
|
||||
httpEsExporter1.export(ImmutableList.of(newRandomMarvelDoc()));
|
||||
logger.debug("--> checking for template");
|
||||
return findMarvelTemplate();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private HttpESExporter getEsExporter() {
|
||||
AgentService service = internalCluster().getInstance(AgentService.class);
|
||||
return (HttpESExporter) service.getExporters().iterator().next();
|
||||
}
|
||||
|
||||
private HttpESExporter getEsExporter(String node) {
|
||||
AgentService service = internalCluster().getInstance(AgentService.class, node);
|
||||
return (HttpESExporter) service.getExporters().iterator().next();
|
||||
}
|
||||
|
||||
final static AtomicLong timeStampGenerator = new AtomicLong();
|
||||
|
||||
private MarvelDoc newRandomMarvelDoc() {
|
||||
return IndexMarvelDoc.createMarvelDoc(internalCluster().getClusterName(), "test_marvelDoc", timeStampGenerator.incrementAndGet(),
|
||||
"test_index", randomInt(), randomLong(), randomLong(), randomLong());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* 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.marvel.license;
|
||||
|
||||
import com.carrotsearch.randomizedtesting.RandomizedTest;
|
||||
import com.carrotsearch.randomizedtesting.SysGlobals;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.elasticsearch.common.component.AbstractComponent;
|
||||
import org.elasticsearch.common.inject.AbstractModule;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.inject.Module;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.license.core.License;
|
||||
import org.elasticsearch.license.plugin.core.LicensesClientService;
|
||||
import org.elasticsearch.license.plugin.core.LicensesService;
|
||||
import org.elasticsearch.marvel.MarvelPlugin;
|
||||
import org.elasticsearch.marvel.mode.Mode;
|
||||
import org.elasticsearch.plugins.AbstractPlugin;
|
||||
import org.elasticsearch.plugins.PluginsService;
|
||||
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
||||
import org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.SUITE;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
@ClusterScope(scope = SUITE, transportClientRatio = 0, numClientNodes = 0)
|
||||
public class LicenseIntegrationTests extends ElasticsearchIntegrationTest {
|
||||
|
||||
@Override
|
||||
protected Settings nodeSettings(int nodeOrdinal) {
|
||||
return Settings.settingsBuilder()
|
||||
.put(super.nodeSettings(nodeOrdinal))
|
||||
.put("plugin.types", MarvelPlugin.class.getName() + "," + MockLicensePlugin.class.getName())
|
||||
.put(PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, false)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnableDisableLicense() {
|
||||
assertMarvelMode(Mode.STANDARD);
|
||||
disableLicensing();
|
||||
|
||||
assertMarvelMode(Mode.LITE);
|
||||
enableLicensing();
|
||||
|
||||
assertMarvelMode(Mode.STANDARD);
|
||||
}
|
||||
|
||||
private void assertMarvelMode(Mode expected) {
|
||||
LicenseService licenseService = internalCluster().getInstance(LicenseService.class);
|
||||
assertNotNull(licenseService);
|
||||
assertThat(licenseService.mode(), equalTo(expected));
|
||||
}
|
||||
|
||||
|
||||
public static void disableLicensing() {
|
||||
for (MockLicenseService service : internalCluster().getInstances(MockLicenseService.class)) {
|
||||
service.disable();
|
||||
}
|
||||
}
|
||||
|
||||
public static void enableLicensing() {
|
||||
for (MockLicenseService service : internalCluster().getInstances(MockLicenseService.class)) {
|
||||
service.enable();
|
||||
}
|
||||
}
|
||||
|
||||
public static class MockLicensePlugin extends AbstractPlugin {
|
||||
|
||||
public static final String NAME = "internal-test-licensing";
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends Module>> modules() {
|
||||
return ImmutableSet.<Class<? extends Module>>of(InternalLicenseModule.class);
|
||||
}
|
||||
}
|
||||
|
||||
public static class InternalLicenseModule extends AbstractModule {
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(MockLicenseService.class).asEagerSingleton();
|
||||
bind(LicensesClientService.class).to(MockLicenseService.class);
|
||||
}
|
||||
}
|
||||
|
||||
public static class MockLicenseService extends AbstractComponent implements LicensesClientService {
|
||||
|
||||
static final License DUMMY_LICENSE = License.builder()
|
||||
.feature(LicenseService.FEATURE_NAME)
|
||||
.expiryDate(System.currentTimeMillis())
|
||||
.issueDate(System.currentTimeMillis())
|
||||
.issuedTo("LicensingTests")
|
||||
.issuer("test")
|
||||
.maxNodes(Integer.MAX_VALUE)
|
||||
.signature("_signature")
|
||||
.type("standard")
|
||||
.subscriptionType("all_is_good")
|
||||
.uid(String.valueOf(RandomizedTest.systemPropertyAsInt(SysGlobals.CHILDVM_SYSPROP_JVM_ID, 0)) + System.identityHashCode(LicenseIntegrationTests.class))
|
||||
.build();
|
||||
|
||||
private final List<Listener> listeners = new ArrayList<>();
|
||||
|
||||
@Inject
|
||||
public MockLicenseService(Settings settings) {
|
||||
super(settings);
|
||||
enable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(String s, LicensesService.TrialLicenseOptions trialLicenseOptions, Collection<LicensesService.ExpirationCallback> collection, Listener listener) {
|
||||
listeners.add(listener);
|
||||
enable();
|
||||
}
|
||||
|
||||
public void enable() {
|
||||
// enabled all listeners (incl. shield)
|
||||
for (Listener listener : listeners) {
|
||||
listener.onEnabled(DUMMY_LICENSE);
|
||||
}
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
// only disable watcher listener (we need shield to work)
|
||||
for (Listener listener : listeners) {
|
||||
listener.onDisabled(DUMMY_LICENSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
es.logger.level=DEBUG
|
||||
log4j.rootLogger=${es.logger.level}, out
|
||||
|
||||
log4j.logger.org.apache.http=INFO, out
|
||||
log4j.additivity.org.apache.http=false
|
||||
|
||||
log4j.appender.out=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.out.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.out.layout.conversionPattern=[%d{ISO8601}][%-5p][%-25c] %m%n
|
||||
log4j.logger.org.elasticsearch.marvel.agent=TRACE, out
|
||||
log4j.additivity.org.elasticsearch.marvel.agent=false
|
Loading…
Reference in New Issue