diff --git a/reports-scheduler/build.gradle b/reports-scheduler/build.gradle index caadd6b2..0f68710d 100644 --- a/reports-scheduler/build.gradle +++ b/reports-scheduler/build.gradle @@ -127,6 +127,9 @@ dependencies { compile "${group}:common-utils:${opendistroVersion}.2" compileOnly "${group}:opendistro-job-scheduler-spi:${opendistroVersion}.0" compile group: 'com.google.guava', name: 'guava', version: '15.0' + compile "org.json:json:20180813" + compile group: 'com.github.wnameless', name: 'json-flattener', version: '0.1.0' + testImplementation( 'org.assertj:assertj-core:3.16.1', 'org.junit.jupiter:junit-jupiter-api:5.6.2' diff --git a/reports-scheduler/gradle.properties b/reports-scheduler/gradle.properties index 6de2cfcd..52ef91df 100644 --- a/reports-scheduler/gradle.properties +++ b/reports-scheduler/gradle.properties @@ -1,5 +1,5 @@ # -# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). # You may not use this file except in compliance with the License. diff --git a/reports-scheduler/src/main/java/com/amazon/opendistroforelasticsearch/reportsscheduler/metrics/BasicCounter.java b/reports-scheduler/src/main/java/com/amazon/opendistroforelasticsearch/reportsscheduler/metrics/BasicCounter.java new file mode 100644 index 00000000..db5de4c9 --- /dev/null +++ b/reports-scheduler/src/main/java/com/amazon/opendistroforelasticsearch/reportsscheduler/metrics/BasicCounter.java @@ -0,0 +1,55 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.metrics; + +import java.util.concurrent.atomic.LongAdder; + +/** + * Counter to hold accumulative value over time. + */ +public class BasicCounter implements Counter { + private final LongAdder count = new LongAdder(); + + /** + * {@inheritDoc} + */ + @Override + public void increment() { + count.increment(); + } + + /** + * {@inheritDoc} + */ + @Override + public void add(long n) { + count.add(n); + } + + /** + * {@inheritDoc} + */ + @Override + public Long getValue() { + return count.longValue(); + } + + /** Reset the count value to zero*/ + @Override + public void reset() { + count.reset(); + } +} diff --git a/reports-scheduler/src/main/java/com/amazon/opendistroforelasticsearch/reportsscheduler/metrics/Counter.java b/reports-scheduler/src/main/java/com/amazon/opendistroforelasticsearch/reportsscheduler/metrics/Counter.java new file mode 100644 index 00000000..3270f347 --- /dev/null +++ b/reports-scheduler/src/main/java/com/amazon/opendistroforelasticsearch/reportsscheduler/metrics/Counter.java @@ -0,0 +1,34 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.metrics; + +/** + * Defines a generic counter. + */ +public interface Counter { + + /** Increments the count value by 1 unit*/ + void increment(); + + /** Increments the count value by n unit*/ + void add(long n); + + /** Retrieves the count value accumulated upto this call*/ + T getValue(); + + /** Resets the count value to initial value when Counter is created*/ + void reset(); +} diff --git a/reports-scheduler/src/main/java/com/amazon/opendistroforelasticsearch/reportsscheduler/metrics/Metrics.java b/reports-scheduler/src/main/java/com/amazon/opendistroforelasticsearch/reportsscheduler/metrics/Metrics.java new file mode 100644 index 00000000..246a0075 --- /dev/null +++ b/reports-scheduler/src/main/java/com/amazon/opendistroforelasticsearch/reportsscheduler/metrics/Metrics.java @@ -0,0 +1,197 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.metrics; + +import com.github.wnameless.json.unflattener.JsonUnflattener; +import org.json.JSONObject; + +/** + * Enum to hold all the metrics that need to be logged into _opendistro/_reports/l_ocal/stats API + */ +public enum Metrics { + + REQUEST_TOTAL("request_total", new BasicCounter()), + REQUEST_INTERVAL_COUNT("request_count", new RollingCounter()), + REQUEST_SUCCESS("success_count", new RollingCounter()), + REQUEST_USER_ERROR("failed_request_count_user_error", new RollingCounter()), + REQUEST_SYSTEM_ERROR("failed_request_count_system_error", new RollingCounter()), + + /** + * Exceptions from: + * @see com.amazon.opendistroforelasticsearch.reportsscheduler.action.PluginBaseAction + */ + REPORT_EXCEPTIONS_ES_STATUS_EXCEPTION("exception.es_status", new RollingCounter()), + REPORT_EXCEPTIONS_ES_SECURITY_EXCEPTION("exception.es_security", new RollingCounter()), + REPORT_EXCEPTIONS_VERSION_CONFLICT_ENGINE_EXCEPTION("exception.version_conflict_engine", new RollingCounter()), + REPORT_EXCEPTIONS_INDEX_NOT_FOUND_EXCEPTION("exception.index_not_found", new RollingCounter()), + REPORT_EXCEPTIONS_INVALID_INDEX_NAME_EXCEPTION("exception.invalid_index_name", new RollingCounter()), + REPORT_EXCEPTIONS_ILLEGAL_ARGUMENT_EXCEPTION("exception.illegal_argument", new RollingCounter()), + REPORT_EXCEPTIONS_ILLEGAL_STATE_EXCEPTION("exception.illegal_state", new RollingCounter()), + REPORT_EXCEPTIONS_IO_EXCEPTION("exception.io", new RollingCounter()), + REPORT_EXCEPTIONS_INTERNAL_SERVER_ERROR("exception.internal_server_error", new RollingCounter()), + + // ==== Per REST endpoint metrics ==== // + + // POST _opendistro/_reports/definition + REPORT_DEFINITION_CREATE_TOTAL("report_definition.create.total", new BasicCounter()), + REPORT_DEFINITION_CREATE_INTERVAL_COUNT("report_definition.create.count", new RollingCounter()), + REPORT_DEFINITION_CREATE_USER_ERROR("report_definition.create.user_error", new RollingCounter()), + REPORT_DEFINITION_CREATE_SYSTEM_ERROR("report_definition.create.system_error", new RollingCounter()), + + + // PUT _opendistro/_reports/definition/{reportDefinitionId} + REPORT_DEFINITION_UPDATE_TOTAL("report_definition.update.total", new BasicCounter()), + REPORT_DEFINITION_UPDATE_INTERVAL_COUNT("report_definition.update.count", new RollingCounter()), + REPORT_DEFINITION_UPDATE_USER_ERROR_MISSING_REPORT_DEF_DETAILS( + "report_definition.update.user_error.missing_report_def_details", new RollingCounter()), + REPORT_DEFINITION_UPDATE_USER_ERROR_INVALID_REPORT_DEF_ID( + "report_definition.update.user_error.invalid_report_def_id", new RollingCounter()), + REPORT_DEFINITION_UPDATE_USER_ERROR_INVALID_REPORT_DEF( + "report_definition.update.user_error.invalid_report_definition", new RollingCounter()), + REPORT_DEFINITION_UPDATE_SYSTEM_ERROR("report_definition.update.system_error", new RollingCounter()), + + + // GET _opendistro/_reports/definition/{reportDefinitionId} + REPORT_DEFINITION_INFO_TOTAL("report_definition.info.total", new BasicCounter()), + REPORT_DEFINITION_INFO_INTERVAL_COUNT("report_definition.info.count", new RollingCounter()), + REPORT_DEFINITION_INFO_USER_ERROR_MISSING_REPORT_DEF_DETAILS( + "report_definition.info.user_error.missing_report_def_details", new RollingCounter()), + REPORT_DEFINITION_INFO_USER_ERROR_INVALID_REPORT_DEF_ID( + "report_definition.info.user_error.invalid_report_def_id", new RollingCounter()), + REPORT_DEFINITION_INFO_SYSTEM_ERROR("report_definition.info.system_error", new RollingCounter()), + + + // DELETE _opendistro/_reports/definition/{reportDefinitionId} + REPORT_DEFINITION_DELETE_TOTAL("report_definition.delete.total", new BasicCounter()), + REPORT_DEFINITION_DELETE_INTERVAL_COUNT("report_definition.delete.count", new RollingCounter()), + REPORT_DEFINITION_DELETE_USER_ERROR_MISSING_REPORT_DEF_DETAILS( + "report_definition.delete.user_error.missing_report_def_details", new RollingCounter()), + REPORT_DEFINITION_DELETE_USER_ERROR_INVALID_REPORT_DEF_ID( + "report_definition.delete.user_error.invalid_report_def_id", new RollingCounter()), + REPORT_DEFINITION_DELETE_SYSTEM_ERROR("report_definition.delete.system_error", new RollingCounter()), + + + // GET _opendistro/_reports/definitions/[?[fromIndex=0]&[maxItems=100]] + REPORT_DEFINITION_LIST_TOTAL("report_definition.list.total",new BasicCounter()), + REPORT_DEFINITION_LIST_INTERVAL_COUNT("report_definition.list.count", new RollingCounter()), + REPORT_DEFINITION_LIST_USER_ERROR_INVALID_FROM_INDEX( + "report_definition.list.user_error.invalid_from_index", new RollingCounter()), + REPORT_DEFINITION_LIST_SYSTEM_ERROR("report_definition.list.system_error", new RollingCounter()), + + + // POST _opendistro/_reports/instance/{reportInstanceId} + REPORT_INSTANCE_UPDATE_TOTAL("report_instance.update.total", new BasicCounter()), + REPORT_INSTANCE_UPDATE_INTERVAL_COUNT("report_instance.update.count", new RollingCounter()), + REPORT_INSTANCE_UPDATE_USER_ERROR_MISSING_REPORT_INSTANCE( + "report_instance.update.user_error.missing_report_instance", new RollingCounter()), + REPORT_INSTANCE_UPDATE_USER_ERROR_INVALID_STATUS( + "report_instance.update.user_error.invalid_status", new RollingCounter()), + REPORT_INSTANCE_UPDATE_USER_ERROR_INVALID_REPORT_ID( + "report_instance.update.user_error.invalid_report_id", new RollingCounter()), + REPORT_INSTANCE_UPDATE_SYSTEM_ERROR("report_instance.update.system_error", new RollingCounter()), + + + // GET _opendistro/_reports/instance/{reportInstanceId} + REPORT_INSTANCE_INFO_TOTAL("report_instance.info.total", new BasicCounter()), + REPORT_INSTANCE_INFO_INTERVAL_COUNT("report_instance.info.count", new RollingCounter()), + REPORT_INSTANCE_INFO_USER_ERROR_MISSING_REPORT_INSTANCE( + "report_instance.info.user_error.missing_report_instance", + new RollingCounter() + ), + REPORT_INSTANCE_INFO_USER_ERROR_INVALID_REPORT_ID( + "report_instance.info.user_error.invalid_report_id", new RollingCounter()), + REPORT_INSTANCE_INFO_SYSTEM_ERROR("report_instance.info.system_error", new RollingCounter()), + + + // GET _opendistro/_reports/instances + REPORT_INSTANCE_LIST_TOTAL("report_instance.list.total", new BasicCounter()), + REPORT_INSTANCE_LIST_INTERVAL_COUNT("report_instance.list.count", new RollingCounter()), + REPORT_INSTANCE_LIST_USER_ERROR_INVALID_FROM_INDEX( + "report_instance.list.user_error.invalid_from_index", new RollingCounter()), + REPORT_INSTANCE_LIST_SYSTEM_ERROR("report_instance.list.system_error", new RollingCounter()), + + + // PUT _opendistro/_reports/on_demand + REPORT_FROM_DEFINITION_TOTAL("on_demand.create.total", new BasicCounter()), + REPORT_FROM_DEFINITION_INTERVAL_COUNT("on_demand.create.count", new RollingCounter()), + REPORT_FROM_DEFINITION_USER_ERROR_INVALID_BEGIN_TIME( + "on_demand.create.user_error.invalid_begin_time", new RollingCounter()), + REPORT_FROM_DEFINITION_USER_ERROR_INVALID_END_TIME( + "on_demand.create.user_error.invalid_end_time", new RollingCounter()), + REPORT_FROM_DEFINITION_USER_ERROR_INVALID_STATUS( + "on_demand.create.user_error.invalid_status", new RollingCounter()), + REPORT_FROM_DEFINITION_SYSTEM_ERROR("on_demand.create.system_error", new RollingCounter()), + + + // POST _opendistro/_reports/on_demand/{reportDefinitionId} + REPORT_FROM_DEFINITION_ID_TOTAL("on_demand_from_definition.create.total", new BasicCounter()), + REPORT_FROM_DEFINITION_ID_INTERVAL_COUNT("on_demand_from_definition.create.count", new RollingCounter()), + REPORT_FROM_DEFINITION_ID_USER_ERROR_INVALID_REPORT_DEF_ID( + "on_demand_from_definition.create.user_error.invalid_report_def_id", new RollingCounter()), + REPORT_FROM_DEFINITION_ID_SYSTEM_ERROR("on_demand_from_definition.create.system_error", new RollingCounter()), + + + REPORT_SECURITY_PERMISSION_ERROR("es_security_permission_error", new RollingCounter()), + REPORT_PERMISSION_USER_ERROR("permission_user_error", new RollingCounter()); + + private final String name; + private final Counter counter; + + Metrics(String name, Counter counter) { + this.name = name; + this.counter = counter; + } + + public String getName() { + return name; + } + + public Counter getCounter() { + return counter; + } + + private static final Metrics[] values = values(); + + /** + * Converts the enum metric values to JSON string + */ + public static String collectToJSON() { + JSONObject metricsJSONObject = new JSONObject(); + for (Metrics metric: values) { + metricsJSONObject.put(metric.name, metric.counter.getValue()); + } + return metricsJSONObject.toString(); + } + + /** + * Unflattens the JSON to nested JSON for easy readability and parsing + * The metric name is unflattened in the output JSON on the period '.' delimiter + * + * For ex: { "a.b.c_d" : 2 } becomes + *{ + * "a" : { + * "b" : { + * "c_d" : 2 + * } + * } + * } + */ + + public static String collectToFlattenedJSON() { + return JsonUnflattener.unflatten(collectToJSON()); + } +} diff --git a/reports-scheduler/src/main/java/com/amazon/opendistroforelasticsearch/reportsscheduler/metrics/RollingCounter.java b/reports-scheduler/src/main/java/com/amazon/opendistroforelasticsearch/reportsscheduler/metrics/RollingCounter.java new file mode 100644 index 00000000..1a8c3024 --- /dev/null +++ b/reports-scheduler/src/main/java/com/amazon/opendistroforelasticsearch/reportsscheduler/metrics/RollingCounter.java @@ -0,0 +1,112 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.metrics; + +import java.time.Clock; +import java.util.concurrent.ConcurrentSkipListMap; + +/** + * Rolling counter. The count is refreshed every interval. In every interval the count is cumulative. + */ +public class RollingCounter implements Counter { + private static final long METRICS_ROLLING_WINDOW_VALUE = 3600L; + private static final long METRICS_ROLLING_INTERVAL_VALUE = 60L; + + private final long capacity; + private final long window; + private final long interval; + private final Clock clock; + private final ConcurrentSkipListMap timeToCountMap = new ConcurrentSkipListMap<>(); + + public RollingCounter() { + this(METRICS_ROLLING_WINDOW_VALUE, METRICS_ROLLING_INTERVAL_VALUE); + } + + public RollingCounter(long window, long interval, Clock clock) { + this.window = window; + this.interval = interval; + this.clock = clock; + capacity = window / interval * 2; + } + + public RollingCounter(long window, long interval) { + this(window, interval, Clock.systemDefaultZone()); + } + + /** + * {@inheritDoc} + */ + @Override + public void increment() { + add(1L); + } + + /** + * {@inheritDoc} + */ + @Override + public void add(long n) { + trim(); + timeToCountMap.compute(getKey(clock.millis()), (k, v) -> (v == null) ? n : v + n); + } + + /** + * {@inheritDoc} + */ + @Override + public Long getValue() { + return getValue(getPreKey(clock.millis())); + } + + /** + * {@inheritDoc} + */ + public long getValue(long key) { + Long res = timeToCountMap.get(key); + if (res == null) { + return 0; + } + return res; + } + + private void trim() { + if (timeToCountMap.size() > capacity) { + timeToCountMap.headMap(getKey(clock.millis() - window * 1000)).clear(); + } + } + + private long getKey(long millis) { + return millis / 1000 / this.interval; + } + + private long getPreKey(long millis) { + return getKey(millis) - 1; + } + + /** + * Number of existing intervals + */ + public int size() { + return timeToCountMap.size(); + } + + /** + * Remove all the items from counter + */ + public void reset() { + timeToCountMap.clear(); + } +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/ReportsSchedulerPlugin.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/ReportsSchedulerPlugin.kt index 2ab96e4b..bd33c9fd 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/ReportsSchedulerPlugin.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/ReportsSchedulerPlugin.kt @@ -38,9 +38,11 @@ import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.Report import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.ReportInstanceListRestHandler import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.ReportInstancePollRestHandler import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.ReportInstanceRestHandler +import com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler.ReportStatsRestHandler import com.amazon.opendistroforelasticsearch.reportsscheduler.scheduler.ReportDefinitionJobParser import com.amazon.opendistroforelasticsearch.reportsscheduler.scheduler.ReportDefinitionJobRunner import com.amazon.opendistroforelasticsearch.reportsscheduler.settings.PluginSettings + import org.elasticsearch.action.ActionRequest import org.elasticsearch.action.ActionResponse import org.elasticsearch.client.Client @@ -153,7 +155,8 @@ class ReportsSchedulerPlugin : Plugin(), ActionPlugin, JobSchedulerExtension { ReportInstanceRestHandler(), ReportInstanceListRestHandler(), OnDemandReportRestHandler(), - ReportInstancePollRestHandler() + ReportInstancePollRestHandler(), + ReportStatsRestHandler() ) } diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/action/PluginBaseAction.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/action/PluginBaseAction.kt index c24240fa..2beab1f8 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/action/PluginBaseAction.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/action/PluginBaseAction.kt @@ -19,6 +19,7 @@ package com.amazon.opendistroforelasticsearch.reportsscheduler.action import com.amazon.opendistroforelasticsearch.commons.ConfigConstants.OPENDISTRO_SECURITY_USER_INFO_THREAD_CONTEXT import com.amazon.opendistroforelasticsearch.commons.authuser.User import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.LOG_PREFIX +import com.amazon.opendistroforelasticsearch.reportsscheduler.metrics.Metrics import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -67,31 +68,40 @@ abstract class PluginBaseAction RestChannelConsumer { + Metrics.REPORT_FROM_DEFINITION_TOTAL.counter.increment() + Metrics.REPORT_FROM_DEFINITION_INTERVAL_COUNT.counter.increment() client.execute(InContextReportCreateAction.ACTION_TYPE, InContextReportCreateRequest(request.contentParserNextToken()), RestResponseToXContentListener(it)) } POST -> RestChannelConsumer { + Metrics.REPORT_FROM_DEFINITION_ID_TOTAL.counter.increment() + Metrics.REPORT_FROM_DEFINITION_ID_INTERVAL_COUNT.counter.increment() client.execute(OnDemandReportCreateAction.ACTION_TYPE, OnDemandReportCreateRequest.parse(request.contentParserNextToken(), request.param(REPORT_DEFINITION_ID_FIELD)), RestResponseToXContentListener(it)) diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/PluginBaseHandler.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/PluginBaseHandler.kt new file mode 100644 index 00000000..0dc6c920 --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/PluginBaseHandler.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler + +import com.amazon.opendistroforelasticsearch.reportsscheduler.metrics.Metrics +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.rest.BaseRestHandler +import org.elasticsearch.rest.RestRequest + +abstract class PluginBaseHandler : BaseRestHandler() { + + /** + * {@inheritDoc} + */ + override fun prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer { + Metrics.REQUEST_TOTAL.counter.increment() + Metrics.REQUEST_INTERVAL_COUNT.counter.increment() + return executeRequest(request, client) + } + + protected abstract fun executeRequest(request: RestRequest, client: NodeClient): RestChannelConsumer +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportDefinitionListRestHandler.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportDefinitionListRestHandler.kt index c7819132..60cb6ee6 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportDefinitionListRestHandler.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportDefinitionListRestHandler.kt @@ -18,12 +18,13 @@ package com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.BASE_REPORTS_URI import com.amazon.opendistroforelasticsearch.reportsscheduler.action.GetAllReportDefinitionsAction import com.amazon.opendistroforelasticsearch.reportsscheduler.action.ReportDefinitionActions +import com.amazon.opendistroforelasticsearch.reportsscheduler.metrics.Metrics import com.amazon.opendistroforelasticsearch.reportsscheduler.model.GetAllReportDefinitionsRequest import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestTag.FROM_INDEX_FIELD import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestTag.MAX_ITEMS_FIELD import com.amazon.opendistroforelasticsearch.reportsscheduler.settings.PluginSettings + import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.rest.BaseRestHandler import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer import org.elasticsearch.rest.BytesRestResponse import org.elasticsearch.rest.RestHandler.Route @@ -35,7 +36,7 @@ import org.elasticsearch.rest.RestStatus * Rest handler for getting list of report definitions. * This handler uses [ReportDefinitionActions]. */ -internal class ReportDefinitionListRestHandler : BaseRestHandler() { +internal class ReportDefinitionListRestHandler : PluginBaseHandler() { companion object { private const val REPORT_DEFINITION_LIST_ACTION = "report_definition_list_actions" private const val LIST_REPORT_DEFINITIONS_URL = "$BASE_REPORTS_URI/definitions" @@ -66,11 +67,13 @@ internal class ReportDefinitionListRestHandler : BaseRestHandler() { /** * {@inheritDoc} */ - override fun prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer { + override fun executeRequest(request: RestRequest, client: NodeClient): RestChannelConsumer { val from = request.param(FROM_INDEX_FIELD)?.toIntOrNull() ?: 0 val maxItems = request.param(MAX_ITEMS_FIELD)?.toIntOrNull() ?: PluginSettings.defaultItemsQueryCount return when (request.method()) { GET -> RestChannelConsumer { + Metrics.REPORT_DEFINITION_LIST_TOTAL.counter.increment() + Metrics.REPORT_DEFINITION_LIST_INTERVAL_COUNT.counter.increment() client.execute(GetAllReportDefinitionsAction.ACTION_TYPE, GetAllReportDefinitionsRequest(from, maxItems), RestResponseToXContentListener(it)) diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportDefinitionRestHandler.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportDefinitionRestHandler.kt index dce33d51..3821757f 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportDefinitionRestHandler.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportDefinitionRestHandler.kt @@ -21,6 +21,7 @@ import com.amazon.opendistroforelasticsearch.reportsscheduler.action.DeleteRepor import com.amazon.opendistroforelasticsearch.reportsscheduler.action.GetReportDefinitionAction import com.amazon.opendistroforelasticsearch.reportsscheduler.action.ReportDefinitionActions import com.amazon.opendistroforelasticsearch.reportsscheduler.action.UpdateReportDefinitionAction +import com.amazon.opendistroforelasticsearch.reportsscheduler.metrics.Metrics import com.amazon.opendistroforelasticsearch.reportsscheduler.model.CreateReportDefinitionRequest import com.amazon.opendistroforelasticsearch.reportsscheduler.model.DeleteReportDefinitionRequest import com.amazon.opendistroforelasticsearch.reportsscheduler.model.GetReportDefinitionRequest @@ -28,7 +29,6 @@ import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestTag.REPO import com.amazon.opendistroforelasticsearch.reportsscheduler.model.UpdateReportDefinitionRequest import com.amazon.opendistroforelasticsearch.reportsscheduler.util.contentParserNextToken import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.rest.BaseRestHandler import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer import org.elasticsearch.rest.BytesRestResponse import org.elasticsearch.rest.RestHandler.Route @@ -43,7 +43,7 @@ import org.elasticsearch.rest.RestStatus * Rest handler for report definitions lifecycle management. * This handler uses [ReportDefinitionActions]. */ -internal class ReportDefinitionRestHandler : BaseRestHandler() { +internal class ReportDefinitionRestHandler : PluginBaseHandler() { companion object { private const val REPORT_DEFINITION_ACTION = "report_definition_actions" private const val REPORT_DEFINITION_URL = "$BASE_REPORTS_URI/definition" @@ -102,25 +102,33 @@ internal class ReportDefinitionRestHandler : BaseRestHandler() { /** * {@inheritDoc} */ - override fun prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer { + override fun executeRequest(request: RestRequest, client: NodeClient): RestChannelConsumer { return when (request.method()) { POST -> RestChannelConsumer { + Metrics.REPORT_DEFINITION_CREATE_TOTAL.counter.increment() + Metrics.REPORT_DEFINITION_CREATE_INTERVAL_COUNT.counter.increment() client.execute(CreateReportDefinitionAction.ACTION_TYPE, CreateReportDefinitionRequest(request.contentParserNextToken()), RestResponseToXContentListener(it)) } PUT -> RestChannelConsumer { + Metrics.REPORT_DEFINITION_UPDATE_TOTAL.counter.increment() + Metrics.REPORT_DEFINITION_UPDATE_INTERVAL_COUNT.counter.increment() client.execute( UpdateReportDefinitionAction.ACTION_TYPE, UpdateReportDefinitionRequest(request.contentParserNextToken(), request.param(REPORT_DEFINITION_ID_FIELD)), RestResponseToXContentListener(it)) } GET -> RestChannelConsumer { + Metrics.REPORT_DEFINITION_INFO_TOTAL.counter.increment() + Metrics.REPORT_DEFINITION_INFO_INTERVAL_COUNT.counter.increment() client.execute(GetReportDefinitionAction.ACTION_TYPE, GetReportDefinitionRequest(request.param(REPORT_DEFINITION_ID_FIELD)), RestResponseToXContentListener(it)) } DELETE -> RestChannelConsumer { + Metrics.REPORT_DEFINITION_DELETE_TOTAL.counter.increment() + Metrics.REPORT_DEFINITION_DELETE_INTERVAL_COUNT.counter.increment() client.execute(DeleteReportDefinitionAction.ACTION_TYPE, DeleteReportDefinitionRequest(request.param(REPORT_DEFINITION_ID_FIELD)), RestResponseToXContentListener(it)) diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportInstanceListRestHandler.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportInstanceListRestHandler.kt index a03a2b9c..e82f137a 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportInstanceListRestHandler.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportInstanceListRestHandler.kt @@ -18,12 +18,12 @@ package com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.BASE_REPORTS_URI import com.amazon.opendistroforelasticsearch.reportsscheduler.action.GetAllReportInstancesAction import com.amazon.opendistroforelasticsearch.reportsscheduler.action.ReportInstanceActions +import com.amazon.opendistroforelasticsearch.reportsscheduler.metrics.Metrics import com.amazon.opendistroforelasticsearch.reportsscheduler.model.GetAllReportInstancesRequest import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestTag.FROM_INDEX_FIELD import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestTag.MAX_ITEMS_FIELD import com.amazon.opendistroforelasticsearch.reportsscheduler.settings.PluginSettings import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.rest.BaseRestHandler import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer import org.elasticsearch.rest.BytesRestResponse import org.elasticsearch.rest.RestHandler.Route @@ -35,7 +35,7 @@ import org.elasticsearch.rest.RestStatus * Rest handler for getting list of report instances. * This handler uses [ReportInstanceActions]. */ -internal class ReportInstanceListRestHandler : BaseRestHandler() { +internal class ReportInstanceListRestHandler : PluginBaseHandler() { companion object { private const val REPORT_INSTANCE_LIST_ACTION = "report_instance_list_actions" private const val LIST_REPORT_INSTANCES_URL = "$BASE_REPORTS_URI/instances" @@ -73,11 +73,13 @@ internal class ReportInstanceListRestHandler : BaseRestHandler() { /** * {@inheritDoc} */ - override fun prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer { + override fun executeRequest(request: RestRequest, client: NodeClient): RestChannelConsumer { val from = request.param(FROM_INDEX_FIELD)?.toIntOrNull() ?: 0 val maxItems = request.param(MAX_ITEMS_FIELD)?.toIntOrNull() ?: PluginSettings.defaultItemsQueryCount return when (request.method()) { GET -> RestChannelConsumer { + Metrics.REPORT_INSTANCE_LIST_TOTAL.counter.increment() + Metrics.REPORT_INSTANCE_LIST_INTERVAL_COUNT.counter.increment() client.execute(GetAllReportInstancesAction.ACTION_TYPE, GetAllReportInstancesRequest(from, maxItems), RestResponseToXContentListener(it)) diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportInstancePollRestHandler.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportInstancePollRestHandler.kt index 73ddc15c..3669da4e 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportInstancePollRestHandler.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportInstancePollRestHandler.kt @@ -20,7 +20,6 @@ import com.amazon.opendistroforelasticsearch.reportsscheduler.action.PollReportI import com.amazon.opendistroforelasticsearch.reportsscheduler.action.ReportInstanceActions import com.amazon.opendistroforelasticsearch.reportsscheduler.model.PollReportInstanceRequest import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.rest.BaseRestHandler import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer import org.elasticsearch.rest.BytesRestResponse import org.elasticsearch.rest.RestHandler.Route @@ -32,7 +31,7 @@ import org.elasticsearch.rest.RestStatus * Rest handler for getting list of report instances. * This handler uses [ReportInstanceActions]. */ -internal class ReportInstancePollRestHandler : BaseRestHandler() { +internal class ReportInstancePollRestHandler : PluginBaseHandler() { companion object { private const val REPORT_INSTANCE_POLL_ACTION = "report_instance_poll_actions" private const val POLL_REPORT_INSTANCE_URL = "$BASE_REPORTS_URI/poll_instance" @@ -70,7 +69,7 @@ internal class ReportInstancePollRestHandler : BaseRestHandler() { /** * {@inheritDoc} */ - override fun prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer { + override fun executeRequest(request: RestRequest, client: NodeClient): RestChannelConsumer { return when (request.method()) { GET -> RestChannelConsumer { client.execute(PollReportInstanceAction.ACTION_TYPE, diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportInstanceRestHandler.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportInstanceRestHandler.kt index e7256112..2eebb10e 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportInstanceRestHandler.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportInstanceRestHandler.kt @@ -19,12 +19,12 @@ import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPl import com.amazon.opendistroforelasticsearch.reportsscheduler.action.GetReportInstanceAction import com.amazon.opendistroforelasticsearch.reportsscheduler.action.ReportInstanceActions import com.amazon.opendistroforelasticsearch.reportsscheduler.action.UpdateReportInstanceStatusAction +import com.amazon.opendistroforelasticsearch.reportsscheduler.metrics.Metrics import com.amazon.opendistroforelasticsearch.reportsscheduler.model.GetReportInstanceRequest import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestTag.REPORT_INSTANCE_ID_FIELD import com.amazon.opendistroforelasticsearch.reportsscheduler.model.UpdateReportInstanceStatusRequest import com.amazon.opendistroforelasticsearch.reportsscheduler.util.contentParserNextToken import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.rest.BaseRestHandler import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer import org.elasticsearch.rest.BytesRestResponse import org.elasticsearch.rest.RestHandler.Route @@ -37,7 +37,7 @@ import org.elasticsearch.rest.RestStatus * Rest handler for report instances lifecycle management. * This handler uses [ReportInstanceActions]. */ -internal class ReportInstanceRestHandler : BaseRestHandler() { +internal class ReportInstanceRestHandler : PluginBaseHandler() { companion object { private const val REPORT_INSTANCE_LIST_ACTION = "report_instance_actions" private const val REPORT_INSTANCE_URL = "$BASE_REPORTS_URI/instance" @@ -82,15 +82,19 @@ internal class ReportInstanceRestHandler : BaseRestHandler() { /** * {@inheritDoc} */ - override fun prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer { + override fun executeRequest(request: RestRequest, client: NodeClient): RestChannelConsumer { val reportInstanceId = request.param(REPORT_INSTANCE_ID_FIELD) ?: throw IllegalArgumentException("Must specify id") return when (request.method()) { POST -> RestChannelConsumer { + Metrics.REPORT_INSTANCE_UPDATE_TOTAL.counter.increment() + Metrics.REPORT_INSTANCE_UPDATE_INTERVAL_COUNT.counter.increment() client.execute(UpdateReportInstanceStatusAction.ACTION_TYPE, UpdateReportInstanceStatusRequest.parse(request.contentParserNextToken(), reportInstanceId), RestResponseToXContentListener(it)) } GET -> RestChannelConsumer { + Metrics.REPORT_INSTANCE_INFO_TOTAL.counter.increment() + Metrics.REPORT_INSTANCE_INFO_INTERVAL_COUNT.counter.increment() client.execute(GetReportInstanceAction.ACTION_TYPE, GetReportInstanceRequest(reportInstanceId), RestResponseToXContentListener(it)) diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportStatsRestHandler.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportStatsRestHandler.kt new file mode 100644 index 00000000..91e2b834 --- /dev/null +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/ReportStatsRestHandler.kt @@ -0,0 +1,81 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ +package com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler + +import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPlugin.Companion.BASE_REPORTS_URI +import com.amazon.opendistroforelasticsearch.reportsscheduler.metrics.Metrics +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.rest.BaseRestHandler +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.BytesRestResponse +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.GET +import org.elasticsearch.rest.RestStatus + +/** + * Rest handler for getting reporting backend stats + */ +internal class ReportStatsRestHandler : BaseRestHandler() { + companion object { + private const val REPORT_STATS_ACTION = "report_definition_stats" + private const val REPORT_STATS_URL = "$BASE_REPORTS_URI/_local/stats" + } + + /** + * {@inheritDoc} + */ + override fun getName(): String { + return REPORT_STATS_ACTION + } + + /** + * {@inheritDoc} + */ + override fun routes(): List { + return listOf( + /** + * Get reporting backend stats + * Request URL: GET REPORT_STATS_URL + * Response body derived from: Ref [com.amazon.opendistroforelasticsearch.reportsscheduler.metrics.Metrics] + */ + Route(GET, "$REPORT_STATS_URL") + ) + } + + /** + * {@inheritDoc} + */ + override fun responseParams(): Set { + return setOf() + } + + /** + * {@inheritDoc} + */ + override fun prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer { + return when (request.method()) { + // TODO: Wrap this into TransportAction + GET -> RestChannelConsumer { + // it.sendResponse(BytesRestResponse(RestStatus.OK, Metrics.getInstance().collectToFlattenedJSON())) + it.sendResponse(BytesRestResponse(RestStatus.OK, Metrics.collectToFlattenedJSON())) + } + else -> RestChannelConsumer { + it.sendResponse(BytesRestResponse(RestStatus.METHOD_NOT_ALLOWED, "${request.method()} is not allowed")) + } + } + } +} diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/RestResponseToXContentListener.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/RestResponseToXContentListener.kt index 325faff8..c13c275d 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/RestResponseToXContentListener.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/resthandler/RestResponseToXContentListener.kt @@ -16,8 +16,12 @@ package com.amazon.opendistroforelasticsearch.reportsscheduler.resthandler +import com.amazon.opendistroforelasticsearch.reportsscheduler.metrics.Metrics import com.amazon.opendistroforelasticsearch.reportsscheduler.model.BaseResponse +import org.elasticsearch.common.xcontent.XContentBuilder +import org.elasticsearch.rest.BytesRestResponse import org.elasticsearch.rest.RestChannel +import org.elasticsearch.rest.RestResponse import org.elasticsearch.rest.RestStatus import org.elasticsearch.rest.action.RestToXContentListener @@ -26,7 +30,24 @@ import org.elasticsearch.rest.action.RestToXContentListener * {@link ToXContent} and automatically builds an XContent based response * (wrapping the toXContent in startObject/endObject). */ -internal class RestResponseToXContentListener(channel: RestChannel) : RestToXContentListener(channel) { +internal class RestResponseToXContentListener(channel: RestChannel) : RestToXContentListener( + channel +) { + override fun buildResponse(response: Response, builder: XContentBuilder?): RestResponse? { + super.buildResponse(response, builder) + + Metrics.REQUEST_TOTAL.counter.increment() + Metrics.REQUEST_INTERVAL_COUNT.counter.increment() + + when (response.getStatus()) { + in RestStatus.OK..RestStatus.MULTI_STATUS -> Metrics.REQUEST_SUCCESS.counter.increment() + RestStatus.FORBIDDEN -> Metrics.REPORT_SECURITY_PERMISSION_ERROR.counter.increment() + in RestStatus.UNAUTHORIZED..RestStatus.TOO_MANY_REQUESTS -> Metrics.REQUEST_USER_ERROR.counter.increment() + else -> Metrics.REQUEST_SYSTEM_ERROR.counter.increment() + } + return BytesRestResponse(getStatus(response), builder) + } + /** * {@inheritDoc} */ diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/security/UserAccessManager.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/security/UserAccessManager.kt index a178bd4e..c8053d81 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/security/UserAccessManager.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/security/UserAccessManager.kt @@ -17,6 +17,7 @@ package com.amazon.opendistroforelasticsearch.reportsscheduler.security import com.amazon.opendistroforelasticsearch.commons.authuser.User +import com.amazon.opendistroforelasticsearch.reportsscheduler.metrics.Metrics import com.amazon.opendistroforelasticsearch.reportsscheduler.settings.PluginSettings import com.amazon.opendistroforelasticsearch.reportsscheduler.settings.PluginSettings.FilterBy import org.elasticsearch.ElasticsearchStatusException @@ -48,6 +49,7 @@ internal object UserAccessManager { */ fun validateUser(user: User?) { if (isUserPrivateTenant(user) && user?.name == null) { + Metrics.REPORT_PERMISSION_USER_ERROR.counter.increment() throw ElasticsearchStatusException("User name not provided for private tenant access", RestStatus.FORBIDDEN) } @@ -56,20 +58,26 @@ internal object UserAccessManager { } FilterBy.User -> { // User name must be present user?.name - ?: throw ElasticsearchStatusException("Filter-by enabled with security disabled", - RestStatus.FORBIDDEN) + ?: run { + Metrics.REPORT_PERMISSION_USER_ERROR.counter.increment() + throw ElasticsearchStatusException("Filter-by enabled with security disabled", + RestStatus.FORBIDDEN) + } } FilterBy.Roles -> { // backend roles must be present if (user == null || user.roles.isNullOrEmpty()) { + Metrics.REPORT_PERMISSION_USER_ERROR.counter.increment() throw ElasticsearchStatusException("User doesn't have roles configured. Contact administrator.", RestStatus.FORBIDDEN) } else if (user.roles.stream().filter { !PluginSettings.ignoredRoles.contains(it) }.count() == 0L) { + Metrics.REPORT_PERMISSION_USER_ERROR.counter.increment() throw ElasticsearchStatusException("No distinguishing roles configured. Contact administrator.", RestStatus.FORBIDDEN) } } FilterBy.BackendRoles -> { // backend roles must be present if (user?.backendRoles.isNullOrEmpty()) { + Metrics.REPORT_PERMISSION_USER_ERROR.counter.increment() throw ElasticsearchStatusException("User doesn't have backend roles configured. Contact administrator.", RestStatus.FORBIDDEN) } @@ -83,6 +91,7 @@ internal object UserAccessManager { fun validatePollingUser(user: User?) { if (user != null) { // Check only if security is enabled if (user.name != KIBANA_SERVER_USER) { + Metrics.REPORT_PERMISSION_USER_ERROR.counter.increment() throw ElasticsearchStatusException("Permission denied", RestStatus.FORBIDDEN) } } diff --git a/reports-scheduler/src/test/java/com/amazon/opendistroforelasticsearch/reportsscheduler/metrics/BasicCounterTest.java b/reports-scheduler/src/test/java/com/amazon/opendistroforelasticsearch/reportsscheduler/metrics/BasicCounterTest.java new file mode 100644 index 00000000..64a39ef7 --- /dev/null +++ b/reports-scheduler/src/test/java/com/amazon/opendistroforelasticsearch/reportsscheduler/metrics/BasicCounterTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.metrics; + +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +public class BasicCounterTest { + + @Test + public void increment() { + BasicCounter counter = new BasicCounter(); + for (int i=0; i<5; ++i) { + counter.increment(); + } + + assertThat(counter.getValue(), equalTo(5L)); + } + + @Test + public void incrementN() { + BasicCounter counter = new BasicCounter(); + counter.add(5); + + assertThat(counter.getValue(), equalTo(5L)); + } + +} diff --git a/reports-scheduler/src/test/java/com/amazon/opendistroforelasticsearch/reportsscheduler/metrics/RollingCounterTest.java b/reports-scheduler/src/test/java/com/amazon/opendistroforelasticsearch/reportsscheduler/metrics/RollingCounterTest.java new file mode 100644 index 00000000..4ed43fa8 --- /dev/null +++ b/reports-scheduler/src/test/java/com/amazon/opendistroforelasticsearch/reportsscheduler/metrics/RollingCounterTest.java @@ -0,0 +1,89 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.reportsscheduler.metrics; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.time.Clock; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.lessThanOrEqualTo; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class RollingCounterTest { + + @Mock + Clock clock; + + @Test + public void increment() { + RollingCounter counter = new RollingCounter(3, 1, clock); + for (int i=0; i<5; ++i) { + counter.increment(); + } + + assertThat(counter.getValue(), equalTo(0L)); + + when(clock.millis()).thenReturn(1000L); // 1 second passed + assertThat(counter.getValue(), equalTo(5L)); + + counter.increment(); + counter.increment(); + + when(clock.millis()).thenReturn(2000L); // 1 second passed + assertThat(counter.getValue(), lessThanOrEqualTo(3L)); + + when(clock.millis()).thenReturn(3000L); // 1 second passed + assertThat(counter.getValue(), equalTo(0L)); + + } + + @Test + public void add() { + RollingCounter counter = new RollingCounter(3, 1, clock); + + counter.add(6); + assertThat(counter.getValue(), equalTo(0L)); + + when(clock.millis()).thenReturn(1000L); // 1 second passed + assertThat(counter.getValue(), equalTo(6L)); + + counter.add(4); + when(clock.millis()).thenReturn(2000L); // 1 second passed + assertThat(counter.getValue(), equalTo(4L)); + + when(clock.millis()).thenReturn(3000L); // 1 second passed + assertThat(counter.getValue(), equalTo(0L)); + } + + @Test + public void trim() { + RollingCounter counter = new RollingCounter(2, 1, clock); + + for (int i=1; i<6; ++i) { + counter.increment(); + assertThat(counter.size(), equalTo(i)); + when(clock.millis()).thenReturn(i * 1000L); // i seconds passed + } + counter.increment(); + assertThat(counter.size(), lessThanOrEqualTo(3)); + } +}