diff --git a/pom.xml b/pom.xml index f1009b72c48..1768ccf9af1 100644 --- a/pom.xml +++ b/pom.xml @@ -111,6 +111,21 @@ test + + org.apache.httpcomponents + httpclient + 4.3.5 + test + + + + org.apache.lucene + lucene-expressions + ${lucene.maven.version} + compile + true + + @@ -254,6 +269,25 @@ true + + + + ${basedir}/src/test/resources + + **/*.* + + + + + ${basedir}/rest-api-spec + rest-api-spec + + api/*.json + test/**/*.yaml + + + + org.apache.maven.plugins diff --git a/rest-api-spec/api/stats.json b/rest-api-spec/api/stats.json new file mode 100644 index 00000000000..5d17ae24851 --- /dev/null +++ b/rest-api-spec/api/stats.json @@ -0,0 +1,20 @@ +{ + "watcher_stats": { + "documentation": "http://elastic.co/alertsdocs", + "methods": ["GET"], + "url": { + "path": "/_watcher/stats", + "paths": ["/_watcher/stats"], + "parts": { + }, + "params": { + "pretty": { + "type": "boolean", + "description": "Pretty the output", + "default": false + } + } + }, + "body": null + } +} diff --git a/rest-api-spec/test/stats/10_basic.yaml b/rest-api-spec/test/stats/10_basic.yaml new file mode 100644 index 00000000000..c5ababd8358 --- /dev/null +++ b/rest-api-spec/test/stats/10_basic.yaml @@ -0,0 +1,9 @@ +--- +"Test watcher stats output": + + - do: + watcher_stats: + pretty: true + - match: { "watch_service_state": "started" } + - match: { "watch_count": 0 } + - match: { "execution_queue": { "size": 0, "max_size": 0 } } diff --git a/src/main/java/org/elasticsearch/watcher/rest/action/RestWatcherStatsAction.java b/src/main/java/org/elasticsearch/watcher/rest/action/RestWatcherStatsAction.java index 05c992a139c..6bab1973265 100644 --- a/src/main/java/org/elasticsearch/watcher/rest/action/RestWatcherStatsAction.java +++ b/src/main/java/org/elasticsearch/watcher/rest/action/RestWatcherStatsAction.java @@ -37,6 +37,7 @@ public class RestWatcherStatsAction extends WatcherRestHandler { watcherClient.watcherStats(new WatcherStatsRequest(), new RestBuilderListener(restChannel) { @Override public RestResponse buildResponse(WatcherStatsResponse watcherStatsResponse, XContentBuilder builder) throws Exception { + builder.startObject(); builder.field("watch_service_state", watcherStatsResponse.getWatchServiceState().toString().toLowerCase(Locale.ROOT)) .field("watch_count", watcherStatsResponse.getWatchesCount()); @@ -44,7 +45,7 @@ public class RestWatcherStatsAction extends WatcherRestHandler { .field("size", watcherStatsResponse.getExecutionQueueSize()) .field("max_size", watcherStatsResponse.getWatchExecutionQueueMaxSize()) .endObject(); - + builder.endObject(); return new BytesRestResponse(OK, builder); } diff --git a/src/test/java/org/elasticsearch/watcher/test/rest/WatcherRestTests.java b/src/test/java/org/elasticsearch/watcher/test/rest/WatcherRestTests.java new file mode 100644 index 00000000000..217947f035d --- /dev/null +++ b/src/test/java/org/elasticsearch/watcher/test/rest/WatcherRestTests.java @@ -0,0 +1,182 @@ +/* + * 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.watcher.test.rest; + +import com.carrotsearch.randomizedtesting.annotations.Name; +import org.apache.lucene.util.AbstractRandomizedTest; +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.common.base.Charsets; +import org.elasticsearch.common.io.FileSystemUtils; +import org.elasticsearch.common.io.Streams; +import org.elasticsearch.common.settings.ImmutableSettings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.license.plugin.LicensePlugin; +import org.elasticsearch.node.internal.InternalNode; +import org.elasticsearch.shield.ShieldPlugin; +import org.elasticsearch.shield.authc.esusers.ESUsersRealm; +import org.elasticsearch.test.ElasticsearchIntegrationTest; +import org.elasticsearch.test.junit.annotations.TestLogging; +import org.elasticsearch.test.rest.ElasticsearchRestTests; +import org.elasticsearch.test.rest.RestTestCandidate; +import org.elasticsearch.watcher.WatcherPlugin; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; + + +@AbstractRandomizedTest.Rest +@ElasticsearchIntegrationTest.ClusterScope(scope = ElasticsearchIntegrationTest.Scope.SUITE, numClientNodes = 1, transportClientRatio = 0, numDataNodes = 1, randomDynamicTemplates = false) +@TestLogging("_root:DEBUG") +public class WatcherRestTests extends ElasticsearchRestTests { + + private final boolean shieldEnabled = randomBoolean(); + + public WatcherRestTests(@Name("yaml") RestTestCandidate testCandidate) { + super(testCandidate); + } + + @Override + protected Settings nodeSettings(int nodeOrdinal) { + return ImmutableSettings.builder() + .put(super.nodeSettings(nodeOrdinal)) + .put("scroll.size", randomIntBetween(1, 100)) + .put("plugin.types", WatcherPlugin.class.getName() + "," + + (shieldEnabled ? ShieldPlugin.class.getName() + "," : "") + + "," + LicensePlugin.class.getName()) + .put(InternalNode.HTTP_ENABLED, true) + .put("shield.user", "admin:changeme") + .put(ShieldSettings.settings(shieldEnabled)) + .build(); + } + + /** + * Used to obtain settings for the REST client that is used to send REST requests. + */ + @Override + protected Settings restClientSettings() { + if (shieldEnabled) { + return ImmutableSettings.builder() + .put("client.transport.sniff", false) + .put("plugin.types", WatcherPlugin.class.getName() + "," + + (shieldEnabled ? ShieldPlugin.class.getName() + "," : "") + + "," + LicensePlugin.class.getName()) + .put(InternalNode.HTTP_ENABLED, true) + .put("shield.user", "admin:changeme") + .put(ShieldSettings.settings(shieldEnabled)) + .build(); + } + + return ImmutableSettings.builder() + .put("plugin.types", WatcherPlugin.class.getName()) + .put(InternalNode.HTTP_ENABLED, true) + .build(); + } + + @Override + protected Settings transportClientSettings() { + if (shieldEnabled) { + return ImmutableSettings.builder() + .put(super.transportClientSettings()) + .put("client.transport.sniff", false) + .put("plugin.types", WatcherPlugin.class.getName() + "," + + (shieldEnabled ? ShieldPlugin.class.getName() + "," : "")) + .put(ShieldSettings.settings(shieldEnabled)) + .put("shield.user", "admin:changeme") + .put(InternalNode.HTTP_ENABLED, true) + .build(); + } + + return ImmutableSettings.builder() + .put("plugin.types", WatcherPlugin.class.getName()) + .put(InternalNode.HTTP_ENABLED, true) + .build(); + } + + + + /** Shield related settings */ + + public static class ShieldSettings { + + public static final String IP_FILTER = "allow: all\n"; + + public static final String USERS = "test:{plain}changeme\n" + + "admin:{plain}changeme\n" + + "monitor:{plain}changeme"; + + public static final String USER_ROLES = "test:test\n" + + "admin:admin\n" + + "monitor:monitor"; + + public static final String ROLES = + "test:\n" + // a user for the test infra. + " cluster: all, manage_watcher\n" + + " indices:\n" + + " '*': all\n" + + "\n" + + "admin:\n" + + " cluster: manage_watcher, cluster:monitor/nodes/info, cluster:monitor/state, cluster:monitor/health, cluster:admin/repository/delete\n" + + " indices:\n" + + " '*': all, indices:admin/template/delete\n" + + "\n" + + "monitor:\n" + + " cluster: monitor_watcher, cluster:monitor/nodes/info\n" + + "\n" + ; + + public static Settings settings(boolean enabled) { + ImmutableSettings.Builder builder = ImmutableSettings.builder(); + if (!enabled) { + return builder.put("shield.enabled", false).build(); + } + + File folder = createFolder(globalTempDir(), "watcher_shield"); + return builder.put("shield.enabled", true) + .put("shield.user", "test:changeme") + .put("shield.authc.realms.esusers.type", ESUsersRealm.TYPE) + .put("shield.authc.anonymous.username","anonymous_user") + .put("shield.authc.anonymous.roles", "monitor") + .put("shield.authc.realms.esusers.order", 0) + .put("shield.authc.realms.esusers.files.users", writeFile(folder, "users", USERS)) + .put("shield.authc.realms.esusers.files.users_roles", writeFile(folder, "users_roles", USER_ROLES)) + .put("shield.authz.store.files.roles", writeFile(folder, "roles.yml", ROLES)) + .put("shield.transport.n2n.ip_filter.file", writeFile(folder, "ip_filter.yml", IP_FILTER)) + .put("shield.audit.enabled", true) + .build(); + } + + static File createFolder(File parent, String name) { + File createdFolder = new File(parent, name); + //the directory might exist e.g. if the global cluster gets restarted, then we recreate the directory as well + if (createdFolder.exists()) { + if (!FileSystemUtils.deleteRecursively(createdFolder)) { + throw new RuntimeException("could not delete existing temporary folder: " + createdFolder.getAbsolutePath()); + } + } + if (!createdFolder.mkdir()) { + throw new RuntimeException("could not create temporary folder: " + createdFolder.getAbsolutePath()); + } + return createdFolder; + } + + static String writeFile(File folder, String name, String content) { + return writeFile(folder, name, content.getBytes(Charsets.UTF_8)); + } + + static String writeFile(File folder, String name, byte[] content) { + Path file = folder.toPath().resolve(name); + try { + Streams.copy(content, file.toFile()); + } catch (IOException e) { + throw new ElasticsearchException("error writing file in test", e); + } + return file.toFile().getAbsolutePath(); + } + } + + +}