diff --git a/Makefile.am b/Makefile.am index d2ad17539f..d4cd5a01dd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -147,6 +147,7 @@ tsdb_SRC := \ src/tsd/AnnotationRpc.java \ src/tsd/BadRequestException.java \ src/tsd/ConnectionManager.java \ + src/tsd/DropCachesRpc.java \ src/tsd/GnuplotException.java \ src/tsd/GraphHandler.java \ src/tsd/HttpJsonSerializer.java \ @@ -164,6 +165,7 @@ tsdb_SRC := \ src/tsd/RpcHandler.java \ src/tsd/RpcPlugin.java \ src/tsd/RpcManager.java \ + src/tsd/RpcUtil.java \ src/tsd/RTPublisher.java \ src/tsd/SearchRpc.java \ src/tsd/StaticFileRpc.java \ diff --git a/src/tsd/AnnotationRpc.java b/src/tsd/AnnotationRpc.java index 2ac6c23903..c6dd803092 100644 --- a/src/tsd/AnnotationRpc.java +++ b/src/tsd/AnnotationRpc.java @@ -47,7 +47,9 @@ final class AnnotationRpc implements HttpRpc { */ public void execute(final TSDB tsdb, HttpQuery query) throws IOException { final HttpMethod method = query.getAPIMethod(); - + + RpcUtil.allowedMethods(method, HttpMethod.GET.getName(), HttpMethod.POST.getName(), HttpMethod.DELETE.getName(), HttpMethod.PUT.getName()); + final String[] uri = query.explodeAPIPath(); final String endpoint = uri.length > 1 ? uri[1] : ""; if (endpoint != null && endpoint.toLowerCase().endsWith("bulk")) { @@ -125,11 +127,6 @@ public Deferred call(Boolean success) throws Exception { throw new RuntimeException(e); } query.sendStatusOnly(HttpResponseStatus.NO_CONTENT); - - } else { - throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED, - "Method not allowed", "The HTTP method [" + method.getName() + - "] is not permitted for this endpoint"); } } @@ -141,14 +138,12 @@ public Deferred call(Boolean success) throws Exception { * @param query The query to parse and respond to */ void executeBulk(final TSDB tsdb, final HttpMethod method, HttpQuery query) { + RpcUtil.allowedMethods(query.method(), HttpMethod.PUT.getName(), HttpMethod.POST.getName(), HttpMethod.DELETE.getName()); + if (method == HttpMethod.POST || method == HttpMethod.PUT) { executeBulkUpdate(tsdb, method, query); } else if (method == HttpMethod.DELETE) { executeBulkDelete(tsdb, query); - } else { - throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED, - "Method not allowed", "The HTTP method [" + query.method().getName() + - "] is not permitted for this endpoint"); } } diff --git a/src/tsd/DropCachesRpc.java b/src/tsd/DropCachesRpc.java new file mode 100644 index 0000000000..8a273d136d --- /dev/null +++ b/src/tsd/DropCachesRpc.java @@ -0,0 +1,88 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2013 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.tsd; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Pattern; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.base.Splitter; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.Atomics; +import com.stumbleupon.async.Callback; +import com.stumbleupon.async.Deferred; + +import org.jboss.netty.channel.Channel; +import org.jboss.netty.handler.codec.http.HttpMethod; +import org.jboss.netty.handler.codec.http.HttpResponseStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.opentsdb.tools.BuildData; +import net.opentsdb.core.Aggregators; +import net.opentsdb.core.TSDB; +import net.opentsdb.query.filter.TagVFilter; +import net.opentsdb.stats.StatsCollector; +import net.opentsdb.utils.Config; +import net.opentsdb.utils.JSON; +import net.opentsdb.utils.PluginLoader; + +import java.io.IOException; + +/** The "dropcaches" command. */ +public final class DropCachesRpc implements TelnetRpc, HttpRpc { + private static final Logger LOG = LoggerFactory.getLogger(DropCachesRpc.class); + + public Deferred execute(final TSDB tsdb, final Channel chan, + final String[] cmd) { + dropCaches(tsdb, chan); + chan.write("Caches dropped.\n"); + return Deferred.fromResult(null); + } + + public void execute(final TSDB tsdb, final HttpQuery query) + throws IOException { + + // only accept GET + RpcUtil.allowedMethods(query.method(), HttpMethod.GET.getName(), HttpMethod.DELETE.getName()); + + dropCaches(tsdb, query.channel()); + + if (query.apiVersion() > 0) { + final HashMap response = new HashMap(); + response.put("status", "200"); + response.put("message", "Caches dropped"); + query.sendReply(query.serializer().formatDropCachesV1(response)); + } else { // deprecated API + query.sendReply("Caches dropped.\n"); + } + } + + /** Drops in memory caches. */ + private void dropCaches(final TSDB tsdb, final Channel chan) { + LOG.warn(chan + " Dropping all in-memory caches."); + tsdb.dropCaches(); + } +} \ No newline at end of file diff --git a/src/tsd/GraphHandler.java b/src/tsd/GraphHandler.java index 2669d6b8c6..5128e24321 100644 --- a/src/tsd/GraphHandler.java +++ b/src/tsd/GraphHandler.java @@ -35,6 +35,7 @@ import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JsonMappingException; +import org.jboss.netty.handler.codec.http.HttpMethod; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -106,6 +107,10 @@ public GraphHandler() { } public void execute(final TSDB tsdb, final HttpQuery query) { + + // only accept GET/POST + RpcUtil.allowedMethods(query.method(), HttpMethod.GET.getName(), HttpMethod.POST.getName()); + if (!query.hasQueryStringParam("json") && !query.hasQueryStringParam("png") && !query.hasQueryStringParam("ascii")) { diff --git a/src/tsd/HttpRpc.java b/src/tsd/HttpRpc.java index 40dec97cc4..73b3fe783c 100644 --- a/src/tsd/HttpRpc.java +++ b/src/tsd/HttpRpc.java @@ -26,6 +26,6 @@ interface HttpRpc { * @param tsdb The TSDB to use. * @param query The HTTP query to execute. */ - void execute(TSDB tsdb, HttpQuery query) throws IOException; + void execute(TSDB tsdb, HttpQuery query) throws BadRequestException, IOException; } diff --git a/src/tsd/LogsRpc.java b/src/tsd/LogsRpc.java index 7aa91259a7..da8afa8cd1 100644 --- a/src/tsd/LogsRpc.java +++ b/src/tsd/LogsRpc.java @@ -12,6 +12,7 @@ // see . package net.opentsdb.tsd; +import org.jboss.netty.handler.codec.http.HttpMethod; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.JsonGenerationException; @@ -35,8 +36,13 @@ final class LogsRpc implements HttpRpc { public void execute(final TSDB tsdb, final HttpQuery query) - throws JsonGenerationException, IOException { + throws BadRequestException, IOException { + + // only accept GET/POST + RpcUtil.allowedMethods(query.method(), HttpMethod.GET.getName(), HttpMethod.POST.getName()); + LogIterator logmsgs = new LogIterator(); + if (query.hasQueryStringParam("json")) { ArrayList logs = new ArrayList(); for (String log : logmsgs) { diff --git a/src/tsd/PutDataPointRpc.java b/src/tsd/PutDataPointRpc.java index 71cd8ecc5d..553761dbb2 100644 --- a/src/tsd/PutDataPointRpc.java +++ b/src/tsd/PutDataPointRpc.java @@ -109,16 +109,13 @@ public String toString() { * @since 2.0 */ public void execute(final TSDB tsdb, final HttpQuery query) - throws IOException { + throws BadRequestException, IOException { requests.incrementAndGet(); - + + // only accept POST - if (query.method() != HttpMethod.POST) { - throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED, - "Method not allowed", "The HTTP method [" + query.method().getName() + - "] is not permitted for this endpoint"); - } - + RpcUtil.allowedMethods(query.method(), HttpMethod.POST.getName()); + final List dps = query.serializer().parsePutV1(); if (dps.size() < 1) { throw new BadRequestException("No datapoints found in content"); diff --git a/src/tsd/QueryRpc.java b/src/tsd/QueryRpc.java index 2f6d92be24..f10ec7ccc3 100644 --- a/src/tsd/QueryRpc.java +++ b/src/tsd/QueryRpc.java @@ -81,16 +81,12 @@ final class QueryRpc implements HttpRpc { */ @Override public void execute(final TSDB tsdb, final HttpQuery query) - throws IOException { - + throws BadRequestException, IOException { + // only accept GET/POST/DELETE - if (query.method() != HttpMethod.GET && query.method() != HttpMethod.POST && - query.method() != HttpMethod.DELETE) { - throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED, - "Method not allowed", "The HTTP method [" + query.method().getName() + - "] is not permitted for this endpoint"); - } - if (query.method() == HttpMethod.DELETE && + RpcUtil.allowedMethods(query.method(), HttpMethod.GET.getName(), HttpMethod.DELETE.getName(), HttpMethod.POST.getName()); + + if (query.method() == HttpMethod.DELETE && !tsdb.getConfig().getBoolean("tsd.http.query.allow_delete")) { throw new BadRequestException(HttpResponseStatus.BAD_REQUEST, "Bad request", diff --git a/src/tsd/RpcHandler.java b/src/tsd/RpcHandler.java index 69224ca8b5..66187e5d22 100644 --- a/src/tsd/RpcHandler.java +++ b/src/tsd/RpcHandler.java @@ -249,6 +249,14 @@ private boolean applyCorsConfig(final HttpRequest req, final AbstractHttpQuery q * @param req The parsed HTTP request. */ private void handleHttpQuery(final TSDB tsdb, final Channel chan, final HttpRequest req) { + // quick bail if not GET/POST/OPTIONS/PUT/DELETE, no other methods are allowed anywhere + try { + RpcUtil.allowedMethods(req.getMethod(), HttpMethod.GET.getName(), HttpMethod.POST.getName(), + HttpMethod.OPTIONS.getName(), HttpMethod.PUT.getName(), HttpMethod.DELETE.getName()); + } catch (BadRequestException bre) { + sendStatusAndClose(chan, HttpResponseStatus.METHOD_NOT_ALLOWED); + } + AbstractHttpQuery abstractQuery = null; try { abstractQuery = createQueryInstance(tsdb, req, chan); diff --git a/src/tsd/RpcManager.java b/src/tsd/RpcManager.java index 1814326c34..6a9b41dbdc 100644 --- a/src/tsd/RpcManager.java +++ b/src/tsd/RpcManager.java @@ -76,7 +76,7 @@ */ public final class RpcManager { private static final Logger LOG = LoggerFactory.getLogger(RpcManager.class); - + /** This is base path where {@link HttpRpcPlugin}s are rooted. It's used * to match incoming requests. */ @VisibleForTesting @@ -132,8 +132,6 @@ public static synchronized RpcManager instance(final TSDB tsdb) { } final RpcManager manager = new RpcManager(tsdb); - final String mode = Strings.nullToEmpty(tsdb.getConfig().getString("tsd.mode")); - // Load any plugins that are enabled via Config. Fail if any plugin cannot be loaded. final ImmutableList.Builder rpcBuilder = ImmutableList.builder(); @@ -145,14 +143,14 @@ public static synchronized RpcManager instance(final TSDB tsdb) { final ImmutableMap.Builder telnetBuilder = ImmutableMap.builder(); final ImmutableMap.Builder httpBuilder = ImmutableMap.builder(); - manager.initializeBuiltinRpcs(mode, telnetBuilder, httpBuilder); + manager.initializeBuiltinRpcs(telnetBuilder, httpBuilder); manager.telnet_commands = telnetBuilder.build(); manager.http_commands = httpBuilder.build(); final ImmutableMap.Builder httpPluginsBuilder = ImmutableMap.builder(); if (tsdb.getConfig().hasProperty("tsd.http.rpc.plugins")) { final String[] plugins = tsdb.getConfig().getString("tsd.http.rpc.plugins").split(","); - manager.initializeHttpRpcPlugins(mode, plugins, httpPluginsBuilder); + manager.initializeHttpRpcPlugins(plugins, httpPluginsBuilder); } manager.http_plugin_commands = httpPluginsBuilder.build(); @@ -242,81 +240,84 @@ boolean isHttpRpcPluginPath(final String uri) { /** * Load and init instances of {@link TelnetRpc}s and {@link HttpRpc}s. * These are not generally configurable via TSDB config. - * @param mode is this TSD in read/write ("rw") or read-only ("ro") - * mode? * @param telnet a map of telnet command names to {@link TelnetRpc} * instances. * @param http a map of API endpoints to {@link HttpRpc} instances. */ - private void initializeBuiltinRpcs(final String mode, - final ImmutableMap.Builder telnet, + private void initializeBuiltinRpcs(final ImmutableMap.Builder telnet, final ImmutableMap.Builder http) { + + final String mode = Strings.nullToEmpty(tsdb.getConfig().getString("tsd.mode")); + final Boolean enableApi = tsdb.getConfig().getString("tsd.core.enable_api").equals("true"); + final Boolean enableUi = tsdb.getConfig().getString("tsd.core.enable_ui").equals("true"); + final Boolean enableDieDieDie = tsdb.getConfig().getString("tsd.no_diediedie").equals("false"); + if (mode.equals("rw") || mode.equals("wo")) { final PutDataPointRpc put = new PutDataPointRpc(); telnet.put("put", put); - http.put("api/put", put); + if (enableApi) { + http.put("api/put", put); + } } if (mode.equals("rw") || mode.equals("ro")) { - http.put("", new HomePage()); final StaticFileRpc staticfile = new StaticFileRpc(); - http.put("favicon.ico", staticfile); - http.put("s", staticfile); - final StatsRpc stats = new StatsRpc(); - telnet.put("stats", stats); - http.put("stats", stats); - http.put("api/stats", stats); + final DropCachesRpc dropcaches = new DropCachesRpc(); + final ListAggregators aggregators = new ListAggregators(); + final SuggestRpc suggest_rpc = new SuggestRpc(); + final AnnotationRpc annotation_rpc = new AnnotationRpc(); + final Version version = new Version(); - final DropCaches dropcaches = new DropCaches(); + telnet.put("stats", stats); telnet.put("dropcaches", dropcaches); - http.put("dropcaches", dropcaches); - http.put("api/dropcaches", dropcaches); + telnet.put("version", version); + telnet.put("exit", new Exit()); + telnet.put("help", new Help()); - final ListAggregators aggregators = new ListAggregators(); - http.put("aggregators", aggregators); - http.put("api/aggregators", aggregators); + if (enableUi) { + http.put("", new HomePage()); + http.put("aggregators", aggregators); + http.put("dropcaches", dropcaches); + http.put("favicon.ico", staticfile); + http.put("logs", new LogsRpc()); + http.put("q", new GraphHandler()); + http.put("s", staticfile); + http.put("stats", stats); + http.put("suggest", suggest_rpc); + http.put("version", version); + } - final SuggestRpc suggest_rpc = new SuggestRpc(); - http.put("suggest", suggest_rpc); - http.put("api/suggest", suggest_rpc); - - http.put("logs", new LogsRpc()); - http.put("q", new GraphHandler()); - http.put("api/serializers", new Serializers()); - http.put("api/uid", new UniqueIdRpc()); - http.put("api/query", new QueryRpc()); - http.put("api/tree", new TreeRpc()); - { - final AnnotationRpc annotation_rpc = new AnnotationRpc(); + if (enableApi) { + http.put("api/aggregators", aggregators); http.put("api/annotation", annotation_rpc); http.put("api/annotations", annotation_rpc); - } - http.put("api/search", new SearchRpc()); - http.put("api/config", new ShowConfig()); - - if (tsdb.getConfig().getString("tsd.no_diediedie").equals("false")) { - final DieDieDie diediedie = new DieDieDie(); - telnet.put("diediedie", diediedie); - http.put("diediedie", diediedie); - } - { - final Version version = new Version(); - telnet.put("version", version); - http.put("version", version); + http.put("api/config", new ShowConfig()); + http.put("api/dropcaches", dropcaches); + http.put("api/query", new QueryRpc()); + http.put("api/search", new SearchRpc()); + http.put("api/serializers", new Serializers()); + http.put("api/stats", stats); + http.put("api/suggest", suggest_rpc); + http.put("api/tree", new TreeRpc()); + http.put("api/uid", new UniqueIdRpc()); http.put("api/version", version); } + } - telnet.put("exit", new Exit()); - telnet.put("help", new Help()); + if (enableDieDieDie) { + final DieDieDie diediedie = new DieDieDie(); + telnet.put("diediedie", diediedie); + if (enableUi) { + http.put("diediedie", diediedie); + } } } /** * Load and init the {@link HttpRpcPlugin}s provided as an array of * {@code pluginClassNames}. - * @param mode is this TSD in read/write ("rw") or read-only ("ro") - * mode? + * @param pluginClassNames fully-qualified class names that are * instances of {@link HttpRpcPlugin}s * @param http a map of canonicalized paths @@ -324,9 +325,11 @@ private void initializeBuiltinRpcs(final String mode, * to {@link HttpRpcPlugin} instance. */ @VisibleForTesting - protected void initializeHttpRpcPlugins(final String mode, - final String[] pluginClassNames, + protected void initializeHttpRpcPlugins(final String[] pluginClassNames, final ImmutableMap.Builder http) { + + final String mode = Strings.nullToEmpty(tsdb.getConfig().getString("tsd.mode")); + for (final String plugin : pluginClassNames) { final HttpRpcPlugin rpc = createAndInitialize(plugin, HttpRpcPlugin.class); validateHttpRpcPluginPath(rpc.getPath()); @@ -577,12 +580,8 @@ private static final class ListAggregators implements HttpRpc { public void execute(final TSDB tsdb, final HttpQuery query) throws IOException { - // only accept GET/POST - if (query.method() != HttpMethod.GET && query.method() != HttpMethod.POST) { - throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED, - "Method not allowed", "The HTTP method [" + query.method().getName() + - "] is not permitted for this endpoint"); - } + // only accept GET + RpcUtil.allowedMethods(query.method(), HttpMethod.GET.getName()); if (query.apiVersion() > 0) { query.sendReply( @@ -607,12 +606,8 @@ public Deferred execute(final TSDB tsdb, final Channel chan, public void execute(final TSDB tsdb, final HttpQuery query) throws IOException { - // only accept GET/POST - if (query.method() != HttpMethod.GET && query.method() != HttpMethod.POST) { - throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED, - "Method not allowed", "The HTTP method [" + query.method().getName() + - "] is not permitted for this endpoint"); - } + // only accept GET + RpcUtil.allowedMethods(query.method(), HttpMethod.GET.getName()); final HashMap version = new HashMap(); version.put("version", BuildData.version); @@ -643,64 +638,23 @@ public void execute(final TSDB tsdb, final HttpQuery query) throws } } } - - /** The "dropcaches" command. */ - private static final class DropCaches implements TelnetRpc, HttpRpc { - public Deferred execute(final TSDB tsdb, final Channel chan, - final String[] cmd) { - dropCaches(tsdb, chan); - chan.write("Caches dropped.\n"); - return Deferred.fromResult(null); - } - public void execute(final TSDB tsdb, final HttpQuery query) - throws IOException { - dropCaches(tsdb, query.channel()); - - // only accept GET/POST - if (query.method() != HttpMethod.GET && query.method() != HttpMethod.POST) { - throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED, - "Method not allowed", "The HTTP method [" + query.method().getName() + - "] is not permitted for this endpoint"); - } - - if (query.apiVersion() > 0) { - final HashMap response = new HashMap(); - response.put("status", "200"); - response.put("message", "Caches dropped"); - query.sendReply(query.serializer().formatDropCachesV1(response)); - } else { // deprecated API - query.sendReply("Caches dropped.\n"); - } - } - - /** Drops in memory caches. */ - private void dropCaches(final TSDB tsdb, final Channel chan) { - LOG.warn(chan + " Dropping all in-memory caches."); - tsdb.dropCaches(); - } - } - - /** The /api/formatters endpoint + /** The /api/formatters endpoint * @since 2.0 */ private static final class Serializers implements HttpRpc { - public void execute(final TSDB tsdb, final HttpQuery query) + public void execute(final TSDB tsdb, final HttpQuery query) throws IOException { - // only accept GET/POST - if (query.method() != HttpMethod.GET && query.method() != HttpMethod.POST) { - throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED, - "Method not allowed", "The HTTP method [" + query.method().getName() + - "] is not permitted for this endpoint"); - } - + // only accept GET + RpcUtil.allowedMethods(query.method(), HttpMethod.GET.getName()); + switch (query.apiVersion()) { case 0: case 1: query.sendReply(query.serializer().formatSerializersV1()); break; - default: - throw new BadRequestException(HttpResponseStatus.NOT_IMPLEMENTED, - "Requested API version not implemented", "Version " + + default: + throw new BadRequestException(HttpResponseStatus.NOT_IMPLEMENTED, + "Requested API version not implemented", "Version " + query.apiVersion() + " is not implemented"); } } @@ -709,13 +663,9 @@ public void execute(final TSDB tsdb, final HttpQuery query) private static final class ShowConfig implements HttpRpc { @Override public void execute(TSDB tsdb, HttpQuery query) throws IOException { - // only accept GET/POST - if (query.method() != HttpMethod.GET && query.method() != HttpMethod.POST) { - throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED, - "Method not allowed", "The HTTP method [" + query.method().getName() + - "] is not permitted for this endpoint"); - } - + // only accept GET + RpcUtil.allowedMethods(query.method(), HttpMethod.GET.getName()); + final String[] uri = query.explodeAPIPath(); final String endpoint = uri.length > 1 ? uri[1].toLowerCase() : ""; diff --git a/src/tsd/RpcUtil.java b/src/tsd/RpcUtil.java new file mode 100644 index 0000000000..07660e208f --- /dev/null +++ b/src/tsd/RpcUtil.java @@ -0,0 +1,37 @@ +// This file is part of OpenTSDB. +// Copyright (C) 2010-2014 The OpenTSDB Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2.1 of the License, or (at your +// option) any later version. This program is distributed in the hope that it +// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. You should have received a copy +// of the GNU Lesser General Public License along with this program. If not, +// see . +package net.opentsdb.tsd; + +import org.jboss.netty.handler.codec.http.HttpMethod; +import org.jboss.netty.handler.codec.http.HttpResponseStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +public class RpcUtil { + + private static final Logger LOG = LoggerFactory.getLogger(RpcUtil.class); + + public static void allowedMethods(HttpMethod requestMethod, String... allowedMethods) { + for(String method : allowedMethods) { + LOG.debug(String.format("Trying Method: %s", method)); + if (requestMethod.getName() == method) { + LOG.debug(String.format("Method Allowed: %s", method)); + return; + } + } + throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED, + "Method not allowed", "The HTTP method [" + requestMethod.getName() + "] is not permitted for this endpoint"); + } +} diff --git a/src/tsd/SearchRpc.java b/src/tsd/SearchRpc.java index 64b3ee38f7..aa6a2e026e 100644 --- a/src/tsd/SearchRpc.java +++ b/src/tsd/SearchRpc.java @@ -55,12 +55,9 @@ final class SearchRpc implements HttpRpc { */ @Override public void execute(TSDB tsdb, HttpQuery query) { - final HttpMethod method = query.getAPIMethod(); - if (method != HttpMethod.GET && method != HttpMethod.POST) { - throw new BadRequestException("Unsupported method: " + method.getName()); - } - + RpcUtil.allowedMethods(method, HttpMethod.GET.getName(), HttpMethod.POST.getName()); + // the uri will be /api/vX/search/ or /api/search/ final String[] uri = query.explodeAPIPath(); final String endpoint = uri.length > 1 ? uri[1] : ""; diff --git a/src/tsd/StaticFileRpc.java b/src/tsd/StaticFileRpc.java index f3f8c552ef..28f4220bf9 100644 --- a/src/tsd/StaticFileRpc.java +++ b/src/tsd/StaticFileRpc.java @@ -15,6 +15,7 @@ import java.io.IOException; import net.opentsdb.core.TSDB; +import org.jboss.netty.handler.codec.http.HttpMethod; /** Implements the "/s" endpoint to serve static files. */ final class StaticFileRpc implements HttpRpc { @@ -26,13 +27,18 @@ public StaticFileRpc() { } public void execute(final TSDB tsdb, final HttpQuery query) - throws IOException { + throws BadRequestException, IOException { + + // only accept GET + RpcUtil.allowedMethods(query.method(), HttpMethod.GET.getName()); + final String uri = query.request().getUri(); if ("/favicon.ico".equals(uri)) { query.sendFile(tsdb.getConfig().getDirectoryName("tsd.http.staticroot") + "/favicon.ico", 31536000 /*=1yr*/); return; } + if (uri.length() < 3) { // Must be at least 3 because of the "/s/". throw new BadRequestException("URI too short " + uri + ""); } @@ -41,6 +47,7 @@ public void execute(final TSDB tsdb, final HttpQuery query) if (uri.indexOf("..", 3) > 0) { throw new BadRequestException("Malformed URI " + uri + ""); } + final int questionmark = uri.indexOf('?', 3); final int pathend = questionmark > 0 ? questionmark : uri.length(); query.sendFile(tsdb.getConfig().getDirectoryName("tsd.http.staticroot") diff --git a/src/tsd/StatsRpc.java b/src/tsd/StatsRpc.java index bd0ee910d1..7dd67c2503 100644 --- a/src/tsd/StatsRpc.java +++ b/src/tsd/StatsRpc.java @@ -12,6 +12,7 @@ // see . package net.opentsdb.tsd; +import java.io.IOException; import java.lang.management.GarbageCollectorMXBean; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; @@ -67,18 +68,14 @@ public Deferred execute(final TSDB tsdb, final Channel chan, } /** - * HTTP resposne handler + * HTTP response handler * @param tsdb The TSDB to which we belong * @param query The query to parse and respond to */ - public void execute(final TSDB tsdb, final HttpQuery query) { - // only accept GET/POST - if (query.method() != HttpMethod.GET && query.method() != HttpMethod.POST) { - throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED, - "Method not allowed", "The HTTP method [" + query.method().getName() + - "] is not permitted for this endpoint"); - } - + public void execute(final TSDB tsdb, final HttpQuery query) throws BadRequestException, IOException { + + RpcUtil.allowedMethods(query.method(), HttpMethod.GET.getName(), HttpMethod.POST.getName()); + try { final String[] uri = query.explodeAPIPath(); final String endpoint = uri.length > 1 ? uri[1].toLowerCase() : ""; diff --git a/src/tsd/SuggestRpc.java b/src/tsd/SuggestRpc.java index 7c8601ddf8..6e4c677919 100644 --- a/src/tsd/SuggestRpc.java +++ b/src/tsd/SuggestRpc.java @@ -39,14 +39,10 @@ final class SuggestRpc implements HttpRpc { */ public void execute(final TSDB tsdb, final HttpQuery query) throws IOException { - + // only accept GET/POST - if (query.method() != HttpMethod.GET && query.method() != HttpMethod.POST) { - throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED, - "Method not allowed", "The HTTP method [" + query.method().getName() + - "] is not permitted for this endpoint"); - } - + RpcUtil.allowedMethods(query.method(), HttpMethod.GET.getName(), HttpMethod.POST.getName()); + final String type; final String q; final String max; diff --git a/src/tsd/TreeRpc.java b/src/tsd/TreeRpc.java index 380c4eb308..3cdfe436fd 100644 --- a/src/tsd/TreeRpc.java +++ b/src/tsd/TreeRpc.java @@ -52,6 +52,9 @@ final class TreeRpc implements HttpRpc { */ @Override public void execute(TSDB tsdb, HttpQuery query) throws IOException { + // only accept GET/POST + RpcUtil.allowedMethods(query.method(), HttpMethod.GET.getName(), HttpMethod.POST.getName()); + // the uri will be /api/vX/tree/? or /api/tree/? final String[] uri = query.explodeAPIPath(); final String endpoint = uri.length > 1 ? uri[1] : ""; @@ -208,11 +211,9 @@ private void handleTree(TSDB tsdb, HttpQuery query) { * @throws BadRequestException if the request was invalid. */ private void handleBranch(TSDB tsdb, HttpQuery query) { - if (query.getAPIMethod() != HttpMethod.GET) { - throw new BadRequestException(HttpResponseStatus.BAD_REQUEST, - "Unsupported HTTP request method"); - } - + + RpcUtil.allowedMethods(query.getAPIMethod(), HttpMethod.GET.getName(), HttpMethod.POST.getName()); + try { final int tree_id = parseTreeId(query, false); final String branch_hex = diff --git a/src/tsd/UniqueIdRpc.java b/src/tsd/UniqueIdRpc.java index a9057866f8..fe2c839237 100644 --- a/src/tsd/UniqueIdRpc.java +++ b/src/tsd/UniqueIdRpc.java @@ -87,12 +87,8 @@ public void execute(TSDB tsdb, HttpQuery query) throws IOException { * @param query The query for this request */ private void handleAssign(final TSDB tsdb, final HttpQuery query) { - // only accept GET And POST - if (query.method() != HttpMethod.GET && query.method() != HttpMethod.POST) { - throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED, - "Method not allowed", "The HTTP method [" + query.method().getName() + - "] is not permitted for this endpoint"); - } + // only accept GET/POST + RpcUtil.allowedMethods(query.method(), HttpMethod.GET.getName(), HttpMethod.POST.getName()); final HashMap> source; if (query.method() == HttpMethod.POST) { @@ -160,55 +156,57 @@ private void handleAssign(final TSDB tsdb, final HttpQuery query) { private void handleUIDMeta(final TSDB tsdb, final HttpQuery query) { final HttpMethod method = query.getAPIMethod(); + RpcUtil.allowedMethods(method, HttpMethod.GET.getName(), HttpMethod.POST.getName(), HttpMethod.PUT.getName(), HttpMethod.DELETE.getName()); + // GET if (method == HttpMethod.GET) { - + final String uid = query.getRequiredQueryStringParam("uid"); final UniqueIdType type = UniqueId.stringToUniqueIdType( - query.getRequiredQueryStringParam("type")); + query.getRequiredQueryStringParam("type")); try { final UIDMeta meta = UIDMeta.getUIDMeta(tsdb, type, uid) - .joinUninterruptibly(); + .joinUninterruptibly(); query.sendReply(query.serializer().formatUidMetaV1(meta)); } catch (NoSuchUniqueId e) { - throw new BadRequestException(HttpResponseStatus.NOT_FOUND, - "Could not find the requested UID", e); + throw new BadRequestException(HttpResponseStatus.NOT_FOUND, + "Could not find the requested UID", e); } catch (Exception e) { throw new RuntimeException(e); } - // POST + // POST } else if (method == HttpMethod.POST || method == HttpMethod.PUT) { - + final UIDMeta meta; if (query.hasContent()) { meta = query.serializer().parseUidMetaV1(); } else { meta = this.parseUIDMetaQS(query); } - + /** * Storage callback used to determine if the storage call was successful * or not. Also returns the updated object from storage. */ class SyncCB implements Callback, Boolean> { - + @Override public Deferred call(Boolean success) throws Exception { if (!success) { throw new BadRequestException( - HttpResponseStatus.INTERNAL_SERVER_ERROR, - "Failed to save the UIDMeta to storage", - "This may be caused by another process modifying storage data"); + HttpResponseStatus.INTERNAL_SERVER_ERROR, + "Failed to save the UIDMeta to storage", + "This may be caused by another process modifying storage data"); } - + return UIDMeta.getUIDMeta(tsdb, meta.getType(), meta.getUID()); } - + } - + try { - final Deferred process_meta = meta.syncToStorage(tsdb, - method == HttpMethod.PUT).addCallbackDeferring(new SyncCB()); + final Deferred process_meta = meta.syncToStorage(tsdb, + method == HttpMethod.PUT).addCallbackDeferring(new SyncCB()); final UIDMeta updated_meta = process_meta.joinUninterruptibly(); tsdb.indexUIDMeta(updated_meta); query.sendReply(query.serializer().formatUidMetaV1(updated_meta)); @@ -217,37 +215,32 @@ public Deferred call(Boolean success) throws Exception { } catch (IllegalArgumentException e) { throw new BadRequestException(e); } catch (NoSuchUniqueId e) { - throw new BadRequestException(HttpResponseStatus.NOT_FOUND, - "Could not find the requested UID", e); + throw new BadRequestException(HttpResponseStatus.NOT_FOUND, + "Could not find the requested UID", e); } catch (Exception e) { throw new RuntimeException(e); } - // DELETE + // DELETE } else if (method == HttpMethod.DELETE) { - + final UIDMeta meta; if (query.hasContent()) { meta = query.serializer().parseUidMetaV1(); } else { meta = this.parseUIDMetaQS(query); } - try { + try { meta.delete(tsdb).joinUninterruptibly(); tsdb.deleteUIDMeta(meta); } catch (IllegalArgumentException e) { throw new BadRequestException("Unable to delete UIDMeta information", e); } catch (NoSuchUniqueId e) { - throw new BadRequestException(HttpResponseStatus.NOT_FOUND, - "Could not find the requested UID", e); + throw new BadRequestException(HttpResponseStatus.NOT_FOUND, + "Could not find the requested UID", e); } catch (Exception e) { throw new RuntimeException(e); } query.sendStatusOnly(HttpResponseStatus.NO_CONTENT); - - } else { - throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED, - "Method not allowed", "The HTTP method [" + method.getName() + - "] is not permitted for this endpoint"); } } @@ -259,6 +252,8 @@ public Deferred call(Boolean success) throws Exception { private void handleTSMeta(final TSDB tsdb, final HttpQuery query) { final HttpMethod method = query.getAPIMethod(); + RpcUtil.allowedMethods(method, HttpMethod.GET.getName(), HttpMethod.POST.getName(), HttpMethod.DELETE.getName(), HttpMethod.PUT.getName()); + // GET if (method == HttpMethod.GET) { @@ -445,10 +440,6 @@ public Boolean call(Boolean exists) throws Exception { throw new BadRequestException("Unable to delete TSMeta information", e); } query.sendStatusOnly(HttpResponseStatus.NO_CONTENT); - } else { - throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED, - "Method not allowed", "The HTTP method [" + method.getName() + - "] is not permitted for this endpoint"); } } @@ -491,14 +482,12 @@ private UIDMeta parseUIDMetaQS(final HttpQuery query) { * @param query The query for this request */ private void handleRename(final TSDB tsdb, final HttpQuery query) { + // only accept GET and POST - if (query.method() != HttpMethod.GET && query.method() != HttpMethod.POST) { - throw new BadRequestException(HttpResponseStatus.METHOD_NOT_ALLOWED, - "Method not allowed", "The HTTP method[" + query.method().getName() + - "] is not permitted for this endpoint"); - } + RpcUtil.allowedMethods(query.method(), HttpMethod.GET.getName(), HttpMethod.POST.getName()); final HashMap source; + if (query.method() == HttpMethod.POST) { source = query.serializer().parseUidRenameV1(); } else { diff --git a/src/utils/Config.java b/src/utils/Config.java index 4927d43fbb..782bf36430 100644 --- a/src/utils/Config.java +++ b/src/utils/Config.java @@ -488,6 +488,8 @@ protected void setDefaults() { default_map.put("tsd.core.auto_create_tagks", "true"); default_map.put("tsd.core.auto_create_tagvs", "true"); default_map.put("tsd.core.connections.limit", "0"); + default_map.put("tsd.core.enable_api", "true"); + default_map.put("tsd.core.enable_ui", "true"); default_map.put("tsd.core.meta.enable_realtime_ts", "false"); default_map.put("tsd.core.meta.enable_realtime_uid", "false"); default_map.put("tsd.core.meta.enable_tsuid_incrementing", "false");